diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2018-04-04 22:32:59 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2018-04-04 22:35:13 +0200 |
commit | 332e4115c978094b3630cde62a7154a4fcd564d8 (patch) | |
tree | 6b07958a7e9a430d6475b0dc21e08c343e162ecf /shell | |
parent | 61407807ab6b9505041a74ad96ffb4aeab937d36 (diff) | |
download | busybox-w32-332e4115c978094b3630cde62a7154a4fcd564d8.tar.gz busybox-w32-332e4115c978094b3630cde62a7154a4fcd564d8.tar.bz2 busybox-w32-332e4115c978094b3630cde62a7154a4fcd564d8.zip |
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>
Diffstat (limited to 'shell')
-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 641531209..9fb9b5f32 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -730,10 +730,8 @@ struct parse_context { | |||
730 | struct variable { | 730 | struct variable { |
731 | struct variable *next; | 731 | struct variable *next; |
732 | char *varstr; /* points to "name=" portion */ | 732 | char *varstr; /* points to "name=" portion */ |
733 | #if ENABLE_HUSH_LOCAL | ||
734 | unsigned func_nest_level; | ||
735 | #endif | ||
736 | int max_len; /* if > 0, name is part of initial env; else name is malloced */ | 733 | int max_len; /* if > 0, name is part of initial env; else name is malloced */ |
734 | uint16_t var_nest_level; | ||
737 | smallint flg_export; /* putenv should be done on this var */ | 735 | smallint flg_export; /* putenv should be done on this var */ |
738 | smallint flg_read_only; | 736 | smallint flg_read_only; |
739 | }; | 737 | }; |
@@ -922,12 +920,13 @@ struct globals { | |||
922 | const char *cwd; | 920 | const char *cwd; |
923 | struct variable *top_var; | 921 | struct variable *top_var; |
924 | char **expanded_assignments; | 922 | char **expanded_assignments; |
923 | struct variable **shadowed_vars_pp; | ||
924 | unsigned var_nest_level; | ||
925 | #if ENABLE_HUSH_FUNCTIONS | 925 | #if ENABLE_HUSH_FUNCTIONS |
926 | struct function *top_func; | ||
927 | # if ENABLE_HUSH_LOCAL | 926 | # if ENABLE_HUSH_LOCAL |
928 | struct variable **shadowed_vars_pp; | 927 | unsigned func_nest_level; /* solely to prevent "local v" in non-functions */ |
929 | unsigned func_nest_level; | ||
930 | # endif | 928 | # endif |
929 | struct function *top_func; | ||
931 | #endif | 930 | #endif |
932 | /* Signal and trap handling */ | 931 | /* Signal and trap handling */ |
933 | #if ENABLE_HUSH_FAST | 932 | #if ENABLE_HUSH_FAST |
@@ -2131,7 +2130,7 @@ static const char* FAST_FUNC get_local_var_value(const char *name) | |||
2131 | #define SETFLAG_EXPORT (1 << 0) | 2130 | #define SETFLAG_EXPORT (1 << 0) |
2132 | #define SETFLAG_UNEXPORT (1 << 1) | 2131 | #define SETFLAG_UNEXPORT (1 << 1) |
2133 | #define SETFLAG_MAKE_RO (1 << 2) | 2132 | #define SETFLAG_MAKE_RO (1 << 2) |
2134 | #define SETFLAG_LOCAL_SHIFT 3 | 2133 | #define SETFLAG_VARLVL_SHIFT 3 |
2135 | static int set_local_var(char *str, unsigned flags) | 2134 | static int set_local_var(char *str, unsigned flags) |
2136 | { | 2135 | { |
2137 | struct variable **cur_pp; | 2136 | struct variable **cur_pp; |
@@ -2139,7 +2138,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2139 | char *free_me = NULL; | 2138 | char *free_me = NULL; |
2140 | char *eq_sign; | 2139 | char *eq_sign; |
2141 | int name_len; | 2140 | int name_len; |
2142 | IF_HUSH_LOCAL(unsigned local_lvl = (flags >> SETFLAG_LOCAL_SHIFT);) | 2141 | unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT); |
2143 | 2142 | ||
2144 | eq_sign = strchr(str, '='); | 2143 | eq_sign = strchr(str, '='); |
2145 | if (!eq_sign) { /* not expected to ever happen? */ | 2144 | if (!eq_sign) { /* not expected to ever happen? */ |
@@ -2176,8 +2175,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2176 | unsetenv(str); | 2175 | unsetenv(str); |
2177 | *eq_sign = '='; | 2176 | *eq_sign = '='; |
2178 | } | 2177 | } |
2179 | #if ENABLE_HUSH_LOCAL | 2178 | if (cur->var_nest_level < local_lvl) { |
2180 | if (cur->func_nest_level < local_lvl) { | ||
2181 | /* New variable is declared as local, | 2179 | /* New variable is declared as local, |
2182 | * and existing one is global, or local | 2180 | * and existing one is global, or local |
2183 | * from enclosing function. | 2181 | * from enclosing function. |
@@ -2197,7 +2195,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2197 | flags |= SETFLAG_EXPORT; | 2195 | flags |= SETFLAG_EXPORT; |
2198 | break; | 2196 | break; |
2199 | } | 2197 | } |
2200 | #endif | 2198 | |
2201 | if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { | 2199 | if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { |
2202 | free_and_exp: | 2200 | free_and_exp: |
2203 | free(str); | 2201 | free(str); |
@@ -2225,7 +2223,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2225 | 2223 | ||
2226 | /* Not found - create new variable struct */ | 2224 | /* Not found - create new variable struct */ |
2227 | cur = xzalloc(sizeof(*cur)); | 2225 | cur = xzalloc(sizeof(*cur)); |
2228 | IF_HUSH_LOCAL(cur->func_nest_level = local_lvl;) | 2226 | cur->var_nest_level = local_lvl; |
2229 | cur->next = *cur_pp; | 2227 | cur->next = *cur_pp; |
2230 | *cur_pp = cur; | 2228 | *cur_pp = cur; |
2231 | 2229 | ||
@@ -5509,7 +5507,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5509 | goto parse_error2; | 5507 | goto parse_error2; |
5510 | default: | 5508 | default: |
5511 | if (HUSH_DEBUG) | 5509 | if (HUSH_DEBUG) |
5512 | bb_error_msg_and_die("BUG: unexpected %c\n", ch); | 5510 | bb_error_msg_and_die("BUG: unexpected %c", ch); |
5513 | } | 5511 | } |
5514 | } /* while (1) */ | 5512 | } /* while (1) */ |
5515 | 5513 | ||
@@ -7362,8 +7360,9 @@ static void exec_function(char ***to_free, | |||
7362 | // for "more correctness" we might want to close those extra fds here: | 7360 | // for "more correctness" we might want to close those extra fds here: |
7363 | //? close_saved_fds_and_FILE_fds(); | 7361 | //? close_saved_fds_and_FILE_fds(); |
7364 | 7362 | ||
7365 | /* "we are in function, ok to use return" */ | 7363 | /* "we are in a function, ok to use return" */ |
7366 | G_flag_return_in_progress = -1; | 7364 | G_flag_return_in_progress = -1; |
7365 | G.var_nest_level++; | ||
7367 | IF_HUSH_LOCAL(G.func_nest_level++;) | 7366 | IF_HUSH_LOCAL(G.func_nest_level++;) |
7368 | 7367 | ||
7369 | /* On MMU, funcp->body is always non-NULL */ | 7368 | /* On MMU, funcp->body is always non-NULL */ |
@@ -7383,6 +7382,47 @@ static void exec_function(char ***to_free, | |||
7383 | # endif | 7382 | # endif |
7384 | } | 7383 | } |
7385 | 7384 | ||
7385 | static void enter_var_nest_level(void) | ||
7386 | { | ||
7387 | G.var_nest_level++; | ||
7388 | |||
7389 | /* Try: f() { echo -n .; f; }; f | ||
7390 | * struct variable::var_nest_level is uint16_t, | ||
7391 | * thus limiting recursion to < 2^16. | ||
7392 | * In any case, with 8 Mbyte stack SEGV happens | ||
7393 | * not too long after 2^16 recursions anyway. | ||
7394 | */ | ||
7395 | if (G.var_nest_level > 0xff00) | ||
7396 | bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); | ||
7397 | } | ||
7398 | |||
7399 | static void leave_var_nest_level(void) | ||
7400 | { | ||
7401 | struct variable *cur; | ||
7402 | struct variable **cur_pp; | ||
7403 | |||
7404 | cur_pp = &G.top_var; | ||
7405 | while ((cur = *cur_pp) != NULL) { | ||
7406 | if (cur->var_nest_level < G.var_nest_level) { | ||
7407 | cur_pp = &cur->next; | ||
7408 | continue; | ||
7409 | } | ||
7410 | /* Unexport */ | ||
7411 | if (cur->flg_export) | ||
7412 | bb_unsetenv(cur->varstr); | ||
7413 | /* Remove from global list */ | ||
7414 | *cur_pp = cur->next; | ||
7415 | /* Free */ | ||
7416 | if (!cur->max_len) | ||
7417 | free(cur->varstr); | ||
7418 | free(cur); | ||
7419 | } | ||
7420 | |||
7421 | G.var_nest_level--; | ||
7422 | if (HUSH_DEBUG && (int)G.var_nest_level < 0) | ||
7423 | bb_error_msg_and_die("BUG: nesting underflow"); | ||
7424 | } | ||
7425 | |||
7386 | static int run_function(const struct function *funcp, char **argv) | 7426 | static int run_function(const struct function *funcp, char **argv) |
7387 | { | 7427 | { |
7388 | int rc; | 7428 | int rc; |
@@ -7394,6 +7434,8 @@ static int run_function(const struct function *funcp, char **argv) | |||
7394 | /* "we are in function, ok to use return" */ | 7434 | /* "we are in function, ok to use return" */ |
7395 | sv_flg = G_flag_return_in_progress; | 7435 | sv_flg = G_flag_return_in_progress; |
7396 | G_flag_return_in_progress = -1; | 7436 | G_flag_return_in_progress = -1; |
7437 | |||
7438 | enter_var_nest_level(); | ||
7397 | IF_HUSH_LOCAL(G.func_nest_level++;) | 7439 | IF_HUSH_LOCAL(G.func_nest_level++;) |
7398 | 7440 | ||
7399 | /* On MMU, funcp->body is always non-NULL */ | 7441 | /* On MMU, funcp->body is always non-NULL */ |
@@ -7408,30 +7450,9 @@ static int run_function(const struct function *funcp, char **argv) | |||
7408 | rc = run_list(funcp->body); | 7450 | rc = run_list(funcp->body); |
7409 | } | 7451 | } |
7410 | 7452 | ||
7411 | # if ENABLE_HUSH_LOCAL | 7453 | IF_HUSH_LOCAL(G.func_nest_level--;) |
7412 | { | 7454 | leave_var_nest_level(); |
7413 | struct variable *var; | ||
7414 | struct variable **var_pp; | ||
7415 | 7455 | ||
7416 | var_pp = &G.top_var; | ||
7417 | while ((var = *var_pp) != NULL) { | ||
7418 | if (var->func_nest_level < G.func_nest_level) { | ||
7419 | var_pp = &var->next; | ||
7420 | continue; | ||
7421 | } | ||
7422 | /* Unexport */ | ||
7423 | if (var->flg_export) | ||
7424 | bb_unsetenv(var->varstr); | ||
7425 | /* Remove from global list */ | ||
7426 | *var_pp = var->next; | ||
7427 | /* Free */ | ||
7428 | if (!var->max_len) | ||
7429 | free(var->varstr); | ||
7430 | free(var); | ||
7431 | } | ||
7432 | G.func_nest_level--; | ||
7433 | } | ||
7434 | # endif | ||
7435 | G_flag_return_in_progress = sv_flg; | 7456 | G_flag_return_in_progress = sv_flg; |
7436 | 7457 | ||
7437 | restore_G_args(&sv, argv); | 7458 | restore_G_args(&sv, argv); |
@@ -9959,8 +9980,8 @@ static int helper_export_local(char **argv, unsigned flags) | |||
9959 | # if ENABLE_HUSH_LOCAL | 9980 | # if ENABLE_HUSH_LOCAL |
9960 | /* Is this "local" bltin? */ | 9981 | /* Is this "local" bltin? */ |
9961 | if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) { | 9982 | if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) { |
9962 | unsigned lvl = flags >> SETFLAG_LOCAL_SHIFT; | 9983 | unsigned lvl = flags >> SETFLAG_VARLVL_SHIFT; |
9963 | if (var && var->func_nest_level == lvl) { | 9984 | if (var && var->var_nest_level == lvl) { |
9964 | /* "local x=abc; ...; local x" - ignore second local decl */ | 9985 | /* "local x=abc; ...; local x" - ignore second local decl */ |
9965 | continue; | 9986 | continue; |
9966 | } | 9987 | } |
@@ -10045,7 +10066,7 @@ static int FAST_FUNC builtin_local(char **argv) | |||
10045 | return EXIT_FAILURE; /* bash compat */ | 10066 | return EXIT_FAILURE; /* bash compat */ |
10046 | } | 10067 | } |
10047 | argv++; | 10068 | argv++; |
10048 | return helper_export_local(argv, G.func_nest_level << SETFLAG_LOCAL_SHIFT); | 10069 | return helper_export_local(argv, G.var_nest_level << SETFLAG_VARLVL_SHIFT); |
10049 | } | 10070 | } |
10050 | #endif | 10071 | #endif |
10051 | 10072 | ||