diff --git a/capability_linux.go b/capability_linux.go index 8334530..994bb13 100644 --- a/capability_linux.go +++ b/capability_linux.go @@ -12,6 +12,7 @@ import ( "fmt" "io" "os" + "strconv" "strings" "sync" "syscall" @@ -23,43 +24,42 @@ const ( linuxCapVer3 = 0x20080522 ) -var capLastCap Cap - -func init() { - if initLastCap() == nil { - CAP_LAST_CAP = capLastCap - if capLastCap > 31 { - capUpperMask = (uint32(1) << (uint(capLastCap) - 31)) - 1 - } else { - capUpperMask = 0 - } - } -} - -func initLastCap() error { - if capLastCap != 0 { - return nil - } - +// LastCap returns highest valid capability of the running kernel. +var LastCap = sync.OnceValues(func() (Cap, error) { f, err := os.Open("/proc/sys/kernel/cap_last_cap") if err != nil { - return err + return 0, err } - defer f.Close() - var b []byte = make([]byte, 11) - _, err = f.Read(b) + buf := make([]byte, 11) + l, err := f.Read(buf) + f.Close() if err != nil { - return err + return 0, err } + buf = buf[:l] - fmt.Sscanf(string(b), "%d", &capLastCap) + last, err := strconv.Atoi(strings.TrimSpace(string(buf))) + if err != nil { + return 0, err + } + return Cap(last), nil +}) - return nil +func capUpperMask() uint32 { + last, err := LastCap() + if err != nil || last < 32 { + return 0 + } + return (uint32(1) << (uint(last) - 31)) - 1 } func mkStringCap(c Capabilities, which CapType) (ret string) { - for i, first := Cap(0), true; i <= CAP_LAST_CAP; i++ { + last, err := LastCap() + if err != nil { + return "" + } + for i, first := Cap(0), true; i <= last; i++ { if !c.Get(which, i) { continue } @@ -179,7 +179,8 @@ func (c *capsV3) Full(which CapType) bool { if (data[0] & 0xffffffff) != 0xffffffff { return false } - return (data[1] & capUpperMask) == capUpperMask + mask := capUpperMask() + return (data[1] & mask) == mask } func (c *capsV3) Set(which CapType, caps ...Cap) { @@ -327,6 +328,10 @@ func (c *capsV3) Load() (err error) { } func (c *capsV3) Apply(kind CapType) (err error) { + last, err := LastCap() + if err != nil { + return err + } if kind&BOUNDS == BOUNDS { var data [2]capData err = capget(&c.hdr, &data[0]) @@ -334,7 +339,7 @@ func (c *capsV3) Apply(kind CapType) (err error) { return } if (1<= 2.6.32, and + // - CAP_MAC_ADMIN (33) was added in 2.6.25; + // - CAP_SYSLOG (34) was added in 2.6.38; + // - CAP_CHECKPOINT_RESTORE (40) was added in 5.9, and it is + // the last added capability as of today (July 2024); + // LastCap return value should be between minCap and maxCap. + minCap := CAP_MAC_ADMIN + maxCap := CAP_CHECKPOINT_RESTORE + if last < minCap { + t.Fatalf("LastCap returned %d (%s), expected >= %d (%s)", + last, last, minCap, minCap) + } + if last > maxCap { + t.Fatalf("LastCap returned %d, expected <= %d (%s). Package needs to be updated.", + last, maxCap, maxCap) + } +} diff --git a/capability_test.go b/capability_test.go index f974086..f44e9cf 100644 --- a/capability_test.go +++ b/capability_test.go @@ -43,6 +43,10 @@ func TestState(t *testing.T) { } } + last, err := LastCap() + if err != nil { + t.Fatal(err) + } capf := new(capsFile) capf.data.version = 2 for _, tc := range []struct { @@ -51,9 +55,9 @@ func TestState(t *testing.T) { sets CapType max Cap }{ - {"v3", new(capsV3), EFFECTIVE | PERMITTED | BOUNDING, CAP_LAST_CAP}, + {"v3", new(capsV3), EFFECTIVE | PERMITTED | BOUNDING, last}, {"file_v1", new(capsFile), EFFECTIVE | PERMITTED, CAP_AUDIT_CONTROL}, - {"file_v2", capf, EFFECTIVE | PERMITTED, CAP_LAST_CAP}, + {"file_v2", capf, EFFECTIVE | PERMITTED, last}, } { testEmpty(tc.name, tc.c, tc.sets) tc.c.Fill(CAPS | BOUNDS) @@ -62,14 +66,14 @@ func TestState(t *testing.T) { tc.c.Clear(CAPS | BOUNDS) testEmpty(tc.name, tc.c, tc.sets) for i := CapType(1); i <= BOUNDING; i <<= 1 { - for j := Cap(0); j <= CAP_LAST_CAP; j++ { + for j := Cap(0); j <= last; j++ { tc.c.Set(i, j) } } testFull(tc.name, tc.c, tc.sets) testGet(tc.name, tc.c, tc.sets, tc.max) for i := CapType(1); i <= BOUNDING; i <<= 1 { - for j := Cap(0); j <= CAP_LAST_CAP; j++ { + for j := Cap(0); j <= last; j++ { tc.c.Unset(i, j) } } diff --git a/enum.go b/enum.go index ad10785..27c89a6 100644 --- a/enum.go +++ b/enum.go @@ -300,10 +300,3 @@ const ( // Introduced in kernel 5.9 CAP_CHECKPOINT_RESTORE = Cap(40) ) - -var ( - // Highest valid capability of the running kernel. - CAP_LAST_CAP = Cap(63) - - capUpperMask = ^uint32(0) -)