diff --git a/capability/CHANGELOG.md b/capability/CHANGELOG.md index 1dd54ad..23614c9 100644 --- a/capability/CHANGELOG.md +++ b/capability/CHANGELOG.md @@ -5,6 +5,13 @@ from https://github.com/syndtr/gocapability/commit/42c35b4376354fd5. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed +* [Load] now reads ambient and bounding capabilities of the current thread, + rather than of the main thread. Before this change, when called from a + thread other than the main one, it could return wrong values. (#273) + ## [0.5.0] - 2026-10-23 ### Breaking changes @@ -111,6 +118,7 @@ This is an initial release since the fork. [ListKnown]: https://pkg.go.dev/github.com/moby/sys/capability#ListKnown [ListSupported]: https://pkg.go.dev/github.com/moby/sys/capability#ListSupported [List]: https://pkg.go.dev/github.com/moby/sys/capability#List +[Load]: https://pkg.go.dev/github.com/moby/sys/capability#Capabilities.Load [NewFile2]: https://pkg.go.dev/github.com/moby/sys/capability#NewFile2 [NewFile]: https://pkg.go.dev/github.com/moby/sys/capability#NewFile [NewPid2]: https://pkg.go.dev/github.com/moby/sys/capability#NewPid2 diff --git a/capability/capability_linux.go b/capability/capability_linux.go index af40f85..7f40302 100644 --- a/capability/capability_linux.go +++ b/capability/capability_linux.go @@ -13,6 +13,8 @@ import ( "fmt" "io" "os" + "runtime" + "strconv" "strings" "sync" "syscall" @@ -338,18 +340,41 @@ func (c *capsV3) String() (ret string) { return mkString(c, BOUNDING) } +// openStatus opens the status file of the current thread (rather than of +// the thread group leader, as /proc/self/status is), since capabilities +// are per-thread. +func openStatus() (*os.File, error) { + f, err := os.Open("/proc/thread-self/status") + if !errors.Is(err, os.ErrNotExist) { + return f, err + } + // Pre-3.17 kernel, no /proc/thread-self. + f, err = os.Open("/proc/self/task/" + strconv.Itoa(syscall.Gettid()) + "/status") + if !errors.Is(err, os.ErrNotExist) { + return f, err + } + // Our pid namespace doesn't match the one of /proc mount. + return os.Open("/proc/self/status") +} + func (c *capsV3) Load() (err error) { + if c.hdr.pid == 0 { + // Make sure capget and status file refer to the same thread. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + } + err = capget(&c.hdr, &c.data[0]) if err != nil { return } - path := "/proc/self/status" - if c.hdr.pid != 0 { - path = fmt.Sprintf("/proc/%d/status", c.hdr.pid) + var f *os.File + if c.hdr.pid == 0 { + f, err = openStatus() + } else { + f, err = os.Open(fmt.Sprintf("/proc/%d/status", c.hdr.pid)) } - - f, err := os.Open(path) if err != nil { return } diff --git a/capability/capability_test.go b/capability/capability_test.go index fa6d076..2f48c8f 100644 --- a/capability/capability_test.go +++ b/capability/capability_test.go @@ -16,6 +16,16 @@ import ( . "github.com/moby/sys/capability" ) +func init() { + if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { + // Lock the main goroutine to the main thread, so that tests + // (which run in separate goroutines) never run on the main + // thread. This ensures per-thread capabilities are handled + // properly. + runtime.LockOSThread() + } +} + func requirePCapSet(t testing.TB) { t.Helper() pid, err := NewPid2(0)