]> Cypherpunks repositories - gostls13.git/commitdiff
os: don't follow symlinks on Windows when O_CREATE|O_EXCL
authorDamien Neil <dneil@google.com>
Tue, 13 May 2025 22:35:19 +0000 (15:35 -0700)
committerGopher Robot <gobot@golang.org>
Wed, 14 May 2025 19:32:33 +0000 (12:32 -0700)
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.

Fixes #73702
Fixes CVE-2025-0913

Change-Id: Ieb46a6780c5e9a6090b09cd34290f04a8e3b0ca5
Reviewed-on: https://go-review.googlesource.com/c/go/+/672396
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>
src/internal/syscall/windows/at_windows.go
src/os/os_test.go
src/syscall/syscall_windows.go

index 41ab5d7b52ae9c6e032abe9c29094b4a20fef092..87a84c3da5aa6cfcb6feaae1178b48ca4af28fda 100644 (file)
@@ -94,6 +94,7 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc
        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:
index c6c08d062ac7f7c2f99964dd77493d2e717ef905..b2b98f3b523a0bf241212a422f48ad34891e11dc 100644 (file)
@@ -2299,6 +2299,31 @@ func TestFilePermissions(t *testing.T) {
 
 }
 
+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, 0o666)
+               } else {
+                       f, err = r.OpenFile(link, O_WRONLY|O_CREATE|O_EXCL, 0o666)
+               }
+               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 {
index f7fca07301b0756fa9f45b60b31515dcf64a7059..2b0482db27f8730443291c345c1d058780e2d66e 100644 (file)
@@ -403,15 +403,16 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
        //
        // Instead, we ftruncate the file after opening when O_TRUNC is set.
        var createmode uint32
+       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
        }
-       var attrs uint32 = FILE_ATTRIBUTE_NORMAL
        if perm&S_IWRITE == 0 {
                attrs = FILE_ATTRIBUTE_READONLY
        }