chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#if defined(__linux__)
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
#include "exec_command.h"
|
||||
|
||||
#ifndef SYS_close_range
|
||||
#define SYS_close_range 436
|
||||
#endif
|
||||
|
||||
#ifndef CLOSE_RANGE_CLOEXEC
|
||||
#define CLOSE_RANGE_CLOEXEC 0x4
|
||||
#endif
|
||||
|
||||
static int mark_cloexec(int fd) {
|
||||
int flags = fcntl(fd, F_GETFD);
|
||||
|
||||
if (flags == -1) return flags;
|
||||
if (flags & FD_CLOEXEC) return 0;
|
||||
|
||||
return fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
||||
}
|
||||
|
||||
static int cloexec_from(int min_fd) {
|
||||
#if defined(__linux__)
|
||||
// First try close_range.
|
||||
long ret = syscall(SYS_close_range, min_fd, ~0U, CLOSE_RANGE_CLOEXEC);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
const char* dirpath = "/proc/self/fd";
|
||||
#elif defined(__APPLE__)
|
||||
const char* dirpath = "/dev/fd";
|
||||
#endif
|
||||
DIR *dp = opendir(dirpath);
|
||||
if (!dp) return -1;
|
||||
|
||||
int dp_fd = dirfd(dp);
|
||||
struct dirent *de;
|
||||
|
||||
while ((de = readdir(dp))) {
|
||||
if (de->d_name[0] == '.') continue;
|
||||
|
||||
char *end;
|
||||
long val = strtol(de->d_name, &end, 10);
|
||||
if (*end || val < 0 || val > INT_MAX) continue;
|
||||
|
||||
int fd = (int)val;
|
||||
if (fd < min_fd || fd == dp_fd) continue;
|
||||
|
||||
int ret = mark_cloexec(fd);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
close(dp_fd);
|
||||
closedir(dp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void exec_command_attrs_init(struct exec_command_attrs *attrs) {
|
||||
attrs->setpgid = 0;
|
||||
attrs->pgid = 0;
|
||||
attrs->setsid = 0;
|
||||
attrs->setctty = 0;
|
||||
attrs->ctty = 0;
|
||||
attrs->mask = 0;
|
||||
attrs->uid = -1;
|
||||
attrs->gid = -1;
|
||||
attrs->pdeathSignal = 0;
|
||||
attrs->setfgpgrp = 0;
|
||||
}
|
||||
|
||||
static void child_handler(const int sync_pipes[2], const char *executable,
|
||||
char *const args[], char *const environment[],
|
||||
const int file_handles[], const int file_handle_count,
|
||||
const char *cwd, const sigset_t old_mask,
|
||||
const struct exec_command_attrs attrs) {
|
||||
int i = 0;
|
||||
int err = 0;
|
||||
int fd_index = 0;
|
||||
int fd_table[file_handle_count];
|
||||
struct rlimit limits = {0};
|
||||
int syncfd = sync_pipes[1];
|
||||
struct sigaction action = {0};
|
||||
|
||||
// Closing our parent's side of the pipe
|
||||
if (close(sync_pipes[0]) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Setup process group and foreground before clearing signal mask.
|
||||
if (attrs.setpgid) {
|
||||
if (setpgid(0, attrs.pgid) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
// Make the new process group the foreground process group so it can read from the TTY.
|
||||
if (attrs.setfgpgrp) {
|
||||
if (tcsetpgrp(STDIN_FILENO, getpgrp()) < 0) {
|
||||
if (errno != ENOTTY && errno != ENXIO) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear sighandlers
|
||||
action.sa_flags = 0;
|
||||
action.sa_handler = SIG_DFL;
|
||||
sigemptyset(&action.sa_mask);
|
||||
for (i = 0; i < NSIG; i++) {
|
||||
sigaction(i, &action, 0);
|
||||
}
|
||||
|
||||
sigset_t local_mask;
|
||||
sigemptyset(&local_mask);
|
||||
if (pthread_sigmask(SIG_SETMASK, &local_mask, NULL) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// start shuffling fds.
|
||||
// look at all the file handles and find the highest one,
|
||||
// use that for our pipe,
|
||||
//
|
||||
// Then, we need to start dup2 the fds starting for the final process
|
||||
// at 0-n.
|
||||
// as an example we have this list of FDs that should be passed to the
|
||||
// process:
|
||||
//
|
||||
/*
|
||||
The index of this list is the final result that the new process expects.
|
||||
The values are open fds provided from the parent process.
|
||||
[0] == 12
|
||||
[1] == 7
|
||||
[2] == 9
|
||||
[3] == 0
|
||||
|
||||
We also have a pipe to sync the child and parent so that adds an additional
|
||||
parameter to consider.
|
||||
|
||||
So we start by finding the highest open fd in the list, then move our pipe to
|
||||
the next.
|
||||
|
||||
i.e. fd12 is highest so move our pipe to fd13
|
||||
|
||||
Now start moving all the fds above our pipe as we will need to start placing
|
||||
the fds in the child process into the right order. Make sure they are all
|
||||
marked cloexec.
|
||||
|
||||
pipe == 13
|
||||
[0] == 12 dup2 14
|
||||
[1] == 7 dup2 15
|
||||
[2] == 9 dup2 16
|
||||
[3] == 0 dup2 17
|
||||
|
||||
Now overwrite the fd table for the child with the current index.
|
||||
|
||||
Make index == fd.
|
||||
|
||||
pipe == 13
|
||||
[0] == 14 dup2 0
|
||||
[1] == 15 dup2 1
|
||||
[2] == 16 dup2 2
|
||||
[3] == 17 dup2 3
|
||||
|
||||
Clear cloexec on this new fds.
|
||||
*/
|
||||
|
||||
// find the highest fd value in our list.
|
||||
for (i = 0; i < file_handle_count; i++) {
|
||||
if (file_handles[i] > fd_index) {
|
||||
fd_index = file_handles[i];
|
||||
}
|
||||
fd_table[i] = file_handles[i];
|
||||
}
|
||||
// now fd_index is == to the highest fd in our list of handles.
|
||||
// Increment it and set our pipe to it.
|
||||
fd_index++;
|
||||
|
||||
if (syncfd != fd_index) {
|
||||
if (dup2(syncfd, fd_index) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
if (close(syncfd) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
syncfd = fd_index;
|
||||
}
|
||||
fd_index++;
|
||||
|
||||
// make sure our syncfd retains its cloexec
|
||||
if (fcntl(syncfd, F_SETFD, FD_CLOEXEC) == -1) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// move the rest of the fds up above our index if they don't match the index.
|
||||
for (i = 0; i < file_handle_count; i++) {
|
||||
if (fd_table[i] == i) {
|
||||
continue;
|
||||
}
|
||||
if (dup2(fd_table[i], fd_index) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
if (fcntl(fd_index, F_SETFD, FD_CLOEXEC) == -1) {
|
||||
goto fail;
|
||||
}
|
||||
fd_table[i] = fd_index;
|
||||
fd_index++;
|
||||
}
|
||||
|
||||
// now create the child process's final fd table. where i == i
|
||||
for (i = 0; i < file_handle_count; i++) {
|
||||
if (fd_table[i] != i) {
|
||||
if (dup2(fd_table[i], i) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
// now fd[i] should == i
|
||||
// clear cloexec as this fd is where we want it.
|
||||
if (fcntl(i, F_SETFD, 0) == -1) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.setsid) {
|
||||
if (setsid() == -1) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.setctty) {
|
||||
if (ioctl(attrs.ctty, TIOCSCTTY, 0)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
// Set parent death signal if specified
|
||||
if (attrs.pdeathSignal != 0) {
|
||||
if (prctl(PR_SET_PDEATHSIG, attrs.pdeathSignal) != 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// close exec everything outside of our child's fd_table.
|
||||
if (cloexec_from(file_handle_count) != 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// set gid
|
||||
if (attrs.gid != -1) {
|
||||
if (setgid(attrs.gid) != 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
// set uid
|
||||
if (attrs.uid != -1) {
|
||||
if (setreuid(attrs.uid, attrs.uid) != 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (cwd != NULL) {
|
||||
if (chdir(cwd)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
execve(executable, args, environment);
|
||||
fail:
|
||||
err = errno;
|
||||
if (err) {
|
||||
// send our error to the parent
|
||||
while (write(syncfd, &err, sizeof(err)) < 0)
|
||||
;
|
||||
}
|
||||
exit(127);
|
||||
}
|
||||
|
||||
int exec_command(pid_t *result, const char *executable, char *const args[],
|
||||
char *const envp[], const int file_handles[],
|
||||
const int file_handle_count, const char *working_directory,
|
||||
struct exec_command_attrs *attrs) {
|
||||
pid_t pid = 0;
|
||||
int err = 0;
|
||||
int sync_pipe[2];
|
||||
sigset_t old_mask;
|
||||
|
||||
sigset_t all;
|
||||
sigfillset(&all);
|
||||
|
||||
if (pipe(sync_pipe)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (pthread_sigmask(SIG_SETMASK, &all, &old_mask) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid == -1) {
|
||||
close(sync_pipe[0]);
|
||||
close(sync_pipe[1]);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// hand off to child
|
||||
child_handler(sync_pipe, executable, args, envp, file_handles,
|
||||
file_handle_count, working_directory, old_mask, *attrs);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// handle parent operations
|
||||
if (close(sync_pipe[1]) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// sync with our child process
|
||||
err = 0;
|
||||
ssize_t size = read(sync_pipe[0], &err, sizeof(err));
|
||||
// -- we didn't get an errno back
|
||||
if (size != sizeof(err)) {
|
||||
// will be used as return result
|
||||
err = 0;
|
||||
} else {
|
||||
// we did get an errno back from the child process and our
|
||||
// err var is set to that errno
|
||||
// lets set our errno and then reap the process
|
||||
errno = err;
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0);
|
||||
// lets continue our journey below
|
||||
}
|
||||
|
||||
if (close(sync_pipe[0]) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
if (err) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
(*result) = pid;
|
||||
err = 0;
|
||||
fail:
|
||||
if (pthread_sigmask(SIG_SETMASK, &old_mask, 0) < 0) {
|
||||
printf("restoring signal mask: %s\n", strerror(errno));
|
||||
}
|
||||
if (err) {
|
||||
printf("exec_command execve: %s\n", strerror(err));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user