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  };