...

Source file src/github.com/opencontainers/runc/libcontainer/integration/execin_test.go

Documentation: github.com/opencontainers/runc/libcontainer/integration

     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  		var out bytes.Buffer
   219  		unexistent := &libcontainer.Process{
   220  			Cwd:    "/",
   221  			Args:   []string{"unexistent"},
   222  			Env:    standardEnvironment,
   223  			Stderr: &out,
   224  		}
   225  		err = container.Run(unexistent)
   226  		if err == nil {
   227  			t.Fatal("Should be an error")
   228  		}
   229  		if !strings.Contains(err.Error(), "executable file not found") {
   230  			t.Fatalf("Should be error about not found executable, got %s", err)
   231  		}
   232  		if !bytes.Contains(out.Bytes(), []byte("executable file not found")) {
   233  			t.Fatalf("executable file not found error not delivered to stdio:\n%s", out.String())
   234  		}
   235  	}
   236  }
   237  
   238  func TestExecInTTY(t *testing.T) {
   239  	if testing.Short() {
   240  		return
   241  	}
   242  	t.Skip("racy; see https://github.com/opencontainers/runc/issues/2425")
   243  	config := newTemplateConfig(t, nil)
   244  	container, err := newContainer(t, config)
   245  	ok(t, err)
   246  	defer destroyContainer(container)
   247  
   248  	// Execute a first process in the container
   249  	stdinR, stdinW, err := os.Pipe()
   250  	ok(t, err)
   251  	process := &libcontainer.Process{
   252  		Cwd:   "/",
   253  		Args:  []string{"cat"},
   254  		Env:   standardEnvironment,
   255  		Stdin: stdinR,
   256  		Init:  true,
   257  	}
   258  	err = container.Run(process)
   259  	_ = stdinR.Close()
   260  	defer func() {
   261  		_ = stdinW.Close()
   262  		if _, err := process.Wait(); err != nil {
   263  			t.Log(err)
   264  		}
   265  	}()
   266  	ok(t, err)
   267  
   268  	ps := &libcontainer.Process{
   269  		Cwd:  "/",
   270  		Args: []string{"ps"},
   271  		Env:  standardEnvironment,
   272  	}
   273  
   274  	// Repeat to increase chances to catch a race; see
   275  	// https://github.com/opencontainers/runc/issues/2425.
   276  	for i := 0; i < 300; i++ {
   277  		var stdout bytes.Buffer
   278  
   279  		parent, child, err := utils.NewSockPair("console")
   280  		ok(t, err)
   281  		ps.ConsoleSocket = child
   282  
   283  		done := make(chan (error))
   284  		go func() {
   285  			f, err := utils.RecvFd(parent)
   286  			if err != nil {
   287  				done <- fmt.Errorf("RecvFd: %w", err)
   288  				return
   289  			}
   290  			c, err := console.ConsoleFromFile(f)
   291  			if err != nil {
   292  				done <- fmt.Errorf("ConsoleFromFile: %w", err)
   293  				return
   294  			}
   295  			err = console.ClearONLCR(c.Fd())
   296  			if err != nil {
   297  				done <- fmt.Errorf("ClearONLCR: %w", err)
   298  				return
   299  			}
   300  			// An error from io.Copy is expected once the terminal
   301  			// is gone, so we deliberately ignore it.
   302  			_, _ = io.Copy(&stdout, c)
   303  			done <- nil
   304  		}()
   305  
   306  		err = container.Run(ps)
   307  		ok(t, err)
   308  
   309  		select {
   310  		case <-time.After(5 * time.Second):
   311  			t.Fatal("Waiting for copy timed out")
   312  		case err := <-done:
   313  			ok(t, err)
   314  		}
   315  
   316  		waitProcess(ps, t)
   317  		_ = parent.Close()
   318  		_ = child.Close()
   319  
   320  		out := stdout.String()
   321  		if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") {
   322  			t.Fatalf("unexpected running process, output %q", out)
   323  		}
   324  		if strings.Contains(out, "\r") {
   325  			t.Fatalf("unexpected carriage-return in output %q", out)
   326  		}
   327  	}
   328  }
   329  
   330  func TestExecInEnvironment(t *testing.T) {
   331  	if testing.Short() {
   332  		return
   333  	}
   334  	config := newTemplateConfig(t, nil)
   335  	container, err := newContainer(t, config)
   336  	ok(t, err)
   337  	defer destroyContainer(container)
   338  
   339  	// Execute a first process in the container
   340  	stdinR, stdinW, err := os.Pipe()
   341  	ok(t, err)
   342  	process := &libcontainer.Process{
   343  		Cwd:   "/",
   344  		Args:  []string{"cat"},
   345  		Env:   standardEnvironment,
   346  		Stdin: stdinR,
   347  		Init:  true,
   348  	}
   349  	err = container.Run(process)
   350  	_ = stdinR.Close()
   351  	defer stdinW.Close() //nolint: errcheck
   352  	ok(t, err)
   353  
   354  	buffers := newStdBuffers()
   355  	process2 := &libcontainer.Process{
   356  		Cwd:  "/",
   357  		Args: []string{"env"},
   358  		Env: []string{
   359  			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
   360  			"DEBUG=true",
   361  			"DEBUG=false",
   362  			"ENV=test",
   363  		},
   364  		Stdin:  buffers.Stdin,
   365  		Stdout: buffers.Stdout,
   366  		Stderr: buffers.Stderr,
   367  		Init:   true,
   368  	}
   369  	err = container.Run(process2)
   370  	ok(t, err)
   371  	waitProcess(process2, t)
   372  
   373  	_ = stdinW.Close()
   374  	waitProcess(process, t)
   375  
   376  	out := buffers.Stdout.String()
   377  	// check execin's process environment
   378  	if !strings.Contains(out, "DEBUG=false") ||
   379  		!strings.Contains(out, "ENV=test") ||
   380  		!strings.Contains(out, "HOME=/root") ||
   381  		!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
   382  		strings.Contains(out, "DEBUG=true") {
   383  		t.Fatalf("unexpected running process, output %q", out)
   384  	}
   385  }
   386  
   387  func TestExecinPassExtraFiles(t *testing.T) {
   388  	if testing.Short() {
   389  		return
   390  	}
   391  	config := newTemplateConfig(t, nil)
   392  	container, err := newContainer(t, config)
   393  	ok(t, err)
   394  	defer destroyContainer(container)
   395  
   396  	// Execute a first process in the container
   397  	stdinR, stdinW, err := os.Pipe()
   398  	ok(t, err)
   399  	process := &libcontainer.Process{
   400  		Cwd:   "/",
   401  		Args:  []string{"cat"},
   402  		Env:   standardEnvironment,
   403  		Stdin: stdinR,
   404  		Init:  true,
   405  	}
   406  	err = container.Run(process)
   407  	_ = stdinR.Close()
   408  	defer stdinW.Close() //nolint: errcheck
   409  	ok(t, err)
   410  
   411  	var stdout bytes.Buffer
   412  	pipeout1, pipein1, err := os.Pipe()
   413  	ok(t, err)
   414  	pipeout2, pipein2, err := os.Pipe()
   415  	ok(t, err)
   416  	inprocess := &libcontainer.Process{
   417  		Cwd:        "/",
   418  		Args:       []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
   419  		Env:        []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
   420  		ExtraFiles: []*os.File{pipein1, pipein2},
   421  		Stdin:      nil,
   422  		Stdout:     &stdout,
   423  	}
   424  	err = container.Run(inprocess)
   425  	ok(t, err)
   426  
   427  	waitProcess(inprocess, t)
   428  	_ = stdinW.Close()
   429  	waitProcess(process, t)
   430  
   431  	out := stdout.String()
   432  	// fd 5 is the directory handle for /proc/$$/fd
   433  	if out != "0 1 2 3 4 5" {
   434  		t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out)
   435  	}
   436  	buf := []byte{0}
   437  	_, err = pipeout1.Read(buf)
   438  	ok(t, err)
   439  	out1 := string(buf)
   440  	if out1 != "1" {
   441  		t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
   442  	}
   443  
   444  	_, err = pipeout2.Read(buf)
   445  	ok(t, err)
   446  	out2 := string(buf)
   447  	if out2 != "2" {
   448  		t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
   449  	}
   450  }
   451  
   452  func TestExecInOomScoreAdj(t *testing.T) {
   453  	if testing.Short() {
   454  		return
   455  	}
   456  	config := newTemplateConfig(t, nil)
   457  	config.OomScoreAdj = ptrInt(200)
   458  	container, err := newContainer(t, config)
   459  	ok(t, err)
   460  	defer destroyContainer(container)
   461  
   462  	stdinR, stdinW, err := os.Pipe()
   463  	ok(t, err)
   464  	process := &libcontainer.Process{
   465  		Cwd:   "/",
   466  		Args:  []string{"cat"},
   467  		Env:   standardEnvironment,
   468  		Stdin: stdinR,
   469  		Init:  true,
   470  	}
   471  	err = container.Run(process)
   472  	_ = stdinR.Close()
   473  	defer stdinW.Close() //nolint: errcheck
   474  	ok(t, err)
   475  
   476  	buffers := newStdBuffers()
   477  	ps := &libcontainer.Process{
   478  		Cwd:    "/",
   479  		Args:   []string{"/bin/sh", "-c", "cat /proc/self/oom_score_adj"},
   480  		Env:    standardEnvironment,
   481  		Stdin:  buffers.Stdin,
   482  		Stdout: buffers.Stdout,
   483  		Stderr: buffers.Stderr,
   484  	}
   485  	err = container.Run(ps)
   486  	ok(t, err)
   487  	waitProcess(ps, t)
   488  
   489  	_ = stdinW.Close()
   490  	waitProcess(process, t)
   491  
   492  	out := buffers.Stdout.String()
   493  	if oomScoreAdj := strings.TrimSpace(out); oomScoreAdj != strconv.Itoa(*config.OomScoreAdj) {
   494  		t.Fatalf("expected oomScoreAdj to be %d, got %s", *config.OomScoreAdj, oomScoreAdj)
   495  	}
   496  }
   497  
   498  func TestExecInUserns(t *testing.T) {
   499  	if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
   500  		t.Skip("Test requires userns.")
   501  	}
   502  	if testing.Short() {
   503  		return
   504  	}
   505  	config := newTemplateConfig(t, &tParam{userns: true})
   506  	container, err := newContainer(t, config)
   507  	ok(t, err)
   508  	defer destroyContainer(container)
   509  
   510  	// Execute a first process in the container
   511  	stdinR, stdinW, err := os.Pipe()
   512  	ok(t, err)
   513  
   514  	process := &libcontainer.Process{
   515  		Cwd:   "/",
   516  		Args:  []string{"cat"},
   517  		Env:   standardEnvironment,
   518  		Stdin: stdinR,
   519  		Init:  true,
   520  	}
   521  	err = container.Run(process)
   522  	_ = stdinR.Close()
   523  	defer stdinW.Close() //nolint: errcheck
   524  	ok(t, err)
   525  
   526  	initPID, err := process.Pid()
   527  	ok(t, err)
   528  	initUserns, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/user", initPID))
   529  	ok(t, err)
   530  
   531  	buffers := newStdBuffers()
   532  	process2 := &libcontainer.Process{
   533  		Cwd:  "/",
   534  		Args: []string{"readlink", "/proc/self/ns/user"},
   535  		Env: []string{
   536  			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
   537  		},
   538  		Stdout: buffers.Stdout,
   539  		Stderr: os.Stderr,
   540  	}
   541  	err = container.Run(process2)
   542  	ok(t, err)
   543  	waitProcess(process2, t)
   544  	_ = stdinW.Close()
   545  	waitProcess(process, t)
   546  
   547  	if out := strings.TrimSpace(buffers.Stdout.String()); out != initUserns {
   548  		t.Errorf("execin userns(%s), wanted %s", out, initUserns)
   549  	}
   550  }
   551  

View as plain text