hush: audit and fix "interactive shell" setup code.

function                                             old     new   delta
block_signals                                          -     139    +139
maybe_set_to_sigexit                                   -      47     +47
run_list                                            2018    2030     +12
expand_variables                                    2155    2165     +10
maybe_set_sighandler                                  47       -     -47
hush_main                                            992     918     -74
------------------------------------------------------------------------------
(add/remove: 2/1 grow/shrink: 2/1 up/down: 208/-121)           Total: 87 bytes
This commit is contained in:
Denis Vlasenko 2009-04-05 19:13:39 +00:00
parent 46f9b6db80
commit f937528571

View File

@ -850,8 +850,6 @@ static void free_strings(char **strings)
* Note: as a result, we do not use signal handlers much. The only uses * Note: as a result, we do not use signal handlers much. The only uses
* are to count SIGCHLDs [disabled - bug somewhere, + bloat] * are to count SIGCHLDs [disabled - bug somewhere, + bloat]
* and to restore tty pgrp on signal-induced exit. * and to restore tty pgrp on signal-induced exit.
*
* TODO: check/fix wait builtin to be interruptible.
*/ */
//static void SIGCHLD_handler(int sig UNUSED_PARAM) //static void SIGCHLD_handler(int sig UNUSED_PARAM)
@ -859,36 +857,6 @@ static void free_strings(char **strings)
// G.count_SIGCHLD++; // G.count_SIGCHLD++;
//} //}
/* called once at shell init */
static void init_signal_mask(void)
{
unsigned sig;
unsigned mask = (1 << SIGQUIT);
if (G_interactive_fd) {
mask = 0
| (1 << SIGQUIT)
| (1 << SIGTERM)
| (1 << SIGHUP)
#if ENABLE_HUSH_JOB
| (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP)
#endif
| (1 << SIGINT)
;
}
G.non_DFL_mask = mask;
sigprocmask(SIG_SETMASK, NULL, &G.blocked_set);
sig = 0;
while (mask) {
if (mask & 1)
sigaddset(&G.blocked_set, sig);
mask >>= 1;
sig++;
}
sigdelset(&G.blocked_set, SIGCHLD);
sigprocmask(SIG_SETMASK, &G.blocked_set, &G.inherited_set);
}
static int check_and_run_traps(int sig) static int check_and_run_traps(int sig)
{ {
static const struct timespec zero_timespec = { 0, 0 }; static const struct timespec zero_timespec = { 0, 0 };
@ -934,7 +902,6 @@ static int check_and_run_traps(int sig)
} }
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
/* Restores tty foreground process group, and exits. /* Restores tty foreground process group, and exits.
* May be called as signal handler for fatal signal * May be called as signal handler for fatal signal
* (will faithfully resend signal to itself, producing correct exit state) * (will faithfully resend signal to itself, producing correct exit state)
@ -957,48 +924,6 @@ static void sigexit(int sig)
kill_myself_with_sig(sig); /* does not return */ kill_myself_with_sig(sig); /* does not return */
} }
/* helper */
static void maybe_set_sighandler(int sig)
{
void (*handler)(int);
/* non_DFL_mask'ed signals are, well, masked,
* no need to set handler for them.
*/
if (!((G.non_DFL_mask >> sig) & 1)) {
handler = signal(sig, sigexit);
if (handler == SIG_IGN) /* oops... restore back to IGN! */
signal(sig, handler);
}
}
/* Used only to set handler to restore pgrp on exit */
static void set_fatal_signals_to_sigexit(void)
{
if (HUSH_DEBUG) {
maybe_set_sighandler(SIGILL );
maybe_set_sighandler(SIGFPE );
maybe_set_sighandler(SIGBUS );
maybe_set_sighandler(SIGSEGV);
maybe_set_sighandler(SIGTRAP);
} /* else: hush is perfect. what SEGV? */
maybe_set_sighandler(SIGABRT);
/* bash 3.2 seems to handle these just like 'fatal' ones */
maybe_set_sighandler(SIGPIPE);
maybe_set_sighandler(SIGALRM);
maybe_set_sighandler(SIGHUP );
/* if we aren't interactive... but in this case
* we never want to restore pgrp on exit, and this fn is not called */
/*maybe_set_sighandler(SIGTERM);*/
/*maybe_set_sighandler(SIGINT );*/
}
#else /* !JOB */
#define set_fatal_signals_to_sigexit(handler) ((void)0)
#endif #endif
/* Restores tty foreground process group, and exits. */ /* Restores tty foreground process group, and exits. */
@ -1007,6 +932,7 @@ static void hush_exit(int exitcode)
{ {
if (G.traps && G.traps[0] && G.traps[0][0]) { if (G.traps && G.traps[0] && G.traps[0][0]) {
char *argv[] = { NULL, xstrdup(G.traps[0]), NULL }; char *argv[] = { NULL, xstrdup(G.traps[0]), NULL };
//TODO: do we need to prevent recursion?
builtin_eval(argv); builtin_eval(argv);
free(argv[1]); free(argv[1]);
} }
@ -2896,7 +2822,7 @@ static int run_pipe(struct pipe *pi)
command->pid = BB_MMU ? fork() : vfork(); command->pid = BB_MMU ? fork() : vfork();
if (!command->pid) { /* child */ if (!command->pid) { /* child */
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
die_sleep = 0; /* let nofork's xfuncs die */ die_sleep = 0; /* do not restore tty pgrp on xfunc death */
/* Every child adds itself to new process group /* Every child adds itself to new process group
* with pgid == pid_of_first_child_in_pipe */ * with pgid == pid_of_first_child_in_pipe */
@ -2930,7 +2856,10 @@ static int run_pipe(struct pipe *pi)
/* pseudo_exec() does not return */ /* pseudo_exec() does not return */
} }
/* parent */ /* parent or error */
#if ENABLE_HUSH_JOB
die_sleep = -1; /* restore tty pgrp on xfunc death */
#endif
#if !BB_MMU #if !BB_MMU
/* Clean up after vforked child */ /* Clean up after vforked child */
clean_up_after_re_execute(); clean_up_after_re_execute();
@ -3900,6 +3829,9 @@ static FILE *generate_stream_from_string(const char *s)
bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork"); bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
if (pid == 0) { /* child */ if (pid == 0) { /* child */
#if ENABLE_HUSH_JOB
die_sleep = 0; /* do not restore tty pgrp on xfunc death */
#endif
/* Process substitution is not considered to be usual /* Process substitution is not considered to be usual
* 'command execution'. * 'command execution'.
* SUSv3 says ctrl-Z should be ignored, ctrl-C should not. * SUSv3 says ctrl-Z should be ignored, ctrl-C should not.
@ -3909,8 +3841,6 @@ static FILE *generate_stream_from_string(const char *s)
+ (1 << SIGTTIN) + (1 << SIGTTIN)
+ (1 << SIGTTOU) + (1 << SIGTTOU)
, SIG_IGN); , SIG_IGN);
if (ENABLE_HUSH_JOB)
die_sleep = 0; /* let nofork's xfuncs die */
close(channel[0]); /* NB: close _first_, then move fd! */ close(channel[0]); /* NB: close _first_, then move fd! */
xmove_fd(channel[1], 1); xmove_fd(channel[1], 1);
/* Prevent it from trying to handle ctrl-z etc */ /* Prevent it from trying to handle ctrl-z etc */
@ -3920,8 +3850,8 @@ static FILE *generate_stream_from_string(const char *s)
_exit(G.last_return_code); _exit(G.last_return_code);
#else #else
/* We re-execute after vfork on NOMMU. This makes this script safe: /* We re-execute after vfork on NOMMU. This makes this script safe:
* yes "0123456789012345678901234567890" | dd bs=32 count=64k >TESTFILE * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG
* huge=`cat TESTFILE` # was blocking here forever * huge=`cat BIG` # was blocking here forever
* echo OK * echo OK
*/ */
re_execute_shell(s); re_execute_shell(s);
@ -3929,6 +3859,9 @@ static FILE *generate_stream_from_string(const char *s)
} }
/* parent */ /* parent */
#if ENABLE_HUSH_JOB
die_sleep = -1; /* restore tty pgrp on xfunc death */
#endif
clean_up_after_re_execute(); clean_up_after_re_execute();
close(channel[1]); close(channel[1]);
pf = fdopen(channel[0], "r"); pf = fdopen(channel[0], "r");
@ -4945,31 +4878,81 @@ static void parse_and_run_file(FILE *f)
parse_and_run_stream(&input, ';'); parse_and_run_stream(&input, ';');
} }
#if ENABLE_HUSH_JOB /* Called a few times only (or even once if "sh -c") */
/* Make sure we have a controlling tty. If we get started under a job static void block_signals(int second_time)
* aware app (like bash for example), make sure we are now in charge so
* we don't fight over who gets the foreground */
static void setup_job_control(void)
{ {
pid_t shell_pgrp; unsigned sig;
unsigned mask;
shell_pgrp = getpgrp(); mask = (1 << SIGQUIT);
if (G_interactive_fd) {
/* If we were ran as 'hush &', mask = 0
* sleep until we are in the foreground. */ | (1 << SIGQUIT)
while (tcgetpgrp(G_interactive_fd) != shell_pgrp) { | (1 << SIGTERM)
/* Send TTIN to ourself (should stop us) */ | (1 << SIGHUP)
kill(- shell_pgrp, SIGTTIN); #if ENABLE_HUSH_JOB
shell_pgrp = getpgrp(); | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP)
#endif
| (1 << SIGINT)
;
} }
G.non_DFL_mask = mask;
if (!second_time)
sigprocmask(SIG_SETMASK, NULL, &G.blocked_set);
sig = 0;
while (mask) {
if (mask & 1)
sigaddset(&G.blocked_set, sig);
mask >>= 1;
sig++;
}
sigdelset(&G.blocked_set, SIGCHLD);
sigprocmask(SIG_SETMASK, &G.blocked_set,
second_time ? NULL : &G.inherited_set);
/* POSIX allows shell to re-enable SIGCHLD
* even if it was SIG_IGN on entry */
// G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
if (!second_time)
signal(SIGCHLD, SIG_DFL); // SIGCHLD_handler);
}
#if ENABLE_HUSH_JOB
/* helper */
static void maybe_set_to_sigexit(int sig)
{
void (*handler)(int);
/* non_DFL_mask'ed signals are, well, masked,
* no need to set handler for them.
*/
if (!((G.non_DFL_mask >> sig) & 1)) {
handler = signal(sig, sigexit);
if (handler == SIG_IGN) /* oops... restore back to IGN! */
signal(sig, handler);
}
}
/* Set handlers to restore tty pgrm and exit */
static void set_fatal_handlers(void)
{
/* We _must_ restore tty pgrp on fatal signals */ /* We _must_ restore tty pgrp on fatal signals */
set_fatal_signals_to_sigexit(); if (HUSH_DEBUG) {
maybe_set_to_sigexit(SIGILL );
/* Put ourselves in our own process group. */ maybe_set_to_sigexit(SIGFPE );
bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */ maybe_set_to_sigexit(SIGBUS );
/* Grab control of the terminal. */ maybe_set_to_sigexit(SIGSEGV);
tcsetpgrp(G_interactive_fd, getpid()); maybe_set_to_sigexit(SIGTRAP);
} /* else: hush is perfect. what SEGV? */
maybe_set_to_sigexit(SIGABRT);
/* bash 3.2 seems to handle these just like 'fatal' ones */
maybe_set_to_sigexit(SIGPIPE);
maybe_set_to_sigexit(SIGALRM);
maybe_set_to_sigexit(SIGHUP );
/* if we are interactive, SIGTERM and SIGINT are masked.
* if we aren't interactive... but in this case
* we never want to restore pgrp on exit, and this fn is not called */
/*maybe_set_to_sigexit(SIGTERM);*/
/*maybe_set_to_sigexit(SIGINT );*/
} }
#endif #endif
@ -4994,7 +4977,7 @@ int hush_main(int argc, char **argv)
.flg_export = 1, .flg_export = 1,
.flg_read_only = 1, .flg_read_only = 1,
}; };
int signal_mask_is_inited = 0;
int opt; int opt;
char **e; char **e;
struct variable *cur_var; struct variable *cur_var;
@ -5060,6 +5043,7 @@ int hush_main(int argc, char **argv)
optind--; optind--;
} /* else -c 'script' PAR0 PAR1: $0 is PAR0 */ } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */
G.global_argc = argc - optind; G.global_argc = argc - optind;
block_signals(0); /* 0: called 1st time */
parse_and_run_string(optarg); parse_and_run_string(optarg);
goto final_return; goto final_return;
case 'i': case 'i':
@ -5104,10 +5088,12 @@ int hush_main(int argc, char **argv)
bb_show_usage(); bb_show_usage();
#endif #endif
} }
} } /* option parsing loop */
if (!G.root_pid) if (!G.root_pid)
G.root_pid = getpid(); G.root_pid = getpid();
/* If we are login shell... */
if (argv[0] && argv[0][0] == '-') { if (argv[0] && argv[0][0] == '-') {
FILE *input; FILE *input;
/* XXX what should argv be while sourcing /etc/profile? */ /* XXX what should argv be while sourcing /etc/profile? */
@ -5115,26 +5101,57 @@ int hush_main(int argc, char **argv)
input = fopen_for_read("/etc/profile"); input = fopen_for_read("/etc/profile");
if (input != NULL) { if (input != NULL) {
close_on_exec_on(fileno(input)); close_on_exec_on(fileno(input));
block_signals(0); /* 0: called 1st time */
signal_mask_is_inited = 1;
parse_and_run_file(input); parse_and_run_file(input);
fclose(input); fclose(input);
} }
/* bash: after sourcing /etc/profile,
* tries to source (in the given order):
* ~/.bash_profile, ~/.bash_login, ~/.profile,
* stopping of first found. --noprofile turns this off.
* bash also sources ~/.bash_logout on exit.
* If called as sh, skips .bash_XXX files.
*/
} }
#if ENABLE_HUSH_JOB if (argv[optind]) {
FILE *input;
/*
* Non-interactive "bash <script>" sources $BASH_ENV here
* (without scanning $PATH).
* If called as sh, does the same but with $ENV.
*/
debug_printf("running script '%s'\n", argv[optind]);
G.global_argv = argv + optind;
G.global_argc = argc - optind;
input = xfopen_for_read(argv[optind]);
close_on_exec_on(fileno(input));
if (!signal_mask_is_inited)
block_signals(0); /* 0: called 1st time */
parse_and_run_file(input);
#if ENABLE_FEATURE_CLEAN_UP
fclose(input);
#endif
goto final_return;
}
/* Up to here, shell was non-interactive. Now it may become one. */
/* A shell is interactive if the '-i' flag was given, or if all of /* A shell is interactive if the '-i' flag was given, or if all of
* the following conditions are met: * the following conditions are met:
* no -c command * no -c command
* no arguments remaining or the -s flag given * no arguments remaining or the -s flag given
* standard input is a terminal * standard input is a terminal
* standard output is a terminal * standard output is a terminal
* Refer to Posix.2, the description of the 'sh' utility. */ * Refer to Posix.2, the description of the 'sh' utility.
if (argv[optind] == NULL */
&& isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) #if ENABLE_HUSH_JOB
) { if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
G.saved_tty_pgrp = tcgetpgrp(STDIN_FILENO); G.saved_tty_pgrp = tcgetpgrp(STDIN_FILENO);
debug_printf("saved_tty_pgrp=%d\n", G.saved_tty_pgrp); debug_printf("saved_tty_pgrp:%d\n", G.saved_tty_pgrp);
if (G.saved_tty_pgrp >= 0) { if (G.saved_tty_pgrp >= 0) {
/* try to dup to high fd#, >= 255 */ /* try to dup stdin to high fd#, >= 255 */
G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
if (G_interactive_fd < 0) { if (G_interactive_fd < 0) {
/* try to dup to any fd */ /* try to dup to any fd */
@ -5147,12 +5164,35 @@ int hush_main(int argc, char **argv)
// to (inadvertently) close/redirect it // to (inadvertently) close/redirect it
} }
} }
init_signal_mask(); /* note: ensures SIGCHLD is not masked */ debug_printf("interactive_fd:%d\n", G_interactive_fd);
debug_printf("interactive_fd=%d\n", G_interactive_fd);
if (G_interactive_fd) { if (G_interactive_fd) {
fcntl(G_interactive_fd, F_SETFD, FD_CLOEXEC); pid_t shell_pgrp;
/* Looks like they want an interactive shell */
setup_job_control(); /* We are indeed interactive shell, and we will perform
* job control. Setting up for that. */
close_on_exec_on(G_interactive_fd);
/* If we were run as 'hush &', sleep until we are
* in the foreground (tty pgrp == our pgrp).
* If we get started under a job aware app (like bash),
* make sure we are now in charge so we don't fight over
* who gets the foreground */
while (1) {
shell_pgrp = getpgrp();
G.saved_tty_pgrp = tcgetpgrp(G_interactive_fd);
if (G.saved_tty_pgrp == shell_pgrp)
break;
/* send TTIN to ourself (should stop us) */
kill(- shell_pgrp, SIGTTIN);
}
/* Block some signals */
block_signals(signal_mask_is_inited);
/* Set other signals to restore saved_tty_pgrp */
set_fatal_handlers();
/* Put ourselves in our own process group */
bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
/* Grab control of the terminal */
tcsetpgrp(G_interactive_fd, getpid());
/* -1 is special - makes xfuncs longjmp, not exit /* -1 is special - makes xfuncs longjmp, not exit
* (we reset die_sleep = 0 whereever we [v]fork) */ * (we reset die_sleep = 0 whereever we [v]fork) */
die_sleep = -1; die_sleep = -1;
@ -5160,12 +5200,12 @@ int hush_main(int argc, char **argv)
/* xfunc has failed! die die die */ /* xfunc has failed! die die die */
hush_exit(xfunc_error_retval); hush_exit(xfunc_error_retval);
} }
} } else if (!signal_mask_is_inited) {
block_signals(0); /* 0: called 1st time */
} /* else: block_signals(0) was done before */
#elif ENABLE_HUSH_INTERACTIVE #elif ENABLE_HUSH_INTERACTIVE
/* no job control compiled, only prompt/line editing */ /* No job control compiled in, only prompt/line editing */
if (argv[optind] == NULL if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
&& isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
) {
G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
if (G_interactive_fd < 0) { if (G_interactive_fd < 0) {
/* try to dup to any fd */ /* try to dup to any fd */
@ -5174,45 +5214,32 @@ int hush_main(int argc, char **argv)
/* give up */ /* give up */
G_interactive_fd = 0; G_interactive_fd = 0;
} }
if (G_interactive_fd) {
fcntl(G_interactive_fd, F_SETFD, FD_CLOEXEC);
}
} }
init_signal_mask(); /* note: ensures SIGCHLD is not masked */
#else
//TODO: we didn't do it for -c or /etc/profile! Shouldn't we?
init_signal_mask();
#endif
/* POSIX allows shell to re-enable SIGCHLD
* even if it was SIG_IGN on entry */
//TODO: we didn't do it for -c or /etc/profile! Shouldn't we?
// G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
signal(SIGCHLD, SIG_DFL); // SIGCHLD_handler);
#if ENABLE_HUSH_INTERACTIVE && !ENABLE_FEATURE_SH_EXTRA_QUIET
if (G_interactive_fd) { if (G_interactive_fd) {
close_on_exec_on(G_interactive_fd);
block_signals(signal_mask_is_inited);
} else if (!signal_mask_is_inited) {
block_signals(0);
}
#else
/* We have interactiveness code disabled */
if (!signal_mask_is_inited) {
block_signals(0);
}
#endif
/* bash:
* if interactive but not a login shell, sources ~/.bashrc
* (--norc turns this off, --rcfile <file> overrides)
*/
if (!ENABLE_FEATURE_SH_EXTRA_QUIET && G_interactive_fd) {
printf("\n\n%s hush - the humble shell v"HUSH_VER_STR"\n", bb_banner); printf("\n\n%s hush - the humble shell v"HUSH_VER_STR"\n", bb_banner);
printf("Enter 'help' for a list of built-in commands.\n\n"); printf("Enter 'help' for a list of built-in commands.\n\n");
} }
#endif
if (argv[optind] == NULL) { parse_and_run_file(stdin);
parse_and_run_file(stdin);
} else {
FILE *input;
debug_printf("\nrunning script '%s'\n", argv[optind]);
G.global_argv = argv + optind;
G.global_argc = argc - optind;
input = xfopen_for_read(argv[optind]);
fcntl(fileno(input), F_SETFD, FD_CLOEXEC);
parse_and_run_file(input);
#if ENABLE_FEATURE_CLEAN_UP
fclose(input);
#endif
}
final_return: final_return:
#if ENABLE_FEATURE_CLEAN_UP #if ENABLE_FEATURE_CLEAN_UP
if (G.cwd != bb_msg_unknown) if (G.cwd != bb_msg_unknown)
free((char*)G.cwd); free((char*)G.cwd);