diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go index 7cf2fb65751..18955cf95c9 100644 --- a/libcontainer/configs/config.go +++ b/libcontainer/configs/config.go @@ -31,12 +31,13 @@ type IDMap struct { // for syscalls. Additional architectures can be added by specifying them in // Architectures. type Seccomp struct { - DefaultAction Action `json:"default_action"` - Architectures []string `json:"architectures"` - Syscalls []*Syscall `json:"syscalls"` - DefaultErrnoRet *uint `json:"default_errno_ret"` - ListenerPath string `json:"listener_path,omitempty"` - ListenerMetadata string `json:"listener_metadata,omitempty"` + DefaultAction Action `json:"default_action"` + Architectures []string `json:"architectures"` + Flags []specs.LinuxSeccompFlag `json:"flags"` + Syscalls []*Syscall `json:"syscalls"` + DefaultErrnoRet *uint `json:"default_errno_ret"` + ListenerPath string `json:"listener_path,omitempty"` + ListenerMetadata string `json:"listener_metadata,omitempty"` } // Action is taken upon rule match in Seccomp diff --git a/libcontainer/seccomp/seccomp_linux.go b/libcontainer/seccomp/seccomp_linux.go index 8c12af72be9..5fce47ef4be 100644 --- a/libcontainer/seccomp/seccomp_linux.go +++ b/libcontainer/seccomp/seccomp_linux.go @@ -13,6 +13,7 @@ import ( "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/seccomp/patchbpf" + "github.com/opencontainers/runtime-spec/specs-go" ) var ( @@ -86,6 +87,29 @@ func InitSeccomp(config *configs.Seccomp) (int, error) { } } + // Add extra flags + for _, flag := range config.Flags { + switch specs.LinuxSeccompFlag(flag) { + case "SECCOMP_FILTER_FLAG_TSYNC": + // libseccomp-golang always use filterAttrTsync when + // possible so all goroutines will receive the same + // rules, so there is nothing to do. It does not make + // sense to apply the seccomp filter on only one + // thread; other threads will be terminated after exec + // anyway. + case specs.LinuxSeccompFlagLog: + if err := filter.SetLogBit(true); err != nil { + return -1, fmt.Errorf("error adding log flag to seccomp filter: %w", err) + } + case specs.LinuxSeccompFlagSpecAllow: + if err := filter.SetSSB(true); err != nil { + return -1, fmt.Errorf("error adding SSB flag to seccomp filter: %w", err) + } + default: + return -1, fmt.Errorf("seccomp flags %q not yet supported by runc", flag) + } + } + // Unset no new privs bit if err := filter.SetNoNewPrivsBit(false); err != nil { return -1, fmt.Errorf("error setting no new privileges: %w", err) diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index 7ff9f098d6a..0bca2a2657b 100644 --- a/libcontainer/specconv/spec_linux.go +++ b/libcontainer/specconv/spec_linux.go @@ -1016,14 +1016,22 @@ func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) { return nil, nil } - // We don't currently support seccomp flags. - if len(config.Flags) != 0 { - return nil, errors.New("seccomp flags are not yet supported by runc") - } - newConfig := new(configs.Seccomp) newConfig.Syscalls = []*configs.Syscall{} + // The list of flags defined in runtime-spec is a subset of the flags + // in the seccomp() syscall + for _, flag := range config.Flags { + switch flag { + case "SECCOMP_FILTER_FLAG_TSYNC": + // Tsync can be silently ignored + case specs.LinuxSeccompFlagLog, specs.LinuxSeccompFlagSpecAllow: + newConfig.Flags = append(newConfig.Flags, flag) + default: + return nil, fmt.Errorf("seccomp flags %q not yet supported by runc", flag) + } + } + if len(config.Architectures) > 0 { newConfig.Architectures = []string{} for _, arch := range config.Architectures { diff --git a/tests/integration/seccomp.bats b/tests/integration/seccomp.bats index c24eeb26a0f..55e6dc817ab 100644 --- a/tests/integration/seccomp.bats +++ b/tests/integration/seccomp.bats @@ -66,6 +66,38 @@ function teardown() { [[ "$output" == *"Network is down"* ]] } +@test "runc run [seccomp] (SECCOMP_FILTER_FLAG_*)" { + # Linux 4.14: SECCOMP_FILTER_FLAG_LOG + # Linux 4.17: SECCOMP_FILTER_FLAG_SPEC_ALLOW + requires_kernel 4.17 + SECCOMP_FILTER_FLAGS=( + '' # no flag + '"SECCOMP_FILTER_FLAG_LOG"' + '"SECCOMP_FILTER_FLAG_SPEC_ALLOW"' + '"SECCOMP_FILTER_FLAG_TSYNC"' + '"SECCOMP_FILTER_FLAG_LOG","SECCOMP_FILTER_FLAG_SPEC_ALLOW"' + '"SECCOMP_FILTER_FLAG_LOG","SECCOMP_FILTER_FLAG_TSYNC"' + '"SECCOMP_FILTER_FLAG_SPEC_ALLOW","SECCOMP_FILTER_FLAG_TSYNC"' + '"SECCOMP_FILTER_FLAG_LOG","SECCOMP_FILTER_FLAG_SPEC_ALLOW","SECCOMP_FILTER_FLAG_TSYNC"' + ) + for flags in "${SECCOMP_FILTER_FLAGS[@]}"; do + update_config ' .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"] + | .process.noNewPrivileges = false + | .linux.seccomp = { + "defaultAction":"SCMP_ACT_ALLOW", + "architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32","SCMP_ARCH_X86_64","SCMP_ARCH_AARCH64","SCMP_ARCH_ARM"], + "flags":['"${flags}"'], + "syscalls":[{"names":["mkdir"], "action":"SCMP_ACT_ERRNO"}] + }' + + # This test checks that the flags are accepted without errors but does + # not check they are effectively applied + runc run test_busybox + [ "$status" -ne 0 ] + [[ "$output" == *"mkdir:"*"/dev/shm/foo"*"Operation not permitted"* ]] + done +} + @test "runc run [seccomp] (SCMP_ACT_KILL)" { update_config ' .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"] | .process.noNewPrivileges = false