]> Cypherpunks repositories - gostls13.git/commitdiff
syscall: simplify O_TRUNC handling on Windows
authorqmuntal <quimmuntal@gmail.com>
Wed, 9 Oct 2024 08:48:07 +0000 (10:48 +0200)
committerQuim Muntal <quimmuntal@gmail.com>
Wed, 9 Oct 2024 17:23:27 +0000 (17:23 +0000)
The current implementation of O_TRUNC in syscall.Open on Windows is
prone to TOCTOU issues, as it opens the file twice if the first open
detects that the file doesn't exist. The file could
be created in between the two open calls, leading to the creation
of a new file with the undesired readonly attribute.

This CL implements O_TRUNC by just calling CreateFile once without
taking O_TRUNCATE into account, and then using Ftruncate if O_TRUNC is
set to truncate the file.

Updates #38225.

Change-Id: Ic3ad1bab75c9a1c16f99c8c5bed867c5dbc3a23b
Reviewed-on: https://go-review.googlesource.com/c/go/+/618836
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
src/syscall/syscall_windows.go

index 359681e920756fec14a1fe73072165e80e655b98..08120b3f2a62393f6d345b6aed0969e2003c61f9 100644 (file)
@@ -370,42 +370,23 @@ func Open(path string, mode int, perm uint32) (fd Handle, err error) {
        if mode&O_CLOEXEC == 0 {
                sa = makeInheritSa()
        }
+       // We don't use CREATE_ALWAYS, because when opening a file with
+       // FILE_ATTRIBUTE_READONLY these will replace an existing file
+       // with a new, read-only one. See https://go.dev/issue/38225.
+       //
+       // Instead, we ftruncate the file after opening when O_TRUNC is set.
        var createmode uint32
        switch {
        case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
                createmode = CREATE_NEW
-       case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC):
-               createmode = CREATE_ALWAYS
        case mode&O_CREAT == O_CREAT:
                createmode = OPEN_ALWAYS
-       case mode&O_TRUNC == O_TRUNC:
-               createmode = TRUNCATE_EXISTING
        default:
                createmode = OPEN_EXISTING
        }
        var attrs uint32 = FILE_ATTRIBUTE_NORMAL
        if perm&S_IWRITE == 0 {
                attrs = FILE_ATTRIBUTE_READONLY
-               if createmode == CREATE_ALWAYS {
-                       // We have been asked to create a read-only file.
-                       // If the file already exists, the semantics of
-                       // the Unix open system call is to preserve the
-                       // existing permissions. If we pass CREATE_ALWAYS
-                       // and FILE_ATTRIBUTE_READONLY to CreateFile,
-                       // and the file already exists, CreateFile will
-                       // change the file permissions.
-                       // Avoid that to preserve the Unix semantics.
-                       h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
-                       switch e {
-                       case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND:
-                               // File does not exist. These are the same
-                               // errors as Errno.Is checks for ErrNotExist.
-                               // Carry on to create the file.
-                       default:
-                               // Success or some different error.
-                               return h, e
-                       }
-               }
        }
        if createmode == OPEN_EXISTING && access == GENERIC_READ {
                // Necessary for opening directory handles.
@@ -415,7 +396,18 @@ func Open(path string, mode int, perm uint32) (fd Handle, err error) {
                const _FILE_FLAG_WRITE_THROUGH = 0x80000000
                attrs |= _FILE_FLAG_WRITE_THROUGH
        }
-       return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
+       h, err := CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
+       if err != nil {
+               return InvalidHandle, err
+       }
+       if mode&O_TRUNC == O_TRUNC {
+               err = Ftruncate(h, 0)
+               if err != nil {
+                       CloseHandle(h)
+                       return InvalidHandle, err
+               }
+       }
+       return h, nil
 }
 
 func Read(fd Handle, p []byte) (n int, err error) {