src/internal/syscall/windows/at_windows.go | 1 + src/os/os_test.go | 25 +++++++++++++++++++++++++ src/syscall/syscall_windows.go | 29 +++++++++++++++-------------- diff --git a/src/internal/syscall/windows/at_windows.go b/src/internal/syscall/windows/at_windows.go index 18429773c060e78e1ee3bdc3dff0c4202cea6c44..05170b218eac015adb26d590547cc4378ae98adf 100644 --- a/src/internal/syscall/windows/at_windows.go +++ b/src/internal/syscall/windows/at_windows.go @@ -88,6 +88,7 @@ var disposition uint32 switch { case flag&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL): disposition = FILE_CREATE + options |= FILE_OPEN_REPARSE_POINT // don't follow symlinks case flag&syscall.O_CREAT == syscall.O_CREAT: disposition = FILE_OPEN_IF default: diff --git a/src/os/os_test.go b/src/os/os_test.go index 4ddbe6022bfe14884e772040a8b953ff5ce39afc..3b348f93d25772c84a73154691d20f73be031dde 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -2223,6 +2223,31 @@ } } +func TestOpenFileCreateExclDanglingSymlink(t *testing.T) { + testMaybeRooted(t, func(t *testing.T, r *Root) { + const link = "link" + if err := Symlink("does_not_exist", link); err != nil { + t.Fatal(err) + } + var f *File + var err error + if r == nil { + f, err = OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o444) + } else { + f, err = r.OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o444) + } + if err == nil { + f.Close() + } + if !errors.Is(err, ErrExist) { + t.Errorf("OpenFile of a dangling symlink with O_CREATE|O_EXCL = %v, want ErrExist", err) + } + if _, err := Stat(link); err == nil { + t.Errorf("OpenFile of a dangling symlink with O_CREATE|O_EXCL created a file") + } + }) +} + // TestFileRDWRFlags tests the O_RDONLY, O_WRONLY, and O_RDWR flags. func TestFileRDWRFlags(t *testing.T) { for _, test := range []struct { diff --git a/src/syscall/syscall_windows.go b/src/syscall/syscall_windows.go index 344f6c325cd8fb54720ce3046c90a647067e76e2..1c5a67778fa15c78ebd63d4a23c5e7d4e4df1512 100644 --- a/src/syscall/syscall_windows.go +++ b/src/syscall/syscall_windows.go @@ -376,6 +376,20 @@ var sa *SecurityAttributes if flag&O_CLOEXEC == 0 { sa = makeInheritSa() } + var attrs uint32 = FILE_ATTRIBUTE_NORMAL + if perm&S_IWRITE == 0 { + attrs = FILE_ATTRIBUTE_READONLY + } + if flag&O_WRONLY == 0 && flag&O_RDWR == 0 { + // We might be opening or creating a directory. + // CreateFile requires FILE_FLAG_BACKUP_SEMANTICS + // to work with directories. + attrs |= FILE_FLAG_BACKUP_SEMANTICS + } + if flag&O_SYNC != 0 { + const _FILE_FLAG_WRITE_THROUGH = 0x80000000 + 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. @@ -385,24 +399,11 @@ 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 - } - var attrs uint32 = FILE_ATTRIBUTE_NORMAL - if perm&S_IWRITE == 0 { - attrs = FILE_ATTRIBUTE_READONLY - } - if flag&O_WRONLY == 0 && flag&O_RDWR == 0 { - // We might be opening or creating a directory. - // CreateFile requires FILE_FLAG_BACKUP_SEMANTICS - // to work with directories. - attrs |= FILE_FLAG_BACKUP_SEMANTICS - } - if flag&O_SYNC != 0 { - const _FILE_FLAG_WRITE_THROUGH = 0x80000000 - attrs |= _FILE_FLAG_WRITE_THROUGH } h, err := createFile(namep, access, sharemode, sa, createmode, attrs, 0) if h == InvalidHandle {