Skip to content

Commit

Permalink
Remove init and CAP_LAST_CAP, add LastCap
Browse files Browse the repository at this point in the history
One (minor) issue with this package is it has func init which reads a
file in /proc, making the start of any program which imports the package
a bit slower.

Let's switch to lazy initialization, i.e. only read the file when needed.
Unfortunately, this can not be done in a non-disruptive manner, since we
have a public variable CAP_LAST_CAP.

So, this is a disruptive change, and anyone who's using CAP_LAST_CAP
should change their code to something like this:

	last, err := capability.LastCap()
	if err != nil {
		return err
	}

Also, add a test case for LastCap.

Reported-by: ningmingxiao <ning.mingxiao@zte.com.cn>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
  • Loading branch information
kolyshkin committed Jul 30, 2024
1 parent c9a8d67 commit 58343c8
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 41 deletions.
66 changes: 36 additions & 30 deletions capability_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"fmt"
"io"
"os"
"strconv"
"strings"
"sync"
"syscall"
Expand All @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -327,14 +328,18 @@ 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])
if err != nil {
return
}
if (1<<uint(CAP_SETPCAP))&data[0].effective != 0 {
for i := Cap(0); i <= CAP_LAST_CAP; i++ {
for i := Cap(0); i <= last; i++ {
if c.Get(BOUNDING, i) {
continue
}
Expand All @@ -359,7 +364,7 @@ func (c *capsV3) Apply(kind CapType) (err error) {
}

if kind&AMBS == AMBS {
for i := Cap(0); i <= CAP_LAST_CAP; i++ {
for i := Cap(0); i <= last; i++ {
action := pr_CAP_AMBIENT_LOWER
if c.Get(AMBIENT, i) {
action = pr_CAP_AMBIENT_RAISE
Expand Down Expand Up @@ -440,7 +445,8 @@ func (c *capsFile) 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 *capsFile) Set(which CapType, caps ...Cap) {
Expand Down
29 changes: 29 additions & 0 deletions capability_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package capability

import "testing"

func TestLastCap(t *testing.T) {
last, err := LastCap()
if err != nil {
t.Fatal(err)
}

// Sanity checks.
//
// Based on the fact Go 1.18+ supports Linux >= 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)
}
}
12 changes: 8 additions & 4 deletions capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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)
}
}
Expand Down
7 changes: 0 additions & 7 deletions enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)

0 comments on commit 58343c8

Please sign in to comment.