From: Denys Vlasenko Date: Sat, 14 Apr 2018 12:50:47 +0000 (+0200) Subject: ash: expand: Fix buffer overflow in expandmeta X-Git-Tag: 1_29_0~80 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=d5f5045b43bd7a55402dfcecde511a2c207d07b7;p=oweals%2Fbusybox.git ash: expand: Fix buffer overflow in expandmeta Upstream commit: Date: Sun, 25 Mar 2018 16:38:00 +0800 expand: Fix buffer overflow in expandmeta The native version of expandmeta allocates a buffer that may be overrun for two reasons. First of all the size is 1 byte too small but this is normally hidden because the minimum size is rounded up to 2048 bytes. Secondly, if the directory level is deep enough, any buffer can be overrun. This patch fixes both problems by calling realloc when necessary. Signed-off-by: Herbert Xu function old new delta expmeta 517 635 +118 expandarg 990 996 +6 mklocal 288 290 +2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 126/0) Total: 126 bytes Signed-off-by: Denys Vlasenko --- diff --git a/shell/ash.c b/shell/ash.c index 40ca82d0b..051cc671f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7575,9 +7575,16 @@ expandmeta(struct strlist *str /*, int flag*/) /* * Do metacharacter (i.e. *, ?, [...]) expansion. */ +typedef struct exp_t { + char *dir; + unsigned dir_max; +} exp_t; static void -expmeta(char *expdir, char *enddir, char *name) +expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) { +#define expdir exp->dir +#define expdir_max exp->dir_max + char *enddir = expdir + expdir_len; char *p; const char *cp; char *start; @@ -7620,15 +7627,15 @@ expmeta(char *expdir, char *enddir, char *name) } } if (metaflag == 0) { /* we've reached the end of the file name */ - if (enddir != expdir) - metaflag++; + if (!expdir_len) + return; p = name; do { if (*p == '\\') p++; *enddir++ = *p; } while (*p++); - if (metaflag == 0 || lstat(expdir, &statb) >= 0) + if (lstat(expdir, &statb) == 0) addfname(expdir); return; } @@ -7641,19 +7648,14 @@ expmeta(char *expdir, char *enddir, char *name) *enddir++ = *p++; } while (p < start); } - if (enddir == expdir) { + *enddir = '\0'; + cp = expdir; + expdir_len = enddir - cp; + if (!expdir_len) cp = "."; - } else if (enddir == expdir + 1 && *expdir == '/') { - cp = "/"; - } else { - cp = expdir; - enddir[-1] = '\0'; - } dirp = opendir(cp); if (dirp == NULL) return; - if (enddir != expdir) - enddir[-1] = '/'; if (*endname == 0) { atend = 1; } else { @@ -7661,6 +7663,7 @@ expmeta(char *expdir, char *enddir, char *name) *endname = '\0'; endname += esc + 1; } + name_len -= endname - name; matchdot = 0; p = start; if (*p == '\\') @@ -7675,16 +7678,30 @@ expmeta(char *expdir, char *enddir, char *name) strcpy(enddir, dp->d_name); addfname(expdir); } else { - for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) - continue; - p[-1] = '/'; - expmeta(expdir, p, endname); + unsigned offset; + unsigned len; + + p = stpcpy(enddir, dp->d_name); + *p = '/'; + + offset = p - expdir + 1; + len = offset + name_len + NAME_MAX; + if (len > expdir_max) { + len += PATH_MAX; + expdir = ckrealloc(expdir, len); + expdir_max = len; + } + + expmeta(exp, endname, name_len, offset); + enddir = expdir + expdir_len; } } } closedir(dirp); if (!atend) endname[-esc - 1] = esc ? '\\' : '/'; +#undef expdir +#undef expdir_max } static struct strlist * @@ -7757,10 +7774,11 @@ expandmeta(struct strlist *str /*, int flag*/) /* TODO - EXP_REDIR */ while (str) { - char *expdir; + exp_t exp; struct strlist **savelastp; struct strlist *sp; char *p; + unsigned len; if (fflag) goto nometa; @@ -7770,13 +7788,12 @@ expandmeta(struct strlist *str /*, int flag*/) INT_OFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); - { - int i = strlen(str->text); -//BUGGY estimation of how long expanded name can be - expdir = ckmalloc(i < 2048 ? 2048 : i+1); - } - expmeta(expdir, expdir, p); - free(expdir); + len = strlen(p); + exp.dir_max = len + PATH_MAX; + exp.dir = ckmalloc(exp.dir_max); + + expmeta(&exp, p, len, 0); + free(exp.dir); if (p != str->text) free(p); INT_ON;