Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

Introduce support for syscall filtering in containers #237 #384

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
FROM golang:1.4

RUN apt-get update && apt-get install -y libseccomp2 libseccomp-dev
RUN go get golang.org/x/tools/cmd/cover

ENV GOPATH $GOPATH:/go/src/github.com/docker/libcontainer/vendor
Expand All @@ -17,7 +18,7 @@ WORKDIR /go/src/github.com/docker/libcontainer
RUN cp sample_configs/minimal.json /busybox/container.json

RUN go get -d -v ./...
RUN make direct-install
RUN make TEST_TAGS='-tags seccomp' direct-install

ENTRYPOINT ["/dind"]
CMD ["make", "direct-test"]
CMD ["make", "TEST_TAGS=-tags seccomp", "direct-test"]
9 changes: 8 additions & 1 deletion configs/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package configs

import "fmt"
import (
"fmt"

"github.com/docker/libcontainer/security/seccomp"
)

type Rlimit struct {
Type int `json:"type"`
Expand Down Expand Up @@ -96,6 +100,9 @@ type Config struct {
// ReadonlyPaths specifies paths within the container's rootfs to remount as read-only
// so that these files prevent any writes.
ReadonlyPaths []string `json:"readonly_paths"`

// SeccompConfig holds information on system calls to be restricted in the container
SeccompConfig seccomp.Config `json:"seccomp_config,omitempty"`
}

// Gets the root uid for the process on host which could be non-zero
Expand Down
208 changes: 208 additions & 0 deletions integration/seccomp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// +build seccomp,linux,cgo

package integration

import (
"strings"
"syscall"
"testing"

"github.com/docker/libcontainer"
"github.com/docker/libcontainer/security/seccomp"
)

func TestSeccompDenyGetcwd(t *testing.T) {
if testing.Short() {
return
}

rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)

config := newTemplateConfig(rootfs)
config.SeccompConfig = seccomp.Config{
Enable: true,
WhitelistToggle: false,
Syscalls: []*seccomp.BlockedSyscall{
{
Name: "getcwd",
},
},
}

container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()

buffers := newStdBuffers()
pwd := &libcontainer.Process{
Args: []string{"pwd"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}

err = container.Start(pwd)
if err != nil {
t.Fatal(err)
}
ps, err := pwd.Wait()
if err == nil {
t.Fatal("Expecting error (negative return code); instead exited cleanly!")
}

var exitCode int
status := ps.Sys().(syscall.WaitStatus)
if status.Exited() {
exitCode = status.ExitStatus()
} else if status.Signaled() {
exitCode = -int(status.Signal())
} else {
t.Fatalf("Unrecognized exit reason!")
}

if exitCode == 0 {
t.Fatalf("Getcwd should fail with negative exit code, instead got %d!", exitCode)
}

expected := "pwd: getcwd: Operation not permitted"
actual := strings.Trim(buffers.Stderr.String(), "\n")
if actual != expected {
t.Fatalf("Expected output %s but got %s\n", expected, actual)
}
}

func TestSeccompPermitWriteConditional(t *testing.T) {
if testing.Short() {
return
}

rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)

config := newTemplateConfig(rootfs)
config.SeccompConfig = seccomp.Config{
Enable: true,
WhitelistToggle: false,
Syscalls: []*seccomp.BlockedSyscall{
{
Name: "write",
Conditions: []seccomp.SyscallCondition{
{
Argument: 0,
Operator: ">",
ValueOne: 1,
},
},
},
},
}

container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()

buffers := newStdBuffers()
dmesg := &libcontainer.Process{
Args: []string{"busybox", "ls", "/"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}

err = container.Start(dmesg)
if err != nil {
t.Fatal(err)
}
if _, err := dmesg.Wait(); err != nil {
t.Fatalf("%s: %s", err, buffers.Stderr)
}
}

func TestSeccompDenyWriteConditional(t *testing.T) {
if testing.Short() {
return
}

rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)

config := newTemplateConfig(rootfs)
config.SeccompConfig = seccomp.Config{
Enable: true,
WhitelistToggle: false,
Syscalls: []*seccomp.BlockedSyscall{
{
Name: "write",
Conditions: []seccomp.SyscallCondition{
{
Argument: 0,
Operator: ">",
ValueOne: 1,
},
},
},
},
}

container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()

buffers := newStdBuffers()
dmesg := &libcontainer.Process{
Args: []string{"busybox", "ls", "does_not_exist"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}

err = container.Start(dmesg)
if err != nil {
t.Fatal(err)
}

ps, err := dmesg.Wait()
if err == nil {
t.Fatal("Expecting negative return, instead got 0!")
}

var exitCode int
status := ps.Sys().(syscall.WaitStatus)
if status.Exited() {
exitCode = status.ExitStatus()
} else if status.Signaled() {
exitCode = -int(status.Signal())
} else {
t.Fatalf("Unrecognized exit reason!")
}

if exitCode == 0 {
t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode)
}

// We're denying write to stderr, so we expect an empty buffer
expected := ""
actual := strings.Trim(buffers.Stderr.String(), "\n")
if actual != expected {
t.Fatalf("Expected output %s but got %s\n", expected, actual)
}
}
Loading