From b5115a13e2ff08e30be84d4bc01de96996d507f0 Mon Sep 17 00:00:00 2001 From: Eduard Zateev Date: Sun, 18 Oct 2026 10:33:03 +0200 Subject: [PATCH] capability: fall back to prctl when procfs is absent LastCap fails in root filesystems without /proc/sys/kernel/cap_last_cap. Probe PR_CAPBSET_READ on the running kernel when the procfs file is missing, and stop at the first unsupported capability. Add a regression test that compares this fallback with the procfs result. Fixes: #265 Signed-off-by: Eduard Zateev --- capability/capability_linux.go | 28 +++++++++++++++++++++++++--- capability/lastcap_linux_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 capability/lastcap_linux_test.go diff --git a/capability/capability_linux.go b/capability/capability_linux.go index 234b1efb..ce7be00e 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 00000000..64668ab7 --- /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) + } +}