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>
This commit is contained in:
Denys Vlasenko 2018-04-04 22:32:59 +02:00
parent 61407807ab
commit 332e4115c9

View File

@ -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;
#if ENABLE_HUSH_FUNCTIONS
struct function *top_func;
# if ENABLE_HUSH_LOCAL
struct variable **shadowed_vars_pp;
unsigned func_nest_level;
unsigned var_nest_level;
#if ENABLE_HUSH_FUNCTIONS
# if ENABLE_HUSH_LOCAL
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