aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2026-01-27 07:28:39 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2026-01-27 08:03:15 +0100
commit22b96c0ddfc546a7dd9344f7bf7f40cc25364e57 (patch)
tree15a0170cba36babf117ccc4305592b12d5c4e4b0
parent28e4d2b854d7072c510f416acd2eea5dcdbc346a (diff)
downloadbusybox-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.c96
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
1122static 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
1122static FILE *tracefile; 1147static 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 */
3777static void 3806static void
3778signal_handler(int signo) 3807signal_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