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 <herbert@gondor.apana.org.au>

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2020-02-17 16:02:40 +01:00
parent b0d2dc7d62
commit 6c4f87e411

View File

@ -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 * The variable path (passed by reference) should be set to the start
* of the path before the first call; padvance will update * of the path before the first call; padvance will update
* this value as it proceeds. Successive calls to padvance will return * 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 * 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 * pathopt will be set to point to it; otherwise pathopt will be set to
* NULL. * 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 const char *pathopt; /* set by padvance */
static int 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; const char *p;
char *q; char *q;
const char *start; const char *start;
size_t qlen;
size_t len; size_t len;
if (*path == NULL) if (*path == NULL)
return -1; return -1;
lpathopt = NULL;
start = *path; start = *path;
for (p = start; *p && *p != ':' && *p != '%'; p++)
continue; if (*start == '%' && (p = legal_pathopt(start + 1, term, magic))) {
len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ lpathopt = start + 1;
q = growstackto(len); start = p;
if (p != start) { term = ":";
q = mempcpy(q, start, p - start); }
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++ = '/'; *q++ = '/';
} }
strcpy(q, name); strcpy(q, name);
pathopt = NULL;
if (*p == '%') { return qlen;
pathopt = ++p; }
while (*p && *p != ':')
p++; static int
} padvance(const char **path, const char *name)
if (*p == ':') {
*path = p + 1; return padvance_magic(path, name, 1);
else
*path = NULL;
return len;
} }
@ -8217,11 +8270,10 @@ printentry(struct tblentry *cmdp)
} }
/* /*
* Clear out command entries. The argument specifies the first entry in * Clear out command entries.
* PATH which has changed.
*/ */
static void static void
clearcmdentry(int firstchange) clearcmdentry(void)
{ {
struct tblentry **tblp; struct tblentry **tblp;
struct tblentry **pp; struct tblentry **pp;
@ -8231,10 +8283,8 @@ clearcmdentry(int firstchange)
for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) {
pp = tblp; pp = tblp;
while ((cmdp = *pp) != NULL) { while ((cmdp = *pp) != NULL) {
if ((cmdp->cmdtype == CMDNORMAL && if (cmdp->cmdtype == CMDNORMAL
cmdp->param.index >= firstchange) || (cmdp->cmdtype == CMDBUILTIN && builtinloc > 0)
|| (cmdp->cmdtype == CMDBUILTIN &&
builtinloc >= firstchange)
) { ) {
*pp = cmdp->next; *pp = cmdp->next;
free(cmdp); free(cmdp);
@ -8334,7 +8384,7 @@ hashcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
char *name; char *name;
if (nextopt("r") != '\0') { if (nextopt("r") != '\0') {
clearcmdentry(0); clearcmdentry();
return 0; return 0;
} }
@ -8395,42 +8445,28 @@ hashcd(void)
* Called with interrupts off. * Called with interrupts off.
*/ */
static void FAST_FUNC static void FAST_FUNC
changepath(const char *new) changepath(const char *newval)
{ {
const char *old; const char *new;
int firstchange;
int idx; int idx;
int idx_bltin; int bltin;
old = pathval(); new = newval;
firstchange = 9999; /* assume no change */
idx = 0; idx = 0;
idx_bltin = -1; bltin = -1;
for (;;) { for (;;) {
if (*old != *new) { if (*new == '%' && prefix(new + 1, "builtin")) {
firstchange = idx; bltin = idx;
if ((*old == '\0' && *new == ':')
|| (*old == ':' && *new == '\0')
) {
firstchange++;
}
old = new; /* ignore subsequent differences */
}
if (*new == '\0')
break; break;
if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) }
idx_bltin = idx; new = strchr(new, ':');
if (*new == ':') if (!new)
idx++; break;
idx++;
new++; new++;
old++;
} }
if (builtinloc < 0 && idx_bltin >= 0) builtinloc = bltin;
builtinloc = idx_bltin; /* zap builtins */ clearcmdentry();
if (builtinloc >= 0 && idx_bltin < 0)
firstchange = 0;
clearcmdentry(firstchange);
builtinloc = idx_bltin;
} }
enum { enum {
TEOF, TEOF,
@ -11024,7 +11060,7 @@ chkmail(void)
for (;;) { for (;;) {
int len; int len;
len = padvance(&mpath, nullstr); len = padvance_magic(&mpath, nullstr, 2);
if (!len) if (!len)
break; break;
p = stackblock(); p = stackblock();
@ -13360,7 +13396,9 @@ find_dot_file(char *basename)
while ((len = padvance(&path, basename)) >= 0) { while ((len = padvance(&path, basename)) >= 0) {
fullname = stackblock(); 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. */ /* This will be freed by the caller. */
return stalloc(len); return stalloc(len);
} }
@ -13566,17 +13604,19 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
idx = -1; idx = -1;
loop: loop:
while ((len = padvance(&path, name)) >= 0) { while ((len = padvance(&path, name)) >= 0) {
const char *lpathopt = pathopt;
fullname = stackblock(); fullname = stackblock();
idx++; idx++;
if (pathopt) { if (lpathopt) {
if (prefix(pathopt, "builtin")) { if (*lpathopt == 'b') {
if (bcmd) if (bcmd)
goto builtin_success; goto builtin_success;
continue; continue;
} } else if (!(act & DO_NOFUNC)) {
if ((act & DO_NOFUNC) /* handled below */
|| !prefix(pathopt, "func") } else {
) { /* ignore unimplemented options */ /* ignore unimplemented options */
continue; 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 */ e = EACCES; /* if we fail, this will be the error */
if (!S_ISREG(statb.st_mode)) if (!S_ISREG(statb.st_mode))
continue; continue;
if (pathopt) { /* this is a %func directory */ if (lpathopt) { /* this is a %func directory */
stalloc(len); stalloc(len);
/* NB: stalloc will return space pointed by fullname /* NB: stalloc will return space pointed by fullname
* (because we don't have any intervening allocations * (because we don't have any intervening allocations