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