diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2026-01-27 07:28:39 +0100 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2026-01-27 08:03:15 +0100 |
| commit | 22b96c0ddfc546a7dd9344f7bf7f40cc25364e57 (patch) | |
| tree | 15a0170cba36babf117ccc4305592b12d5c4e4b0 | |
| parent | 28e4d2b854d7072c510f416acd2eea5dcdbc346a (diff) | |
| download | busybox-w32-22b96c0ddfc546a7dd9344f7bf7f40cc25364e57.tar.gz busybox-w32-22b96c0ddfc546a7dd9344f7bf7f40cc25364e57.tar.bz2 busybox-w32-22b96c0ddfc546a7dd9344f7bf7f40cc25364e57.zip | |
ash: unset traps before entering NOEXEC programs after [v]fork
If we don't do that, if INT trap was set, ^C will set a flag
"run trap later" and _return_, which is not expected by the NOFORK!
function old new delta
clear_traps - 107 +107
evalcommand 1617 1631 +14
shellexec 471 476 +5
setsignal 333 327 -6
forkchild 620 480 -140
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 2/2 up/down: 126/-146) Total: -20 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
| -rw-r--r-- | shell/ash.c | 96 |
1 files changed, 89 insertions, 7 deletions
diff --git a/shell/ash.c b/shell/ash.c index ff7f42609..97ad95a28 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
| @@ -192,6 +192,7 @@ | |||
| 192 | #define DEBUG_PID 1 | 192 | #define DEBUG_PID 1 |
| 193 | #define DEBUG_SIG 1 | 193 | #define DEBUG_SIG 1 |
| 194 | #define DEBUG_INTONOFF 0 | 194 | #define DEBUG_INTONOFF 0 |
| 195 | #define DEBUG_SHOW_MEMUSE 0 | ||
| 195 | 196 | ||
| 196 | #define PROFILE 0 | 197 | #define PROFILE 0 |
| 197 | 198 | ||
| @@ -1117,6 +1118,30 @@ freefunc(struct funcnode *f) | |||
| 1117 | 1118 | ||
| 1118 | /* ============ Debugging output */ | 1119 | /* ============ Debugging output */ |
| 1119 | 1120 | ||
| 1121 | #if DEBUG_SHOW_MEMUSE | ||
| 1122 | static void dbg_show_dirtymem(const char *msg) | ||
| 1123 | { | ||
| 1124 | char *p, buf[2*1024]; | ||
| 1125 | int sz = open_read_close("/proc/self/smaps_rollup", buf, sizeof(buf)-1); | ||
| 1126 | if (sz <= 0) | ||
| 1127 | return; | ||
| 1128 | buf[sz] = '\0'; | ||
| 1129 | p = buf; | ||
| 1130 | for (;;) { | ||
| 1131 | char *e = strchrnul(p, '\n'); | ||
| 1132 | if (strncmp(p, "Private_Dirty:", 14) == 0) { | ||
| 1133 | p = skip_whitespace(p + 14); | ||
| 1134 | bb_error_msg("%s:%.*s", msg, (int)(e - p), p); | ||
| 1135 | break; | ||
| 1136 | } | ||
| 1137 | if (!*e) break; | ||
| 1138 | p = e + 1; | ||
| 1139 | } | ||
| 1140 | } | ||
| 1141 | #else | ||
| 1142 | # define dbg_show_dirtymem(msg) ((void)0) | ||
| 1143 | #endif | ||
| 1144 | |||
| 1120 | #if DEBUG | 1145 | #if DEBUG |
| 1121 | 1146 | ||
| 1122 | static FILE *tracefile; | 1147 | static FILE *tracefile; |
| @@ -3773,6 +3798,10 @@ ignoresig(int signo) | |||
| 3773 | /* | 3798 | /* |
| 3774 | * Only one usage site - in setsignal() | 3799 | * Only one usage site - in setsignal() |
| 3775 | * This function is called onsig() in dash | 3800 | * This function is called onsig() in dash |
| 3801 | * | ||
| 3802 | * The handler is called only for SIGINT, SIGCHLD, signals with not-"" traps | ||
| 3803 | * (grep for S_CATCH handling to see that). | ||
| 3804 | * All other signals are either SIG_DFL or SIG_IGN. | ||
| 3776 | */ | 3805 | */ |
| 3777 | static void | 3806 | static void |
| 3778 | signal_handler(int signo) | 3807 | signal_handler(int signo) |
| @@ -3789,9 +3818,10 @@ signal_handler(int signo) | |||
| 3789 | return; | 3818 | return; |
| 3790 | } | 3819 | } |
| 3791 | #if ENABLE_FEATURE_EDITING | 3820 | #if ENABLE_FEATURE_EDITING |
| 3821 | //TODO: don't do it if it's SIGCHLD? | ||
| 3792 | bb_got_signal = signo; /* for read_line_input / read builtin: "we got a signal" */ | 3822 | bb_got_signal = signo; /* for read_line_input / read builtin: "we got a signal" */ |
| 3793 | #endif | 3823 | #endif |
| 3794 | gotsig[signo - 1] = 1; | 3824 | gotsig[signo - 1] = 1; /* "run a trap for this later" */ |
| 3795 | pending_sig = signo; | 3825 | pending_sig = signo; |
| 3796 | 3826 | ||
| 3797 | if (signo == SIGINT && !trap[SIGINT]) { | 3827 | if (signo == SIGINT && !trap[SIGINT]) { |
| @@ -3819,13 +3849,13 @@ setsignal(int signo) | |||
| 3819 | 3849 | ||
| 3820 | t = trap[signo]; | 3850 | t = trap[signo]; |
| 3821 | new_act = S_DFL; | 3851 | new_act = S_DFL; |
| 3822 | if (t != NULL) { /* trap for this sig is set */ | 3852 | if (t != NULL) { |
| 3853 | /* Trap for this sig is set */ | ||
| 3823 | new_act = S_CATCH; | 3854 | new_act = S_CATCH; |
| 3824 | if (t[0] == '\0') /* trap is "": ignore this sig */ | 3855 | if (t[0] == '\0') /* trap is "": ignore this sig */ |
| 3825 | new_act = S_IGN; | 3856 | new_act = S_IGN; |
| 3826 | } | 3857 | } else |
| 3827 | 3858 | if (rootshell && /*TRUE: new_act == S_DFL &&*/ !lvforked) { | |
| 3828 | if (rootshell && new_act == S_DFL && !lvforked) { | ||
| 3829 | switch (signo) { | 3859 | switch (signo) { |
| 3830 | case SIGINT: | 3860 | case SIGINT: |
| 3831 | if (iflag || minusc || sflag == 0) | 3861 | if (iflag || minusc || sflag == 0) |
| @@ -3840,7 +3870,10 @@ setsignal(int signo) | |||
| 3840 | * "In all cases, bash ignores SIGQUIT. Non-builtin | 3870 | * "In all cases, bash ignores SIGQUIT. Non-builtin |
| 3841 | * commands run by bash have signal handlers | 3871 | * commands run by bash have signal handlers |
| 3842 | * set to the values inherited by the shell | 3872 | * set to the values inherited by the shell |
| 3843 | * from its parent". */ | 3873 | * from its parent". |
| 3874 | * This is not POSIX, but it avoids additional | ||
| 3875 | * core files from shells when users press ^\ | ||
| 3876 | */ | ||
| 3844 | new_act = S_IGN; | 3877 | new_act = S_IGN; |
| 3845 | break; | 3878 | break; |
| 3846 | case SIGTERM: | 3879 | case SIGTERM: |
| @@ -5395,7 +5428,7 @@ forkchild(struct job *jp, union node *n, int mode) | |||
| 5395 | } | 5428 | } |
| 5396 | } | 5429 | } |
| 5397 | if (oldlvl == 0) { | 5430 | if (oldlvl == 0) { |
| 5398 | if (iflag) { /* why if iflag only? */ | 5431 | if (iflag) { /* -i ignores INT/TERM, un-ignore them */ |
| 5399 | setsignal(SIGINT); | 5432 | setsignal(SIGINT); |
| 5400 | setsignal(SIGTERM); | 5433 | setsignal(SIGTERM); |
| 5401 | } | 5434 | } |
| @@ -8449,10 +8482,52 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c | |||
| 8449 | #if ENABLE_FEATURE_SH_STANDALONE | 8482 | #if ENABLE_FEATURE_SH_STANDALONE |
| 8450 | if (applet_no >= 0) { | 8483 | if (applet_no >= 0) { |
| 8451 | if (!vforked && APPLET_IS_NOEXEC(applet_no)) { | 8484 | if (!vforked && APPLET_IS_NOEXEC(applet_no)) { |
| 8485 | dbg_show_dirtymem("dirtymem in NOEXEC tryexec"); | ||
| 8452 | clearenv(); | 8486 | clearenv(); |
| 8453 | while (*envp) | 8487 | while (*envp) |
| 8454 | putenv(*envp++); | 8488 | putenv(*envp++); |
| 8455 | popredir(/*drop:*/ 1); | 8489 | popredir(/*drop:*/ 1); |
| 8490 | //FIXME: exec resets all non-DFL, non-IGN signal handlers to DFL, | ||
| 8491 | //but we _don't_ exec, such signals will reach ash's handler instead! | ||
| 8492 | //Maybe add code there to set the handler to DFL, and signal itself? | ||
| 8493 | // This works for "CMD [| CMD]..." pipes: | ||
| 8494 | //vforkexec() | ||
| 8495 | // vfork(); | ||
| 8496 | // forkchild(jp, n, FORK_FG); // this resets TSTP,TTOU,INT,TERM,QUIT to DFL | ||
| 8497 | // shellexec(argv[0], argv, path, idx) | ||
| 8498 | // tryexec() | ||
| 8499 | // we are here | ||
| 8500 | // And for "exec CMD": | ||
| 8501 | //execcmd() | ||
| 8502 | // iflag = 0; | ||
| 8503 | // mflag = 0; | ||
| 8504 | // optschanged(); // this resets TSTP,TTOU,INT,TERM to DFL | ||
| 8505 | // shlvl++; | ||
| 8506 | // setsignal(SIGQUIT); // this resets QUIT to DFL | ||
| 8507 | // shellexec() | ||
| 8508 | // tryexec() | ||
| 8509 | // we are here | ||
| 8510 | // But ash -c 'LAST_CMD_DOESNT_FORK' does not work! | ||
| 8511 | //evalcommand() | ||
| 8512 | // if (!(flags & EV_EXIT) || may_have_traps) | ||
| 8513 | // // we don't use this branch | ||
| 8514 | // //else: | ||
| 8515 | // shellexec() | ||
| 8516 | // tryexec() | ||
| 8517 | // we are here | ||
| 8518 | //SIGINT works 'by accident' (sets DFL+signals itself) | ||
| 8519 | //SIGQUIT is IGNORED! | ||
| 8520 | //Fixed by adding in the evalcommand() before the shown shellexec(): | ||
| 8521 | // shlvl++; | ||
| 8522 | // setsignal(SIGQUIT); | ||
| 8523 | // //TODO: setsignal(TSTP,TTOU,INT,TERM) too? | ||
| 8524 | // | ||
| 8525 | // With traps set, this: | ||
| 8526 | // ash -c 'trap "echo HERE!" INT; exec xargs' | ||
| 8527 | // didn't work: ^C sets a "run trap later" flag and _returns_, | ||
| 8528 | // which is not expected by the NOFORK'ed xargs! | ||
| 8529 | // clear_traps() helps with this: | ||
| 8530 | clear_traps(); | ||
| 8456 | run_noexec_applet_and_exit(applet_no, cmd, argv); | 8531 | run_noexec_applet_and_exit(applet_no, cmd, argv); |
| 8457 | } | 8532 | } |
| 8458 | /* re-exec ourselves with the new arguments */ | 8533 | /* re-exec ourselves with the new arguments */ |
| @@ -10791,6 +10866,9 @@ evalcommand(union node *cmd, int flags) | |||
| 10791 | jp = vforkexec(cmd, argv, path, cmdentry.u.index); | 10866 | jp = vforkexec(cmd, argv, path, cmdentry.u.index); |
| 10792 | break; | 10867 | break; |
| 10793 | } | 10868 | } |
| 10869 | /* Testcase: ash -c 'ONE_CMD' will use EV_EXIT for ONE_CMD */ | ||
| 10870 | shlvl++; /* dash does not need it because it doesn't ignore SIGQUIT */ | ||
| 10871 | setsignal(SIGQUIT); /* we do (bash compat) */ | ||
| 10794 | shellexec(argv[0], argv, path, cmdentry.u.index); | 10872 | shellexec(argv[0], argv, path, cmdentry.u.index); |
| 10795 | /* NOTREACHED */ | 10873 | /* NOTREACHED */ |
| 10796 | } /* default */ | 10874 | } /* default */ |
| @@ -14930,6 +15008,8 @@ int ash_main(int argc UNUSED_PARAM, char **argv) | |||
| 14930 | struct stackmark smark; | 15008 | struct stackmark smark; |
| 14931 | int login_sh; | 15009 | int login_sh; |
| 14932 | 15010 | ||
| 15011 | dbg_show_dirtymem("dirtymem at main start"); | ||
| 15012 | |||
| 14933 | /* Initialize global data */ | 15013 | /* Initialize global data */ |
| 14934 | INIT_G_misc(); | 15014 | INIT_G_misc(); |
| 14935 | INIT_G_memstack(); | 15015 | INIT_G_memstack(); |
| @@ -15030,6 +15110,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) | |||
| 15030 | // ash -sc 'echo $-' | 15110 | // ash -sc 'echo $-' |
| 15031 | // continue reading input from stdin after running 'echo'. | 15111 | // continue reading input from stdin after running 'echo'. |
| 15032 | // bash does not do this: it prints "hBcs" and exits. | 15112 | // bash does not do this: it prints "hBcs" and exits. |
| 15113 | dbg_show_dirtymem("dirtymem before -c CMD"); | ||
| 15033 | evalstring(minusc, EV_EXIT); | 15114 | evalstring(minusc, EV_EXIT); |
| 15034 | } | 15115 | } |
| 15035 | 15116 | ||
| @@ -15062,6 +15143,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) | |||
| 15062 | } | 15143 | } |
| 15063 | #endif | 15144 | #endif |
| 15064 | state4: /* XXX ??? - why isn't this before the "if" statement */ | 15145 | state4: /* XXX ??? - why isn't this before the "if" statement */ |
| 15146 | dbg_show_dirtymem("dirtymem at cmdloop"); | ||
| 15065 | cmdloop(1); | 15147 | cmdloop(1); |
| 15066 | } | 15148 | } |
| 15067 | #if PROFILE | 15149 | #if PROFILE |
