Skip to content

Commit

Permalink
Merge pull request #6 from kolyshkin/cap-vers
Browse files Browse the repository at this point in the history
Remove init(), CAP_LAST_CAP; add LastCap
  • Loading branch information
kolyshkin authored Jul 30, 2024
2 parents 2926cc7 + 58343c8 commit 634ecd5
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 54 deletions.
95 changes: 52 additions & 43 deletions capability_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"fmt"
"io"
"os"
"strconv"
"strings"
"sync"
"syscall"
)

Expand All @@ -22,50 +24,42 @@ const (
linuxCapVer3 = 0x20080522
)

var (
capVers uint32
capLastCap Cap
)

func init() {
var hdr capHeader
_ = capget(&hdr, nil)
capVers = hdr.version

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 @@ -96,18 +90,27 @@ func mkString(c Capabilities, max CapType) (ret string) {
return
}

func newPid(pid int) (c Capabilities, err error) {
switch capVers {
case 0:
err = errors.New("unable to get capability version from the kernel")
var capVersion = sync.OnceValues(func() (uint32, error) {
var hdr capHeader
err := capget(&hdr, nil)
return hdr.version, err
})

func newPid(pid int) (c Capabilities, retErr error) {
ver, err := capVersion()
if err != nil {
retErr = fmt.Errorf("unable to get capability version from the kernel: %w", err)
return
}
switch ver {
case linuxCapVer1, linuxCapVer2:
err = errors.New("old/unsupported capability version (kernel older than 2.6.26?)")
retErr = errors.New("old/unsupported capability version (kernel older than 2.6.26?)")
default:
// Either linuxCapVer3, or an unknown/future version such as v4.
// In the latter case, we fall back to v3 hoping the kernel is
// backward-compatible to v3.
p := new(capsV3)
p.hdr.version = capVers
p.hdr.version = ver
p.hdr.pid = int32(pid)
c = p
}
Expand Down Expand Up @@ -176,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 @@ -324,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 @@ -356,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 @@ -437,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 634ecd5

Please sign in to comment.