Repository: opencontainers/runc
Pull request #2863 (2021-03-19 09:56:20)

capabilities.Caps: use a map for capability-types
This extracts the refactoring commits from https://github.com/opencontainers/runc/pull/2854


Patch:
diff --git a/libcontainer/capabilities/capabilities.go b/libcontainer/capabilities/capabilities.go
index adbf6330..dea33159 100644
--- a/libcontainer/capabilities/capabilities.go
+++ b/libcontainer/capabilities/capabilities.go
@@ -10,9 +10,18 @@ import (
 	"github.com/syndtr/gocapability/capability"
 )
 
-const allCapabilityTypes = capability.CAPS | capability.BOUNDS | capability.AMBS
+const allCapabilityTypes = capability.CAPS | capability.BOUNDING | capability.AMBIENT
 
-var capabilityMap map[string]capability.Cap
+var (
+	capabilityMap map[string]capability.Cap
+	capTypes      = []capability.CapType{
+		capability.BOUNDING,
+		capability.PERMITTED,
+		capability.INHERITABLE,
+		capability.EFFECTIVE,
+		capability.AMBIENT,
+	}
+)
 
 func init() {
 	capabilityMap = make(map[string]capability.Cap, capability.CAP_LAST_CAP+1)
@@ -24,37 +33,41 @@ func init() {
 	}
 }
 
-// New creates a new Caps from the given Capabilities config.
+// New creates a new Caps from the given Capabilities config. Unknown Capabilities
+// or Capabilities that are unavailable in the current environment produce an error.
 func New(capConfig *configs.Capabilities) (*Caps, error) {
 	var (
-		err  error
-		caps Caps
+		err error
+		c   = Caps{caps: make(map[capability.CapType][]capability.Cap, len(capTypes))}
 	)
 
-	if caps.bounding, err = capSlice(capConfig.Bounding); err != nil {
+	if c.caps[capability.BOUNDING], err = capSlice(capConfig.Bounding); err != nil {
 		return nil, err
 	}
-	if caps.effective, err = capSlice(capConfig.Effective); err != nil {
+	if c.caps[capability.EFFECTIVE], err = capSlice(capConfig.Effective); err != nil {
 		return nil, err
 	}
-	if caps.inheritable, err = capSlice(capConfig.Inheritable); err != nil {
+	if c.caps[capability.INHERITABLE], err = capSlice(capConfig.Inheritable); err != nil {
 		return nil, err
 	}
-	if caps.permitted, err = capSlice(capConfig.Permitted); err != nil {
+	if c.caps[capability.PERMITTED], err = capSlice(capConfig.Permitted); err != nil {
 		return nil, err
 	}
-	if caps.ambient, err = capSlice(capConfig.Ambient); err != nil {
+	if c.caps[capability.AMBIENT], err = capSlice(capConfig.Ambient); err != nil {
 		return nil, err
 	}
-	if caps.pid, err = capability.NewPid2(0); err != nil {
+	if c.pid, err = capability.NewPid2(0); err != nil {
 		return nil, err
 	}
-	if err = caps.pid.Load(); err != nil {
+	if err = c.pid.Load(); err != nil {
 		return nil, err
 	}
-	return &caps, nil
+	return &c, nil
 }
 
+// capSlice converts the slice of capability names in caps, to their numeric
+// equivalent, and returns them as a slice. Unknown or unavailable capabilities
+// produce an error.
 func capSlice(caps []string) ([]capability.Cap, error) {
 	out := make([]capability.Cap, len(caps))
 	for i, c := range caps {
@@ -69,28 +82,22 @@ func capSlice(caps []string) ([]capability.Cap, error) {
 
 // Caps holds the capabilities for a container.
 type Caps struct {
-	pid         capability.Capabilities
-	bounding    []capability.Cap
-	effective   []capability.Cap
-	inheritable []capability.Cap
-	permitted   []capability.Cap
-	ambient     []capability.Cap
+	pid  capability.Capabilities
+	caps map[capability.CapType][]capability.Cap
 }
 
 // ApplyBoundingSet sets the capability bounding set to those specified in the whitelist.
 func (c *Caps) ApplyBoundingSet() error {
-	c.pid.Clear(capability.BOUNDS)
-	c.pid.Set(capability.BOUNDS, c.bounding...)
-	return c.pid.Apply(capability.BOUNDS)
+	c.pid.Clear(capability.BOUNDING)
+	c.pid.Set(capability.BOUNDING, c.caps[capability.BOUNDING]...)
+	return c.pid.Apply(capability.BOUNDING)
 }
 
 // Apply sets all the capabilities for the current process in the config.
 func (c *Caps) ApplyCaps() error {
 	c.pid.Clear(allCapabilityTypes)
-	c.pid.Set(capability.BOUNDS, c.bounding...)
-	c.pid.Set(capability.PERMITTED, c.permitted...)
-	c.pid.Set(capability.INHERITABLE, c.inheritable...)
-	c.pid.Set(capability.EFFECTIVE, c.effective...)
-	c.pid.Set(capability.AMBIENT, c.ambient...)
+	for _, g := range capTypes {
+		c.pid.Set(g, c.caps[g]...)
+	}
 	return c.pid.Apply(allCapabilityTypes)
 }


Test patch:
diff --git a/libcontainer/capabilities/capabilities_linux_test.go b/libcontainer/capabilities/capabilities_linux_test.go
new file mode 100644
index 00000000..36b8d186
--- /dev/null
+++ b/libcontainer/capabilities/capabilities_linux_test.go
@@ -0,0 +1,39 @@
+package capabilities
+
+import (
+	"testing"
+
+	"github.com/opencontainers/runc/libcontainer/configs"
+	"github.com/syndtr/gocapability/capability"
+)
+
+func TestNew(t *testing.T) {
+	cs := []string{"CAP_CHOWN"}
+	conf := configs.Capabilities{
+		Bounding:    cs,
+		Effective:   cs,
+		Inheritable: cs,
+		Permitted:   cs,
+		Ambient:     cs,
+	}
+
+	caps, err := New(&conf)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if len(caps.caps) != len(capTypes) {
+		t.Errorf("expected %d capability types, got %d: %v", len(capTypes), len(caps.caps), caps.caps)
+	}
+
+	for _, cType := range capTypes {
+		if i := len(caps.caps[cType]); i != 1 {
+			t.Errorf("expected 1 capability for %s, got %d: %v", cType, i, caps.caps[cType])
+			continue
+		}
+		if caps.caps[cType][0] != capability.CAP_CHOWN {
+			t.Errorf("expected CAP_CHOWN, got %s: ", caps.caps[cType][0])
+			continue
+		}
+	}
+}