diff --git a/CHANGELOG.md b/CHANGELOG.md index a4e249459..c9e20de71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Running a container which does not have its own mount namespace no longer changes the propagation of host's `/` and of the parent mount of the container rootfs (they were made private or slave). (#5537) +- runc now errors out when seccomp `defaultErrnoRet` or `errnoRet` is set for + an action which does not support it (i.e. anything other than + `SCMP_ACT_ERRNO` and `SCMP_ACT_TRACE`), as required by the runtime spec. + Previously, such values were silently ignored. (#5550, #5564) ## [1.6.0-rc.1] - 2026-10-06 diff --git a/libcontainer/configs/validate/validator.go b/libcontainer/configs/validate/validator.go index 5960661db..df046dd2a 100644 --- a/libcontainer/configs/validate/validator.go +++ b/libcontainer/configs/validate/validator.go @@ -32,6 +32,7 @@ func Validate(config *configs.Config) error { rootfs, rootlessEUIDCheck, scheduler, + seccompCheck, selinuxCheck, sysctl, uts, @@ -543,3 +544,24 @@ func memoryPolicy(config *configs.Config) error { } return nil } + +func seccompCheck(config *configs.Config) error { + sc := config.Seccomp + if sc == nil { + return nil + } + // The runtime spec requires an error if errnoRet + // is set for an action that does not support it. + supportsErrno := func(a configs.Action) bool { + return a == configs.Errno || a == configs.Trace + } + if sc.DefaultErrnoRet != nil && !supportsErrno(sc.DefaultAction) { + return errors.New("seccomp: defaultErrnoRet is only supported for SCMP_ACT_ERRNO and SCMP_ACT_TRACE actions") + } + for _, call := range sc.Syscalls { + if call != nil && call.ErrnoRet != nil && !supportsErrno(call.Action) { + return fmt.Errorf("seccomp: syscall %q: errnoRet is only supported for SCMP_ACT_ERRNO and SCMP_ACT_TRACE actions", call.Name) + } + } + return nil +} diff --git a/libcontainer/configs/validate/validator_test.go b/libcontainer/configs/validate/validator_test.go index fd7a26337..b0bf75ba6 100644 --- a/libcontainer/configs/validate/validator_test.go +++ b/libcontainer/configs/validate/validator_test.go @@ -1168,3 +1168,80 @@ func TestDevValidName(t *testing.T) { }) } } + +func TestValidateSeccompErrnoRet(t *testing.T) { + errno := uint(1) + testCases := []struct { + name string + seccomp *configs.Seccomp + isErr bool + }{ + { + name: "defaultErrnoRet with SCMP_ACT_ERRNO", + seccomp: &configs.Seccomp{ + DefaultAction: configs.Errno, + DefaultErrnoRet: &errno, + }, + }, + { + name: "defaultErrnoRet with SCMP_ACT_TRACE", + seccomp: &configs.Seccomp{ + DefaultAction: configs.Trace, + DefaultErrnoRet: &errno, + }, + }, + { + name: "defaultErrnoRet with SCMP_ACT_ALLOW", + seccomp: &configs.Seccomp{ + DefaultAction: configs.Allow, + DefaultErrnoRet: &errno, + }, + isErr: true, + }, + { + name: "errnoRet with SCMP_ACT_ERRNO", + seccomp: &configs.Seccomp{ + DefaultAction: configs.Allow, + Syscalls: []*configs.Syscall{ + {Name: "mkdir", Action: configs.Errno, ErrnoRet: &errno}, + }, + }, + }, + { + name: "errnoRet with SCMP_ACT_KILL_PROCESS", + seccomp: &configs.Seccomp{ + DefaultAction: configs.Allow, + Syscalls: []*configs.Syscall{ + {Name: "mkdir", Action: configs.KillProcess, ErrnoRet: &errno}, + }, + }, + isErr: true, + }, + { + name: "errnoRet with redundant SCMP_ACT_ALLOW", + seccomp: &configs.Seccomp{ + DefaultAction: configs.Allow, + Syscalls: []*configs.Syscall{ + {Name: "mkdir", Action: configs.Allow, ErrnoRet: &errno}, + }, + }, + isErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config := &configs.Config{ + Rootfs: "/var", + Seccomp: tc.seccomp, + } + err := Validate(config) + if tc.isErr && err == nil { + t.Error("expected error, got nil") + } + if !tc.isErr && err != nil { + t.Errorf("expected nil, got error %v", err) + } + }) + } +} diff --git a/tests/integration/seccomp.bats b/tests/integration/seccomp.bats index 87dc8aa23..414dd80c9 100644 --- a/tests/integration/seccomp.bats +++ b/tests/integration/seccomp.bats @@ -60,6 +60,27 @@ function teardown() { assert_output --partial "Network is down" } +@test "runc run [seccomp] (errnoRet with unsupported action)" { + update_config '.process.args = ["/bin/true"]' + + # defaultErrnoRet with a default action not supporting it. + update_config '.linux.seccomp = { + "defaultAction":"SCMP_ACT_ALLOW", + "defaultErrnoRet": 1 + }' + run ! runc run test_busybox + assert_output --partial "defaultErrnoRet is only supported for" + + # errnoRet with a syscall action not supporting it (the rule is + # redundant as it is the same as the default action). + update_config '.linux.seccomp = { + "defaultAction":"SCMP_ACT_ALLOW", + "syscalls":[{"names":["mkdir","mkdirat"], "action":"SCMP_ACT_ALLOW", "errnoRet": 1}] + }' + run ! runc run test_busybox + assert_output --regexp 'syscall .*mkdir.*: errnoRet is only supported for' +} + # Prints the numeric value of provided seccomp flags combination. # The parameter is flags string, as supplied in OCI spec, for example # '"SECCOMP_FILTER_FLAG_TSYNC","SECCOMP_FILTER_FLAG_LOG"'.