aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-02-10 15:35:17 -0800
committerGopher Robot <gobot@golang.org>2025-02-13 15:29:56 -0800
commit807a51b391c8a8d949d6fa00c26953ba0f8ae267 (patch)
tree318c1e6490e53fd5ce21f13cab2bab8b17d8e079 /src
parent187fd2698d2f9fc2fc52aa7d4c0922552f848e98 (diff)
downloadgo-807a51b391c8a8d949d6fa00c26953ba0f8ae267.tar.xz
os: add Root.Chown
For #67002 Change-Id: I546537618cbe32217fa72264d49db2b1a1d3b6db Reviewed-on: https://go-review.googlesource.com/c/go/+/648295 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com>
Diffstat (limited to 'src')
-rw-r--r--src/internal/syscall/unix/asm_darwin.s1
-rw-r--r--src/internal/syscall/unix/asm_openbsd.s2
-rw-r--r--src/internal/syscall/unix/at.go18
-rw-r--r--src/internal/syscall/unix/at_aix.go1
-rw-r--r--src/internal/syscall/unix/at_darwin.go22
-rw-r--r--src/internal/syscall/unix/at_libc.go22
-rw-r--r--src/internal/syscall/unix/at_openbsd.go22
-rw-r--r--src/internal/syscall/unix/at_solaris.go1
-rw-r--r--src/internal/syscall/unix/at_sysnum_dragonfly.go1
-rw-r--r--src/internal/syscall/unix/at_sysnum_freebsd.go1
-rw-r--r--src/internal/syscall/unix/at_sysnum_linux.go1
-rw-r--r--src/internal/syscall/unix/at_sysnum_netbsd.go1
-rw-r--r--src/internal/syscall/unix/at_wasip1.go5
-rw-r--r--src/os/root.go6
-rw-r--r--src/os/root_noopenat.go10
-rw-r--r--src/os/root_openat.go10
-rw-r--r--src/os/root_unix.go8
-rw-r--r--src/os/root_unix_test.go87
-rw-r--r--src/os/root_windows.go4
19 files changed, 222 insertions, 1 deletions
diff --git a/src/internal/syscall/unix/asm_darwin.s b/src/internal/syscall/unix/asm_darwin.s
index de6e01ee4a..0f28cd1e39 100644
--- a/src/internal/syscall/unix/asm_darwin.s
+++ b/src/internal/syscall/unix/asm_darwin.s
@@ -26,3 +26,4 @@ TEXT ·libc_faccessat_trampoline(SB),NOSPLIT,$0-0; JMP libc_faccessat(SB)
TEXT ·libc_readlinkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_readlinkat(SB)
TEXT ·libc_mkdirat_trampoline(SB),NOSPLIT,$0-0; JMP libc_mkdirat(SB)
TEXT ·libc_fchmodat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchmodat(SB)
+TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchownat(SB)
diff --git a/src/internal/syscall/unix/asm_openbsd.s b/src/internal/syscall/unix/asm_openbsd.s
index 306ef4664d..b804a52714 100644
--- a/src/internal/syscall/unix/asm_openbsd.s
+++ b/src/internal/syscall/unix/asm_openbsd.s
@@ -16,3 +16,5 @@ TEXT ·libc_mkdirat_trampoline(SB),NOSPLIT,$0-0
JMP libc_mkdirat(SB)
TEXT ·libc_fchmodat_trampoline(SB),NOSPLIT,$0-0
JMP libc_fchmodat(SB)
+TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0
+ JMP libc_fchownat(SB)
diff --git a/src/internal/syscall/unix/at.go b/src/internal/syscall/unix/at.go
index 2a29dd6a5a..794f8ace14 100644
--- a/src/internal/syscall/unix/at.go
+++ b/src/internal/syscall/unix/at.go
@@ -96,3 +96,21 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
}
return nil
}
+
+func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
+ p, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall.Syscall6(fchownatTrap,
+ uintptr(dirfd),
+ uintptr(unsafe.Pointer(p)),
+ uintptr(uid),
+ uintptr(gid),
+ uintptr(flags),
+ 0)
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
diff --git a/src/internal/syscall/unix/at_aix.go b/src/internal/syscall/unix/at_aix.go
index e679efc344..aa188cdb76 100644
--- a/src/internal/syscall/unix/at_aix.go
+++ b/src/internal/syscall/unix/at_aix.go
@@ -5,6 +5,7 @@
package unix
//go:cgo_import_dynamic libc_fchmodat fchmodat "libc.a/shr_64.o"
+//go:cgo_import_dynamic libc_fchownat fchownat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_fstatat fstatat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_openat openat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.a/shr_64.o"
diff --git a/src/internal/syscall/unix/at_darwin.go b/src/internal/syscall/unix/at_darwin.go
index 759b0943f5..75d7b45569 100644
--- a/src/internal/syscall/unix/at_darwin.go
+++ b/src/internal/syscall/unix/at_darwin.go
@@ -80,3 +80,25 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
}
return nil
}
+
+func libc_fchownat_trampoline()
+
+//go:cgo_import_dynamic libc_fchownat fchownat "/usr/lib/libSystem.B.dylib"
+
+func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
+ p, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_fchownat_trampoline),
+ uintptr(dirfd),
+ uintptr(unsafe.Pointer(p)),
+ uintptr(uid),
+ uintptr(gid),
+ uintptr(flags),
+ 0)
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
diff --git a/src/internal/syscall/unix/at_libc.go b/src/internal/syscall/unix/at_libc.go
index f88e09d31d..137e0e0936 100644
--- a/src/internal/syscall/unix/at_libc.go
+++ b/src/internal/syscall/unix/at_libc.go
@@ -17,6 +17,7 @@ import (
//go:linkname procReadlinkat libc_readlinkat
//go:linkname procMkdirat libc_mkdirat
//go:linkname procFchmodat libc_fchmodat
+//go:linkname procFchownat libc_chownat
var (
procFstatat,
@@ -24,7 +25,8 @@ var (
procUnlinkat,
procReadlinkat,
procMkdirat,
- procFchmodat uintptr
+ procFchmodat,
+ procFchownat uintptr
)
func Unlinkat(dirfd int, path string, flags int) error {
@@ -126,3 +128,21 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
}
return nil
}
+
+func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
+ p, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall6(uintptr(unsafe.Pointer(&procFchownat)), 4,
+ uintptr(dirfd),
+ uintptr(unsafe.Pointer(p)),
+ uintptr(uid),
+ uintptr(gid),
+ uintptr(flags),
+ 0)
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
diff --git a/src/internal/syscall/unix/at_openbsd.go b/src/internal/syscall/unix/at_openbsd.go
index 26ca70322b..771cb063e0 100644
--- a/src/internal/syscall/unix/at_openbsd.go
+++ b/src/internal/syscall/unix/at_openbsd.go
@@ -71,3 +71,25 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
}
return nil
}
+
+//go:cgo_import_dynamic libc_fchownat fchownat "libc.so"
+
+func libc_fchownat_trampoline()
+
+func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
+ p, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_fchmodat_trampoline),
+ uintptr(dirfd),
+ uintptr(unsafe.Pointer(p)),
+ uintptr(uid),
+ uintptr(gid),
+ uintptr(flags),
+ 0)
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
diff --git a/src/internal/syscall/unix/at_solaris.go b/src/internal/syscall/unix/at_solaris.go
index a4910f1003..f84e8e35da 100644
--- a/src/internal/syscall/unix/at_solaris.go
+++ b/src/internal/syscall/unix/at_solaris.go
@@ -14,6 +14,7 @@ func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, e
//go:cgo_import_dynamic libc_faccessat faccessat "libc.so"
//go:cgo_import_dynamic libc_fchmodat fchmodat "libc.so"
+//go:cgo_import_dynamic libc_fchownat fchownat "libc.so"
//go:cgo_import_dynamic libc_fstatat fstatat "libc.so"
//go:cgo_import_dynamic libc_openat openat "libc.so"
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so"
diff --git a/src/internal/syscall/unix/at_sysnum_dragonfly.go b/src/internal/syscall/unix/at_sysnum_dragonfly.go
index 84c60c47b8..1e89e97f38 100644
--- a/src/internal/syscall/unix/at_sysnum_dragonfly.go
+++ b/src/internal/syscall/unix/at_sysnum_dragonfly.go
@@ -13,6 +13,7 @@ const (
readlinkatTrap uintptr = syscall.SYS_READLINKAT
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
+ fchownatTrap uintptr = syscall.SYS_FCHOWNAT
AT_EACCESS = 0x4
AT_FDCWD = 0xfffafdcd
diff --git a/src/internal/syscall/unix/at_sysnum_freebsd.go b/src/internal/syscall/unix/at_sysnum_freebsd.go
index 22ff4e7e89..59a8c2ce5a 100644
--- a/src/internal/syscall/unix/at_sysnum_freebsd.go
+++ b/src/internal/syscall/unix/at_sysnum_freebsd.go
@@ -20,4 +20,5 @@ const (
readlinkatTrap uintptr = syscall.SYS_READLINKAT
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
+ fchownatTrap uintptr = syscall.SYS_FCHOWNAT
)
diff --git a/src/internal/syscall/unix/at_sysnum_linux.go b/src/internal/syscall/unix/at_sysnum_linux.go
index 8fba319cab..35cc4307e9 100644
--- a/src/internal/syscall/unix/at_sysnum_linux.go
+++ b/src/internal/syscall/unix/at_sysnum_linux.go
@@ -12,6 +12,7 @@ const (
readlinkatTrap uintptr = syscall.SYS_READLINKAT
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
+ fchownatTrap uintptr = syscall.SYS_FCHOWNAT
)
const (
diff --git a/src/internal/syscall/unix/at_sysnum_netbsd.go b/src/internal/syscall/unix/at_sysnum_netbsd.go
index f2b7a4f9eb..bb946b6581 100644
--- a/src/internal/syscall/unix/at_sysnum_netbsd.go
+++ b/src/internal/syscall/unix/at_sysnum_netbsd.go
@@ -13,6 +13,7 @@ const (
readlinkatTrap uintptr = syscall.SYS_READLINKAT
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
+ fchownatTrap uintptr = syscall.SYS_FCHOWNAT
)
const (
diff --git a/src/internal/syscall/unix/at_wasip1.go b/src/internal/syscall/unix/at_wasip1.go
index 7289317110..3fdc95436c 100644
--- a/src/internal/syscall/unix/at_wasip1.go
+++ b/src/internal/syscall/unix/at_wasip1.go
@@ -106,6 +106,11 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
return syscall.ENOSYS
}
+func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
+ // WASI preview 1 doesn't support changing file ownership.
+ return syscall.ENOSYS
+}
+
//go:wasmimport wasi_snapshot_preview1 path_create_directory
//go:noescape
func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno
diff --git a/src/os/root.go b/src/os/root.go
index cd26144ab7..fd3b603ed8 100644
--- a/src/os/root.go
+++ b/src/os/root.go
@@ -151,6 +151,12 @@ func (r *Root) Mkdir(name string, perm FileMode) error {
return rootMkdir(r, name, perm)
}
+// Chown changes the numeric uid and gid of the named file in the root.
+// See [Chown] for more details.
+func (r *Root) Chown(name string, uid, gid int) error {
+ return rootChown(r, name, uid, gid)
+}
+
// Remove removes the named file or (empty) directory in the root.
// See [Remove] for more details.
func (r *Root) Remove(name string) error {
diff --git a/src/os/root_noopenat.go b/src/os/root_noopenat.go
index 819486f289..919e78c777 100644
--- a/src/os/root_noopenat.go
+++ b/src/os/root_noopenat.go
@@ -105,6 +105,16 @@ func rootChmod(r *Root, name string, mode FileMode) error {
return nil
}
+func rootChown(r *Root, name string, uid, gid int) error {
+ if err := checkPathEscapes(r, name); err != nil {
+ return &PathError{Op: "chownat", Path: name, Err: err}
+ }
+ if err := Chown(joinPath(r.root.name, name), uid, gid); err != nil {
+ return &PathError{Op: "chownat", Path: name, Err: underlyingError(err)}
+ }
+ return nil
+}
+
func rootMkdir(r *Root, name string, perm FileMode) error {
if err := checkPathEscapes(r, name); err != nil {
return &PathError{Op: "mkdirat", Path: name, Err: err}
diff --git a/src/os/root_openat.go b/src/os/root_openat.go
index d98d2e3675..65d3eacf4d 100644
--- a/src/os/root_openat.go
+++ b/src/os/root_openat.go
@@ -77,6 +77,16 @@ func rootChmod(r *Root, name string, mode FileMode) error {
return nil
}
+func rootChown(r *Root, name string, uid, gid int) error {
+ _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+ return struct{}{}, chownat(parent, name, uid, gid)
+ })
+ if err != nil {
+ return &PathError{Op: "chownat", Path: name, Err: err}
+ }
+ return err
+}
+
func rootMkdir(r *Root, name string, perm FileMode) error {
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, mkdirat(parent, name, perm)
diff --git a/src/os/root_unix.go b/src/os/root_unix.go
index 06da8da15e..76d6b74eb7 100644
--- a/src/os/root_unix.go
+++ b/src/os/root_unix.go
@@ -157,6 +157,14 @@ func chmodat(parent int, name string, mode FileMode) error {
})
}
+func chownat(parent int, name string, uid, gid int) error {
+ return afterResolvingSymlink(parent, name, func() error {
+ return ignoringEINTR(func() error {
+ return unix.Fchownat(parent, name, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
+ })
+ })
+}
+
func mkdirat(fd int, name string, perm FileMode) error {
return ignoringEINTR(func() error {
return unix.Mkdirat(fd, name, syscallMode(perm))
diff --git a/src/os/root_unix_test.go b/src/os/root_unix_test.go
new file mode 100644
index 0000000000..280efc6875
--- /dev/null
+++ b/src/os/root_unix_test.go
@@ -0,0 +1,87 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1
+
+package os_test
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "syscall"
+ "testing"
+)
+
+func TestRootChown(t *testing.T) {
+ if runtime.GOOS == "wasip1" {
+ t.Skip("Chown not supported on " + runtime.GOOS)
+ }
+
+ // Look up the current default uid/gid.
+ f := newFile(t)
+ dir, err := f.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ sys := dir.Sys().(*syscall.Stat_t)
+
+ groups, err := os.Getgroups()
+ if err != nil {
+ t.Fatalf("getgroups: %v", err)
+ }
+ groups = append(groups, os.Getgid())
+ for _, test := range rootTestCases {
+ test.run(t, func(t *testing.T, target string, root *os.Root) {
+ if target != "" {
+ if err := os.WriteFile(target, nil, 0o666); err != nil {
+ t.Fatal(err)
+ }
+ }
+ for _, gid := range groups {
+ err := root.Chown(test.open, -1, gid)
+ if errEndsTest(t, err, test.wantError, "root.Chown(%q, -1, %v)", test.open, gid) {
+ return
+ }
+ checkUidGid(t, target, int(sys.Uid), gid)
+ }
+ })
+ }
+}
+
+func TestRootConsistencyChown(t *testing.T) {
+ if runtime.GOOS == "wasip1" {
+ t.Skip("Chown not supported on " + runtime.GOOS)
+ }
+ groups, err := os.Getgroups()
+ if err != nil {
+ t.Fatalf("getgroups: %v", err)
+ }
+ var gid int
+ if len(groups) == 0 {
+ gid = os.Getgid()
+ } else {
+ gid = groups[0]
+ }
+ for _, test := range rootConsistencyTestCases {
+ test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
+ chown := os.Chown
+ lstat := os.Lstat
+ if r != nil {
+ chown = r.Chown
+ lstat = r.Lstat
+ }
+ err := chown(path, -1, gid)
+ if err != nil {
+ return "", err
+ }
+ fi, err := lstat(path)
+ if err != nil {
+ return "", err
+ }
+ sys := fi.Sys().(*syscall.Stat_t)
+ return fmt.Sprintf("%v %v", sys.Uid, sys.Gid), nil
+ })
+ }
+}
diff --git a/src/os/root_windows.go b/src/os/root_windows.go
index 9b57d5648e..4f391cb2a7 100644
--- a/src/os/root_windows.go
+++ b/src/os/root_windows.go
@@ -276,6 +276,10 @@ func chmodat(parent syscall.Handle, name string, mode FileMode) error {
return windows.SetFileInformationByHandle(h, windows.FileBasicInfo, unsafe.Pointer(&fbi), uint32(unsafe.Sizeof(fbi)))
}
+func chownat(parent syscall.Handle, name string, uid, gid int) error {
+ return syscall.EWINDOWS // matches syscall.Chown
+}
+
func mkdirat(dirfd syscall.Handle, name string, perm FileMode) error {
return windows.Mkdirat(dirfd, name, syscallMode(perm))
}