ソースを参照

inital seccomp support

Signed-off-by: Jessica Frazelle <acidburn@docker.com>
Jessica Frazelle 9 年 前
コミット
6707f4b9b6

+ 1 - 0
container/container_unix.go

@@ -51,6 +51,7 @@ type Container struct {
 	ShmPath         string
 	MqueuePath      string
 	ResolvConfPath  string
+	SeccompProfile  string
 }
 
 // CreateDaemonEnvironment returns the list of all environment variables given the list of

+ 1 - 0
daemon/container_operations_unix.go

@@ -236,6 +236,7 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro
 		Pid:                pid,
 		ReadonlyRootfs:     c.HostConfig.ReadonlyRootfs,
 		RemappedRoot:       remappedRoot,
+		SeccompProfile:     c.SeccompProfile,
 		UIDMapping:         uidMap,
 		UTS:                uts,
 	}

+ 10 - 0
daemon/daemon_test.go

@@ -152,6 +152,16 @@ func TestParseSecurityOpt(t *testing.T) {
 		t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", container.AppArmorProfile)
 	}
 
+	// test seccomp
+	sp := "/path/to/seccomp_test.json"
+	config.SecurityOpt = []string{"seccomp:" + sp}
+	if err := parseSecurityOpt(container, config); err != nil {
+		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
+	}
+	if container.SeccompProfile != sp {
+		t.Fatalf("Unexpected AppArmorProfile, expected: %q, got %q", sp, container.SeccompProfile)
+	}
+
 	// test valid label
 	config.SecurityOpt = []string{"label:user:USER"}
 	if err := parseSecurityOpt(container, config); err != nil {

+ 2 - 0
daemon/daemon_unix.go

@@ -74,6 +74,8 @@ func parseSecurityOpt(container *container.Container, config *runconfig.HostConf
 			labelOpts = append(labelOpts, con[1])
 		case "apparmor":
 			container.AppArmorProfile = con[1]
+		case "seccomp":
+			container.SeccompProfile = con[1]
 		default:
 			return fmt.Errorf("Invalid --security-opt: %q", opt)
 		}

+ 1 - 0
daemon/execdriver/driver_unix.go

@@ -118,6 +118,7 @@ type Command struct {
 	Pid                *Pid              `json:"pid"`
 	ReadonlyRootfs     bool              `json:"readonly_rootfs"`
 	RemappedRoot       *User             `json:"remap_root"`
+	SeccompProfile     string            `json:"seccomp_profile"`
 	UIDMapping         []idtools.IDMap   `json:"uidmapping"`
 	UTS                *UTS              `json:"uts"`
 }

+ 8 - 2
daemon/execdriver/native/create.go

@@ -19,8 +19,8 @@ import (
 
 // createContainer populates and configures the container type with the
 // data provided by the execdriver.Command
-func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (*configs.Config, error) {
-	container := execdriver.InitContainer(c)
+func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (container *configs.Config, err error) {
+	container = execdriver.InitContainer(c)
 
 	if err := d.createIpc(container, c); err != nil {
 		return nil, err
@@ -82,6 +82,12 @@ func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks)
 		container.AppArmorProfile = c.AppArmorProfile
 	}
 
+	if c.SeccompProfile != "" {
+		container.Seccomp, err = loadSeccompProfile(c.SeccompProfile)
+		if err != nil {
+			return nil, err
+		}
+	}
 	if err := execdriver.SetupCgroups(container, c); err != nil {
 		return nil, err
 	}

+ 94 - 0
daemon/execdriver/native/seccomp.go

@@ -0,0 +1,94 @@
+// +build linux
+
+package native
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+
+	"github.com/opencontainers/runc/libcontainer/configs"
+	"github.com/opencontainers/runc/libcontainer/seccomp"
+	"github.com/opencontainers/specs"
+)
+
+func loadSeccompProfile(path string) (*configs.Seccomp, error) {
+	f, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, fmt.Errorf("Opening seccomp profile failed: %v", err)
+	}
+
+	var config specs.Seccomp
+	if err := json.Unmarshal(f, &config); err != nil {
+		return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err)
+	}
+
+	return setupSeccomp(&config)
+}
+
+func setupSeccomp(config *specs.Seccomp) (newConfig *configs.Seccomp, err error) {
+	if config == nil {
+		return nil, nil
+	}
+
+	// No default action specified, no syscalls listed, assume seccomp disabled
+	if config.DefaultAction == "" && len(config.Syscalls) == 0 {
+		return nil, nil
+	}
+
+	newConfig = new(configs.Seccomp)
+	newConfig.Syscalls = []*configs.Syscall{}
+
+	// if config.Architectures == 0 then libseccomp will figure out the architecture to use
+	if len(config.Architectures) > 0 {
+		newConfig.Architectures = []string{}
+		for _, arch := range config.Architectures {
+			newArch, err := seccomp.ConvertStringToArch(string(arch))
+			if err != nil {
+				return nil, err
+			}
+			newConfig.Architectures = append(newConfig.Architectures, newArch)
+		}
+	}
+
+	// Convert default action from string representation
+	newConfig.DefaultAction, err = seccomp.ConvertStringToAction(string(config.DefaultAction))
+	if err != nil {
+		return nil, err
+	}
+
+	// Loop through all syscall blocks and convert them to libcontainer format
+	for _, call := range config.Syscalls {
+		newAction, err := seccomp.ConvertStringToAction(string(call.Action))
+		if err != nil {
+			return nil, err
+		}
+
+		newCall := configs.Syscall{
+			Name:   call.Name,
+			Action: newAction,
+			Args:   []*configs.Arg{},
+		}
+
+		// Loop through all the arguments of the syscall and convert them
+		for _, arg := range call.Args {
+			newOp, err := seccomp.ConvertStringToOperator(string(arg.Op))
+			if err != nil {
+				return nil, err
+			}
+
+			newArg := configs.Arg{
+				Index:    arg.Index,
+				Value:    arg.Value,
+				ValueTwo: arg.ValueTwo,
+				Op:       newOp,
+			}
+
+			newCall.Args = append(newCall.Args, &newArg)
+		}
+
+		newConfig.Syscalls = append(newConfig.Syscalls, &newCall)
+	}
+
+	return newConfig, nil
+}

+ 56 - 0
integration-cli/docker_cli_run_test.go

@@ -3789,3 +3789,59 @@ func (s *DockerSuite) TestRunWithOomScoreAdjInvalidRange(c *check.C) {
 		c.Fatalf("Expected output to contain %q, got %q instead", expected, out)
 	}
 }
+
+// TestRunSeccompProfileDenyUnshare checks that 'docker run --security-opt seccomp:/tmp/profile.json jess/unshare unshare' exits with operation not permitted.
+func (s *DockerSuite) TestRunSeccompProfileDenyUnshare(c *check.C) {
+	testRequires(c, SameHostDaemon)
+	jsonData := `{
+	"defaultAction": "SCMP_ACT_ALLOW",
+	"syscalls": [
+		{
+			"name": "unshare",
+			"action": "SCMP_ACT_ERRNO"
+		}
+	]
+}`
+	tmpFile, err := ioutil.TempFile("", "profile.json")
+	defer tmpFile.Close()
+	if err != nil {
+		c.Fatal(err)
+	}
+
+	if _, err := tmpFile.Write([]byte(jsonData)); err != nil {
+		c.Fatal(err)
+	}
+	runCmd := exec.Command(dockerBinary, "run", "--security-opt", "seccomp:"+tmpFile.Name(), "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc")
+	out, _, _ := runCommandWithOutput(runCmd)
+	if !strings.Contains(out, "Operation not permitted") {
+		c.Fatalf("expected unshare with seccomp profile denied to fail, got %s", out)
+	}
+}
+
+// TestRunSeccompProfileDenyChmod checks that 'docker run --security-opt seccomp:/tmp/profile.json busybox chmod 400 /etc/hostname' exits with operation not permitted.
+func (s *DockerSuite) TestRunSeccompProfileDenyChmod(c *check.C) {
+	testRequires(c, SameHostDaemon)
+	jsonData := `{
+	"defaultAction": "SCMP_ACT_ALLOW",
+	"syscalls": [
+		{
+			"name": "chmod",
+			"action": "SCMP_ACT_ERRNO"
+		}
+	]
+}`
+	tmpFile, err := ioutil.TempFile("", "profile.json")
+	defer tmpFile.Close()
+	if err != nil {
+		c.Fatal(err)
+	}
+
+	if _, err := tmpFile.Write([]byte(jsonData)); err != nil {
+		c.Fatal(err)
+	}
+	runCmd := exec.Command(dockerBinary, "run", "--security-opt", "seccomp:"+tmpFile.Name(), "busybox", "chmod", "400", "/etc/hostname")
+	out, _, _ := runCommandWithOutput(runCmd)
+	if !strings.Contains(out, "Operation not permitted") {
+		c.Fatalf("expected chmod with seccomp profile denied to fail, got %s", out)
+	}
+}