Skip to content

Commit

Permalink
system.c: Refactor bbs_exec APIs.
Browse files Browse the repository at this point in the history
Over time, more and more individual APIs were added to handle different
use cases, such as headless execution, isolated execution, and more
recently, ability to retain host networking in the container. In order
to allow future capabilities to be added without having to add or change
APIs, move most of these settings into a struct and pass that into a
common, unified API instead.
  • Loading branch information
InterLinked1 committed Nov 17, 2024
1 parent 9b28077 commit 3c3c2c3
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 128 deletions.
83 changes: 20 additions & 63 deletions bbs/system.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,53 +423,6 @@ int bbs_argv_from_str(char **argv, int argc, char *s)
return c;
}

/* Forward declaration */
static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fdout, const char *filename, char *const argv[], char *const envp[], int isolated);

int bbs_execvp(struct bbs_node *node, const char *filename, char *const argv[])
{
return __bbs_execvpe_fd(node, 1, -1, -1, filename, argv, NULL, 0);
}

int bbs_execvp_isolated(struct bbs_node *node, const char *filename, char *const argv[])
{
return __bbs_execvpe_fd(node, 1, -1, -1, filename, argv, NULL, 1);
}

int bbs_execvp_isolated_networked(struct bbs_node *node, const char *filename, char *const argv[])
{
return __bbs_execvpe_fd(node, 1, -1, -1, filename, argv, NULL, 2);
}

int bbs_execvp_headless(struct bbs_node *node, const char *filename, char *const argv[])
{
if (!node) {
bbs_warning("It is not necessary to use %s if node is NULL\n", __func__);
}
return __bbs_execvpe_fd(node, 0, -1, -1, filename, argv, NULL, 0);
}

int bbs_execvp_fd(struct bbs_node *node, int fdin, int fdout, const char *filename, char *const argv[])
{
return __bbs_execvpe_fd(node, 1, fdin, fdout, filename, argv, NULL, 0);
}

int bbs_execvp_fd_headless(struct bbs_node *node, int fdin, int fdout, const char *filename, char *const argv[])
{
if (!node) {
bbs_warning("It is not necessary to use %s if node is NULL\n", __func__);
}
return __bbs_execvpe_fd(node, 0, fdin, fdout, filename, argv, NULL, 0);
}

int bbs_execvpe_fd_headless(struct bbs_node *node, int fdin, int fdout, const char *filename, char *const argv[], char *const envp[])
{
if (!node) {
bbs_warning("It is not necessary to use %s if node is NULL\n", __func__);
}
return __bbs_execvpe_fd(node, 0, fdin, fdout, filename, argv, envp, 0);
}

#ifdef ISOEXEC_SUPPORTED
static int update_map(const char *mapping, const char *map_file, int map_len)
{
Expand Down Expand Up @@ -728,20 +681,20 @@ static ssize_t full_read(int fd, char *restrict buf, size_t len)
/*!
* \brief Execute an external program. Most calls to exec() should funnel through this function...
* \param node
* \param usenode
* \param fdin
* \param fdout
* \param e
* \param filename Program name to execute
* \param argv Arguments
* \param envp Environment (optional)
* \param isolated Isolation level. 0 = no isolation. 1 = isolated in separate namespace, no network. 2 = isolated in separate namespace, sharing host network
* \retval -1 on failure
* \return Result of program execution
*/
static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fdout, const char *filename, char *const argv[], char *const envp[], int isolated)
int __bbs_execvpe(struct bbs_node *node, struct bbs_exec_params *e, const char *filename, char *const argv[], char *const envp[], const char *file, int lineno, const char *func)
{
pid_t pid;
struct termios term;
int usenode = e->usenode;
int fdin = e->fdin, fdout = e->fdout;
int fd = fdout;
int res = -1;
int pfd[2], procpipe[2];
Expand All @@ -755,8 +708,12 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
#define MYENVP_SIZE 5 /* End with 3 NULLs so we can add up to 2 env vars if needed */
char *myenvp[MYENVP_SIZE] = { fullpath, fullterm, NULL, NULL, NULL }; /* Last NULL is always the sentinel */

if (e->usenode) {
bbs_soft_assert(node != NULL);
}

#ifdef __FreeBSD__
if (isolated) {
if (e->isolated) {
/* The mount() API is not implemented in clone_container for FreeBSD.
* Additionally, CLONE_NEW... is not available for FreeBSD.
* As such, these preclude isoexec from being used on that platform. */
Expand All @@ -774,7 +731,7 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
snprintf(fullpath, sizeof(fullpath), "PATH=%s", parentpath);
}

bbs_debug(6, "node: %p, usenode: %d, fdin: %d, fdout: %d, filename: %s, isolated: %s\n", node, usenode, fdin, fdout, filename, isolated ? "yes" : "no");
bbs_debug(6, "%s:%d (%s) node: %p, usenode: %d, fdin: %d, fdout: %d, filename: %s, isolated: %s\n", file, lineno, func, node, usenode, fdin, fdout, filename, e->isolated ? "yes" : "no");
if (node && usenode && (fdin != -1 || fdout != -1)) {
bbs_warning("fdin/fdout should not be provided if usenode == 1 (node is preferred, fdin/fdout will be ignored)\n");
}
Expand Down Expand Up @@ -821,11 +778,11 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd

#ifdef ISOEXEC_SUPPORTED
/* If we have flags, we need to use clone(2). Otherwise, just use fork(2) */
if (isolated) {
if (e->isolated) {
int flags = 0;
/* We need to do more than fork() allows */
flags |= SIGCHLD | CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET | CLONE_NEWUSER; /* fork() sets SIGCHLD implicitly. */
if (isolated == 2) {
if (e->net) {
/* Keep network connectivity */
flags &= ~CLONE_NEWNET;
}
Expand Down Expand Up @@ -864,14 +821,14 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
}

if (pid == -1) {
bbs_error("%s failed (%s): %s\n", isolated ? "clone" : "fork", filename, strerror(errno));
if (isolated) {
bbs_error("%s failed (%s): %s\n", e->isolated ? "clone" : "fork", filename, strerror(errno));
if (e->isolated) {
close(procpipe[0]);
close(procpipe[1]);
}
return -1;
} else if (pid == 0) { /* Child */
if (!isolated) {
if (!e->isolated) {
/* Immediately install a dummy signal handler for SIGWINCH.
* Until we call exec, the child retains the parent's signal handlers.
* However, if we have a node, we immediately call bbs_node_update_winsize
Expand Down Expand Up @@ -904,7 +861,7 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
fd = pfd[1]; /* Use write end of pipe */
fdout = fd;
}
exec_pre(fdin, fdout, isolated ? procpipe[0] : -1); /* If we still need procpipe, don't close that */
exec_pre(fdin, fdout, e->isolated ? procpipe[0] : -1); /* If we still need procpipe, don't close that */
if (node && usenode) {
/* Set controlling terminal, or otherwise shells don't fully work properly. */
if (set_controlling_term(STDIN_FILENO)) { /* we dup2'd this to the slavefd. This is NOT the parent's STDIN. */
Expand All @@ -921,7 +878,7 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
#ifndef pivot_root
#define pivot_root(new, old) syscall(SYS_pivot_root, new, old)
#endif
if (isolated) {
if (e->isolated) {
struct utsname uts;
char pidbuf[15];
char oldroot[384 + STRLEN("/.old")], newroot[384];
Expand Down Expand Up @@ -1109,7 +1066,7 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
* But the parent will know that we failed since we return errno, and parent can log it. */
/* Also, use _exit, not exit, since exit will execute the parent's atexit function, etc.
* That matters because exec failed, so atexit is still registered. */
if (isolated) {
if (e->isolated) {
int saved_errno = errno;
fprintf(stderr, "%s: %s\n", filename, strerror(errno));
#ifdef DEBUG_NEW_FS
Expand All @@ -1131,7 +1088,7 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
} /* else, parent */

#ifdef ISOEXEC_SUPPORTED
if (isolated) {
if (e->isolated) {
close(procpipe[0]);
res = setup_namespace(pid);
if (!res) {
Expand Down Expand Up @@ -1205,7 +1162,7 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
}

#ifdef ISOEXEC_SUPPORTED
if (isolated) {
if (e->isolated) {
char rootdir[268];
/* Clean up the temporary container, if one was created */
temp_container_root(rootdir, sizeof(rootdir), pid);
Expand Down
4 changes: 3 additions & 1 deletion bbs/transfer.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ int transfer_make_longname(const char *file, struct stat *st, char *buf, size_t

static int recursive_copy(const char *srcfiles, const char *dest)
{
struct bbs_exec_params x;
/* It can probably do a better job than we can */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
Expand All @@ -207,7 +208,8 @@ static int recursive_copy(const char *srcfiles, const char *dest)
* we don't want to overwrite all the user's existing files. */
char *const argv[] = { "cp", "-r", "-n", (char*) srcfiles, (char*) dest, NULL };
#pragma GCC diagnostic pop
return bbs_execvp(NULL, argv[0], argv);
EXEC_PARAMS_INIT_HEADLESS(x);
return bbs_execvp(NULL, &x, argv[0], argv);
}

int bbs_transfer_home_dir(unsigned int userid, char *buf, size_t len)
Expand Down
4 changes: 3 additions & 1 deletion doors/door_evergreen.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

static int evergreen_exec(struct bbs_node *node, const char *args)
{
struct bbs_exec_params x;
char username[64];
char fromname[64];
char fromaddr[64];
Expand Down Expand Up @@ -61,7 +62,8 @@ static int evergreen_exec(struct bbs_node *node, const char *args)
return 0;
}

bbs_execvp(node, "evergreen", argv);
EXEC_PARAMS_INIT(x);
bbs_execvp(node, &x, "evergreen", argv);
bbs_user_semiperm_authorization_token_purge(passwordbuf);
return 0; /* Don't return -1 or the node will abort */
}
Expand Down
4 changes: 3 additions & 1 deletion doors/door_ibbs.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ static int ibbs_exec(struct bbs_node *node, const char *args)

#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
if (access(listfile, R_OK)) {
struct bbs_exec_params x;
char *const argv[] = { "unzip", tmpzip, "-d", "/tmp", NULL };
if (access(tmpzip, R_OK)) {
char url[54];
Expand All @@ -72,7 +73,8 @@ static int ibbs_exec(struct bbs_node *node, const char *args)
* Even though we have a handle to the node, pass NULL for node since we don't need to link STDIN/STDOUT to the unzip command.
* We just need it to execute, and this is more efficient (and safer!) than using system()
*/
if (bbs_execvp_headless(node, "unzip", argv)) {
EXEC_PARAMS_INIT_HEADLESS(x);
if (bbs_execvp(node, &x, "unzip", argv)) {
return 0; /* Don't return -1 or the node will abort */
}
} /* else, file already exists */
Expand Down
10 changes: 3 additions & 7 deletions doors/door_utilities.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ static int calc_exec(struct bbs_node *node, const char *args)
return 0;
}

#if 0
/* Just use the bc shell */
char *argv[3] = { "bc", "-q", NULL }; /* -q = quiet: disable initial banner */
return bbs_execvp(node, "bc", argv);
#endif

/* Create a pipe for passing input and output to/from bc */
if (pipe(stdin)) {
bbs_error("pipe failed: %s\n", strerror(errno));
Expand All @@ -67,6 +61,7 @@ static int calc_exec(struct bbs_node *node, const char *args)
bbs_node_buffer(node);
bbs_node_writef(node, "\r");
for (;;) {
struct bbs_exec_params x;
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
char *argv[3] = { "bc", "-q", NULL }; /* -q = quiet: disable initial banner */
bbs_node_writef(node, "EQ> ");
Expand Down Expand Up @@ -100,7 +95,8 @@ static int calc_exec(struct bbs_node *node, const char *args)
bbs_error("write failed: %s\n", strerror(errno));
continue;
}
res = bbs_execvp_fd_headless(node, stdin[0], stdout[1], "bc", argv);
EXEC_PARAMS_INIT_FD(x, stdin[0], stdout[1]);
res = bbs_execvp(node, &x, "bc", argv);
#pragma GCC diagnostic pop
if (res) {
bbs_node_writef(node, "Execution Error\n");
Expand Down
90 changes: 50 additions & 40 deletions include/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,59 +16,69 @@
/* Forward declarations */
struct bbs_node;

/*!
* \brief Parse a string into an argv. Similar to wordexp, but without the unnecessary (and insecure?) shell expansion overhead
* This function will separate on spaces, but keep a quoted argument together as a single argument.
* \param[out] argv
* \param argc Size of argv
* \param s String to parse
* \retval Value of argc (new argc + 1)
*/
int bbs_argv_from_str(char **argv, int argc, char *s);
/* Execution settings */
struct bbs_exec_params {
/* File descriptor priority is as follows, independently for STDIN/STDOUT:
* 1. If custom file descriptor provided (not -1), use that
* 2. If usenode is TRUE, use node file descriptor
* 3. Create pipes to discard STDIN/STDOUT, akin to /dev/null. */
int fdin; /* Custom file descriptor for STDIN to created process, node->fdin or -1 otherwise */
int fdout; /* Custom file descriptor for STDOUT from created process, node->fdout or -1 otherwise */
int priority; /* CPU priority */
unsigned int usenode:1; /* Whether to use the node for I/O. If FALSE, node will not be used for I/O */
unsigned int isolated:1; /* Whether to create the process in an isolated container */
/* Container parameters */
unsigned int net:1; /* Retain network connectivity through the host, inside the container. Only applicable if isolated is TRUE. */
};

/*!
* \brief Wrapper around execvpe(). This will fork(), then call execvp(), then wait for the child to exit.
* \param node Node to use for I/O. If NULL, output will be written to a temporary pipe and logged, but otherwise discarded
* \param filename Filename of program to execute (path is optional)
* \param argv
* \retval 0 on success, -1 on failure
* \warning Do not call bbs_execvp from a node thread with NULL for node. Use bbs_execvp_headless.
*/
int bbs_execvp(struct bbs_node *node, const char *filename, char *const argv[]);
/* _HEADLESS suffix = Don't use node for I/O
* _FD suffix = Use custom file descriptors for I/O */

/*!
* \brief Wrapper around execvpe() that creates a process in an isolated container. This will clone(), then call execvp(), then wait for the child to exit.
* \param node Node to use for I/O. If NULL, output will be written to a temporary pipe and logged, but otherwise discarded
* \param filename Filename of program to execute (path is optional)
* \param argv
* \retval 0 on success, -1 on failure
* \warning Do not call bbs_execvp from a node thread with NULL for node. Use bbs_execvp_headless.
*/
int bbs_execvp_isolated(struct bbs_node *node, const char *filename, char *const argv[]);
/* Normal init, use node for I/O */
#define EXEC_PARAMS_INIT(x) \
memset(&x, 0, sizeof(struct bbs_exec_params)); \
x.usenode = 1; \
x.fdin = -1; \
x.fdout = -1; \

/* Running on a node, but use custom file descriptors for I/O */
#define EXEC_PARAMS_INIT_FD(x, in, out) \
EXEC_PARAMS_INIT(x); \
x.usenode = 0; /* Setting to 0 is still important, since if fdin or fdout is -1, we don't want to default to using node->fdin or node->fdout */ \
x.fdin = in; \
x.fdout = out;

/*! \brief Same as bbs_execvp_isolated, but retains network connectivity through the host, inside the container */
int bbs_execvp_isolated_networked(struct bbs_node *node, const char *filename, char *const argv[]);
/* No I/O whatsoever (effectively equivalent to > /dev/null < /dev/null) */
#define EXEC_PARAMS_INIT_HEADLESS(x) \
EXEC_PARAMS_INIT(x); \
x.usenode = 0;

/*! \brief Same as bbs_execvp, but node will not be used for I/O. */
int bbs_execvp_headless(struct bbs_node *node, const char *filename, char *const argv[]);
/*! \brief Same as bbs_execvpe, but with no custom envp */
#define bbs_execvp(node, e, filename, argv) bbs_execvpe(node, e, filename, argv, NULL)

/*!
* \brief Wrapper around execvpe(). This will fork(), then call execvp(), then wait for the child to exit.
* \param node Node to use for I/O. If NULL and fdout == -1, output will be written to a temporary pipe and logged, but otherwise discarded
* \param fdin If node is NULL, file descriptor to use for STDIN. If -1, there will be no standard input available to the executed program.
* \param fdout If node is NULL, file descriptor to use for STDOUT/STDERR. If -1, output will be written to a temporary pipe and logged, but otherwise discarded.
* \param node Node to use for I/O. If NULL, output will be written to a temporary pipe and logged, but otherwise discarded
* \param e Execution parameters
* \param filename Filename of program to execute (path is optional)
* \param argv
* \param envp Custom environment (optional)
* \retval 0 on success, -1 on failure
* \warning Do not call bbs_execvp_fd from a node thread with NULL for node. Use bbs_execvp_fd_headless.
* \warning Do not call bbs_execvp from a node thread with NULL for node. Provide the node but set e->usenode to 0.
*/
int bbs_execvp_fd(struct bbs_node *node, int fdin, int fdout, const char *filename, char *const argv[]);
#define bbs_execvpe(node, e, filename, argv, envp) __bbs_execvpe(node, e, filename, argv, envp, __FILE__, __LINE__, __func__)

/*! \brief Same as bbs_execvp_fd, but node will not be used for I/O. */
int bbs_execvp_fd_headless(struct bbs_node *node, int fdin, int fdout, const char *filename, char *const argv[]);
int __bbs_execvpe(struct bbs_node *node, struct bbs_exec_params *e, const char *filename, char *const argv[], char *const envp[], const char *file, int lineno, const char *func) __attribute__((nonnull (2, 3)));

/*! \brief Same as bbs_execvp_fd_headless, but allow passing an envp */
int bbs_execvpe_fd_headless(struct bbs_node *node, int fdin, int fdout, const char *filename, char *const argv[], char *const envp[]);
/*!
* \brief Parse a string into an argv. Similar to wordexp, but without the unnecessary (and insecure?) shell expansion overhead
* This function will separate on spaces, but keep a quoted argument together as a single argument.
* \param[out] argv
* \param argc Size of argv
* \param s String to parse
* \retval Value of argc (new argc + 1)
*/
int bbs_argv_from_str(char **argv, int argc, char *s);

/*! \brief Load system.conf config at startup */
int bbs_init_system(void);
2 changes: 1 addition & 1 deletion modules/mod_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -2416,7 +2416,7 @@ static int cgi_run(struct http_session *http, const char *filename, char *const
#pragma GCC diagnostic pop /* -Wdiscarded-qualifiers */

/* Because we need to run our own logic while the child is running,
* fork and wait on the child directly here, rather than using bbs_execvpe_fd_headless.
* fork and wait on the child directly here, rather than using BBS exec APIs.
* It's just easier in this case. */

/* Create pipes for the CGI's STDIN and STDOUT */
Expand Down
Loading

0 comments on commit 3c3c2c3

Please sign in to comment.