Skip to content

Commit

Permalink
syscall: simplify O_TRUNC handling on Windows
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Damien Neil <[email protected]>
Reviewed-by: Cherry Mui <[email protected]>
  • Loading branch information
qmuntal committed Oct 9, 2024
1 parent 0fd2d4d commit 18131ec
Showing 1 changed file with 17 additions and 25 deletions.
42 changes: 17 additions & 25 deletions src/syscall/syscall_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand Down

0 comments on commit 18131ec

Please sign in to comment.