diff options
author | Denis Vlasenko | 2009-04-05 19:13:39 +0000 |
---|---|---|
committer | Denis Vlasenko | 2009-04-05 19:13:39 +0000 |
commit | f9375285719035dbf2f7003d582c22447ed579f0 (patch) | |
tree | bfc4fcab7ae9f73bebe1e89d34aca33af332f57f | |
parent | 46f9b6db80e4fd0a1c50d435bc387ad28d12abb2 (diff) | |
download | busybox-f9375285719035dbf2f7003d582c22447ed579f0.zip busybox-f9375285719035dbf2f7003d582c22447ed579f0.tar.gz |
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
-rw-r--r-- | shell/hush.c | 323 |
1 files changed, 175 insertions, 148 deletions
diff --git a/shell/hush.c b/shell/hush.c index eba7a86..a3f80d5 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -850,8 +850,6 @@ static void free_strings(char **strings) * Note: as a result, we do not use signal handlers much. The only uses * are to count SIGCHLDs [disabled - bug somewhere, + bloat] * 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) @@ -859,36 +857,6 @@ static void free_strings(char **strings) // 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 const struct timespec zero_timespec = { 0, 0 }; @@ -934,7 +902,6 @@ static int check_and_run_traps(int sig) } #if ENABLE_HUSH_JOB - /* Restores tty foreground process group, and exits. * May be called as signal handler for fatal signal * (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 */ } - -/* 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 /* 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]) { char *argv[] = { NULL, xstrdup(G.traps[0]), NULL }; +//TODO: do we need to prevent recursion? builtin_eval(argv); free(argv[1]); } @@ -2896,7 +2822,7 @@ static int run_pipe(struct pipe *pi) command->pid = BB_MMU ? fork() : vfork(); if (!command->pid) { /* child */ #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 * with pgid == pid_of_first_child_in_pipe */ @@ -2930,7 +2856,10 @@ static int run_pipe(struct pipe *pi) /* 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 /* Clean up after vforked child */ 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"); 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 * 'command execution'. * 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 << SIGTTOU) , SIG_IGN); - if (ENABLE_HUSH_JOB) - die_sleep = 0; /* let nofork's xfuncs die */ close(channel[0]); /* NB: close _first_, then move fd! */ xmove_fd(channel[1], 1); /* 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); #else /* We re-execute after vfork on NOMMU. This makes this script safe: - * yes "0123456789012345678901234567890" | dd bs=32 count=64k >TESTFILE - * huge=`cat TESTFILE` # was blocking here forever + * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG + * huge=`cat BIG` # was blocking here forever * echo OK */ re_execute_shell(s); @@ -3929,6 +3859,9 @@ static FILE *generate_stream_from_string(const char *s) } /* parent */ +#if ENABLE_HUSH_JOB + die_sleep = -1; /* restore tty pgrp on xfunc death */ +#endif clean_up_after_re_execute(); close(channel[1]); pf = fdopen(channel[0], "r"); @@ -4945,31 +4878,81 @@ static void parse_and_run_file(FILE *f) parse_and_run_stream(&input, ';'); } -#if ENABLE_HUSH_JOB -/* Make sure we have a controlling tty. If we get started under a job - * 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) +/* Called a few times only (or even once if "sh -c") */ +static void block_signals(int second_time) { - pid_t shell_pgrp; + unsigned sig; + unsigned mask; - shell_pgrp = getpgrp(); + 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; - /* If we were ran as 'hush &', - * sleep until we are in the foreground. */ - while (tcgetpgrp(G_interactive_fd) != shell_pgrp) { - /* Send TTIN to ourself (should stop us) */ - kill(- shell_pgrp, SIGTTIN); - shell_pgrp = getpgrp(); + 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); - /* We _must_ restore tty pgrp on fatal signals */ - set_fatal_signals_to_sigexit(); + 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); +} - /* 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()); +#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 */ + if (HUSH_DEBUG) { + maybe_set_to_sigexit(SIGILL ); + maybe_set_to_sigexit(SIGFPE ); + maybe_set_to_sigexit(SIGBUS ); + maybe_set_to_sigexit(SIGSEGV); + 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 @@ -4994,7 +4977,7 @@ int hush_main(int argc, char **argv) .flg_export = 1, .flg_read_only = 1, }; - + int signal_mask_is_inited = 0; int opt; char **e; struct variable *cur_var; @@ -5060,6 +5043,7 @@ int hush_main(int argc, char **argv) optind--; } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */ G.global_argc = argc - optind; + block_signals(0); /* 0: called 1st time */ parse_and_run_string(optarg); goto final_return; case 'i': @@ -5104,10 +5088,12 @@ int hush_main(int argc, char **argv) bb_show_usage(); #endif } - } + } /* option parsing loop */ if (!G.root_pid) G.root_pid = getpid(); + + /* If we are login shell... */ if (argv[0] && argv[0][0] == '-') { FILE *input; /* 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"); if (input != NULL) { close_on_exec_on(fileno(input)); + block_signals(0); /* 0: called 1st time */ + signal_mask_is_inited = 1; parse_and_run_file(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 * the following conditions are met: * no -c command * no arguments remaining or the -s flag given * standard input is a terminal * standard output is a terminal - * Refer to Posix.2, the description of the 'sh' utility. */ - if (argv[optind] == NULL - && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) - ) { + * Refer to Posix.2, the description of the 'sh' utility. + */ +#if ENABLE_HUSH_JOB + if (isatty(STDIN_FILENO) && isatty(STDOUT_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) { - /* try to dup to high fd#, >= 255 */ + /* try to dup stdin to high fd#, >= 255 */ G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); if (G_interactive_fd < 0) { /* try to dup to any fd */ @@ -5147,12 +5164,35 @@ int hush_main(int argc, char **argv) // 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) { - fcntl(G_interactive_fd, F_SETFD, FD_CLOEXEC); - /* Looks like they want an interactive shell */ - setup_job_control(); + pid_t shell_pgrp; + + /* 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 * (we reset die_sleep = 0 whereever we [v]fork) */ die_sleep = -1; @@ -5160,12 +5200,12 @@ int hush_main(int argc, char **argv) /* xfunc has failed! die die die */ 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 -/* no job control compiled, only prompt/line editing */ - if (argv[optind] == NULL - && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) - ) { + /* No job control compiled in, only prompt/line editing */ + if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); if (G_interactive_fd < 0) { /* try to dup to any fd */ @@ -5174,45 +5214,32 @@ int hush_main(int argc, char **argv) /* give up */ 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 */ + 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 -//TODO: we didn't do it for -c or /etc/profile! Shouldn't we? - init_signal_mask(); + /* We have interactiveness code disabled */ + if (!signal_mask_is_inited) { + block_signals(0); + } #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); + /* bash: + * if interactive but not a login shell, sources ~/.bashrc + * (--norc turns this off, --rcfile <file> overrides) + */ -#if ENABLE_HUSH_INTERACTIVE && !ENABLE_FEATURE_SH_EXTRA_QUIET - if (G_interactive_fd) { + 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("Enter 'help' for a list of built-in commands.\n\n"); } -#endif - if (argv[optind] == NULL) { - 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 - } + parse_and_run_file(stdin); final_return: - #if ENABLE_FEATURE_CLEAN_UP if (G.cwd != bb_msg_unknown) free((char*)G.cwd); |