os: don't follow symlinks on Windows when O_CREATE|O_EXCL and read-only

Fix a bug in CL 672396, where we add FILE_FLAG_OPEN_REPARSE_POINT to
the attributes passed to CreateFile, but then overwrite the attributes
with FILE_ATTRIBUTE_READONLY when opening a file with a read-only
permissions mode.

For #73702

Change-Id: I6c10bf470054592bafa031732585fc3155c61341
Reviewed-on: https://go-review.googlesource.com/c/go/+/676655
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Damien Neil 2025-05-27 14:16:17 -07:00 committed by Gopher Robot
parent fce9d4515d
commit ed08d2ad09
2 changed files with 17 additions and 17 deletions

View File

@ -2309,9 +2309,9 @@ func TestOpenFileCreateExclDanglingSymlink(t *testing.T) {
var f *File var f *File
var err error var err error
if r == nil { if r == nil {
f, err = OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o666) f, err = OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o444)
} else { } else {
f, err = r.OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o666) f, err = r.OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o444)
} }
if err == nil { if err == nil {
f.Close() f.Close()

View File

@ -398,22 +398,7 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
if flag&O_CLOEXEC == 0 { if flag&O_CLOEXEC == 0 {
sa = makeInheritSa() 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
var attrs uint32 = FILE_ATTRIBUTE_NORMAL var attrs uint32 = FILE_ATTRIBUTE_NORMAL
switch {
case flag&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
createmode = CREATE_NEW
attrs |= FILE_FLAG_OPEN_REPARSE_POINT // don't follow symlinks
case flag&O_CREAT == O_CREAT:
createmode = OPEN_ALWAYS
default:
createmode = OPEN_EXISTING
}
if perm&S_IWRITE == 0 { if perm&S_IWRITE == 0 {
attrs = FILE_ATTRIBUTE_READONLY attrs = FILE_ATTRIBUTE_READONLY
} }
@ -433,6 +418,21 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
const _FILE_FLAG_WRITE_THROUGH = 0x80000000 const _FILE_FLAG_WRITE_THROUGH = 0x80000000
attrs |= _FILE_FLAG_WRITE_THROUGH attrs |= _FILE_FLAG_WRITE_THROUGH
} }
// 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 flag&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
createmode = CREATE_NEW
attrs |= FILE_FLAG_OPEN_REPARSE_POINT // don't follow symlinks
case flag&O_CREAT == O_CREAT:
createmode = OPEN_ALWAYS
default:
createmode = OPEN_EXISTING
}
h, err := createFile(namep, access, sharemode, sa, createmode, attrs, 0) h, err := createFile(namep, access, sharemode, sa, createmode, attrs, 0)
if h == InvalidHandle { if h == InvalidHandle {
if err == ERROR_ACCESS_DENIED && (attrs&FILE_FLAG_BACKUP_SEMANTICS == 0) { if err == ERROR_ACCESS_DENIED && (attrs&FILE_FLAG_BACKUP_SEMANTICS == 0) {