diff --git a/capability/capability_linux.go b/capability/capability_linux.go index 234b1ef..ce7be00 100644 --- a/capability/capability_linux.go +++ b/capability/capability_linux.go @@ -26,14 +26,21 @@ const ( ) var lastCap = sync.OnceValues(func() (Cap, error) { - f, err := os.Open("/proc/sys/kernel/cap_last_cap") + return readLastCap("/proc/sys/kernel/cap_last_cap") +}) + +func readLastCap(path string) (Cap, error) { + f, err := os.Open(path) if err != nil { + if errors.Is(err, os.ErrNotExist) { + return lastCapFromKernel() + } return 0, err } + defer f.Close() buf := make([]byte, 11) l, err := f.Read(buf) - f.Close() if err != nil { return 0, err } @@ -44,7 +51,22 @@ var lastCap = sync.OnceValues(func() (Cap, error) { return 0, err } return Cap(last), nil -}) +} + +func lastCapFromKernel() (Cap, error) { + for cap := Cap(0); ; cap++ { + _, err := prctlRetInt(syscall.PR_CAPBSET_READ, uintptr(cap), 0) + if errors.Is(err, syscall.EINVAL) { + if cap == 0 { + return 0, fmt.Errorf("kernel does not support capability bounding sets") + } + return cap - 1, nil + } + if err != nil { + return 0, fmt.Errorf("querying capability %d: %w", cap, err) + } + } +} func capUpperMask() uint32 { last, err := lastCap() diff --git a/capability/lastcap_linux_test.go b/capability/lastcap_linux_test.go new file mode 100644 index 0000000..64668ab --- /dev/null +++ b/capability/lastcap_linux_test.go @@ -0,0 +1,25 @@ +// Copyright 2026 The Capability Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package capability + +import ( + "path/filepath" + "testing" +) + +func TestLastCapFallsBackWhenProcFileIsMissing(t *testing.T) { + want, err := LastCap() + if err != nil { + t.Fatal(err) + } + + got, err := readLastCap(filepath.Join(t.TempDir(), "cap_last_cap")) + if err != nil { + t.Fatal(err) + } + if got != want { + t.Errorf("LastCap fallback = %d, want %d", got, want) + } +}