NOFORK fixes

"rm -i FILE" and "yes" can now be interrupted by ^C in hush.
This also now works:

$ usleep 19999999
^C
$ echo $?
130

function                                             old     new   delta
run_pipe                                            1668    1711     +43
pseudo_exec_argv                                     312     321      +9
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 52/0)               Total: 52 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2017-08-02 16:37:39 +02:00
parent 95f7953f2c
commit 7c40ddd950
6 changed files with 48 additions and 6 deletions

View File

@ -16,7 +16,8 @@
//config: help //config: help
//config: rm is used to remove files or directories. //config: rm is used to remove files or directories.
//applet:IF_RM(APPLET_NOFORK(rm, rm, BB_DIR_BIN, BB_SUID_DROP, rm)) //applet:IF_RM(APPLET_NOEXEC(rm, rm, BB_DIR_BIN, BB_SUID_DROP, rm))
/* was NOFORK, but then "rm -i FILE" can't be ^C'ed if run by hush */
//kbuild:lib-$(CONFIG_RM) += rm.o //kbuild:lib-$(CONFIG_RM) += rm.o
@ -36,7 +37,7 @@
#include "libbb.h" #include "libbb.h"
/* This is a NOFORK applet. Be very careful! */ /* This is a NOEXEC applet. Be very careful! */
int rm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int rm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int rm_main(int argc UNUSED_PARAM, char **argv) int rm_main(int argc UNUSED_PARAM, char **argv)

View File

@ -12,7 +12,8 @@
//config: help //config: help
//config: print a sequence of numbers //config: print a sequence of numbers
//applet:IF_SEQ(APPLET_NOFORK(seq, seq, BB_DIR_USR_BIN, BB_SUID_DROP, seq)) //applet:IF_SEQ(APPLET_NOEXEC(seq, seq, BB_DIR_USR_BIN, BB_SUID_DROP, seq))
/* was NOFORK, but then "seq 1 999999999" can't be ^C'ed if run by hush */
//kbuild:lib-$(CONFIG_SEQ) += seq.o //kbuild:lib-$(CONFIG_SEQ) += seq.o
@ -26,7 +27,7 @@
#include "libbb.h" #include "libbb.h"
/* This is a NOFORK applet. Be very careful! */ /* This is a NOEXEC applet. Be very careful! */
int seq_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int seq_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int seq_main(int argc, char **argv) int seq_main(int argc, char **argv)

View File

@ -38,6 +38,13 @@ int usleep_main(int argc UNUSED_PARAM, char **argv)
bb_show_usage(); bb_show_usage();
} }
/* Safe wrt NOFORK? (noforks are not allowed to run for
* a long time). Try "usleep 99999999" + ^C + "echo $?"
* in hush with FEATURE_SH_NOFORK=y.
* At least on uclibc, usleep() thanslates to nanosleep()
* which returns early on any signal (even caught one),
* and uclibc does not loop back on EINTR.
*/
usleep(xatou(argv[1])); usleep(xatou(argv[1]));
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@ -17,7 +17,8 @@
//config: yes is used to repeatedly output a specific string, or //config: yes is used to repeatedly output a specific string, or
//config: the default string 'y'. //config: the default string 'y'.
//applet:IF_YES(APPLET_NOFORK(yes, yes, BB_DIR_USR_BIN, BB_SUID_DROP, yes)) //applet:IF_YES(APPLET_NOEXEC(yes, yes, BB_DIR_USR_BIN, BB_SUID_DROP, yes))
/* was NOFORK, but then yes can't be ^C'ed if run by hush */
//kbuild:lib-$(CONFIG_YES) += yes.o //kbuild:lib-$(CONFIG_YES) += yes.o

View File

@ -52,6 +52,9 @@ xargs, find, shells do it (grep for "spawn_and_wait" and
This poses much more serious limitations on what applet can do: This poses much more serious limitations on what applet can do:
* all NOEXEC limitations apply. * all NOEXEC limitations apply.
* do not run for a long time or wait for user input:
hush shell only handles signals (like ^C) after you return
from APPLET_main().
* do not ever exit() or exec(). * do not ever exit() or exec().
- xfuncs are okay. They are using special trick to return - xfuncs are okay. They are using special trick to return
to the caller applet instead of dying when they detect "x" condition. to the caller applet instead of dying when they detect "x" condition.

View File

@ -7363,6 +7363,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
*/ */
close_saved_fds_and_FILE_fds(); close_saved_fds_and_FILE_fds();
//FIXME: should also close saved redir fds //FIXME: should also close saved redir fds
/* Without this, "rm -i FILE" can't be ^C'ed: */
switch_off_special_sigs(G.special_sig_mask);
debug_printf_exec("running applet '%s'\n", argv[0]); debug_printf_exec("running applet '%s'\n", argv[0]);
run_applet_no_and_exit(a, argv[0], argv); run_applet_no_and_exit(a, argv[0], argv);
} }
@ -8045,6 +8047,24 @@ static NOINLINE int run_pipe(struct pipe *pi)
add_vars(old_vars); add_vars(old_vars);
/* clean_up_and_ret0: */ /* clean_up_and_ret0: */
restore_redirects(squirrel); restore_redirects(squirrel);
/*
* Try "usleep 99999999" + ^C + "echo $?"
* with FEATURE_SH_NOFORK=y.
*/
if (!funcp) {
/* It was builtin or nofork.
* if this would be a real fork/execed program,
* it should have died if a fatal sig was received.
* But OTOH, there was no separate process,
* the sig was sent to _shell_, not to non-existing
* child.
* Let's just handle ^C only, this one is obvious:
* we aren't ok with exitcode 0 when ^C was pressed
* during builtin/nofork.
*/
if (sigismember(&G.pending_set, SIGINT))
rcode = 128 + SIGINT;
}
clean_up_and_ret1: clean_up_and_ret1:
free(argv_expanded); free(argv_expanded);
IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
@ -8060,6 +8080,14 @@ static NOINLINE int run_pipe(struct pipe *pi)
if (rcode == 0) { if (rcode == 0) {
debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
argv_expanded[0], argv_expanded[1]); argv_expanded[0], argv_expanded[1]);
/*
* Note: signals (^C) can't interrupt here.
* We remember them and they will be acted upon
* after applet returns.
* This makes applets which can run for a long time
* and/or wait for user input ineligible for NOFORK:
* for example, "yes" or "rm" (rm -i waits for input).
*/
rcode = run_nofork_applet(n, argv_expanded); rcode = run_nofork_applet(n, argv_expanded);
} }
goto clean_up_and_ret; goto clean_up_and_ret;
@ -8491,7 +8519,7 @@ static int run_list(struct pipe *pi)
G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
G.last_bg_pid_exitcode = 0; G.last_bg_pid_exitcode = 0;
debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash say 0 */
rcode = EXIT_SUCCESS; rcode = EXIT_SUCCESS;
goto check_traps; goto check_traps;
} else { } else {
@ -10178,6 +10206,7 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid
/* So, did we get a signal? */ /* So, did we get a signal? */
sig = check_and_run_traps(); sig = check_and_run_traps();
if (sig /*&& sig != SIGCHLD - always true */) { if (sig /*&& sig != SIGCHLD - always true */) {
/* Do this for any (non-ignored) signal, not only for ^C */
ret = 128 + sig; ret = 128 + sig;
break; break;
} }