diff options
author | Denys Vlasenko | 2018-04-04 22:32:59 +0200 |
---|---|---|
committer | Denys Vlasenko | 2018-04-04 22:35:13 +0200 |
commit | 332e4115c978094b3630cde62a7154a4fcd564d8 (patch) | |
tree | 6b07958a7e9a430d6475b0dc21e08c343e162ecf | |
parent | 61407807ab6b9505041a74ad96ffb4aeab937d36 (diff) | |
download | busybox-332e4115c978094b3630cde62a7154a4fcd564d8.zip busybox-332e4115c978094b3630cde62a7154a4fcd564d8.tar.gz |
hush: make var nesting code independent of "local" support
Also, add code to abort at ~65000 function recursion depth.
SEGVing is not as nice as exiting with a message (and restoring termios!):
$ f() { echo -n .; f; }; f
....<many dots later>....hush: fatal recursion (depth 65281)
function old new delta
run_pipe 1826 1890 +64
pseudo_exec_argv 544 554 +10
parse_and_run_file 71 80 +9
i_getch 104 107 +3
done_command 99 102 +3
set_local_var 508 510 +2
helper_export_local 214 215 +1
builtin_local 49 46 -3
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 7/1 up/down: 92/-3) Total: 89 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | shell/hush.c | 101 |
1 files changed, 61 insertions, 40 deletions
diff --git a/shell/hush.c b/shell/hush.c index 6415312..9fb9b5f 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -730,10 +730,8 @@ struct parse_context { struct variable { struct variable *next; char *varstr; /* points to "name=" portion */ -#if ENABLE_HUSH_LOCAL - unsigned func_nest_level; -#endif int max_len; /* if > 0, name is part of initial env; else name is malloced */ + uint16_t var_nest_level; smallint flg_export; /* putenv should be done on this var */ smallint flg_read_only; }; @@ -922,12 +920,13 @@ struct globals { const char *cwd; struct variable *top_var; char **expanded_assignments; + struct variable **shadowed_vars_pp; + unsigned var_nest_level; #if ENABLE_HUSH_FUNCTIONS - struct function *top_func; # if ENABLE_HUSH_LOCAL - struct variable **shadowed_vars_pp; - unsigned func_nest_level; + unsigned func_nest_level; /* solely to prevent "local v" in non-functions */ # endif + struct function *top_func; #endif /* Signal and trap handling */ #if ENABLE_HUSH_FAST @@ -2131,7 +2130,7 @@ static const char* FAST_FUNC get_local_var_value(const char *name) #define SETFLAG_EXPORT (1 << 0) #define SETFLAG_UNEXPORT (1 << 1) #define SETFLAG_MAKE_RO (1 << 2) -#define SETFLAG_LOCAL_SHIFT 3 +#define SETFLAG_VARLVL_SHIFT 3 static int set_local_var(char *str, unsigned flags) { struct variable **cur_pp; @@ -2139,7 +2138,7 @@ static int set_local_var(char *str, unsigned flags) char *free_me = NULL; char *eq_sign; int name_len; - IF_HUSH_LOCAL(unsigned local_lvl = (flags >> SETFLAG_LOCAL_SHIFT);) + unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT); eq_sign = strchr(str, '='); if (!eq_sign) { /* not expected to ever happen? */ @@ -2176,8 +2175,7 @@ static int set_local_var(char *str, unsigned flags) unsetenv(str); *eq_sign = '='; } -#if ENABLE_HUSH_LOCAL - if (cur->func_nest_level < local_lvl) { + if (cur->var_nest_level < local_lvl) { /* New variable is declared as local, * and existing one is global, or local * from enclosing function. @@ -2197,7 +2195,7 @@ static int set_local_var(char *str, unsigned flags) flags |= SETFLAG_EXPORT; break; } -#endif + if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { free_and_exp: free(str); @@ -2225,7 +2223,7 @@ static int set_local_var(char *str, unsigned flags) /* Not found - create new variable struct */ cur = xzalloc(sizeof(*cur)); - IF_HUSH_LOCAL(cur->func_nest_level = local_lvl;) + cur->var_nest_level = local_lvl; cur->next = *cur_pp; *cur_pp = cur; @@ -5509,7 +5507,7 @@ static struct pipe *parse_stream(char **pstring, goto parse_error2; default: if (HUSH_DEBUG) - bb_error_msg_and_die("BUG: unexpected %c\n", ch); + bb_error_msg_and_die("BUG: unexpected %c", ch); } } /* while (1) */ @@ -7362,8 +7360,9 @@ static void exec_function(char ***to_free, // for "more correctness" we might want to close those extra fds here: //? close_saved_fds_and_FILE_fds(); - /* "we are in function, ok to use return" */ + /* "we are in a function, ok to use return" */ G_flag_return_in_progress = -1; + G.var_nest_level++; IF_HUSH_LOCAL(G.func_nest_level++;) /* On MMU, funcp->body is always non-NULL */ @@ -7383,6 +7382,47 @@ static void exec_function(char ***to_free, # endif } +static void enter_var_nest_level(void) +{ + G.var_nest_level++; + + /* Try: f() { echo -n .; f; }; f + * struct variable::var_nest_level is uint16_t, + * thus limiting recursion to < 2^16. + * In any case, with 8 Mbyte stack SEGV happens + * not too long after 2^16 recursions anyway. + */ + if (G.var_nest_level > 0xff00) + bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); +} + +static void leave_var_nest_level(void) +{ + struct variable *cur; + struct variable **cur_pp; + + cur_pp = &G.top_var; + while ((cur = *cur_pp) != NULL) { + if (cur->var_nest_level < G.var_nest_level) { + cur_pp = &cur->next; + continue; + } + /* Unexport */ + if (cur->flg_export) + bb_unsetenv(cur->varstr); + /* Remove from global list */ + *cur_pp = cur->next; + /* Free */ + if (!cur->max_len) + free(cur->varstr); + free(cur); + } + + G.var_nest_level--; + if (HUSH_DEBUG && (int)G.var_nest_level < 0) + bb_error_msg_and_die("BUG: nesting underflow"); +} + static int run_function(const struct function *funcp, char **argv) { int rc; @@ -7394,6 +7434,8 @@ static int run_function(const struct function *funcp, char **argv) /* "we are in function, ok to use return" */ sv_flg = G_flag_return_in_progress; G_flag_return_in_progress = -1; + + enter_var_nest_level(); IF_HUSH_LOCAL(G.func_nest_level++;) /* On MMU, funcp->body is always non-NULL */ @@ -7408,30 +7450,9 @@ static int run_function(const struct function *funcp, char **argv) rc = run_list(funcp->body); } -# if ENABLE_HUSH_LOCAL - { - struct variable *var; - struct variable **var_pp; + IF_HUSH_LOCAL(G.func_nest_level--;) + leave_var_nest_level(); - var_pp = &G.top_var; - while ((var = *var_pp) != NULL) { - if (var->func_nest_level < G.func_nest_level) { - var_pp = &var->next; - continue; - } - /* Unexport */ - if (var->flg_export) - bb_unsetenv(var->varstr); - /* Remove from global list */ - *var_pp = var->next; - /* Free */ - if (!var->max_len) - free(var->varstr); - free(var); - } - G.func_nest_level--; - } -# endif G_flag_return_in_progress = sv_flg; restore_G_args(&sv, argv); @@ -9959,8 +9980,8 @@ static int helper_export_local(char **argv, unsigned flags) # if ENABLE_HUSH_LOCAL /* Is this "local" bltin? */ if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) { - unsigned lvl = flags >> SETFLAG_LOCAL_SHIFT; - if (var && var->func_nest_level == lvl) { + unsigned lvl = flags >> SETFLAG_VARLVL_SHIFT; + if (var && var->var_nest_level == lvl) { /* "local x=abc; ...; local x" - ignore second local decl */ continue; } @@ -10045,7 +10066,7 @@ static int FAST_FUNC builtin_local(char **argv) return EXIT_FAILURE; /* bash compat */ } argv++; - return helper_export_local(argv, G.func_nest_level << SETFLAG_LOCAL_SHIFT); + return helper_export_local(argv, G.var_nest_level << SETFLAG_VARLVL_SHIFT); } #endif |