diff options
| author | Damien Neil <dneil@google.com> | 2025-02-10 15:35:17 -0800 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-02-13 15:29:56 -0800 |
| commit | 807a51b391c8a8d949d6fa00c26953ba0f8ae267 (patch) | |
| tree | 318c1e6490e53fd5ce21f13cab2bab8b17d8e079 /src/os | |
| parent | 187fd2698d2f9fc2fc52aa7d4c0922552f848e98 (diff) | |
| download | go-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/os')
| -rw-r--r-- | src/os/root.go | 6 | ||||
| -rw-r--r-- | src/os/root_noopenat.go | 10 | ||||
| -rw-r--r-- | src/os/root_openat.go | 10 | ||||
| -rw-r--r-- | src/os/root_unix.go | 8 | ||||
| -rw-r--r-- | src/os/root_unix_test.go | 87 | ||||
| -rw-r--r-- | src/os/root_windows.go | 4 |
6 files changed, 125 insertions, 0 deletions
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)) } |
