diff --git a/actool/manifest.go b/actool/manifest.go index 94eff16a..8a59f8bd 100644 --- a/actool/manifest.go +++ b/actool/manifest.go @@ -258,7 +258,19 @@ func patchManifest(im *schema.ImageManifest) error { return fmt.Errorf("cannot parse isolator %q: %v", is, err) } - if _, ok := types.ResourceIsolatorNames[name]; !ok { + _, ok := types.ResourceIsolatorNames[name] + + if name == types.LinuxNoNewPrivsName { + ok = true + kv := strings.Split(is, ",") + if len(kv) != 2 { + return fmt.Errorf("isolator %s: invalid format", name) + } + + isolatorStr = fmt.Sprintf(`{ "name": "%s", "value": %s }`, name, kv[1]) + } + + if !ok { return fmt.Errorf("isolator %s is not supported for patching", name) } diff --git a/examples/image.json b/examples/image.json index 688aecc6..8b0af6f9 100644 --- a/examples/image.json +++ b/examples/image.json @@ -56,6 +56,10 @@ { "name": "os/linux/capabilities-retain-set", "value": {"set": ["CAP_NET_ADMIN", "CAP_NET_BIND_SERVICE"]} + }, + { + "name": "os/linux/no-new-privs", + "value": true } ], "mountPoints": [ diff --git a/schema/types/isolator_linux_specific.go b/schema/types/isolator_linux_specific.go index b390ed91..5c71ade8 100644 --- a/schema/types/isolator_linux_specific.go +++ b/schema/types/isolator_linux_specific.go @@ -22,6 +22,7 @@ import ( const ( LinuxCapabilitiesRetainSetName = "os/linux/capabilities-retain-set" LinuxCapabilitiesRevokeSetName = "os/linux/capabilities-remove-set" + LinuxNoNewPrivsName = "os/linux/no-new-privs" ) var LinuxIsolatorNames = make(map[ACIdentifier]struct{}) @@ -30,12 +31,31 @@ func init() { for name, con := range map[ACIdentifier]IsolatorValueConstructor{ LinuxCapabilitiesRevokeSetName: func() IsolatorValue { return &LinuxCapabilitiesRevokeSet{} }, LinuxCapabilitiesRetainSetName: func() IsolatorValue { return &LinuxCapabilitiesRetainSet{} }, + LinuxNoNewPrivsName: func() IsolatorValue { v := LinuxNoNewPrivs(false); return &v }, } { AddIsolatorName(name, LinuxIsolatorNames) AddIsolatorValueConstructor(name, con) } } +type LinuxNoNewPrivs bool + +func (l LinuxNoNewPrivs) AssertValid() error { + return nil +} + +func (l *LinuxNoNewPrivs) UnmarshalJSON(b []byte) error { + var v bool + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + + *l = LinuxNoNewPrivs(v) + + return nil +} + type LinuxCapabilitiesSet interface { Set() []LinuxCapability AssertValid() error diff --git a/schema/types/isolator_test.go b/schema/types/isolator_test.go index 785c4f02..0b4ef6d3 100644 --- a/schema/types/isolator_test.go +++ b/schema/types/isolator_test.go @@ -25,6 +25,41 @@ func TestIsolatorUnmarshal(t *testing.T) { msg string werr bool }{ + { + `{ + "name": "os/linux/no-new-privs", + "value": true + }`, + false, + }, + { + `{ + "name": "os/linux/no-new-privs", + "value": false + }`, + false, + }, + { + `{ + "name": "os/linux/no-new-privs", + "value": 123 + }`, + true, + }, + { + `{ + "name": "os/linux/no-new-privs", + "value": {"set": ["CAP_KILL"]} + }`, + true, + }, + { + `{ + "name": "os/linux/no-new-privs", + "value": "foo" + }`, + true, + }, { `{ "name": "os/linux/capabilities-retain-set", @@ -166,6 +201,10 @@ func TestIsolatorsGetByName(t *testing.T) { { "name": "os/linux/capabilities-remove-set", "value": {"set": ["CAP_KILL"]} + }, + { + "name": "os/linux/no-new-privs", + "value": true } ] ` @@ -175,11 +214,13 @@ func TestIsolatorsGetByName(t *testing.T) { wlimit int64 wrequest int64 wset []LinuxCapability + wprivs LinuxNoNewPrivs }{ - {"resource/cpu", 1, 30, nil}, - {"resource/memory", 2147483648, 1000000000, nil}, - {"os/linux/capabilities-retain-set", 0, 0, []LinuxCapability{"CAP_KILL"}}, - {"os/linux/capabilities-remove-set", 0, 0, []LinuxCapability{"CAP_KILL"}}, + {"resource/cpu", 1, 30, nil, false}, + {"resource/memory", 2147483648, 1000000000, nil, false}, + {"os/linux/capabilities-retain-set", 0, 0, []LinuxCapability{"CAP_KILL"}, false}, + {"os/linux/capabilities-remove-set", 0, 0, []LinuxCapability{"CAP_KILL"}, false}, + {"os/linux/no-new-privs", 0, 0, nil, LinuxNoNewPrivs(true)}, } var is Isolators @@ -223,6 +264,11 @@ func TestIsolatorsGetByName(t *testing.T) { t.Errorf("#%d: gset=%v, want %v", i, s.Set(), tt.wset) } + case *LinuxNoNewPrivs: + if tt.wprivs != *v { + t.Errorf("#%d: got %v, want %v", i, v, tt.wprivs) + } + default: panic("unexecpected type") } diff --git a/spec/ace.md b/spec/ace.md index 8086b64f..d6466699 100644 --- a/spec/ace.md +++ b/spec/ace.md @@ -195,6 +195,19 @@ Listing a capability in the remove set that is not in the default set such as `C In the example above, the process will only have the two capabilities in its bounding set. The retain set cannot be used in conjunction with the remove set. +#### os/linux/no-new-privs + +* Scope: app + +If set to true the app's process and all its children can never gain new privileges. For details see the corresponding kernel documentation about [prctl/no_new_privs.txt](https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt). + +```json +"name": "os/linux/no-new-privs", +"value": true +``` + +In the example above, the process will have `no_new_privs` set. If the app's executable has i.e. setuid/setgid bits set they will be ignored. + ### Resource Isolators A _resource_ is something that can be consumed by an application (app) or group of applications (pod), such as memory (RAM), CPU, and network bandwidth.