github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/integration/execin_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/containerd/console"
    14  	"github.com/opencontainers/runc/libcontainer"
    15  	"github.com/opencontainers/runc/libcontainer/configs"
    16  	"github.com/opencontainers/runc/libcontainer/utils"
    17  
    18  	"golang.org/x/sys/unix"
    19  )
    20  
    21  func TestExecIn(t *testing.T) {
    22  	if testing.Short() {
    23  		return
    24  	}
    25  	config := newTemplateConfig(t, nil)
    26  	container, err := newContainer(t, config)
    27  	ok(t, err)
    28  	defer destroyContainer(container)
    29  
    30  	// Execute a first process in the container
    31  	stdinR, stdinW, err := os.Pipe()
    32  	ok(t, err)
    33  	process := &libcontainer.Process{
    34  		Cwd:   "/",
    35  		Args:  []string{"cat"},
    36  		Env:   standardEnvironment,
    37  		Stdin: stdinR,
    38  		Init:  true,
    39  	}
    40  	err = container.Run(process)
    41  	_ = stdinR.Close()
    42  	defer stdinW.Close() //nolint: errcheck
    43  	ok(t, err)
    44  
    45  	buffers := newStdBuffers()
    46  	ps := &libcontainer.Process{
    47  		Cwd:    "/",
    48  		Args:   []string{"ps"},
    49  		Env:    standardEnvironment,
    50  		Stdin:  buffers.Stdin,
    51  		Stdout: buffers.Stdout,
    52  		Stderr: buffers.Stderr,
    53  	}
    54  
    55  	err = container.Run(ps)
    56  	ok(t, err)
    57  	waitProcess(ps, t)
    58  	_ = stdinW.Close()
    59  	waitProcess(process, t)
    60  
    61  	out := buffers.Stdout.String()
    62  	if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") {
    63  		t.Fatalf("unexpected running process, output %q", out)
    64  	}
    65  	if strings.Contains(out, "\r") {
    66  		t.Fatalf("unexpected carriage-return in output %q", out)
    67  	}
    68  }
    69  
    70  func TestExecInUsernsRlimit(t *testing.T) {
    71  	if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
    72  		t.Skip("Test requires userns.")
    73  	}
    74  
    75  	testExecInRlimit(t, true)
    76  }
    77  
    78  func TestExecInRlimit(t *testing.T) {
    79  	testExecInRlimit(t, false)
    80  }
    81  
    82  func testExecInRlimit(t *testing.T, userns bool) {
    83  	if testing.Short() {
    84  		return
    85  	}
    86  
    87  	config := newTemplateConfig(t, &tParam{userns: userns})
    88  	container, err := newContainer(t, config)
    89  	ok(t, err)
    90  	defer destroyContainer(container)
    91  
    92  	stdinR, stdinW, err := os.Pipe()
    93  	ok(t, err)
    94  	process := &libcontainer.Process{
    95  		Cwd:   "/",
    96  		Args:  []string{"cat"},
    97  		Env:   standardEnvironment,
    98  		Stdin: stdinR,
    99  		Init:  true,
   100  	}
   101  	err = container.Run(process)
   102  	_ = stdinR.Close()
   103  	defer stdinW.Close() //nolint: errcheck
   104  	ok(t, err)
   105  
   106  	buffers := newStdBuffers()
   107  	ps := &libcontainer.Process{
   108  		Cwd:    "/",
   109  		Args:   []string{"/bin/sh", "-c", "ulimit -n"},
   110  		Env:    standardEnvironment,
   111  		Stdin:  buffers.Stdin,
   112  		Stdout: buffers.Stdout,
   113  		Stderr: buffers.Stderr,
   114  		Rlimits: []configs.Rlimit{
   115  			// increase process rlimit higher than container rlimit to test per-process limit
   116  			{Type: unix.RLIMIT_NOFILE, Hard: 1026, Soft: 1026},
   117  		},
   118  		Init: true,
   119  	}
   120  	err = container.Run(ps)
   121  	ok(t, err)
   122  	waitProcess(ps, t)
   123  
   124  	_ = stdinW.Close()
   125  	waitProcess(process, t)
   126  
   127  	out := buffers.Stdout.String()
   128  	if limit := strings.TrimSpace(out); limit != "1026" {
   129  		t.Fatalf("expected rlimit to be 1026, got %s", limit)
   130  	}
   131  }
   132  
   133  func TestExecInAdditionalGroups(t *testing.T) {
   134  	if testing.Short() {
   135  		return
   136  	}
   137  
   138  	config := newTemplateConfig(t, nil)
   139  	container, err := newContainer(t, config)
   140  	ok(t, err)
   141  	defer destroyContainer(container)
   142  
   143  	// Execute a first process in the container
   144  	stdinR, stdinW, err := os.Pipe()
   145  	ok(t, err)
   146  	process := &libcontainer.Process{
   147  		Cwd:   "/",
   148  		Args:  []string{"cat"},
   149  		Env:   standardEnvironment,
   150  		Stdin: stdinR,
   151  		Init:  true,
   152  	}
   153  	err = container.Run(process)
   154  	_ = stdinR.Close()
   155  	defer stdinW.Close() //nolint: errcheck
   156  	ok(t, err)
   157  
   158  	var stdout bytes.Buffer
   159  	pconfig := libcontainer.Process{
   160  		Cwd:              "/",
   161  		Args:             []string{"sh", "-c", "id", "-Gn"},
   162  		Env:              standardEnvironment,
   163  		Stdin:            nil,
   164  		Stdout:           &stdout,
   165  		AdditionalGroups: []string{"plugdev", "audio"},
   166  	}
   167  	err = container.Run(&pconfig)
   168  	ok(t, err)
   169  
   170  	// Wait for process
   171  	waitProcess(&pconfig, t)
   172  
   173  	_ = stdinW.Close()
   174  	waitProcess(process, t)
   175  
   176  	outputGroups := stdout.String()
   177  
   178  	// Check that the groups output has the groups that we specified
   179  	if !strings.Contains(outputGroups, "audio") {
   180  		t.Fatalf("Listed groups do not contain the audio group as expected: %v", outputGroups)
   181  	}
   182  
   183  	if !strings.Contains(outputGroups, "plugdev") {
   184  		t.Fatalf("Listed groups do not contain the plugdev group as expected: %v", outputGroups)
   185  	}
   186  }
   187  
   188  func TestExecInError(t *testing.T) {
   189  	if testing.Short() {
   190  		return
   191  	}
   192  	config := newTemplateConfig(t, nil)
   193  	container, err := newContainer(t, config)
   194  	ok(t, err)
   195  	defer destroyContainer(container)
   196  
   197  	// Execute a first process in the container
   198  	stdinR, stdinW, err := os.Pipe()
   199  	ok(t, err)
   200  	process := &libcontainer.Process{
   201  		Cwd:   "/",
   202  		Args:  []string{"cat"},
   203  		Env:   standardEnvironment,
   204  		Stdin: stdinR,
   205  		Init:  true,
   206  	}
   207  	err = container.Run(process)
   208  	_ = stdinR.Close()
   209  	defer func() {
   210  		_ = stdinW.Close()
   211  		if _, err := process.Wait(); err != nil {
   212  			t.Log(err)
   213  		}
   214  	}()
   215  	ok(t, err)
   216  
   217  	for i := 0; i < 42; i++ {
   218  		unexistent := &libcontainer.Process{
   219  			Cwd:  "/",
   220  			Args: []string{"unexistent"},
   221  			Env:  standardEnvironment,
   222  		}
   223  		err = container.Run(unexistent)
   224  		if err == nil {
   225  			t.Fatal("Should be an error")
   226  		}
   227  		if !strings.Contains(err.Error(), "executable file not found") {
   228  			t.Fatalf("Should be error about not found executable, got %s", err)
   229  		}
   230  	}
   231  }
   232  
   233  func TestExecInTTY(t *testing.T) {
   234  	if testing.Short() {
   235  		return
   236  	}
   237  	t.Skip("racy; see https://github.com/opencontainers/runc/issues/2425")
   238  	config := newTemplateConfig(t, nil)
   239  	container, err := newContainer(t, config)
   240  	ok(t, err)
   241  	defer destroyContainer(container)
   242  
   243  	// Execute a first process in the container
   244  	stdinR, stdinW, err := os.Pipe()
   245  	ok(t, err)
   246  	process := &libcontainer.Process{
   247  		Cwd:   "/",
   248  		Args:  []string{"cat"},
   249  		Env:   standardEnvironment,
   250  		Stdin: stdinR,
   251  		Init:  true,
   252  	}
   253  	err = container.Run(process)
   254  	_ = stdinR.Close()
   255  	defer func() {
   256  		_ = stdinW.Close()
   257  		if _, err := process.Wait(); err != nil {
   258  			t.Log(err)
   259  		}
   260  	}()
   261  	ok(t, err)
   262  
   263  	ps := &libcontainer.Process{
   264  		Cwd:  "/",
   265  		Args: []string{"ps"},
   266  		Env:  standardEnvironment,
   267  	}
   268  
   269  	// Repeat to increase chances to catch a race; see
   270  	// https://github.com/opencontainers/runc/issues/2425.
   271  	for i := 0; i < 300; i++ {
   272  		var stdout bytes.Buffer
   273  
   274  		parent, child, err := utils.NewSockPair("console")
   275  		ok(t, err)
   276  		ps.ConsoleSocket = child
   277  
   278  		done := make(chan (error))
   279  		go func() {
   280  			f, err := utils.RecvFile(parent)
   281  			if err != nil {
   282  				done <- fmt.Errorf("RecvFile: %w", err)
   283  				return
   284  			}
   285  			c, err := console.ConsoleFromFile(f)
   286  			if err != nil {
   287  				done <- fmt.Errorf("ConsoleFromFile: %w", err)
   288  				return
   289  			}
   290  			err = console.ClearONLCR(c.Fd())
   291  			if err != nil {
   292  				done <- fmt.Errorf("ClearONLCR: %w", err)
   293  				return
   294  			}
   295  			// An error from io.Copy is expected once the terminal
   296  			// is gone, so we deliberately ignore it.
   297  			_, _ = io.Copy(&stdout, c)
   298  			done <- nil
   299  		}()
   300  
   301  		err = container.Run(ps)
   302  		ok(t, err)
   303  
   304  		select {
   305  		case <-time.After(5 * time.Second):
   306  			t.Fatal("Waiting for copy timed out")
   307  		case err := <-done:
   308  			ok(t, err)
   309  		}
   310  
   311  		waitProcess(ps, t)
   312  		_ = parent.Close()
   313  		_ = child.Close()
   314  
   315  		out := stdout.String()
   316  		if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") {
   317  			t.Fatalf("unexpected running process, output %q", out)
   318  		}
   319  		if strings.Contains(out, "\r") {
   320  			t.Fatalf("unexpected carriage-return in output %q", out)
   321  		}
   322  	}
   323  }
   324  
   325  func TestExecInEnvironment(t *testing.T) {
   326  	if testing.Short() {
   327  		return
   328  	}
   329  	config := newTemplateConfig(t, nil)
   330  	container, err := newContainer(t, config)
   331  	ok(t, err)
   332  	defer destroyContainer(container)
   333  
   334  	// Execute a first process in the container
   335  	stdinR, stdinW, err := os.Pipe()
   336  	ok(t, err)
   337  	process := &libcontainer.Process{
   338  		Cwd:   "/",
   339  		Args:  []string{"cat"},
   340  		Env:   standardEnvironment,
   341  		Stdin: stdinR,
   342  		Init:  true,
   343  	}
   344  	err = container.Run(process)
   345  	_ = stdinR.Close()
   346  	defer stdinW.Close() //nolint: errcheck
   347  	ok(t, err)
   348  
   349  	buffers := newStdBuffers()
   350  	process2 := &libcontainer.Process{
   351  		Cwd:  "/",
   352  		Args: []string{"env"},
   353  		Env: []string{
   354  			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
   355  			"DEBUG=true",
   356  			"DEBUG=false",
   357  			"ENV=test",
   358  		},
   359  		Stdin:  buffers.Stdin,
   360  		Stdout: buffers.Stdout,
   361  		Stderr: buffers.Stderr,
   362  		Init:   true,
   363  	}
   364  	err = container.Run(process2)
   365  	ok(t, err)
   366  	waitProcess(process2, t)
   367  
   368  	_ = stdinW.Close()
   369  	waitProcess(process, t)
   370  
   371  	out := buffers.Stdout.String()
   372  	// check execin's process environment
   373  	if !strings.Contains(out, "DEBUG=false") ||
   374  		!strings.Contains(out, "ENV=test") ||
   375  		!strings.Contains(out, "HOME=/root") ||
   376  		!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
   377  		strings.Contains(out, "DEBUG=true") {
   378  		t.Fatalf("unexpected running process, output %q", out)
   379  	}
   380  }
   381  
   382  func TestExecinPassExtraFiles(t *testing.T) {
   383  	if testing.Short() {
   384  		return
   385  	}
   386  	config := newTemplateConfig(t, nil)
   387  	container, err := newContainer(t, config)
   388  	ok(t, err)
   389  	defer destroyContainer(container)
   390  
   391  	// Execute a first process in the container
   392  	stdinR, stdinW, err := os.Pipe()
   393  	ok(t, err)
   394  	process := &libcontainer.Process{
   395  		Cwd:   "/",
   396  		Args:  []string{"cat"},
   397  		Env:   standardEnvironment,
   398  		Stdin: stdinR,
   399  		Init:  true,
   400  	}
   401  	err = container.Run(process)
   402  	_ = stdinR.Close()
   403  	defer stdinW.Close() //nolint: errcheck
   404  	ok(t, err)
   405  
   406  	var stdout bytes.Buffer
   407  	pipeout1, pipein1, err := os.Pipe()
   408  	ok(t, err)
   409  	pipeout2, pipein2, err := os.Pipe()
   410  	ok(t, err)
   411  	inprocess := &libcontainer.Process{
   412  		Cwd:        "/",
   413  		Args:       []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
   414  		Env:        []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
   415  		ExtraFiles: []*os.File{pipein1, pipein2},
   416  		Stdin:      nil,
   417  		Stdout:     &stdout,
   418  	}
   419  	err = container.Run(inprocess)
   420  	ok(t, err)
   421  
   422  	waitProcess(inprocess, t)
   423  	_ = stdinW.Close()
   424  	waitProcess(process, t)
   425  
   426  	out := stdout.String()
   427  	// fd 5 is the directory handle for /proc/$$/fd
   428  	if out != "0 1 2 3 4 5" {
   429  		t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out)
   430  	}
   431  	buf := []byte{0}
   432  	_, err = pipeout1.Read(buf)
   433  	ok(t, err)
   434  	out1 := string(buf)
   435  	if out1 != "1" {
   436  		t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
   437  	}
   438  
   439  	_, err = pipeout2.Read(buf)
   440  	ok(t, err)
   441  	out2 := string(buf)
   442  	if out2 != "2" {
   443  		t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
   444  	}
   445  }
   446  
   447  func TestExecInOomScoreAdj(t *testing.T) {
   448  	if testing.Short() {
   449  		return
   450  	}
   451  	config := newTemplateConfig(t, nil)
   452  	config.OomScoreAdj = ptrInt(200)
   453  	container, err := newContainer(t, config)
   454  	ok(t, err)
   455  	defer destroyContainer(container)
   456  
   457  	stdinR, stdinW, err := os.Pipe()
   458  	ok(t, err)
   459  	process := &libcontainer.Process{
   460  		Cwd:   "/",
   461  		Args:  []string{"cat"},
   462  		Env:   standardEnvironment,
   463  		Stdin: stdinR,
   464  		Init:  true,
   465  	}
   466  	err = container.Run(process)
   467  	_ = stdinR.Close()
   468  	defer stdinW.Close() //nolint: errcheck
   469  	ok(t, err)
   470  
   471  	buffers := newStdBuffers()
   472  	ps := &libcontainer.Process{
   473  		Cwd:    "/",
   474  		Args:   []string{"/bin/sh", "-c", "cat /proc/self/oom_score_adj"},
   475  		Env:    standardEnvironment,
   476  		Stdin:  buffers.Stdin,
   477  		Stdout: buffers.Stdout,
   478  		Stderr: buffers.Stderr,
   479  	}
   480  	err = container.Run(ps)
   481  	ok(t, err)
   482  	waitProcess(ps, t)
   483  
   484  	_ = stdinW.Close()
   485  	waitProcess(process, t)
   486  
   487  	out := buffers.Stdout.String()
   488  	if oomScoreAdj := strings.TrimSpace(out); oomScoreAdj != strconv.Itoa(*config.OomScoreAdj) {
   489  		t.Fatalf("expected oomScoreAdj to be %d, got %s", *config.OomScoreAdj, oomScoreAdj)
   490  	}
   491  }
   492  
   493  func TestExecInUserns(t *testing.T) {
   494  	if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
   495  		t.Skip("Test requires userns.")
   496  	}
   497  	if testing.Short() {
   498  		return
   499  	}
   500  	config := newTemplateConfig(t, &tParam{userns: true})
   501  	container, err := newContainer(t, config)
   502  	ok(t, err)
   503  	defer destroyContainer(container)
   504  
   505  	// Execute a first process in the container
   506  	stdinR, stdinW, err := os.Pipe()
   507  	ok(t, err)
   508  
   509  	process := &libcontainer.Process{
   510  		Cwd:   "/",
   511  		Args:  []string{"cat"},
   512  		Env:   standardEnvironment,
   513  		Stdin: stdinR,
   514  		Init:  true,
   515  	}
   516  	err = container.Run(process)
   517  	_ = stdinR.Close()
   518  	defer stdinW.Close() //nolint: errcheck
   519  	ok(t, err)
   520  
   521  	initPID, err := process.Pid()
   522  	ok(t, err)
   523  	initUserns, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/user", initPID))
   524  	ok(t, err)
   525  
   526  	buffers := newStdBuffers()
   527  	process2 := &libcontainer.Process{
   528  		Cwd:  "/",
   529  		Args: []string{"readlink", "/proc/self/ns/user"},
   530  		Env: []string{
   531  			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
   532  		},
   533  		Stdout: buffers.Stdout,
   534  		Stderr: os.Stderr,
   535  	}
   536  	err = container.Run(process2)
   537  	ok(t, err)
   538  	waitProcess(process2, t)
   539  	_ = stdinW.Close()
   540  	waitProcess(process, t)
   541  
   542  	if out := strings.TrimSpace(buffers.Stdout.String()); out != initUserns {
   543  		t.Errorf("execin userns(%s), wanted %s", out, initUserns)
   544  	}
   545  }