diff --git a/libcontainer/integration/update_test.go b/libcontainer/integration/update_test.go new file mode 100644 index 00000000000..d94c215389f --- /dev/null +++ b/libcontainer/integration/update_test.go @@ -0,0 +1,106 @@ +package integration + +import ( + "bytes" + "os" + "strings" + "testing" + + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/cgroups/systemd" + "github.com/opencontainers/runc/libcontainer/devices" +) + +func testUpdateDevices(t *testing.T, systemd bool) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(t, &tParam{ + rootfs: rootfs, + systemd: systemd, + }) + container, err := newContainer(t, config) + ok(t, err) + defer destroyContainer(container) + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + Init: true, + } + err = container.Run(process) + _ = stdinR.Close() + defer func() { + _ = stdinW.Close() + if _, err := process.Wait(); err != nil { + t.Log(err) + } + }() + ok(t, err) + + var buf bytes.Buffer + devCheck := &libcontainer.Process{ + Cwd: "/", + Args: []string{"/bin/sh", "-c", "echo > /dev/full; cat /dev/null; true"}, + Env: standardEnvironment, + Stderr: &buf, + } + isAllowed := true + expected := map[bool][]string{ + true: { + "write error: No space left on device", // from write to /dev/full + // no error from cat /dev/null + }, + false: { + "/dev/full: Operation not permitted", + `cat: can't open '/dev/null': Operation not permitted`, + }, + } + defaultDevices := config.Cgroups.Resources.Devices + + for i := 0; i < 300; i++ { + // Check the access + buf.Reset() + err = container.Run(devCheck) + ok(t, err) + waitProcess(devCheck, t) + + t.Logf("[%d] allowed: %v output: %s", i, isAllowed, buf.String()) + + for _, exp := range expected[isAllowed] { + if !strings.Contains(buf.String(), exp) { + t.Fatalf("[%d] expected %q, got %q", i, exp, buf.String()) + } + } + + // Now flip the access permission + isAllowed = !isAllowed + if isAllowed { + config.Cgroups.Resources.Devices = defaultDevices + } else { + config.Cgroups.Resources.Devices = []*devices.Rule{} + } + if err := container.Set(*config); err != nil { + t.Fatal(err) + } + } +} + +func TestUpdateDevices(t *testing.T) { + testUpdateDevices(t, false) +} + +func TestUpdateDevicesSystemd(t *testing.T) { + if !systemd.IsRunningSystemd() { + t.Skip("Test requires systemd.") + } + testUpdateDevices(t, true) +} diff --git a/tests/integration/update.bats b/tests/integration/update.bats index 41344a9dc22..e204ea49550 100644 --- a/tests/integration/update.bats +++ b/tests/integration/update.bats @@ -648,29 +648,3 @@ EOF runc resume test_update [ "$status" -eq 0 ] } - -@test "runc update replaces devices cgroup program" { - [[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup - - # Unfortunately we can't update device rules directly with runc ("runc - # update" doesn't support it, and adding support would require ironing out - # some long-standing design issues with device configuration). So instead - # we just run "runc update" many times, relying on the fact that runc will - # re-apply devices cgroup rules on each runc update. - # - # In the past runc would not delete old cgroupv2 eBPF programs, so this - # test ensures that once we go past the program limit (64 stacked programs - # at time of writing) you can still run "runc" update. - - # Run the container in the background. - runc run -d --console-socket "$CONSOLE_SOCKET" test_update - [ "$status" -eq 0 ] - - for new_limit in $(seq 300); do - runc update --pids-limit "$((2 * new_limit))" test_update - [ "$status" -eq 0 ] - done - - # The container should still be running. - testcontainer test_update running -}