Skip to content

Commit

Permalink
profiles/seccomp: use conditional (hard-coded) errNoRet for MIPS/non-…
Browse files Browse the repository at this point in the history
…MIPS

The value of ENOSYS differs between MIPS and non-MIPS architectures. While
this is not problematic for the embedded seccomp profile, it prevents the
profile from being saved as a portable JSON file that can be used for both
architectures.

To work around this situation, we include conditional rules for both arches.
and hard-code the value for ENOSYS in both.

For more details, refer to moby#42836 (comment)
and opencontainers/runtime-spec#1087 (comment)

A test was added, which can be run with:

    go test --tags=seccomp -run TestLoadConditionalClone3 -v ./profiles/seccomp/

    === RUN   TestLoadConditionalClone3
    === RUN   TestLoadConditionalClone3/clone3_default_amd64
    === RUN   TestLoadConditionalClone3/clone3_default_mips
    === RUN   TestLoadConditionalClone3/clone3_cap_sys_admin_amd64
    === RUN   TestLoadConditionalClone3/clone3_cap_sys_admin_mips
    --- PASS: TestLoadConditionalClone3 (0.01s)
        --- PASS: TestLoadConditionalClone3/clone3_default_amd64 (0.00s)
        --- PASS: TestLoadConditionalClone3/clone3_default_mips (0.00s)
        --- PASS: TestLoadConditionalClone3/clone3_cap_sys_admin_amd64 (0.00s)
        --- PASS: TestLoadConditionalClone3/clone3_cap_sys_admin_mips (0.00s)
    PASS
    ok  	github.com/docker/docker/profiles/seccomp	0.015s

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
  • Loading branch information
thaJeztah committed Nov 9, 2021
1 parent 33a3680 commit 37382be
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 5 deletions.
30 changes: 30 additions & 0 deletions profiles/seccomp/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,36 @@
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 38,
"comment": "ENOSYS for clone3 on non-mips architectures",
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
],
"arches": [
"mips3l64n32",
"mips64",
"mips64n32",
"mipsel",
"mipsel64"
]
}
},
{
"names": [
"clone3"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 89,
"comment": "ENOSYS for clone3 on mips architectures",
"includes": {
"arches": [
"mips3l64n32",
"mips64",
"mips64n32",
"mipsel",
"mipsel64"
]
},
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
Expand Down
37 changes: 36 additions & 1 deletion profiles/seccomp/default_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,26 @@ func arches() []Architecture {
}
}

const (
enosys uint = 0x26 // enosys for non-mips architectures.
enosysMIPS uint = 0x59 // enosys for mips architectures.
)

// DefaultProfile defines the allowed syscalls for the default seccomp profile.
func DefaultProfile() *Seccomp {
nosys := uint(unix.ENOSYS)
// The value of ENOSYS differs between MIPS and non-MIPS architectures. While
// this is not problematic for the embedded seccomp profile, it prevents the
// profile from being saved as a portable JSON file that can be used for both
// architectures.
// To work around this situation, we include conditional rules for both arches.
// and hard-code the value for ENOSYS in both.
// For more details, refer to https://github.com/moby/moby/pull/42836#issuecomment-963429850
// and https://github.com/opencontainers/runtime-spec/pull/1087#issuecomment-963463475
var (
nosys = enosys
nosysMIPS = enosysMIPS
)

syscalls := []*Syscall{
{
LinuxSyscall: specs.LinuxSyscall{
Expand Down Expand Up @@ -626,6 +643,24 @@ func DefaultProfile() *Seccomp {
Action: specs.ActErrno,
ErrnoRet: &nosys,
},
Comment: "ENOSYS for clone3 on non-mips architectures",
Excludes: &Filter{
Arches: []string{"mips3l64n32", "mips64", "mips64n32", "mipsel", "mipsel64"},
Caps: []string{"CAP_SYS_ADMIN"},
},
},
{
LinuxSyscall: specs.LinuxSyscall{
Names: []string{
"clone3",
},
Action: specs.ActErrno,
ErrnoRet: &nosysMIPS,
},
Comment: "ENOSYS for clone3 on mips architectures",
Includes: &Filter{
Arches: []string{"mips3l64n32", "mips64", "mips64n32", "mipsel", "mipsel64"},
},
Excludes: &Filter{
Caps: []string{"CAP_SYS_ADMIN"},
},
Expand Down
34 changes: 34 additions & 0 deletions profiles/seccomp/fixtures/conditional_clone3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{
"names": ["clone3"],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": ["CAP_SYS_ADMIN"]
}
},
{
"names": ["clone3"],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 38,
"comment": "ENOSYS for clone3 on non-mips architectures",
"excludes": {
"caps": ["CAP_SYS_ADMIN"],
"arches": ["mips3l64n32", "mips64", "mips64n32", "mipsel", "mipsel64"]
}
},
{
"names": ["clone3"],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 89,
"comment": "ENOSYS for clone3 on mips architectures",
"includes": {
"arches": ["mips3l64n32", "mips64", "mips64n32", "mipsel", "mipsel64"]
},
"excludes": {
"caps": ["CAP_SYS_ADMIN"]
}
}
]
}
13 changes: 9 additions & 4 deletions profiles/seccomp/seccomp_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,21 @@ import (

// GetDefaultProfile returns the default seccomp profile.
func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) {
return setupSeccomp(DefaultProfile(), rs)
return setupSeccomp(DefaultProfile(), rs, runtime.GOARCH)
}

// LoadProfile takes a json string and decodes the seccomp profile.
func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
return loadProfile(body, rs, runtime.GOARCH)
}

// loadProfile is used to override GOARCH for testing.
func loadProfile(body string, rs *specs.Spec, goarch string) (*specs.LinuxSeccomp, error) {
var config Seccomp
if err := json.Unmarshal([]byte(body), &config); err != nil {
return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err)
}
return setupSeccomp(&config, rs)
return setupSeccomp(&config, rs, goarch)
}

// libseccomp string => seccomp arch
Expand Down Expand Up @@ -72,7 +77,7 @@ func inSlice(slice []string, s string) bool {
return false
}

func setupSeccomp(config *Seccomp, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
func setupSeccomp(config *Seccomp, rs *specs.Spec, goarch string) (*specs.LinuxSeccomp, error) {
if config == nil {
return nil, nil
}
Expand All @@ -96,7 +101,7 @@ func setupSeccomp(config *Seccomp, rs *specs.Spec) (*specs.LinuxSeccomp, error)
var (
// Copy all common / standard properties to the output profile
newConfig = &config.LinuxSeccomp
arch = goToNative[runtime.GOARCH]
arch = goToNative[goarch]
)
if seccompArch, ok := nativeToSeccomp[arch]; ok {
for _, a := range config.ArchMap {
Expand Down
69 changes: 69 additions & 0 deletions profiles/seccomp/seccomp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,75 @@ func TestLoadConditional(t *testing.T) {
}
}

func TestLoadConditionalClone3(t *testing.T) {
f, err := os.ReadFile("fixtures/conditional_clone3.json")
if err != nil {
t.Fatal(err)
}
tests := []struct {
doc string
cap string
goarch string
expectedAction specs.LinuxSeccompAction
expectedErrNoRet uint
}{
{
doc: "clone3 default amd64",
goarch: "amd64",
expectedAction: specs.ActErrno,
expectedErrNoRet: enosys,
},
{
doc: "clone3 default mips",
goarch: "mips64",
expectedAction: specs.ActErrno,
expectedErrNoRet: enosysMIPS,
},
{
doc: "clone3 cap_sys_admin amd64",
cap: "CAP_SYS_ADMIN",
goarch: "amd64",
expectedAction: specs.ActAllow,
},
{
doc: "clone3 cap_sys_admin mips",
cap: "CAP_SYS_ADMIN",
goarch: "mips64",
expectedAction: specs.ActAllow,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
rs := createSpec(tc.cap)
p, err := loadProfile(string(f), &rs, tc.goarch)
if err != nil {
t.Fatal(err)
}
if len(p.Syscalls) != 1 {
t.Fatalf("expected 1 syscall in profile, have %d", len(p.Syscalls))
}
sc := p.Syscalls[0]
if sc.Names[0] != "clone3" {
t.Fatalf("expected clone3 syscall, have %s", sc.Names[0])
}
if sc.Action != tc.expectedAction {
t.Fatalf("expected %s action, have %s", tc.expectedAction, sc.Action)
}
if tc.expectedErrNoRet != 0 {
if *sc.ErrnoRet != tc.expectedErrNoRet {
t.Fatalf("expected %d errNoRet, have %d", tc.expectedErrNoRet, *sc.ErrnoRet)
}
} else {
if sc.ErrnoRet != nil {
t.Fatalf("expected errNoRet to be nil, have %d", *sc.ErrnoRet)
}
}
})
}
}

// createSpec() creates a minimum spec for testing
func createSpec(caps ...string) specs.Spec {
rs := specs.Spec{
Expand Down

0 comments on commit 37382be

Please sign in to comment.