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
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()
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()
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
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
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()
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
171 waitProcess(&pconfig, t)
172
173 _ = stdinW.Close()
174 waitProcess(process, t)
175
176 outputGroups := stdout.String()
177
178
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
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
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
275
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
301
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
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()
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
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
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()
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
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()
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
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()
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