ash: exec: Stricter pathopt parsing
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 17 Feb 2020 15:02:40 +0000 (16:02 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 17 Feb 2020 15:02:40 +0000 (16:02 +0100)
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>
shell/ash.c

index e89cecf0b05608dda57288f717e58806b7a5f563..c383cccda6a682e803f2bfee11268f622ee61088 100644 (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
  * 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);
-               *q++ = '/';
+
+       if (*start == '%' && (p = legal_pathopt(start + 1, term, magic))) {
+               lpathopt = start + 1;
+               start = p;
+               term = ":";
        }
-       strcpy(q, name);
-       pathopt = NULL;
+
+       len = strcspn(start, term);
+       p = start + len;
+
        if (*p == '%') {
-               pathopt = ++p;
-               while (*p && *p != ':')
-                       p++;
+               size_t extra = strchrnul(p, ':') - p;
+
+               if (legal_pathopt(p + 1, term, magic))
+                       lpathopt = p + 1;
+               else
+                       len += extra;
+
+               p += extra;
        }
-       if (*p == ':')
-               *path = p + 1;
-       else
-               *path = NULL;
-       return len;
+
+       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);
+
+       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 == '%' && prefix(new + 1, "builtin")) {
+                       bltin = idx;
+                       break;
                }
-               if (*new == '\0')
+               new = strchr(new, ':');
+               if (!new)
                        break;
-               if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin"))
-                       idx_bltin = idx;
-               if (*new == ':')
-                       idx++;
+               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