Skip to content

Commit 4724c2a

Browse files
committed
libct: add integration unit test to check that setnsProcess can retry cmd.Start method if clone3 do not support CLONE_INTO_CGROUP flag
TestCmdRetry is a syntetic test to force retry of cmd.Start method on the new Linux kernels (after 5.7). It can be done if cgroup fd points to the file from non-cgroup tree. In runc there a lot of prefix checks after joining pathes. Also, when container is created from factory, fs2 cgroup manager will choose the path from cgroup tree by default. However, when container is loaded from state.json its path is not checked that's why it is possible to pass the wrong path. Also, we should turn on TestMode in opencontainers/cgroups library. Signed-off-by: Efim Verzakov <[email protected]>
1 parent 506a849 commit 4724c2a

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed

libcontainer/integration/exec_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ import (
1414
"strings"
1515
"syscall"
1616
"testing"
17+
"time"
1718

19+
securejoin "github.com/cyphar/filepath-securejoin"
1820
"github.com/opencontainers/cgroups"
1921
"github.com/opencontainers/cgroups/systemd"
2022
"github.com/opencontainers/runc/internal/linux"
2123
"github.com/opencontainers/runc/internal/pathrs"
2224
"github.com/opencontainers/runc/libcontainer"
2325
"github.com/opencontainers/runc/libcontainer/configs"
2426
"github.com/opencontainers/runc/libcontainer/internal/userns"
27+
"github.com/opencontainers/runc/libcontainer/utils"
2528
"github.com/opencontainers/runtime-spec/specs-go"
2629

2730
"golang.org/x/sys/unix"
@@ -231,6 +234,124 @@ func TestEnter(t *testing.T) {
231234
}
232235
}
233236

237+
const stateFilename = "state.json"
238+
239+
// We need to dump changed state into state file.
240+
func saveState(stateDir string, s *libcontainer.State) (retErr error) {
241+
tmpFile, err := os.CreateTemp(stateDir, "state-")
242+
if err != nil {
243+
return err
244+
}
245+
246+
defer func() {
247+
if retErr != nil {
248+
tmpFile.Close()
249+
os.Remove(tmpFile.Name())
250+
}
251+
}()
252+
253+
err = utils.WriteJSON(tmpFile, s)
254+
if err != nil {
255+
return err
256+
}
257+
err = tmpFile.Close()
258+
if err != nil {
259+
return err
260+
}
261+
262+
stateFilePath := filepath.Join(stateDir, stateFilename)
263+
return os.Rename(tmpFile.Name(), stateFilePath)
264+
}
265+
266+
func TestCmdRetry(t *testing.T) {
267+
if testing.Short() {
268+
return
269+
}
270+
271+
if !cgroups.IsCgroup2UnifiedMode() {
272+
t.Skip("CLONE_INTO_CGROUP works only with cgroup v2")
273+
}
274+
275+
// Create dir to "pseudo" cgroup path
276+
pseudoPath := t.TempDir()
277+
278+
config := newTemplateConfig(t, nil)
279+
280+
name := strings.ReplaceAll(t.Name(), "/", "_") + strconv.FormatInt(-int64(time.Now().Nanosecond()), 35)
281+
root := t.TempDir()
282+
283+
// Container State Dir
284+
stateDir, err := securejoin.SecureJoin(root, name)
285+
ok(t, err)
286+
287+
// Create Container
288+
container, err := libcontainer.Create(root, name, config)
289+
290+
ok(t, err)
291+
defer destroyContainer(container)
292+
293+
// Execute a first process in the container
294+
stdinR, stdinW, err := os.Pipe()
295+
ok(t, err)
296+
297+
var stdout, stdout2 bytes.Buffer
298+
299+
pconfig := libcontainer.Process{
300+
Cwd: "/",
301+
Args: []string{"sh", "-c", "cat"},
302+
Env: standardEnvironment,
303+
Stdin: stdinR,
304+
Stdout: &stdout,
305+
Init: true,
306+
}
307+
308+
err = container.Run(&pconfig)
309+
_ = stdinR.Close()
310+
defer stdinW.Close()
311+
ok(t, err)
312+
313+
state, err := container.State()
314+
ok(t, err)
315+
316+
// Dump our "pseudo" cgroup path as dirPath
317+
state.CgroupPaths[""] = pseudoPath
318+
err = saveState(stateDir, state)
319+
ok(t, err)
320+
321+
// Load container from dumped state
322+
container2, err := libcontainer.Load(root, name)
323+
ok(t, err)
324+
325+
// Execute another process in the container
326+
stdinR2, stdinW2, err := os.Pipe()
327+
ok(t, err)
328+
pconfig2 := libcontainer.Process{
329+
Cwd: "/",
330+
Env: standardEnvironment,
331+
}
332+
pconfig2.Args = []string{"sh", "-c", "cat"}
333+
pconfig2.Stdin = stdinR2
334+
pconfig2.Stdout = &stdout2
335+
336+
// Turn on TestMode in cgroups library to not check path mode and create all necessary files.
337+
cgroups.TestMode = true
338+
defer func() {
339+
cgroups.TestMode = false
340+
}()
341+
342+
err = container2.Run(&pconfig2)
343+
_ = stdinR2.Close()
344+
defer stdinW2.Close()
345+
ok(t, err)
346+
347+
// Wait processes
348+
_ = stdinW2.Close()
349+
waitProcess(&pconfig2, t)
350+
351+
_ = stdinW.Close()
352+
waitProcess(&pconfig, t)
353+
}
354+
234355
func TestProcessEnv(t *testing.T) {
235356
if testing.Short() {
236357
return

0 commit comments

Comments
 (0)