Skip to content

Commit 38d26a8

Browse files
committed
profiles/seccomp: use conditional (hard-coded) errNoRet for MIPS/non-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>
1 parent 33a3680 commit 38d26a8

File tree

5 files changed

+178
-5
lines changed

5 files changed

+178
-5
lines changed

profiles/seccomp/default.json

+30
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,36 @@
635635
],
636636
"action": "SCMP_ACT_ERRNO",
637637
"errnoRet": 38,
638+
"comment": "ENOSYS for non-mips architectures",
639+
"excludes": {
640+
"caps": [
641+
"CAP_SYS_ADMIN"
642+
],
643+
"arches": [
644+
"mips3l64n32",
645+
"mips64",
646+
"mips64n32",
647+
"mipsel",
648+
"mipsel64"
649+
]
650+
}
651+
},
652+
{
653+
"names": [
654+
"clone3"
655+
],
656+
"action": "SCMP_ACT_ERRNO",
657+
"errnoRet": 89,
658+
"comment": "ENOSYS for mips architectures",
659+
"includes": {
660+
"arches": [
661+
"mips3l64n32",
662+
"mips64",
663+
"mips64n32",
664+
"mipsel",
665+
"mipsel64"
666+
]
667+
},
638668
"excludes": {
639669
"caps": [
640670
"CAP_SYS_ADMIN"

profiles/seccomp/default_linux.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,26 @@ func arches() []Architecture {
4141
}
4242
}
4343

44+
const (
45+
enosys uint = 0x26 // enosys for non-mips architectures.
46+
enosysMIPS uint = 0x59 // enosys for mips architectures.
47+
)
48+
4449
// DefaultProfile defines the allowed syscalls for the default seccomp profile.
4550
func DefaultProfile() *Seccomp {
46-
nosys := uint(unix.ENOSYS)
51+
// The value of ENOSYS differs between MIPS and non-MIPS architectures. While
52+
// this is not problematic for the embedded seccomp profile, it prevents the
53+
// profile from being saved as a portable JSON file that can be used for both
54+
// architectures.
55+
// To work around this situation, we include conditional rules for both arches.
56+
// and hard-code the value for ENOSYS in both.
57+
// For more details, refer to https://github.com/moby/moby/pull/42836#issuecomment-963429850
58+
// and https://github.com/opencontainers/runtime-spec/pull/1087#issuecomment-963463475
59+
var (
60+
nosys = enosys
61+
nosysMIPS = enosysMIPS
62+
)
63+
4764
syscalls := []*Syscall{
4865
{
4966
LinuxSyscall: specs.LinuxSyscall{
@@ -626,6 +643,24 @@ func DefaultProfile() *Seccomp {
626643
Action: specs.ActErrno,
627644
ErrnoRet: &nosys,
628645
},
646+
Comment: "ENOSYS for non-mips architectures",
647+
Excludes: &Filter{
648+
Arches: []string{"mips3l64n32", "mips64", "mips64n32", "mipsel", "mipsel64"},
649+
Caps: []string{"CAP_SYS_ADMIN"},
650+
},
651+
},
652+
{
653+
LinuxSyscall: specs.LinuxSyscall{
654+
Names: []string{
655+
"clone3",
656+
},
657+
Action: specs.ActErrno,
658+
ErrnoRet: &nosysMIPS,
659+
},
660+
Comment: "ENOSYS for mips architectures",
661+
Includes: &Filter{
662+
Arches: []string{"mips3l64n32", "mips64", "mips64n32", "mipsel", "mipsel64"},
663+
},
629664
Excludes: &Filter{
630665
Caps: []string{"CAP_SYS_ADMIN"},
631666
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"defaultAction": "SCMP_ACT_ERRNO",
3+
"syscalls": [
4+
{
5+
"names": ["clone3"],
6+
"action": "SCMP_ACT_ALLOW",
7+
"includes": {
8+
"caps": ["CAP_SYS_ADMIN"]
9+
}
10+
},
11+
{
12+
"names": ["clone3"],
13+
"action": "SCMP_ACT_ERRNO",
14+
"errnoRet": 38,
15+
"comment": "ENOSYS for non-mips architectures",
16+
"excludes": {
17+
"caps": ["CAP_SYS_ADMIN"],
18+
"arches": ["mips3l64n32", "mips64", "mips64n32", "mipsel", "mipsel64"]
19+
}
20+
},
21+
{
22+
"names": ["clone3"],
23+
"action": "SCMP_ACT_ERRNO",
24+
"errnoRet": 89,
25+
"comment": "ENOSYS for mips architectures",
26+
"includes": {
27+
"arches": ["mips3l64n32", "mips64", "mips64n32", "mipsel", "mipsel64"]
28+
},
29+
"excludes": {
30+
"caps": ["CAP_SYS_ADMIN"]
31+
}
32+
}
33+
]
34+
}

profiles/seccomp/seccomp_linux.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ import (
1313

1414
// GetDefaultProfile returns the default seccomp profile.
1515
func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) {
16-
return setupSeccomp(DefaultProfile(), rs)
16+
return setupSeccomp(DefaultProfile(), rs, runtime.GOARCH)
1717
}
1818

1919
// LoadProfile takes a json string and decodes the seccomp profile.
2020
func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
21+
return loadProfile(body, rs, runtime.GOARCH)
22+
}
23+
24+
// loadProfile is used to override GOARCH for testing.
25+
func loadProfile(body string, rs *specs.Spec, goarch string) (*specs.LinuxSeccomp, error) {
2126
var config Seccomp
2227
if err := json.Unmarshal([]byte(body), &config); err != nil {
2328
return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err)
2429
}
25-
return setupSeccomp(&config, rs)
30+
return setupSeccomp(&config, rs, goarch)
2631
}
2732

2833
// libseccomp string => seccomp arch
@@ -72,7 +77,7 @@ func inSlice(slice []string, s string) bool {
7277
return false
7378
}
7479

75-
func setupSeccomp(config *Seccomp, rs *specs.Spec) (*specs.LinuxSeccomp, error) {
80+
func setupSeccomp(config *Seccomp, rs *specs.Spec, goarch string) (*specs.LinuxSeccomp, error) {
7681
if config == nil {
7782
return nil, nil
7883
}
@@ -96,7 +101,7 @@ func setupSeccomp(config *Seccomp, rs *specs.Spec) (*specs.LinuxSeccomp, error)
96101
var (
97102
// Copy all common / standard properties to the output profile
98103
newConfig = &config.LinuxSeccomp
99-
arch = goToNative[runtime.GOARCH]
104+
arch = goToNative[goarch]
100105
)
101106
if seccompArch, ok := nativeToSeccomp[arch]; ok {
102107
for _, a := range config.ArchMap {

profiles/seccomp/seccomp_test.go

+69
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,75 @@ func TestLoadConditional(t *testing.T) {
283283
}
284284
}
285285

286+
func TestLoadConditionalClone3(t *testing.T) {
287+
f, err := os.ReadFile("fixtures/conditional_clone3.json")
288+
if err != nil {
289+
t.Fatal(err)
290+
}
291+
tests := []struct {
292+
doc string
293+
cap string
294+
goarch string
295+
expectedAction specs.LinuxSeccompAction
296+
expectedErrNoRet uint
297+
}{
298+
{
299+
doc: "clone3 default amd64",
300+
goarch: "amd64",
301+
expectedAction: specs.ActErrno,
302+
expectedErrNoRet: enosys,
303+
},
304+
{
305+
doc: "clone3 default mips",
306+
goarch: "mips64",
307+
expectedAction: specs.ActErrno,
308+
expectedErrNoRet: enosysMIPS,
309+
},
310+
{
311+
doc: "clone3 cap_sys_admin amd64",
312+
cap: "CAP_SYS_ADMIN",
313+
goarch: "amd64",
314+
expectedAction: specs.ActAllow,
315+
},
316+
{
317+
doc: "clone3 cap_sys_admin mips",
318+
cap: "CAP_SYS_ADMIN",
319+
goarch: "mips64",
320+
expectedAction: specs.ActAllow,
321+
},
322+
}
323+
324+
for _, tc := range tests {
325+
tc := tc
326+
t.Run(tc.doc, func(t *testing.T) {
327+
rs := createSpec(tc.cap)
328+
p, err := loadProfile(string(f), &rs, tc.goarch)
329+
if err != nil {
330+
t.Fatal(err)
331+
}
332+
if len(p.Syscalls) != 1 {
333+
t.Fatalf("expected 1 syscall in profile, have %d", len(p.Syscalls))
334+
}
335+
sc := p.Syscalls[0]
336+
if sc.Names[0] != "clone3" {
337+
t.Fatalf("expected clone3 syscall, have %s", sc.Names[0])
338+
}
339+
if sc.Action != tc.expectedAction {
340+
t.Fatalf("expected %s action, have %s", tc.expectedAction, sc.Action)
341+
}
342+
if tc.expectedErrNoRet != 0 {
343+
if *sc.ErrnoRet != tc.expectedErrNoRet {
344+
t.Fatalf("expected %d errNoRet, have %d", tc.expectedErrNoRet, *sc.ErrnoRet)
345+
}
346+
} else {
347+
if sc.ErrnoRet != nil {
348+
t.Fatalf("expected errNoRet to be nil, have %d", *sc.ErrnoRet)
349+
}
350+
}
351+
})
352+
}
353+
}
354+
286355
// createSpec() creates a minimum spec for testing
287356
func createSpec(caps ...string) specs.Spec {
288357
rs := specs.Spec{

0 commit comments

Comments
 (0)