github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/executor/subprocess.h (about) 1 // Copyright 2024 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 #include <spawn.h> 5 #include <sys/wait.h> 6 #include <unistd.h> 7 8 #include <vector> 9 10 // Subprocess allows to start and wait for a subprocess. 11 class Subprocess 12 { 13 public: 14 Subprocess(const char** argv, const std::vector<std::pair<int, int>>& fds) 15 { 16 posix_spawn_file_actions_t actions; 17 if (posix_spawn_file_actions_init(&actions)) 18 fail("posix_spawn_file_actions_init failed"); 19 int max_fd = 0; 20 for (auto pair : fds) 21 max_fd = std::max(max_fd, pair.second); 22 for (auto pair : fds) { 23 if (pair.first != -1) { 24 // Remapping won't work if fd's overlap with the target range: 25 // we can dup something onto fd we need to dup later, in such case the later fd 26 // will be wrong. Resolving this would require some tricky multi-pass remapping. 27 // So we just require the caller to not do that. 28 if (pair.first <= max_fd) 29 failmsg("bad subprocess fd", "%d->%d max_fd=%d", 30 pair.first, pair.second, max_fd); 31 if (posix_spawn_file_actions_adddup2(&actions, pair.first, pair.second)) 32 fail("posix_spawn_file_actions_adddup2 failed"); 33 } else { 34 if (posix_spawn_file_actions_addclose(&actions, pair.second)) 35 fail("posix_spawn_file_actions_addclose failed"); 36 } 37 } 38 for (int i = max_fd + 1; i < kFdLimit; i++) { 39 if (posix_spawn_file_actions_addclose(&actions, i)) 40 fail("posix_spawn_file_actions_addclose failed"); 41 } 42 43 posix_spawnattr_t attr; 44 if (posix_spawnattr_init(&attr)) 45 fail("posix_spawnattr_init failed"); 46 // Create new process group so that we can kill all processes in the group. 47 if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP)) 48 fail("posix_spawnattr_setflags failed"); 49 50 const char* child_envp[] = { 51 // Tell ASAN to not mess with our NONFAILING and disable leak checking 52 // (somehow lsan is very slow in syzbot arm64 image and we are not very interested 53 // in leaks in the exec subprocess, it does not use malloc/new anyway). 54 "ASAN_OPTIONS=handle_segv=0 allow_user_segv_handler=1 detect_leaks=0", 55 // Disable rseq since we don't use it and we want to [ab]use it ourselves for kernel testing. 56 "GLIBC_TUNABLES=glibc.pthread.rseq=0", 57 nullptr}; 58 59 if (posix_spawnp(&pid_, argv[0], &actions, &attr, 60 const_cast<char**>(argv), const_cast<char**>(child_envp))) 61 fail("posix_spawnp failed"); 62 63 if (posix_spawn_file_actions_destroy(&actions)) 64 fail("posix_spawn_file_actions_destroy failed"); 65 if (posix_spawnattr_destroy(&attr)) 66 fail("posix_spawnattr_destroy failed"); 67 } 68 69 ~Subprocess() 70 { 71 if (pid_) 72 KillAndWait(); 73 } 74 75 int KillAndWait() 76 { 77 if (!pid_) 78 fail("subprocess hasn't started or already waited"); 79 kill(pid_, SIGKILL); 80 int pid = 0; 81 int wstatus = 0; 82 do 83 pid = waitpid(pid_, &wstatus, WAIT_FLAGS); 84 while (pid == -1 && errno == EINTR); 85 if (pid != pid_) 86 failmsg("child wait failed", "pid_=%d pid=%d", pid_, pid); 87 if (WIFSTOPPED(wstatus)) 88 failmsg("child stopped", "status=%d", wstatus); 89 pid_ = 0; 90 return ExitStatus(wstatus); 91 } 92 93 int WaitAndKill(uint64 timeout_ms) 94 { 95 if (!pid_) 96 fail("subprocess hasn't started or already waited"); 97 uint64 start = current_time_ms(); 98 int wstatus = 0; 99 for (;;) { 100 sleep_ms(10); 101 if (waitpid(pid_, &wstatus, WNOHANG | WAIT_FLAGS) == pid_) 102 break; 103 if (current_time_ms() - start > timeout_ms) { 104 kill(-pid_, SIGKILL); 105 kill(pid_, SIGKILL); 106 } 107 } 108 pid_ = 0; 109 return ExitStatus(wstatus); 110 } 111 112 private: 113 int pid_ = 0; 114 115 static int ExitStatus(int wstatus) 116 { 117 if (WIFEXITED(wstatus)) 118 return WEXITSTATUS(wstatus); 119 if (WIFSIGNALED(wstatus)) { 120 // Map signal numbers to some reasonable exit statuses. 121 // We only log them and compare to kFailStatus, so ensure it's not kFailStatus 122 // and not 0, otherwise return the signal as is (e.g. exit status 11 is SIGSEGV). 123 switch (WTERMSIG(wstatus)) { 124 case kFailStatus: 125 return kFailStatus - 1; 126 case 0: 127 return kFailStatus - 2; 128 default: 129 return WTERMSIG(wstatus); 130 } 131 } 132 // This may be possible in WIFSTOPPED case for C programs. 133 return kFailStatus - 3; 134 } 135 136 Subprocess(const Subprocess&) = delete; 137 Subprocess& operator=(const Subprocess&) = delete; 138 };