Browse code

seccomp: allow specifying a custom profile with `--privileged`

`--privileged --security-opt seccomp=<CUSTOM.json>` was ignoring
`<CUSTOM.json>`.

Fix issue 47499

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>

Akihiro Suda authored on 2024/03/05 23:37:29
Showing 3 changed files
... ...
@@ -22,7 +22,11 @@ func WithSeccomp(daemon *Daemon, c *container.Container) coci.SpecOpts {
22 22
 			return nil
23 23
 		}
24 24
 		if c.HostConfig.Privileged {
25
-			return nil
25
+			var err error
26
+			if c.SeccompProfile != "" {
27
+				s.Linux.Seccomp, err = seccomp.LoadProfile(c.SeccompProfile, s)
28
+			}
29
+			return err
26 30
 		}
27 31
 		if !daemon.RawSysInfo().Seccomp {
28 32
 			if c.SeccompProfile != "" && c.SeccompProfile != dconfig.SeccompProfileDefault {
... ...
@@ -40,7 +40,9 @@ func TestWithSeccomp(t *testing.T) {
40 40
 			outSpec: oci.DefaultLinuxSpec(),
41 41
 		},
42 42
 		{
43
-			comment: "privileged container w/ custom profile runs unconfined",
43
+			// Prior to Docker v27, it had resulted in unconfined.
44
+			// https://github.com/moby/moby/pull/47500
45
+			comment: "privileged container w/ custom profile",
44 46
 			daemon: &Daemon{
45 47
 				sysInfo: &sysinfo.SysInfo{Seccomp: true},
46 48
 			},
... ...
@@ -50,8 +52,15 @@ func TestWithSeccomp(t *testing.T) {
50 50
 					Privileged: true,
51 51
 				},
52 52
 			},
53
-			inSpec:  oci.DefaultLinuxSpec(),
54
-			outSpec: oci.DefaultLinuxSpec(),
53
+			inSpec: oci.DefaultLinuxSpec(),
54
+			outSpec: func() coci.Spec {
55
+				s := oci.DefaultLinuxSpec()
56
+				profile := &specs.LinuxSeccomp{
57
+					DefaultAction: specs.LinuxSeccompAction("SCMP_ACT_LOG"),
58
+				}
59
+				s.Linux.Seccomp = profile
60
+				return s
61
+			}(),
55 62
 		},
56 63
 		{
57 64
 			comment: "privileged container w/ default runs unconfined",
... ...
@@ -353,6 +353,51 @@ func TestWorkingDirNormalization(t *testing.T) {
353 353
 
354 354
 			assert.Check(t, is.Equal(inspect.Config.WorkingDir, "/tmp"))
355 355
 		})
356
+	}
357
+}
358
+
359
+func TestSeccomp(t *testing.T) {
360
+	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
361
+
362
+	ctx := setupTest(t)
363
+	apiClient := testEnv.APIClient()
356 364
 
365
+	const confined = `{
366
+ "defaultAction": "SCMP_ACT_ALLOW",
367
+ "syscalls": [ { "names": [ "chown", "chown32", "fchownat" ], "action": "SCMP_ACT_ERRNO" } ]
368
+}
369
+`
370
+	type testCase struct {
371
+		ops              []func(*container.TestContainerConfig)
372
+		expectedExitCode int
373
+	}
374
+	testCases := []testCase{
375
+		{
376
+			ops:              nil,
377
+			expectedExitCode: 0,
378
+		},
379
+		{
380
+			ops:              []func(*container.TestContainerConfig){container.WithPrivileged(true)},
381
+			expectedExitCode: 0,
382
+		},
383
+		{
384
+			ops:              []func(*container.TestContainerConfig){container.WithSecurityOpt("seccomp=" + confined)},
385
+			expectedExitCode: 1,
386
+		},
387
+		{
388
+			// A custom profile should be still enabled, even when --privileged is set
389
+			// https://github.com/moby/moby/issues/47499
390
+			ops:              []func(*container.TestContainerConfig){container.WithPrivileged(true), container.WithSecurityOpt("seccomp=" + confined)},
391
+			expectedExitCode: 1,
392
+		},
393
+	}
394
+	for _, tc := range testCases {
395
+		cID := container.Run(ctx, t, apiClient, tc.ops...)
396
+		res, err := container.Exec(ctx, apiClient, cID, []string{"chown", "42", "/bin/true"})
397
+		assert.NilError(t, err)
398
+		assert.Equal(t, tc.expectedExitCode, res.ExitCode)
399
+		if tc.expectedExitCode != 0 {
400
+			assert.Check(t, is.Contains(res.Stderr(), "Operation not permitted"))
401
+		}
357 402
 	}
358 403
 }