From 6c4f87e411aa5375eaea5a5909a5c610e38c7e70 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 17 Feb 2020 16:02:40 +0100 Subject: [PATCH] ash: exec: Stricter pathopt parsing Upstream comment: Date: Sat, 19 May 2018 02:39:50 +0800 exec: Stricter pathopt parsing This patch changes the parsing of pathopt. First of all only %builtin and %func (with arbitrary suffixes) will be recognised. Any other pathopt will be treated as a normal directory. Furthermore, pathopt can now be specified before the directory, rather than after it. In fact, a future version may remove support for pathopt suffixes. Wherever the pathopt is placed, an optional % may be placed after it to terminate the pathopt. This is so that it is less likely that a genuine directory containing a % sign is parsed as a pathopt. Users of padvance outside of exec.c have also been modified: 1) cd(1) will always treat % characters as part of the path. 2) chkmail will continue to accept arbitrary pathopt. 3) find_dot_file will ignore the %builtin pathopt instead of trying to do a stat in the accompanying directory (which is usually the current directory). The patch also removes the clearcmdentry optimisation where we attempt to only partially flush the table where possible. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 170 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 65 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index e89cecf0b..c383cccda 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2557,8 +2557,31 @@ listvars(int on, int off, struct strlist *lp, char ***end) } -/* ============ Path search helper - * +/* ============ Path search helper */ +static const char * +legal_pathopt(const char *opt, const char *term, int magic) +{ + switch (magic) { + case 0: + opt = NULL; + break; + + case 1: + opt = prefix(opt, "builtin") ?: prefix(opt, "func"); + break; + + default: + opt += strcspn(opt, term); + break; + } + + if (opt && *opt == '%') + opt++; + + return opt; +} + +/* * The variable path (passed by reference) should be set to the start * of the path before the first call; padvance will update * this value as it proceeds. Successive calls to padvance will return @@ -2566,40 +2589,70 @@ listvars(int on, int off, struct strlist *lp, char ***end) * a percent sign) appears in the path entry then the global variable * pathopt will be set to point to it; otherwise pathopt will be set to * NULL. + * + * If magic is 0 then pathopt recognition will be disabled. If magic is + * 1 we shall recognise %builtin/%func. Otherwise we shall accept any + * pathopt. */ static const char *pathopt; /* set by padvance */ static int -padvance(const char **path, const char *name) +padvance_magic(const char **path, const char *name, int magic) { + const char *term = "%:"; + const char *lpathopt; const char *p; char *q; const char *start; + size_t qlen; size_t len; if (*path == NULL) return -1; + + lpathopt = NULL; start = *path; - for (p = start; *p && *p != ':' && *p != '%'; p++) - continue; - len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ - q = growstackto(len); - if (p != start) { - q = mempcpy(q, start, p - start); + + if (*start == '%' && (p = legal_pathopt(start + 1, term, magic))) { + lpathopt = start + 1; + start = p; + term = ":"; + } + + len = strcspn(start, term); + p = start + len; + + if (*p == '%') { + size_t extra = strchrnul(p, ':') - p; + + if (legal_pathopt(p + 1, term, magic)) + lpathopt = p + 1; + else + len += extra; + + p += extra; + } + + pathopt = lpathopt; + *path = *p == ':' ? p + 1 : NULL; + + /* "2" is for '/' and '\0' */ + qlen = len + strlen(name) + 2; + q = growstackto(qlen); + + if (len) { + q = mempcpy(q, start, len); *q++ = '/'; } strcpy(q, name); - pathopt = NULL; - if (*p == '%') { - pathopt = ++p; - while (*p && *p != ':') - p++; - } - if (*p == ':') - *path = p + 1; - else - *path = NULL; - return len; + + return qlen; +} + +static int +padvance(const char **path, const char *name) +{ + return padvance_magic(path, name, 1); } @@ -8217,11 +8270,10 @@ printentry(struct tblentry *cmdp) } /* - * Clear out command entries. The argument specifies the first entry in - * PATH which has changed. + * Clear out command entries. */ static void -clearcmdentry(int firstchange) +clearcmdentry(void) { struct tblentry **tblp; struct tblentry **pp; @@ -8231,10 +8283,8 @@ clearcmdentry(int firstchange) for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { pp = tblp; while ((cmdp = *pp) != NULL) { - if ((cmdp->cmdtype == CMDNORMAL && - cmdp->param.index >= firstchange) - || (cmdp->cmdtype == CMDBUILTIN && - builtinloc >= firstchange) + if (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN && builtinloc > 0) ) { *pp = cmdp->next; free(cmdp); @@ -8334,7 +8384,7 @@ hashcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) char *name; if (nextopt("r") != '\0') { - clearcmdentry(0); + clearcmdentry(); return 0; } @@ -8395,42 +8445,28 @@ hashcd(void) * Called with interrupts off. */ static void FAST_FUNC -changepath(const char *new) +changepath(const char *newval) { - const char *old; - int firstchange; + const char *new; int idx; - int idx_bltin; + int bltin; - old = pathval(); - firstchange = 9999; /* assume no change */ + new = newval; idx = 0; - idx_bltin = -1; + bltin = -1; for (;;) { - if (*old != *new) { - firstchange = idx; - if ((*old == '\0' && *new == ':') - || (*old == ':' && *new == '\0') - ) { - firstchange++; - } - old = new; /* ignore subsequent differences */ - } - if (*new == '\0') + if (*new == '%' && prefix(new + 1, "builtin")) { + bltin = idx; break; - if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) - idx_bltin = idx; - if (*new == ':') - idx++; + } + new = strchr(new, ':'); + if (!new) + break; + idx++; new++; - old++; } - if (builtinloc < 0 && idx_bltin >= 0) - builtinloc = idx_bltin; /* zap builtins */ - if (builtinloc >= 0 && idx_bltin < 0) - firstchange = 0; - clearcmdentry(firstchange); - builtinloc = idx_bltin; + builtinloc = bltin; + clearcmdentry(); } enum { TEOF, @@ -11024,7 +11060,7 @@ chkmail(void) for (;;) { int len; - len = padvance(&mpath, nullstr); + len = padvance_magic(&mpath, nullstr, 2); if (!len) break; p = stackblock(); @@ -13360,7 +13396,9 @@ find_dot_file(char *basename) while ((len = padvance(&path, basename)) >= 0) { fullname = stackblock(); - if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + if ((!pathopt || *pathopt == 'f') + && !stat(fullname, &statb) && S_ISREG(statb.st_mode) + ) { /* This will be freed by the caller. */ return stalloc(len); } @@ -13566,17 +13604,19 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) idx = -1; loop: while ((len = padvance(&path, name)) >= 0) { + const char *lpathopt = pathopt; + fullname = stackblock(); idx++; - if (pathopt) { - if (prefix(pathopt, "builtin")) { + if (lpathopt) { + if (*lpathopt == 'b') { if (bcmd) goto builtin_success; continue; - } - if ((act & DO_NOFUNC) - || !prefix(pathopt, "func") - ) { /* ignore unimplemented options */ + } else if (!(act & DO_NOFUNC)) { + /* handled below */ + } else { + /* ignore unimplemented options */ continue; } } @@ -13599,7 +13639,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) e = EACCES; /* if we fail, this will be the error */ if (!S_ISREG(statb.st_mode)) continue; - if (pathopt) { /* this is a %func directory */ + if (lpathopt) { /* this is a %func directory */ stalloc(len); /* NB: stalloc will return space pointed by fullname * (because we don't have any intervening allocations