NOFORK fixes
authorDenys Vlasenko <vda.linux@googlemail.com>
Wed, 2 Aug 2017 14:37:39 +0000 (16:37 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Wed, 2 Aug 2017 14:37:39 +0000 (16:37 +0200)
"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>
coreutils/rm.c
coreutils/seq.c
coreutils/usleep.c
coreutils/yes.c
docs/nofork_noexec.txt
shell/hush.c

index f91c94570030c36c912d258e2f4ee4cd5c13f6d4..5e4acab8cdf608a8ea601ca3ca2d59e9c5ac9fa5 100644 (file)
@@ -16,7 +16,8 @@
 //config:      help
 //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
 
@@ -36,7 +37,7 @@
 
 #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 UNUSED_PARAM, char **argv)
index f36dbb4ec8b3ccc4b5363e7426cd3e90c268c067..c26ff06b9a575f1e1d1dedf0d5aa9acee36482b7 100644 (file)
@@ -12,7 +12,8 @@
 //config:      help
 //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
 
@@ -26,7 +27,7 @@
 
 #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)
index 7c25aada16378e72307553217ce02b0b36c5ea50..684ab781b179ec73e783e2af6c5f580d7b6f94f0 100644 (file)
@@ -38,6 +38,13 @@ int usleep_main(int argc UNUSED_PARAM, char **argv)
                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]));
 
        return EXIT_SUCCESS;
index ea35d146c57bd5886f828e39558638d9f864a200..c244bfe102817bb0adb35afbb5676d4ef18bbd27 100644 (file)
@@ -17,7 +17,8 @@
 //config:      yes is used to repeatedly output a specific string, or
 //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
 
index a24dd9c27382069fbc044173e8d363092fa63a6a..0ad4e6e605bfa31caf31916d9552ab6b179ce9c1 100644 (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:
 
 * 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().
   - xfuncs are okay. They are using special trick to return
     to the caller applet instead of dying when they detect "x" condition.
index 9f946d82f19577a6036263b7e1faaa31ff3e3d77..cfefb7324e269831b8acadd5318e3f62ccfdedb2 100644 (file)
@@ -7363,6 +7363,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
                                 */
                                close_saved_fds_and_FILE_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]);
                                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);
 /* clean_up_and_ret0: */
                        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:
                        free(argv_expanded);
                        IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
@@ -8060,6 +8080,14 @@ static NOINLINE int run_pipe(struct pipe *pi)
                                if (rcode == 0) {
                                        debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
                                                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);
                                }
                                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_exitcode = 0;
                        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;
                        goto check_traps;
                } 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? */
                sig = check_and_run_traps();
                if (sig /*&& sig != SIGCHLD - always true */) {
+                       /* Do this for any (non-ignored) signal, not only for ^C */
                        ret = 128 + sig;
                        break;
                }