Skip to content

Commit 9f9cf28

Browse files
neildmknyszek
authored andcommitted
[release-branch.go1.24] os: don't follow symlinks on Windows when O_CREATE|O_EXCL
(This cherry-pick includes both CL 672396 and CL 676655.) Match standard Unix behavior: Symlinks are not followed when O_CREATE|O_EXCL is passed to open. Thanks to Junyoung Park and Dong-uk Kim of KAIST Hacking Lab for discovering this issue. For golang#73702 Fixed golang#73720 Fixes CVE-2025-0913 Change-Id: Ieb46a6780c5e9a6090b09cd34290f04a8e3b0ca5 Reviewed-on: https://go-review.googlesource.com/c/go/+/672396 Auto-Submit: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/677215 Reviewed-by: Michael Knyszek <[email protected]> TryBot-Bypass: Michael Knyszek <[email protected]>
1 parent a31c931 commit 9f9cf28

File tree

3 files changed

+41
-14
lines changed

3 files changed

+41
-14
lines changed

src/internal/syscall/windows/at_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func Openat(dirfd syscall.Handle, name string, flag int, perm uint32) (_ syscall
8888
switch {
8989
case flag&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
9090
disposition = FILE_CREATE
91+
options |= FILE_OPEN_REPARSE_POINT // don't follow symlinks
9192
case flag&syscall.O_CREAT == syscall.O_CREAT:
9293
disposition = FILE_OPEN_IF
9394
default:

src/os/os_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,6 +2223,31 @@ func TestFilePermissions(t *testing.T) {
22232223

22242224
}
22252225

2226+
func TestOpenFileCreateExclDanglingSymlink(t *testing.T) {
2227+
testMaybeRooted(t, func(t *testing.T, r *Root) {
2228+
const link = "link"
2229+
if err := Symlink("does_not_exist", link); err != nil {
2230+
t.Fatal(err)
2231+
}
2232+
var f *File
2233+
var err error
2234+
if r == nil {
2235+
f, err = OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o444)
2236+
} else {
2237+
f, err = r.OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o444)
2238+
}
2239+
if err == nil {
2240+
f.Close()
2241+
}
2242+
if !errors.Is(err, ErrExist) {
2243+
t.Errorf("OpenFile of a dangling symlink with O_CREATE|O_EXCL = %v, want ErrExist", err)
2244+
}
2245+
if _, err := Stat(link); err == nil {
2246+
t.Errorf("OpenFile of a dangling symlink with O_CREATE|O_EXCL created a file")
2247+
}
2248+
})
2249+
}
2250+
22262251
// TestFileRDWRFlags tests the O_RDONLY, O_WRONLY, and O_RDWR flags.
22272252
func TestFileRDWRFlags(t *testing.T) {
22282253
for _, test := range []struct {

src/syscall/syscall_windows.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -376,20 +376,6 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
376376
if flag&O_CLOEXEC == 0 {
377377
sa = makeInheritSa()
378378
}
379-
// We don't use CREATE_ALWAYS, because when opening a file with
380-
// FILE_ATTRIBUTE_READONLY these will replace an existing file
381-
// with a new, read-only one. See https://go.dev/issue/38225.
382-
//
383-
// Instead, we ftruncate the file after opening when O_TRUNC is set.
384-
var createmode uint32
385-
switch {
386-
case flag&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
387-
createmode = CREATE_NEW
388-
case flag&O_CREAT == O_CREAT:
389-
createmode = OPEN_ALWAYS
390-
default:
391-
createmode = OPEN_EXISTING
392-
}
393379
var attrs uint32 = FILE_ATTRIBUTE_NORMAL
394380
if perm&S_IWRITE == 0 {
395381
attrs = FILE_ATTRIBUTE_READONLY
@@ -404,6 +390,21 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
404390
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
405391
attrs |= _FILE_FLAG_WRITE_THROUGH
406392
}
393+
// We don't use CREATE_ALWAYS, because when opening a file with
394+
// FILE_ATTRIBUTE_READONLY these will replace an existing file
395+
// with a new, read-only one. See https://go.dev/issue/38225.
396+
//
397+
// Instead, we ftruncate the file after opening when O_TRUNC is set.
398+
var createmode uint32
399+
switch {
400+
case flag&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
401+
createmode = CREATE_NEW
402+
attrs |= FILE_FLAG_OPEN_REPARSE_POINT // don't follow symlinks
403+
case flag&O_CREAT == O_CREAT:
404+
createmode = OPEN_ALWAYS
405+
default:
406+
createmode = OPEN_EXISTING
407+
}
407408
h, err := createFile(namep, access, sharemode, sa, createmode, attrs, 0)
408409
if h == InvalidHandle {
409410
if err == ERROR_ACCESS_DENIED && (flag&O_WRONLY != 0 || flag&O_RDWR != 0) {

0 commit comments

Comments
 (0)