gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/cmd/test_app/main.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Binary test_app is like a swiss knife for tests that need to run anything 16 // inside the sandbox. New functionality can be added with new commands. 17 package main 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "log" 25 "math/rand" 26 "net" 27 "os" 28 "os/exec" 29 "path/filepath" 30 "regexp" 31 "strconv" 32 "strings" 33 sys "syscall" 34 "time" 35 36 "github.com/google/subcommands" 37 "github.com/kr/pty" 38 "gvisor.dev/gvisor/pkg/test/testutil" 39 "gvisor.dev/gvisor/runsc/flag" 40 ) 41 42 func main() { 43 subcommands.Register(subcommands.HelpCommand(), "") 44 subcommands.Register(subcommands.FlagsCommand(), "") 45 subcommands.Register(new(capability), "") 46 subcommands.Register(new(fdReceiver), "") 47 subcommands.Register(new(fdSender), "") 48 subcommands.Register(new(forkBomb), "") 49 subcommands.Register(new(fsTreeCreator), "") 50 subcommands.Register(new(ptyRunner), "") 51 subcommands.Register(new(reaper), "") 52 subcommands.Register(new(syscall), "") 53 subcommands.Register(new(taskTree), "") 54 subcommands.Register(new(uds), "") 55 subcommands.Register(new(zombieTest), "") 56 57 flag.Parse() 58 59 exitCode := subcommands.Execute(context.Background()) 60 os.Exit(int(exitCode)) 61 } 62 63 type fsTreeCreator struct { 64 depth uint 65 numFilesPerLevel uint 66 fileSize uint 67 targetDir string 68 createSymlink bool 69 } 70 71 // Name implements subcommands.Command.Name. 72 func (*fsTreeCreator) Name() string { 73 return "fsTreeCreate" 74 } 75 76 // Synopsis implements subcommands.Command.Synopsys. 77 func (*fsTreeCreator) Synopsis() string { 78 return "creates a filesystem tree of a certain depth, with a certain number of files on each level and each file with a certain size and type, under a certain directory. Some randomization is added on top of this" 79 } 80 81 // Usage implements subcommands.Command.Usage. 82 func (*fsTreeCreator) Usage() string { 83 return "fsTreeCreate <flags>" 84 } 85 86 // SetFlags implements subcommands.Command.SetFlags. 87 func (c *fsTreeCreator) SetFlags(f *flag.FlagSet) { 88 f.UintVar(&c.depth, "depth", 10, "number of levels to create") 89 f.UintVar(&c.numFilesPerLevel, "file-per-level", 10, "number of files to create per level") 90 f.UintVar(&c.fileSize, "file-size", 4096, "size of each file") 91 f.StringVar(&c.targetDir, "target-dir", "/", "directory under which to create the filesystem tree") 92 f.BoolVar(&c.createSymlink, "create-symlink", false, "create symlinks other than the first file per level") 93 } 94 95 // Execute implements subcommands.Command.Execute. 96 func (c *fsTreeCreator) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 97 depth := c.depth + uint(rand.Uint32())%c.depth 98 numFilesPerLevel := c.numFilesPerLevel + uint(rand.Uint32())%c.numFilesPerLevel 99 fileSize := c.fileSize + uint(rand.Uint32())%c.fileSize 100 curDir := c.targetDir 101 if _, err := os.Stat(curDir); os.IsNotExist(err) { 102 if err := os.MkdirAll(curDir, 0777); err != nil { 103 log.Fatalf("error creating directory %q: %v", curDir, err) 104 } 105 } 106 107 data := make([]byte, fileSize) 108 rand.Read(data) 109 for i := uint(0); i < depth; i++ { 110 for j := uint(0); j < numFilesPerLevel; j++ { 111 filePath := filepath.Join(curDir, fmt.Sprintf("file%d", j)) 112 if c.createSymlink && j > 0 { 113 if err := os.Symlink("file0", filePath); err != nil { 114 log.Fatalf("error creating symlink %q: %v", filePath, err) 115 } 116 } else { 117 if err := os.WriteFile(filePath, data, 0666); err != nil { 118 log.Fatalf("error writing file %q: %v", filePath, err) 119 } 120 } 121 } 122 nextDir := filepath.Join(curDir, "dir") 123 if err := os.Mkdir(nextDir, 0777); err != nil { 124 log.Fatalf("error creating directory %q: %v", nextDir, err) 125 } 126 curDir = nextDir 127 } 128 return subcommands.ExitSuccess 129 } 130 131 type uds struct { 132 fileName string 133 socketPath string 134 } 135 136 // Name implements subcommands.Command.Name. 137 func (*uds) Name() string { 138 return "uds" 139 } 140 141 // Synopsis implements subcommands.Command.Synopsys. 142 func (*uds) Synopsis() string { 143 return "creates unix domain socket client and server. Client sends a contant flow of sequential numbers. Server prints them to --file" 144 } 145 146 // Usage implements subcommands.Command.Usage. 147 func (*uds) Usage() string { 148 return "uds <flags>" 149 } 150 151 // SetFlags implements subcommands.Command.SetFlags. 152 func (c *uds) SetFlags(f *flag.FlagSet) { 153 f.StringVar(&c.fileName, "file", "", "name of output file") 154 f.StringVar(&c.socketPath, "socket", "", "path to socket") 155 } 156 157 // Execute implements subcommands.Command.Execute. 158 func (c *uds) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 159 if c.fileName == "" || c.socketPath == "" { 160 log.Fatalf("Flags cannot be empty, given: fileName: %q, socketPath: %q", c.fileName, c.socketPath) 161 return subcommands.ExitFailure 162 } 163 outputFile, err := os.OpenFile(c.fileName, os.O_WRONLY|os.O_CREATE, 0666) 164 if err != nil { 165 log.Fatal("error opening output file:", err) 166 } 167 168 defer os.Remove(c.socketPath) 169 170 listener, err := net.Listen("unix", c.socketPath) 171 if err != nil { 172 log.Fatalf("error listening on socket %q: %v", c.socketPath, err) 173 } 174 175 go server(listener, outputFile) 176 for i := 0; ; i++ { 177 conn, err := net.Dial("unix", c.socketPath) 178 if err != nil { 179 log.Fatal("error dialing:", err) 180 } 181 if _, err := conn.Write([]byte(strconv.Itoa(i))); err != nil { 182 log.Fatal("error writing:", err) 183 } 184 conn.Close() 185 time.Sleep(100 * time.Millisecond) 186 } 187 } 188 189 func server(listener net.Listener, out *os.File) { 190 buf := make([]byte, 16) 191 192 for { 193 c, err := listener.Accept() 194 if err != nil { 195 log.Fatal("error accepting connection:", err) 196 } 197 nr, err := c.Read(buf) 198 if err != nil { 199 log.Fatal("error reading from buf:", err) 200 } 201 data := buf[0:nr] 202 fmt.Fprint(out, string(data)+"\n") 203 } 204 } 205 206 type taskTree struct { 207 depth int 208 width int 209 pause bool 210 } 211 212 // Name implements subcommands.Command. 213 func (*taskTree) Name() string { 214 return "task-tree" 215 } 216 217 // Synopsis implements subcommands.Command. 218 func (*taskTree) Synopsis() string { 219 return "creates a tree of tasks" 220 } 221 222 // Usage implements subcommands.Command. 223 func (*taskTree) Usage() string { 224 return "task-tree <flags>" 225 } 226 227 // SetFlags implements subcommands.Command. 228 func (c *taskTree) SetFlags(f *flag.FlagSet) { 229 f.IntVar(&c.depth, "depth", 1, "number of levels to create") 230 f.IntVar(&c.width, "width", 1, "number of tasks at each level") 231 f.BoolVar(&c.pause, "pause", false, "whether the tasks should pause perpetually") 232 } 233 234 // Execute implements subcommands.Command. 235 func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 236 if c.depth == 0 { 237 log.Printf("Child sleeping, PID: %d\n", os.Getpid()) 238 for { 239 time.Sleep(time.Hour) 240 } 241 } 242 243 log.Printf("Parent %d creating %d children, PID: %d\n", c.depth, c.width, os.Getpid()) 244 245 stop := testutil.StartReaper() 246 defer stop() 247 248 var cmds []*exec.Cmd 249 for i := 0; i < c.width; i++ { 250 cmd := exec.Command( 251 "/proc/self/exe", c.Name(), 252 "--depth", strconv.Itoa(c.depth-1), 253 "--width", strconv.Itoa(c.width), 254 fmt.Sprintf("--pause=%t", c.pause)) 255 cmd.Stdout = os.Stdout 256 cmd.Stderr = os.Stderr 257 258 if err := cmd.Start(); err != nil { 259 log.Fatal("failed to call self:", err) 260 } 261 cmds = append(cmds, cmd) 262 } 263 264 for _, c := range cmds { 265 c.Wait() 266 } 267 268 if c.pause { 269 log.Printf("Parent %d sleeping, PID: %d\n", c.depth, os.Getpid()) 270 for { 271 time.Sleep(time.Hour) 272 } 273 } 274 275 return subcommands.ExitSuccess 276 } 277 278 type forkBomb struct { 279 delay time.Duration 280 } 281 282 // Name implements subcommands.Command. 283 func (*forkBomb) Name() string { 284 return "fork-bomb" 285 } 286 287 // Synopsis implements subcommands.Command. 288 func (*forkBomb) Synopsis() string { 289 return "creates child process until the end of times" 290 } 291 292 // Usage implements subcommands.Command. 293 func (*forkBomb) Usage() string { 294 return "fork-bomb <flags>" 295 } 296 297 // SetFlags implements subcommands.Command. 298 func (c *forkBomb) SetFlags(f *flag.FlagSet) { 299 f.DurationVar(&c.delay, "delay", 100*time.Millisecond, "amount of time to delay creation of child") 300 } 301 302 // Execute implements subcommands.Command. 303 func (c *forkBomb) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 304 time.Sleep(c.delay) 305 306 cmd := exec.Command("/proc/self/exe", c.Name()) 307 cmd.Stdout = os.Stdout 308 cmd.Stderr = os.Stderr 309 if err := cmd.Run(); err != nil { 310 log.Fatal("failed to call self:", err) 311 } 312 return subcommands.ExitSuccess 313 } 314 315 type reaper struct{} 316 317 // Name implements subcommands.Command. 318 func (*reaper) Name() string { 319 return "reaper" 320 } 321 322 // Synopsis implements subcommands.Command. 323 func (*reaper) Synopsis() string { 324 return "reaps all children in a loop" 325 } 326 327 // Usage implements subcommands.Command. 328 func (*reaper) Usage() string { 329 return "reaper <flags>" 330 } 331 332 // SetFlags implements subcommands.Command. 333 func (*reaper) SetFlags(*flag.FlagSet) {} 334 335 // Execute implements subcommands.Command. 336 func (c *reaper) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 337 stop := testutil.StartReaper() 338 defer stop() 339 select {} 340 } 341 342 type syscall struct { 343 sysno uint64 344 } 345 346 // Name implements subcommands.Command. 347 func (*syscall) Name() string { 348 return "syscall" 349 } 350 351 // Synopsis implements subcommands.Command. 352 func (*syscall) Synopsis() string { 353 return "syscall makes a syscall" 354 } 355 356 // Usage implements subcommands.Command. 357 func (*syscall) Usage() string { 358 return "syscall <flags> [arg1 arg2...]" 359 } 360 361 // SetFlags implements subcommands.Command. 362 func (s *syscall) SetFlags(f *flag.FlagSet) { 363 f.Uint64Var(&s.sysno, "syscall", 0, "syscall to call") 364 } 365 366 // Execute implements subcommands.Command. 367 func (s *syscall) Execute(ctx context.Context, f *flag.FlagSet, _ ...any) subcommands.ExitStatus { 368 const maxSyscallArgs = 6 369 numArgs := f.NArg() 370 if numArgs > maxSyscallArgs { 371 fmt.Printf("number of sycall arguments not supported: %d (max is %d)\n", numArgs, maxSyscallArgs) 372 return subcommands.ExitUsageError 373 } 374 var syscallArgs [maxSyscallArgs]uintptr 375 for i := 0; i < numArgs; i++ { 376 uintArg, err := strconv.ParseUint(f.Arg(i), 10, 64) 377 if err != nil { 378 fmt.Printf("not an integer: %q\n", f.Arg(i)) 379 return subcommands.ExitUsageError 380 } 381 syscallArgs[i] = uintptr(uintArg) 382 } 383 var errno sys.Errno 384 switch numArgs { 385 case 0: 386 _, _, errno = sys.Syscall(uintptr(s.sysno), 0, 0, 0) 387 case 3: 388 _, _, errno = sys.Syscall(uintptr(s.sysno), syscallArgs[0], syscallArgs[1], syscallArgs[2]) 389 case 6: 390 _, _, errno = sys.Syscall6(uintptr(s.sysno), syscallArgs[0], syscallArgs[1], syscallArgs[2], syscallArgs[3], syscallArgs[4], syscallArgs[5]) 391 default: 392 fmt.Printf("number of sycall arguments not supported: %d\n", numArgs) 393 return subcommands.ExitUsageError 394 } 395 if errno != 0 { 396 fmt.Printf("syscall(%d, %s) failed: %v\n", s.sysno, strings.Join(f.Args(), ", "), errno) 397 } else { 398 fmt.Printf("syscall(%d, %s) success\n", s.sysno, strings.Join(f.Args(), ", ")) 399 } 400 return subcommands.ExitSuccess 401 } 402 403 type capability struct { 404 enabled uint64 405 disabled uint64 406 } 407 408 // Name implements subcommands.Command. 409 func (*capability) Name() string { 410 return "capability" 411 } 412 413 // Synopsis implements subcommands.Command. 414 func (*capability) Synopsis() string { 415 return "checks if effective capabilities are set/unset" 416 } 417 418 // Usage implements subcommands.Command. 419 func (*capability) Usage() string { 420 return "capability [--enabled=number] [--disabled=number]" 421 } 422 423 // SetFlags implements subcommands.Command. 424 func (c *capability) SetFlags(f *flag.FlagSet) { 425 f.Uint64Var(&c.enabled, "enabled", 0, "") 426 f.Uint64Var(&c.disabled, "disabled", 0, "") 427 } 428 429 // Execute implements subcommands.Command. 430 func (c *capability) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 431 if c.enabled == 0 && c.disabled == 0 { 432 fmt.Println("One of the flags must be set") 433 return subcommands.ExitUsageError 434 } 435 436 status, err := ioutil.ReadFile("/proc/self/status") 437 if err != nil { 438 fmt.Printf("Error reading %q: %v\n", "proc/self/status", err) 439 return subcommands.ExitFailure 440 } 441 re := regexp.MustCompile("CapEff:\t([0-9a-f]+)\n") 442 matches := re.FindStringSubmatch(string(status)) 443 if matches == nil || len(matches) != 2 { 444 fmt.Printf("Effective capabilities not found in\n%s\n", status) 445 return subcommands.ExitFailure 446 } 447 caps, err := strconv.ParseUint(matches[1], 16, 64) 448 if err != nil { 449 fmt.Printf("failed to convert capabilities %q: %v\n", matches[1], err) 450 return subcommands.ExitFailure 451 } 452 453 if c.enabled != 0 && (caps&c.enabled) != c.enabled { 454 fmt.Printf("Missing capabilities, want: %#x: got: %#x\n", c.enabled, caps) 455 return subcommands.ExitFailure 456 } 457 if c.disabled != 0 && (caps&c.disabled) != 0 { 458 fmt.Printf("Extra capabilities found, dont_want: %#x: got: %#x\n", c.disabled, caps) 459 return subcommands.ExitFailure 460 } 461 462 return subcommands.ExitSuccess 463 } 464 465 type ptyRunner struct{} 466 467 // Name implements subcommands.Command. 468 func (*ptyRunner) Name() string { 469 return "pty-runner" 470 } 471 472 // Synopsis implements subcommands.Command. 473 func (*ptyRunner) Synopsis() string { 474 return "runs the given command with an open pty terminal" 475 } 476 477 // Usage implements subcommands.Command. 478 func (*ptyRunner) Usage() string { 479 return "pty-runner [command]" 480 } 481 482 // SetFlags implements subcommands.Command.SetFlags. 483 func (*ptyRunner) SetFlags(f *flag.FlagSet) {} 484 485 // Execute implements subcommands.Command. 486 func (*ptyRunner) Execute(_ context.Context, fs *flag.FlagSet, _ ...any) subcommands.ExitStatus { 487 c := exec.Command(fs.Args()[0], fs.Args()[1:]...) 488 f, err := pty.Start(c) 489 if err != nil { 490 fmt.Printf("pty.Start failed: %v", err) 491 return subcommands.ExitFailure 492 } 493 defer f.Close() 494 495 // Copy stdout from the command to keep this process alive until the 496 // subprocess exits. 497 io.Copy(os.Stdout, f) 498 499 return subcommands.ExitSuccess 500 }