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