mirror of
https://github.com/opencontainers/runc.git
synced 2025-10-13 03:03:56 +08:00

This includes quite a few cleanups and improvements to the way we do synchronisation. The core behaviour is unchanged, but switching to embedding json.RawMessage into the synchronisation structure will allow us to do more complicated synchronisation operations in future patches. The file descriptor passing through the synchronisation system feature will be used as part of the idmapped-mount and bind-mount-source features when switching that code to use the new mount API outside of nsexec.c. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
546 lines
12 KiB
Go
546 lines
12 KiB
Go
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/containerd/console"
|
|
"github.com/opencontainers/runc/libcontainer"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/opencontainers/runc/libcontainer/utils"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func TestExecIn(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
config := newTemplateConfig(t, nil)
|
|
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 stdinW.Close() //nolint: errcheck
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
ps := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"ps"},
|
|
Env: standardEnvironment,
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
}
|
|
|
|
err = container.Run(ps)
|
|
ok(t, err)
|
|
waitProcess(ps, t)
|
|
_ = stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := buffers.Stdout.String()
|
|
if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") {
|
|
t.Fatalf("unexpected running process, output %q", out)
|
|
}
|
|
if strings.Contains(out, "\r") {
|
|
t.Fatalf("unexpected carriage-return in output %q", out)
|
|
}
|
|
}
|
|
|
|
func TestExecInUsernsRlimit(t *testing.T) {
|
|
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
|
|
t.Skip("Test requires userns.")
|
|
}
|
|
|
|
testExecInRlimit(t, true)
|
|
}
|
|
|
|
func TestExecInRlimit(t *testing.T) {
|
|
testExecInRlimit(t, false)
|
|
}
|
|
|
|
func testExecInRlimit(t *testing.T, userns bool) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
|
|
config := newTemplateConfig(t, &tParam{userns: userns})
|
|
container, err := newContainer(t, config)
|
|
ok(t, err)
|
|
defer destroyContainer(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 stdinW.Close() //nolint: errcheck
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
ps := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"/bin/sh", "-c", "ulimit -n"},
|
|
Env: standardEnvironment,
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
Rlimits: []configs.Rlimit{
|
|
// increase process rlimit higher than container rlimit to test per-process limit
|
|
{Type: unix.RLIMIT_NOFILE, Hard: 1026, Soft: 1026},
|
|
},
|
|
Init: true,
|
|
}
|
|
err = container.Run(ps)
|
|
ok(t, err)
|
|
waitProcess(ps, t)
|
|
|
|
_ = stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := buffers.Stdout.String()
|
|
if limit := strings.TrimSpace(out); limit != "1026" {
|
|
t.Fatalf("expected rlimit to be 1026, got %s", limit)
|
|
}
|
|
}
|
|
|
|
func TestExecInAdditionalGroups(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
|
|
config := newTemplateConfig(t, nil)
|
|
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 stdinW.Close() //nolint: errcheck
|
|
ok(t, err)
|
|
|
|
var stdout bytes.Buffer
|
|
pconfig := libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"sh", "-c", "id", "-Gn"},
|
|
Env: standardEnvironment,
|
|
Stdin: nil,
|
|
Stdout: &stdout,
|
|
AdditionalGroups: []string{"plugdev", "audio"},
|
|
}
|
|
err = container.Run(&pconfig)
|
|
ok(t, err)
|
|
|
|
// Wait for process
|
|
waitProcess(&pconfig, t)
|
|
|
|
_ = stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
outputGroups := stdout.String()
|
|
|
|
// Check that the groups output has the groups that we specified
|
|
if !strings.Contains(outputGroups, "audio") {
|
|
t.Fatalf("Listed groups do not contain the audio group as expected: %v", outputGroups)
|
|
}
|
|
|
|
if !strings.Contains(outputGroups, "plugdev") {
|
|
t.Fatalf("Listed groups do not contain the plugdev group as expected: %v", outputGroups)
|
|
}
|
|
}
|
|
|
|
func TestExecInError(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
config := newTemplateConfig(t, nil)
|
|
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)
|
|
|
|
for i := 0; i < 42; i++ {
|
|
unexistent := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"unexistent"},
|
|
Env: standardEnvironment,
|
|
}
|
|
err = container.Run(unexistent)
|
|
if err == nil {
|
|
t.Fatal("Should be an error")
|
|
}
|
|
if !strings.Contains(err.Error(), "executable file not found") {
|
|
t.Fatalf("Should be error about not found executable, got %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExecInTTY(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
t.Skip("racy; see https://github.com/opencontainers/runc/issues/2425")
|
|
config := newTemplateConfig(t, nil)
|
|
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)
|
|
|
|
ps := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"ps"},
|
|
Env: standardEnvironment,
|
|
}
|
|
|
|
// Repeat to increase chances to catch a race; see
|
|
// https://github.com/opencontainers/runc/issues/2425.
|
|
for i := 0; i < 300; i++ {
|
|
var stdout bytes.Buffer
|
|
|
|
parent, child, err := utils.NewSockPair("console")
|
|
ok(t, err)
|
|
ps.ConsoleSocket = child
|
|
|
|
done := make(chan (error))
|
|
go func() {
|
|
f, err := utils.RecvFile(parent)
|
|
if err != nil {
|
|
done <- fmt.Errorf("RecvFile: %w", err)
|
|
return
|
|
}
|
|
c, err := console.ConsoleFromFile(f)
|
|
if err != nil {
|
|
done <- fmt.Errorf("ConsoleFromFile: %w", err)
|
|
return
|
|
}
|
|
err = console.ClearONLCR(c.Fd())
|
|
if err != nil {
|
|
done <- fmt.Errorf("ClearONLCR: %w", err)
|
|
return
|
|
}
|
|
// An error from io.Copy is expected once the terminal
|
|
// is gone, so we deliberately ignore it.
|
|
_, _ = io.Copy(&stdout, c)
|
|
done <- nil
|
|
}()
|
|
|
|
err = container.Run(ps)
|
|
ok(t, err)
|
|
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("Waiting for copy timed out")
|
|
case err := <-done:
|
|
ok(t, err)
|
|
}
|
|
|
|
waitProcess(ps, t)
|
|
_ = parent.Close()
|
|
_ = child.Close()
|
|
|
|
out := stdout.String()
|
|
if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") {
|
|
t.Fatalf("unexpected running process, output %q", out)
|
|
}
|
|
if strings.Contains(out, "\r") {
|
|
t.Fatalf("unexpected carriage-return in output %q", out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExecInEnvironment(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
config := newTemplateConfig(t, nil)
|
|
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 stdinW.Close() //nolint: errcheck
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
process2 := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"env"},
|
|
Env: []string{
|
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
"DEBUG=true",
|
|
"DEBUG=false",
|
|
"ENV=test",
|
|
},
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
Init: true,
|
|
}
|
|
err = container.Run(process2)
|
|
ok(t, err)
|
|
waitProcess(process2, t)
|
|
|
|
_ = stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := buffers.Stdout.String()
|
|
// check execin's process environment
|
|
if !strings.Contains(out, "DEBUG=false") ||
|
|
!strings.Contains(out, "ENV=test") ||
|
|
!strings.Contains(out, "HOME=/root") ||
|
|
!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
|
|
strings.Contains(out, "DEBUG=true") {
|
|
t.Fatalf("unexpected running process, output %q", out)
|
|
}
|
|
}
|
|
|
|
func TestExecinPassExtraFiles(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
config := newTemplateConfig(t, nil)
|
|
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 stdinW.Close() //nolint: errcheck
|
|
ok(t, err)
|
|
|
|
var stdout bytes.Buffer
|
|
pipeout1, pipein1, err := os.Pipe()
|
|
ok(t, err)
|
|
pipeout2, pipein2, err := os.Pipe()
|
|
ok(t, err)
|
|
inprocess := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
|
|
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
|
ExtraFiles: []*os.File{pipein1, pipein2},
|
|
Stdin: nil,
|
|
Stdout: &stdout,
|
|
}
|
|
err = container.Run(inprocess)
|
|
ok(t, err)
|
|
|
|
waitProcess(inprocess, t)
|
|
_ = stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := stdout.String()
|
|
// fd 5 is the directory handle for /proc/$$/fd
|
|
if out != "0 1 2 3 4 5" {
|
|
t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out)
|
|
}
|
|
buf := []byte{0}
|
|
_, err = pipeout1.Read(buf)
|
|
ok(t, err)
|
|
out1 := string(buf)
|
|
if out1 != "1" {
|
|
t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
|
|
}
|
|
|
|
_, err = pipeout2.Read(buf)
|
|
ok(t, err)
|
|
out2 := string(buf)
|
|
if out2 != "2" {
|
|
t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
|
|
}
|
|
}
|
|
|
|
func TestExecInOomScoreAdj(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
config := newTemplateConfig(t, nil)
|
|
config.OomScoreAdj = ptrInt(200)
|
|
container, err := newContainer(t, config)
|
|
ok(t, err)
|
|
defer destroyContainer(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 stdinW.Close() //nolint: errcheck
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
ps := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"/bin/sh", "-c", "cat /proc/self/oom_score_adj"},
|
|
Env: standardEnvironment,
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
}
|
|
err = container.Run(ps)
|
|
ok(t, err)
|
|
waitProcess(ps, t)
|
|
|
|
_ = stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := buffers.Stdout.String()
|
|
if oomScoreAdj := strings.TrimSpace(out); oomScoreAdj != strconv.Itoa(*config.OomScoreAdj) {
|
|
t.Fatalf("expected oomScoreAdj to be %d, got %s", *config.OomScoreAdj, oomScoreAdj)
|
|
}
|
|
}
|
|
|
|
func TestExecInUserns(t *testing.T) {
|
|
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
|
|
t.Skip("Test requires userns.")
|
|
}
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
config := newTemplateConfig(t, &tParam{userns: true})
|
|
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 stdinW.Close() //nolint: errcheck
|
|
ok(t, err)
|
|
|
|
initPID, err := process.Pid()
|
|
ok(t, err)
|
|
initUserns, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/user", initPID))
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
process2 := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: []string{"readlink", "/proc/self/ns/user"},
|
|
Env: []string{
|
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
},
|
|
Stdout: buffers.Stdout,
|
|
Stderr: os.Stderr,
|
|
}
|
|
err = container.Run(process2)
|
|
ok(t, err)
|
|
waitProcess(process2, t)
|
|
_ = stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
if out := strings.TrimSpace(buffers.Stdout.String()); out != initUserns {
|
|
t.Errorf("execin userns(%s), wanted %s", out, initUserns)
|
|
}
|
|
}
|