os: don't follow symlinks on Windows when O_CREATE|O_EXCL

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>
This commit is contained in:
Damien Neil 2025-05-13 15:35:19 -07:00 committed by Gopher Robot
parent 14fc54fc57
commit adcad7bea9
3 changed files with 28 additions and 1 deletions

View 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:

View 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 {

View 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
}