Add synchronization to prevent the parent from running concurrently with the child until the child execs or terminates.

Our approach is to have the parent wait for SIGUSR2, and send that from the child just before it calls exec* or _exit.
This commit is contained in:
Stephen Heumann 2014-11-06 23:29:42 -06:00
parent f52d5394bf
commit 9bc48e5ebf
4 changed files with 121 additions and 42 deletions

View File

@ -868,22 +868,6 @@ int file_is_executable(const char *name) FAST_FUNC;
char *find_executable(const char *filename, char **PATHp) FAST_FUNC;
int executable_exists(const char *filename) FAST_FUNC;
/* BB_EXECxx always execs (it's not doing NOFORK/NOEXEC stuff),
* but it may exec busybox and call applet instead of searching PATH.
*/
#if ENABLE_FEATURE_PREFER_APPLETS
int BB_EXECVP(const char *file, char *const argv[]) FAST_FUNC;
#define BB_EXECLP(prog,cmd,...) \
do { \
if (find_applet_by_name(prog) >= 0) \
execlp(bb_busybox_exec_path, cmd, __VA_ARGS__); \
execlp(prog, cmd, __VA_ARGS__); \
} while (0)
#else
#define BB_EXECVP(prog,cmd) execvp(prog,cmd)
#endif
int BB_EXECVP_or_die(char **argv) NORETURN FAST_FUNC;
/* xvfork() can't be a _function_, return after vfork mangles stack
* in the parent. It must be a macro. */
#define xvfork() \
@ -1716,6 +1700,12 @@ static ALWAYS_INLINE unsigned char bb_ascii_tolower(unsigned char a)
#endif /* !defined(__ORCAC__) */
/* Signal to the parent that it can resume executing after a fork,
* because the child is about to exec or terminate.
*/
int signal_parent_to_resume(void);
/* Simple unit-testing framework */
typedef void (*bbunit_testfunc)(void);

View File

@ -1,25 +1,6 @@
#include <unistd.h>
#include "libbb.h"
/* Like vfork, but calls fn(arg) in the child instead of returning.
* This is designed to match the semantics of GNO's fork2 call.
*/
#ifndef __GNO__
pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
pid_t pid = vfork();
if (pid == 0) {
fn(arg);
}
return pid;
}
#else
pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
return fork2((fn), 1024, 0, "hush (forked)", 2, (arg));
}
#endif
pid_t xvfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
pid_t pid = vfork_and_run(fn, arg);
@ -28,3 +9,62 @@ pid_t xvfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
return pid;
}
/* Like vfork, but calls fn(arg) in the child instead of returning.
* This is designed to match the semantics of GNO's fork2 call.
*/
#ifndef __GNO__
pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
pid_t pid = vfork();
if (pid == 0) {
fn(arg);
}
return pid;
}
#else
/* Turn off all ORCA/C stack repair code to avoid corruption. */
#ifdef __ORCAC__
# pragma optimize 72
#endif
pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
/* GNO's fork2 call will return immediately and allow the parent and
* child processes to execute concurrently using the same memory
* space. To prevent them stomping on each other, we want to get
* behavior like a traditional vfork() implementation, where the
* parent blocks until the child terminates or execs.
*
* Our approach will be to have the child send SIGUSR2 to the parent
* just before it terminates or execs, and block waiting for that here.
*
* It's tempting to use waitpid(). That would have the advantage of
* catching cases where the child process terminates abruptly, but
* GNO's implementation of waitpid() is a wrapper around wait() and
* therefore is buggy: it may swallow the information about termination
* of other child processes (ones that forked earlier and have already
* exec'd), which we want to avoid.
*/
long oldmask;
pid_t pid;
/* Block all signals for now */
oldmask = sigblock(-1);
pid = fork2((fn), 1024, 0, "hush (forked)", 2, (arg));
/* Now wait until we get SIGUSR2 */
sigpause(~sigmask(SIGUSR2));
/* Restore original signal mask */
sigsetmask(oldmask);
return pid;
}
#endif

View File

@ -19,6 +19,8 @@ jmp_buf die_jmp;
void FAST_FUNC xfunc_die(void)
{
int forked;
if (die_sleep) {
if ((ENABLE_FEATURE_PREFER_APPLETS || ENABLE_HUSH)
&& die_sleep < 0
@ -36,5 +38,9 @@ void FAST_FUNC xfunc_die(void)
}
sleep(die_sleep);
}
exit(xfunc_error_retval);
forked = signal_parent_to_resume();
if (!forked)
exit(xfunc_error_retval);
else
_exit(xfunc_error_retval);
}

View File

@ -745,6 +745,14 @@ struct globals {
pid_t root_pid;
pid_t root_ppid;
pid_t last_bg_pid;
#ifdef __GNO__
/* Pid of the hush instance that was exec'd closest to us in the
* process tree. We're running in this pid's memory space, even
* if we've forked. Used to determine if parent should be signaled
* to resume when we terminate or exec.
*/
pid_t last_execed_pid;
#endif
#if ENABLE_HUSH_RANDOM_SUPPORT
random_t random_gen;
#endif
@ -842,7 +850,9 @@ static struct globals G;
G.sa.sa_flags = SA_RESTART; \
} while (0)
#else
# define INIT_G()
# define INIT_G() do { \
G.last_execed_pid = getpid(); \
} while (0)
#endif
/* Used by lineedit. */
@ -1331,6 +1341,19 @@ static void restore_G_args(save_arg_t *sv, char **argv)
}
/* Signal to the parent that it can resume executing after a fork,
* because the child is about to exec or terminate.
*/
int signal_parent_to_resume(void) {
#ifdef __GNO__
if (getpid() != G.last_execed_pid) {
kill(getppid(), SIGUSR2);
return 1;
}
#endif
return 0;
}
/* Basic theory of signal handling in shell
* ========================================
* This does not describe what hush does, rather, it is current understanding
@ -1564,8 +1587,10 @@ static void sigexit(int sig)
}
/* Not a signal, just exit */
if (sig <= 0)
if (sig <= 0) {
signal_parent_to_resume();
_exit(- sig);
}
kill_myself_with_sig(sig); /* does not return */
}
@ -5848,12 +5873,13 @@ static void re_execute_shell(char ***to_free, const char *s,
/* Don't propagate SIG_IGN to the child */
if (SPECIAL_JOBSTOP_SIGS != 0)
switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
signal_parent_to_resume();
execve(bb_busybox_exec_path, argv, pp);
/* Fallback. Useful for init=/bin/hush usage etc */
if (argv[0][0] == '/')
execve(argv[0], argv, pp);
xfunc_error_retval = 127;
bb_error_msg_and_die("can't re-execute the shell");
bb_perror_msg("can't re-execute the shell");
_exit(127); /* bash compat */
}
#endif /* !BB_MMU */
@ -6040,6 +6066,7 @@ static void xforked_child(void *args_struct) {
) {
static const char *const argv[] = { NULL, NULL };
builtin_trap((char**)argv);
signal_parent_to_resume();
exit(0); /* not _exit() - we need to fflush */
}
# if BB_MMU
@ -6198,8 +6225,10 @@ static void xvforked_child(void *grandchild_args) {
#else
pid = xvfork_and_run(xforked_grandchild, grandchild_args);
#endif
if (pid != 0)
if (pid != 0) {
signal_parent_to_resume();
_exit(0);
}
xforked_grandchild(grandchild_args); // Only get here in BB_MMU case
}
@ -6574,6 +6603,7 @@ static void execvp_or_die(char **argv)
/* Don't propagate SIG_IGN to the child */
if (SPECIAL_JOBSTOP_SIGS != 0)
switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
signal_parent_to_resume();
execvp(argv[0], argv);
bb_perror_msg("can't execute '%s'", argv[0]);
_exit(127); /* bash compat */
@ -6638,6 +6668,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
* expand_assignments(): think about ... | var=`sleep 1` | ...
*/
free_strings(new_env);
signal_parent_to_resume();
_exit(EXIT_SUCCESS);
}
@ -6761,6 +6792,7 @@ static void pseudo_exec(nommu_save_t *nommu_save,
/* Case when we are here: ... | >file */
debug_printf_exec(("pseudo_exec'ed null command\n"));
signal_parent_to_resume();
_exit(EXIT_SUCCESS);
}
@ -7499,8 +7531,10 @@ static void forked_child(void *args_struct) {
close(args->pipefds_p->rd);
/* Like bash, explicit redirects override pipes,
* and the pipe fd is available for dup'ing. */
if (setup_redirects(*args->command_p, NULL))
if (setup_redirects(*args->command_p, NULL)) {
signal_parent_to_resume();
_exit(1);
}
/* Stores to nommu_save list of env vars putenv'ed
* (NOMMU, on MMU we don't need that) */
@ -8417,6 +8451,15 @@ int hush_main(int argc, char **argv)
/* We have interactiveness code disabled */
install_special_sighandlers();
#endif
#ifdef __GNO__
/* Don't terminate on SIGUSR2, because we'll be using it to signal when
* it's safe to resume after a fork. We don't need to actually do any
* other handling for it, so just set it to SIG_IGN.
*/
install_sighandler(SIGUSR2, SIG_IGN);
#endif
/* bash:
* if interactive but not a login shell, sources ~/.bashrc
* (--norc turns this off, --rcfile <file> overrides)