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

Commit

Permalink
Introduce support for syscall filtering in containers #237
Browse files Browse the repository at this point in the history
This PR introduces the ability to filter system calls on a per-container basis on Linux, using libseccomp to support multiple architectures.

This adds another layer of security between containers and the kernel. System calls which are unnecessary in a container or problematic from a security perspective can be restricted to prevent their use. Most of the truly problematic syscalls are already restricted by dropping capabilities; this adds an additional, finer-grained layer of protection.

This PR adds a vendored library dependency (Go bindings for libseccomp) and a build dependency on libseccomp >= v2.1. The actual changes to libcontainer are fairly minimal, most of the delta is in the libseccomp bindings.

Docker-DCO-1.1-Signed-off-by: Dan Walsh <[email protected]> (github: rhatdan)
Docker-DCO-1.1-Signed-off-by: Matt Heon <[email protected]> (github: mheon)
  • Loading branch information
mheon committed Mar 16, 2015
1 parent 71a5716 commit 34c8e16
Show file tree
Hide file tree
Showing 14 changed files with 2,643 additions and 3 deletions.
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.SeccompConfig `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.SeccompConfig{
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.SeccompConfig{
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.SeccompConfig{
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

0 comments on commit 34c8e16

Please sign in to comment.