From 6ad5c630dfdcb2b77e15e8eefcb1a2473d58bb6d Mon Sep 17 00:00:00 2001 From: Christiano Haesbaert Date: Fri, 1 Dec 2023 16:34:47 +0100 Subject: [PATCH] providers/linux: Fix linux Capabilities I believe this never worked, as readCapabilities() expected a Capabilities line but was passed the full file as input, it also would allocate its own CapabilityInfo and return it, even though it would fill one member. So change all this: o Scan the file line by line, as to avoid loading a full file into memory, then parse out only the desired lines and pass down. o Pass the structure as a parameter and let each member be filled. o Error out if we failed to report at least one of the capabilities. o Add a test against init(1) as it will """always""" be run as root and have all capabilities, test is a bit conservative but should be ok for now. --- .changelog/196.txt | 3 +++ providers/linux/capabilities_linux.go | 18 +++++++----------- providers/linux/process_linux.go | 21 +++++++++++++++++++-- system_test.go | 22 ++++++++++++++++++++++ 4 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 .changelog/196.txt diff --git a/.changelog/196.txt b/.changelog/196.txt new file mode 100644 index 00000000..3168dbb3 --- /dev/null +++ b/.changelog/196.txt @@ -0,0 +1,3 @@ +```release-note:bug +linux: fix linux capabilities +``` diff --git a/providers/linux/capabilities_linux.go b/providers/linux/capabilities_linux.go index 40bf454d..b89f0b8c 100644 --- a/providers/linux/capabilities_linux.go +++ b/providers/linux/capabilities_linux.go @@ -83,40 +83,36 @@ func capabilityName(num int) string { return strconv.Itoa(num) } -func readCapabilities(content []byte) (*types.CapabilityInfo, error) { - var cap types.CapabilityInfo - - err := parseKeyValue(content, ':', func(key, value []byte) error { +func decodeCapabilityLine(content string, capInfo *types.CapabilityInfo) error { + return parseKeyValue([]byte(content), ':', func(key, value []byte) error { var err error switch string(key) { case "CapInh": - cap.Inheritable, err = decodeBitMap(string(value), capabilityName) + capInfo.Inheritable, err = decodeBitMap(string(value), capabilityName) if err != nil { return err } case "CapPrm": - cap.Permitted, err = decodeBitMap(string(value), capabilityName) + capInfo.Permitted, err = decodeBitMap(string(value), capabilityName) if err != nil { return err } case "CapEff": - cap.Effective, err = decodeBitMap(string(value), capabilityName) + capInfo.Effective, err = decodeBitMap(string(value), capabilityName) if err != nil { return err } case "CapBnd": - cap.Bounding, err = decodeBitMap(string(value), capabilityName) + capInfo.Bounding, err = decodeBitMap(string(value), capabilityName) if err != nil { return err } case "CapAmb": - cap.Ambient, err = decodeBitMap(string(value), capabilityName) + capInfo.Ambient, err = decodeBitMap(string(value), capabilityName) if err != nil { return err } } return nil }) - - return &cap, err } diff --git a/providers/linux/process_linux.go b/providers/linux/process_linux.go index 52bae255..9610da53 100644 --- a/providers/linux/process_linux.go +++ b/providers/linux/process_linux.go @@ -18,6 +18,7 @@ package linux import ( + "bufio" "bytes" "io/ioutil" "os" @@ -213,13 +214,29 @@ func (p *process) Seccomp() (*types.SeccompInfo, error) { return readSeccompFields(content) } +// Returns error if at least one capability errored out, while still +// returning the successful ones func (p *process) Capabilities() (*types.CapabilityInfo, error) { - content, err := ioutil.ReadFile(p.path("status")) + var gotErr error + f, err := os.OpenFile(p.path("status"), os.O_RDONLY, 0644) if err != nil { return nil, err } + defer f.Close() + scanner := bufio.NewScanner(bufio.NewReader(f)) + var capInfo types.CapabilityInfo + for scanner.Scan() { + line := scanner.Text() + if len(line) != 24 || line[:3] != "Cap" { + continue + } + err = decodeCapabilityLine(line, &capInfo) + if err != nil && gotErr == nil { + gotErr = err + } + } - return readCapabilities(content) + return &capInfo, gotErr } func (p *process) User() (types.UserInfo, error) { diff --git a/system_test.go b/system_test.go index efb872e3..44590cf0 100644 --- a/system_test.go +++ b/system_test.go @@ -310,3 +310,25 @@ func TestProcesses(t *testing.T) { info.StartTime) } } + +func TestCapabilities(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("capabilities is linux only") + } + init, err := Process(1) + if err != nil { + t.Fatal(err) + } + v, ok := init.(types.Capabilities) + if !ok { + t.Fatal("capabilities is expected to be implemented for linux") + } + capInfo, err := v.Capabilities() + if err != nil { + t.Fatal(err) + } + totalCaps := 41 + assert.EqualValues(t, len(capInfo.Permitted), totalCaps) + assert.EqualValues(t, len(capInfo.Effective), totalCaps) + assert.EqualValues(t, len(capInfo.Bounding), totalCaps) +}