hush: fix glob() abuse. Code was making unfounded assumptions how
glob() works, and it broke horribly on specific uclibc config.
This commit is contained in:
parent
ff0976248a
commit
d65ea39ffc
191
shell/hush.c
191
shell/hush.c
@ -246,7 +246,7 @@ struct redir_struct {
|
|||||||
redir_type type; /* type of redirection */
|
redir_type type; /* type of redirection */
|
||||||
int fd; /* file descriptor being redirected */
|
int fd; /* file descriptor being redirected */
|
||||||
int dup; /* -1, or file descriptor being duplicated */
|
int dup; /* -1, or file descriptor being duplicated */
|
||||||
glob_t glob_word; /* *word.gl_pathv is the filename */
|
char **glob_word; /* *word.gl_pathv is the filename */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct child_prog {
|
struct child_prog {
|
||||||
@ -256,7 +256,7 @@ struct child_prog {
|
|||||||
smallint subshell; /* flag, non-zero if group must be forked */
|
smallint subshell; /* flag, non-zero if group must be forked */
|
||||||
smallint is_stopped; /* is the program currently running? */
|
smallint is_stopped; /* is the program currently running? */
|
||||||
struct redir_struct *redirects; /* I/O redirections */
|
struct redir_struct *redirects; /* I/O redirections */
|
||||||
glob_t glob_result; /* result of parameter globbing */
|
char **glob_result; /* result of parameter globbing */
|
||||||
struct pipe *family; /* pointer back to the child's parent pipe */
|
struct pipe *family; /* pointer back to the child's parent pipe */
|
||||||
//sp counting seems to be broken... so commented out, grep for '//sp:'
|
//sp counting seems to be broken... so commented out, grep for '//sp:'
|
||||||
//sp: int sp; /* number of SPECIAL_VAR_SYMBOL */
|
//sp: int sp; /* number of SPECIAL_VAR_SYMBOL */
|
||||||
@ -503,9 +503,9 @@ static void pseudo_exec_argv(char **argv) ATTRIBUTE_NORETURN;
|
|||||||
static void pseudo_exec(struct child_prog *child) ATTRIBUTE_NORETURN;
|
static void pseudo_exec(struct child_prog *child) ATTRIBUTE_NORETURN;
|
||||||
static int run_pipe_real(struct pipe *pi);
|
static int run_pipe_real(struct pipe *pi);
|
||||||
/* extended glob support: */
|
/* extended glob support: */
|
||||||
static int globhack(const char *src, int flags, glob_t *pglob);
|
static char **globhack(const char *src, char **strings);
|
||||||
static int glob_needed(const char *s);
|
static int glob_needed(const char *s);
|
||||||
static int xglob(o_string *dest, int flags, glob_t *pglob);
|
static int xglob(o_string *dest, char ***pglob);
|
||||||
/* variable assignment: */
|
/* variable assignment: */
|
||||||
static int is_assignment(const char *s);
|
static int is_assignment(const char *s);
|
||||||
/* data structure manipulation: */
|
/* data structure manipulation: */
|
||||||
@ -548,6 +548,58 @@ static struct variable *get_local_var(const char *name);
|
|||||||
static int set_local_var(char *str, int flg_export);
|
static int set_local_var(char *str, int flg_export);
|
||||||
static void unset_local_var(const char *name);
|
static void unset_local_var(const char *name);
|
||||||
|
|
||||||
|
|
||||||
|
static char **add_strings_to_strings(int need_xstrdup, char **strings, char **add)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
unsigned count1;
|
||||||
|
unsigned count2;
|
||||||
|
char **v;
|
||||||
|
|
||||||
|
v = strings;
|
||||||
|
count1 = 0;
|
||||||
|
if (v) {
|
||||||
|
while (*v) {
|
||||||
|
count1++;
|
||||||
|
v++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count2 = 0;
|
||||||
|
v = add;
|
||||||
|
while (*v) {
|
||||||
|
count2++;
|
||||||
|
v++;
|
||||||
|
}
|
||||||
|
v = xrealloc(strings, (count1 + count2 + 1) * sizeof(char*));
|
||||||
|
v[count1 + count2] = NULL;
|
||||||
|
i = count2;
|
||||||
|
while (--i >= 0)
|
||||||
|
v[count1 + i] = need_xstrdup ? xstrdup(add[i]) : add[i];
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 'add' should be a malloced pointer */
|
||||||
|
static char **add_string_to_strings(char **strings, char *add)
|
||||||
|
{
|
||||||
|
char *v[2];
|
||||||
|
|
||||||
|
v[0] = add;
|
||||||
|
v[1] = NULL;
|
||||||
|
|
||||||
|
return add_strings_to_strings(0, strings, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_strings(char **strings)
|
||||||
|
{
|
||||||
|
if (strings) {
|
||||||
|
char **v = strings;
|
||||||
|
while (*v)
|
||||||
|
free(*v++);
|
||||||
|
free(strings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Table of built-in functions. They can be forked or not, depending on
|
/* Table of built-in functions. They can be forked or not, depending on
|
||||||
* context: within pipes, they fork. As simple commands, they do not.
|
* context: within pipes, they fork. As simple commands, they do not.
|
||||||
* When used in non-forking context, they can change global variables
|
* When used in non-forking context, they can change global variables
|
||||||
@ -1067,16 +1119,14 @@ static void b_reset(o_string *o)
|
|||||||
{
|
{
|
||||||
o->length = 0;
|
o->length = 0;
|
||||||
o->nonnull = 0;
|
o->nonnull = 0;
|
||||||
if (o->data != NULL)
|
if (o->data)
|
||||||
*o->data = '\0';
|
o->data[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
static void b_free(o_string *o)
|
static void b_free(o_string *o)
|
||||||
{
|
{
|
||||||
b_reset(o);
|
|
||||||
free(o->data);
|
free(o->data);
|
||||||
o->data = NULL;
|
memset(o, 0, sizeof(*o));
|
||||||
o->maxlen = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* My analysis of quoting semantics tells me that state information
|
/* My analysis of quoting semantics tells me that state information
|
||||||
@ -1256,13 +1306,13 @@ static int setup_redirects(struct child_prog *prog, int squirrel[])
|
|||||||
struct redir_struct *redir;
|
struct redir_struct *redir;
|
||||||
|
|
||||||
for (redir = prog->redirects; redir; redir = redir->next) {
|
for (redir = prog->redirects; redir; redir = redir->next) {
|
||||||
if (redir->dup == -1 && redir->glob_word.gl_pathv == NULL) {
|
if (redir->dup == -1 && redir->glob_word == NULL) {
|
||||||
/* something went wrong in the parse. Pretend it didn't happen */
|
/* something went wrong in the parse. Pretend it didn't happen */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (redir->dup == -1) {
|
if (redir->dup == -1) {
|
||||||
mode = redir_table[redir->type].mode;
|
mode = redir_table[redir->type].mode;
|
||||||
openfd = open_or_warn(redir->glob_word.gl_pathv[0], mode);
|
openfd = open_or_warn(redir->glob_word[0], mode);
|
||||||
if (openfd < 0) {
|
if (openfd < 0) {
|
||||||
/* this could get lost if stderr has been redirected, but
|
/* this could get lost if stderr has been redirected, but
|
||||||
bash and ash both lose it as well (though zsh doesn't!) */
|
bash and ash both lose it as well (though zsh doesn't!) */
|
||||||
@ -2024,6 +2074,7 @@ static int run_list_real(struct pipe *pi)
|
|||||||
#if ENABLE_HUSH_LOOPS
|
#if ENABLE_HUSH_LOOPS
|
||||||
if (rword == RES_FOR && pi->num_progs) {
|
if (rword == RES_FOR && pi->num_progs) {
|
||||||
if (!for_lcur) {
|
if (!for_lcur) {
|
||||||
|
/* first loop through for */
|
||||||
/* if no variable values after "in" we skip "for" */
|
/* if no variable values after "in" we skip "for" */
|
||||||
if (!pi->next->progs->argv)
|
if (!pi->next->progs->argv)
|
||||||
continue;
|
continue;
|
||||||
@ -2036,17 +2087,16 @@ static int run_list_real(struct pipe *pi)
|
|||||||
}
|
}
|
||||||
free(pi->progs->argv[0]);
|
free(pi->progs->argv[0]);
|
||||||
if (!*for_lcur) {
|
if (!*for_lcur) {
|
||||||
|
/* for loop is over, clean up */
|
||||||
free(for_list);
|
free(for_list);
|
||||||
for_lcur = NULL;
|
for_lcur = NULL;
|
||||||
flag_rep = 0;
|
flag_rep = 0;
|
||||||
pi->progs->argv[0] = for_varname;
|
pi->progs->argv[0] = for_varname;
|
||||||
pi->progs->glob_result.gl_pathv[0] = pi->progs->argv[0];
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* insert next value from for_lcur */
|
/* insert next value from for_lcur */
|
||||||
/* vda: does it need escaping? */
|
/* vda: does it need escaping? */
|
||||||
pi->progs->argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
|
pi->progs->argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
|
||||||
pi->progs->glob_result.gl_pathv[0] = pi->progs->argv[0];
|
|
||||||
}
|
}
|
||||||
if (rword == RES_IN)
|
if (rword == RES_IN)
|
||||||
continue;
|
continue;
|
||||||
@ -2149,8 +2199,8 @@ static int free_pipe(struct pipe *pi, int indent)
|
|||||||
for (a = 0, p = child->argv; *p; a++, p++) {
|
for (a = 0, p = child->argv; *p; a++, p++) {
|
||||||
debug_printf_clean("%s argv[%d] = %s\n", indenter(indent), a, *p);
|
debug_printf_clean("%s argv[%d] = %s\n", indenter(indent), a, *p);
|
||||||
}
|
}
|
||||||
globfree(&child->glob_result);
|
free_strings(child->glob_result);
|
||||||
child->argv = NULL;
|
child->glob_result = NULL;
|
||||||
} else if (child->group) {
|
} else if (child->group) {
|
||||||
debug_printf_clean("%s begin group (subshell:%d)\n", indenter(indent), child->subshell);
|
debug_printf_clean("%s begin group (subshell:%d)\n", indenter(indent), child->subshell);
|
||||||
ret_code = free_pipe_list(child->group, indent+3);
|
ret_code = free_pipe_list(child->group, indent+3);
|
||||||
@ -2162,9 +2212,10 @@ static int free_pipe(struct pipe *pi, int indent)
|
|||||||
debug_printf_clean("%s redirect %d%s", indenter(indent), r->fd, redir_table[r->type].descrip);
|
debug_printf_clean("%s redirect %d%s", indenter(indent), r->fd, redir_table[r->type].descrip);
|
||||||
if (r->dup == -1) {
|
if (r->dup == -1) {
|
||||||
/* guard against the case >$FOO, where foo is unset or blank */
|
/* guard against the case >$FOO, where foo is unset or blank */
|
||||||
if (r->glob_word.gl_pathv) {
|
if (r->glob_word) {
|
||||||
debug_printf_clean(" %s\n", r->glob_word.gl_pathv[0]);
|
debug_printf_clean(" %s\n", r->glob_word[0]);
|
||||||
globfree(&r->glob_word);
|
free_strings(r->glob_word);
|
||||||
|
r->glob_word = NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug_printf_clean("&%d\n", r->dup);
|
debug_printf_clean("&%d\n", r->dup);
|
||||||
@ -2224,80 +2275,79 @@ static int run_list(struct pipe *pi)
|
|||||||
* string into the output structure, removing non-backslashed backslashes.
|
* string into the output structure, removing non-backslashed backslashes.
|
||||||
* If someone can prove me wrong, by performing this function within the
|
* If someone can prove me wrong, by performing this function within the
|
||||||
* original glob(3) api, feel free to rewrite this routine into oblivion.
|
* original glob(3) api, feel free to rewrite this routine into oblivion.
|
||||||
* Return code (0 vs. GLOB_NOSPACE) matches glob(3).
|
|
||||||
* XXX broken if the last character is '\\', check that before calling.
|
* XXX broken if the last character is '\\', check that before calling.
|
||||||
*/
|
*/
|
||||||
static int globhack(const char *src, int flags, glob_t *pglob)
|
static char **globhack(const char *src, char **strings)
|
||||||
{
|
{
|
||||||
int cnt = 0, pathc;
|
int cnt;
|
||||||
const char *s;
|
const char *s;
|
||||||
char *dest;
|
char *v, *dest;
|
||||||
|
|
||||||
for (cnt = 1, s = src; s && *s; s++) {
|
for (cnt = 1, s = src; s && *s; s++) {
|
||||||
if (*s == '\\') s++;
|
if (*s == '\\') s++;
|
||||||
cnt++;
|
cnt++;
|
||||||
}
|
}
|
||||||
dest = xmalloc(cnt);
|
v = dest = xmalloc(cnt);
|
||||||
if (!(flags & GLOB_APPEND)) {
|
|
||||||
pglob->gl_pathv = NULL;
|
|
||||||
pglob->gl_pathc = 0;
|
|
||||||
pglob->gl_offs = 0;
|
|
||||||
pglob->gl_offs = 0;
|
|
||||||
}
|
|
||||||
pathc = ++pglob->gl_pathc;
|
|
||||||
pglob->gl_pathv = xrealloc(pglob->gl_pathv, (pathc+1) * sizeof(*pglob->gl_pathv));
|
|
||||||
pglob->gl_pathv[pathc-1] = dest;
|
|
||||||
pglob->gl_pathv[pathc] = NULL;
|
|
||||||
for (s = src; s && *s; s++, dest++) {
|
for (s = src; s && *s; s++, dest++) {
|
||||||
if (*s == '\\') s++;
|
if (*s == '\\') s++;
|
||||||
*dest = *s;
|
*dest = *s;
|
||||||
}
|
}
|
||||||
*dest = '\0';
|
*dest = '\0';
|
||||||
return 0;
|
|
||||||
|
return add_string_to_strings(strings, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* XXX broken if the last character is '\\', check that before calling */
|
/* XXX broken if the last character is '\\', check that before calling */
|
||||||
static int glob_needed(const char *s)
|
static int glob_needed(const char *s)
|
||||||
{
|
{
|
||||||
for (; *s; s++) {
|
for (; *s; s++) {
|
||||||
if (*s == '\\') s++;
|
if (*s == '\\')
|
||||||
if (strchr("*[?", *s)) return 1;
|
s++;
|
||||||
|
if (strchr("*[?", *s))
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int xglob(o_string *dest, int flags, glob_t *pglob)
|
static int xglob(o_string *dest, char ***pglob)
|
||||||
{
|
{
|
||||||
int gr;
|
|
||||||
|
|
||||||
/* short-circuit for null word */
|
/* short-circuit for null word */
|
||||||
/* we can code this better when the debug_printf's are gone */
|
/* we can code this better when the debug_printf's are gone */
|
||||||
if (dest->length == 0) {
|
if (dest->length == 0) {
|
||||||
if (dest->nonnull) {
|
if (dest->nonnull) {
|
||||||
/* bash man page calls this an "explicit" null */
|
/* bash man page calls this an "explicit" null */
|
||||||
gr = globhack(dest->data, flags, pglob);
|
*pglob = globhack(dest->data, *pglob);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glob_needed(dest->data)) {
|
||||||
|
glob_t globdata;
|
||||||
|
int gr;
|
||||||
|
|
||||||
|
memset(&globdata, 0, sizeof(globdata));
|
||||||
|
gr = glob(dest->data, 0, NULL, &globdata);
|
||||||
|
debug_printf("glob returned %d\n", gr);
|
||||||
|
if (gr == GLOB_NOSPACE)
|
||||||
|
bb_error_msg_and_die("out of memory during glob");
|
||||||
|
if (gr == GLOB_NOMATCH) {
|
||||||
debug_printf("globhack returned %d\n", gr);
|
debug_printf("globhack returned %d\n", gr);
|
||||||
} else {
|
/* quote removal, or more accurately, backslash removal */
|
||||||
|
*pglob = globhack(dest->data, *pglob);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} else if (glob_needed(dest->data)) {
|
if (gr != 0) { /* GLOB_ABORTED ? */
|
||||||
gr = glob(dest->data, flags, NULL, pglob);
|
bb_error_msg("glob(3) error %d", gr);
|
||||||
debug_printf("glob returned %d\n", gr);
|
|
||||||
if (gr == GLOB_NOMATCH) {
|
|
||||||
/* quote removal, or more accurately, backslash removal */
|
|
||||||
gr = globhack(dest->data, flags, pglob);
|
|
||||||
debug_printf("globhack returned %d\n", gr);
|
|
||||||
}
|
}
|
||||||
} else {
|
if (globdata.gl_pathv && globdata.gl_pathv[0])
|
||||||
gr = globhack(dest->data, flags, pglob);
|
*pglob = add_strings_to_strings(1, *pglob, globdata.gl_pathv);
|
||||||
debug_printf("globhack returned %d\n", gr);
|
/* globprint(glob_target); */
|
||||||
|
globfree(&globdata);
|
||||||
|
return gr;
|
||||||
}
|
}
|
||||||
if (gr == GLOB_NOSPACE)
|
|
||||||
bb_error_msg_and_die("out of memory during glob");
|
*pglob = globhack(dest->data, *pglob);
|
||||||
if (gr != 0) { /* GLOB_ABORTED ? */
|
return 0;
|
||||||
bb_error_msg("glob(3) error %d", gr);
|
|
||||||
}
|
|
||||||
/* globprint(glob_target); */
|
|
||||||
return gr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* expand_strvec_to_strvec() takes a list of strings, expands
|
/* expand_strvec_to_strvec() takes a list of strings, expands
|
||||||
@ -2775,7 +2825,7 @@ static int setup_redirect(struct p_context *ctx, int fd, redir_type style,
|
|||||||
}
|
}
|
||||||
redir = xzalloc(sizeof(struct redir_struct));
|
redir = xzalloc(sizeof(struct redir_struct));
|
||||||
/* redir->next = NULL; */
|
/* redir->next = NULL; */
|
||||||
/* redir->glob_word.gl_pathv = NULL; */
|
/* redir->glob_word = NULL; */
|
||||||
if (last_redir) {
|
if (last_redir) {
|
||||||
last_redir->next = redir;
|
last_redir->next = redir;
|
||||||
} else {
|
} else {
|
||||||
@ -2919,8 +2969,8 @@ static int reserved_word(o_string *dest, struct p_context *ctx)
|
|||||||
static int done_word(o_string *dest, struct p_context *ctx)
|
static int done_word(o_string *dest, struct p_context *ctx)
|
||||||
{
|
{
|
||||||
struct child_prog *child = ctx->child;
|
struct child_prog *child = ctx->child;
|
||||||
glob_t *glob_target;
|
char ***glob_target;
|
||||||
int gr, flags = 0;
|
int gr;
|
||||||
|
|
||||||
debug_printf_parse("done_word entered: '%s' %p\n", dest->data, child);
|
debug_printf_parse("done_word entered: '%s' %p\n", dest->data, child);
|
||||||
if (dest->length == 0 && !dest->nonnull) {
|
if (dest->length == 0 && !dest->nonnull) {
|
||||||
@ -2942,11 +2992,9 @@ static int done_word(o_string *dest, struct p_context *ctx)
|
|||||||
return (ctx->res_w == RES_SNTX);
|
return (ctx->res_w == RES_SNTX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glob_target = &child->glob_result;
|
glob_target = &child->argv;
|
||||||
if (child->argv)
|
|
||||||
flags |= GLOB_APPEND;
|
|
||||||
}
|
}
|
||||||
gr = xglob(dest, flags, glob_target);
|
gr = xglob(dest, glob_target);
|
||||||
if (gr != 0) {
|
if (gr != 0) {
|
||||||
debug_printf_parse("done_word return 1: xglob returned %d\n", gr);
|
debug_printf_parse("done_word return 1: xglob returned %d\n", gr);
|
||||||
return 1;
|
return 1;
|
||||||
@ -2954,14 +3002,17 @@ static int done_word(o_string *dest, struct p_context *ctx)
|
|||||||
|
|
||||||
b_reset(dest);
|
b_reset(dest);
|
||||||
if (ctx->pending_redirect) {
|
if (ctx->pending_redirect) {
|
||||||
ctx->pending_redirect = NULL;
|
if (ctx->pending_redirect->glob_word
|
||||||
if (glob_target->gl_pathc != 1) {
|
&& ctx->pending_redirect->glob_word[0]
|
||||||
|
&& ctx->pending_redirect->glob_word[1]
|
||||||
|
) {
|
||||||
|
/* more than one word resulted from globbing redir */
|
||||||
|
ctx->pending_redirect = NULL;
|
||||||
bb_error_msg("ambiguous redirect");
|
bb_error_msg("ambiguous redirect");
|
||||||
debug_printf_parse("done_word return 1: ambiguous redirect\n");
|
debug_printf_parse("done_word return 1: ambiguous redirect\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} else {
|
ctx->pending_redirect = NULL;
|
||||||
child->argv = glob_target->gl_pathv;
|
|
||||||
}
|
}
|
||||||
#if ENABLE_HUSH_LOOPS
|
#if ENABLE_HUSH_LOOPS
|
||||||
if (ctx->res_w == RES_FOR) {
|
if (ctx->res_w == RES_FOR) {
|
||||||
@ -3006,7 +3057,7 @@ static int done_command(struct p_context *ctx)
|
|||||||
/*child->argv = NULL;*/
|
/*child->argv = NULL;*/
|
||||||
/*child->is_stopped = 0;*/
|
/*child->is_stopped = 0;*/
|
||||||
/*child->group = NULL;*/
|
/*child->group = NULL;*/
|
||||||
/*child->glob_result.gl_pathv = NULL;*/
|
/*child->glob_result = NULL;*/
|
||||||
child->family = pi;
|
child->family = pi;
|
||||||
//sp: /*child->sp = 0;*/
|
//sp: /*child->sp = 0;*/
|
||||||
//pt: child->parse_type = ctx->parse_type;
|
//pt: child->parse_type = ctx->parse_type;
|
||||||
|
Loading…
Reference in New Issue
Block a user