[git commit] ash: exec: Stricter pathopt parsing

Denys Vlasenko vda.linux at googlemail.com
Mon Feb 17 15:02:40 UTC 2020


commit: https://git.busybox.net/busybox/commit/?id=6c4f87e411aa5375eaea5a5909a5c610e38c7e70
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

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

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 shell/ash.c | 168 +++++++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 104 insertions(+), 64 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);
-		*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


More information about the busybox-cvs mailing list