From 6bd8f9c067dd0e97c3f612e6c510272c77c5ed2c Mon Sep 17 00:00:00 2001
From: Bruno Chevalier <bmgg.chevalier@gmail.com>
Date: Thu, 18 Aug 2016 10:34:46 +0200
Subject: [PATCH] Add FEATURE_SH_PATH_BEFORE_NOEXEC This feature searches the
 PATH for a suited executable. If the found executable is busybox and if the
 requested applet is present, the NOEXEC trick is done.

---
 applets/applet_tables.c |  3 +-
 include/busybox.h       |  3 +-
 shell/Config.src        | 12 ++++++-
 shell/ash.c             | 92 ++++++++++++++++++++++++++++++++++++++++++++++---
 4 files changed, 102 insertions(+), 8 deletions(-)

diff --git a/applets/applet_tables.c b/applets/applet_tables.c
index 8401a15..8dd0358 100644
--- a/applets/applet_tables.c
+++ b/applets/applet_tables.c
@@ -145,7 +145,8 @@ int main(int argc, char **argv)
 
 #if ENABLE_FEATURE_PREFER_APPLETS \
  || ENABLE_FEATURE_SH_STANDALONE \
- || ENABLE_FEATURE_SH_NOFORK
+ || ENABLE_FEATURE_SH_NOFORK \
+ || ENABLE_FEATURE_SH_PATH_BEFORE_NOEXEC
 	printf("const uint8_t applet_flags[] ALIGN1 = {\n");
 	i = 0;
 	while (i < NUM_APPLETS) {
diff --git a/include/busybox.h b/include/busybox.h
index 6a003d5..9eb212a 100644
--- a/include/busybox.h
+++ b/include/busybox.h
@@ -21,7 +21,8 @@ extern const uint8_t applet_install_loc[] ALIGN1;
 
 #if ENABLE_FEATURE_PREFER_APPLETS \
  || ENABLE_FEATURE_SH_STANDALONE \
- || ENABLE_FEATURE_SH_NOFORK
+ || ENABLE_FEATURE_SH_NOFORK \
+ || ENABLE_FEATURE_SH_PATH_BEFORE_NOEXEC
 # define APPLET_IS_NOFORK(i) (applet_flags[(i)/4] & (1 << (2 * ((i)%4))))
 # define APPLET_IS_NOEXEC(i) (applet_flags[(i)/4] & (1 << ((2 * ((i)%4))+1)))
 #else
diff --git a/shell/Config.src b/shell/Config.src
index e4df359..e8b1ad5 100644
--- a/shell/Config.src
+++ b/shell/Config.src
@@ -88,7 +88,7 @@ config FEATURE_SH_EXTRA_QUIET
 config FEATURE_SH_STANDALONE
 	bool "Standalone shell"
 	default n
-	depends on (HUSH || ASH)
+	depends on (HUSH || ASH) && FEATURE_PREFER_APPLETS
 	help
 	  This option causes busybox shells to use busybox applets
 	  in preference to executables in the PATH whenever possible. For
@@ -118,6 +118,16 @@ config FEATURE_SH_STANDALONE
 #	  that exact location with that exact name, this option will not work at
 #	  all.
 
+config FEATURE_SH_PATH_BEFORE_NOEXEC
+	bool "Search the PATH and do NOEXEC trick if app is busybox"
+	default n
+	depends on (ASH)
+	help
+	  Normally NOEXEC only gets done when applets are preferred, before searching the PATH.
+	  We want to prefer normal applications, by first searching the PATH.
+	  But if the application we are about to execute is busybox and the applet is present,
+	  we want to do the NOEXEC trick.
+
 config FEATURE_SH_NOFORK
 	bool "Run 'nofork' applets directly"
 	default n
diff --git a/shell/ash.c b/shell/ash.c
index 496167f..27e1039 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -55,6 +55,27 @@
 # define CLEAR_RANDOM_T(rnd) ((void)0)
 #endif
 
+#if ENABLE_FEATURE_SH_PATH_BEFORE_NOEXEC
+#include <stdlib.h>
+#include <limits.h>
+static char real_path_bb[PATH_MAX] = {0};
+static char *rp_bb = NULL;
+static size_t real_path_bb_len = 0;
+
+static int fill_in_bb_path(void){
+	int e=0;
+	rp_bb = realpath(&bb_busybox_exec_path[0], &real_path_bb[0]);
+	if(rp_bb == NULL){
+		e = errno;
+		fprintf(stderr, "Could not determine canonnical path to busybox executable.\nbb_busybox_exec_path = %s\nerrno = %d",
+                bb_busybox_exec_path, e);
+		return e;
+	}
+	real_path_bb_len = strlen(real_path_bb);
+	return 0;
+}
+#endif
+
 #include "NUM_APPLETS.h"
 #if NUM_APPLETS == 1
 /* STANDALONE does not make sense, and won't compile */
@@ -67,6 +88,12 @@
 # define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
 #endif
 
+#if ENABLE_FEATURE_SH_PATH_BEFORE_NOEXEC || ENABLE_FEATURE_SH_STANDALONE
+# define IF_FEATURE_SH_STANDALONE_OR_FEATURE_SH_PATH_BEFORE_NOEXEC(...) __VA_ARGS__
+#else
+# define IF_FEATURE_SH_STANDALONE_OR_FEATURE_SH_PATH_BEFORE_NOEXEC(...)
+#endif
+
 #ifndef PIPE_BUF
 # define PIPE_BUF 4096           /* amount of buffering in a pipe */
 #endif
@@ -7370,20 +7397,25 @@ static int builtinloc = -1;     /* index in path of %builtin, or -1 */
 
 
 static void
-tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp)
+tryexec(IF_FEATURE_SH_STANDALONE_OR_FEATURE_SH_PATH_BEFORE_NOEXEC(int applet_no,) char *cmd, char **argv, char **envp)
 {
-#if ENABLE_FEATURE_SH_STANDALONE
+#if ENABLE_FEATURE_SH_STANDALONE || ENABLE_FEATURE_SH_PATH_BEFORE_NOEXEC
 	if (applet_no >= 0) {
 		if (APPLET_IS_NOEXEC(applet_no)) {
+			TRACE(("NOEXEC: tryexec: doing NOEXEC trick for applet %d\n", applet_no));
 			clearenv();
 			while (*envp)
 				putenv(*envp++);
 			run_applet_no_and_exit(applet_no, argv);
+		} else {
+			TRACE(("NOEXEC: tryexec: applet_no >=0, but no NOEXEC applet %d\n", applet_no));
 		}
 		/* re-exec ourselves with the new arguments */
 		execve(bb_busybox_exec_path, argv, envp);
 		/* If they called chroot or otherwise made the binary no longer
 		 * executable, fall through */
+	}else{
+		TRACE(("NOEXEC: tryexec: not doing NOEXEC trick for applet %d\n", applet_no));
 	}
 #endif
 
@@ -7447,7 +7479,7 @@ shellexec(char **argv, const char *path, int idx)
 	int e;
 	char **envp;
 	int exerrno;
-	int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
+	int applet_no = -1; /* used by FEATURE_SH_STANDALONE and FEATURE_SH_PATH_BEFORE_NOEXEC */
 
 	clearredir(/*drop:*/ 1);
 	envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL);
@@ -7456,7 +7488,7 @@ shellexec(char **argv, const char *path, int idx)
 	 || (applet_no = find_applet_by_name(argv[0])) >= 0
 #endif
 	) {
-		tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp);
+		tryexec(IF_FEATURE_SH_STANDALONE_OR_FEATURE_SH_PATH_BEFORE_NOEXEC(applet_no,) argv[0], argv, envp);
 		if (applet_no >= 0) {
 			/* We tried execing ourself, but it didn't work.
 			 * Maybe /proc/self/exe doesn't exist?
@@ -7470,7 +7502,27 @@ shellexec(char **argv, const char *path, int idx)
 		e = ENOENT;
 		while ((cmdname = path_advance(&path, argv[0])) != NULL) {
 			if (--idx < 0 && pathopt == NULL) {
-				tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp);
+#if ENABLE_FEATURE_SH_PATH_BEFORE_NOEXEC
+				{
+					int res=0;
+					char real_path[PATH_MAX] = {0};
+					realpath(cmdname, real_path);
+					if(!rp_bb){
+						res = fill_in_bb_path();
+						if(res!=0){
+							fprintf(stderr, "could not get path to busybox_executable, got errno %d\n",res);
+						}
+					}
+					//check if the absolute path is the path to the busybox executable
+					//if it is, we can do the NOEXEC trick when we are ececuting an applet
+					TRACE(("NOEXEC: real_path = \"%s\" , cmdname = \"%s\", real_path_bb = \"%s\"\n", real_path, cmdname, real_path_bb));
+					if(applet_no < 0 && strncmp(&real_path[0],&real_path_bb[0],real_path_bb_len)==0){
+						applet_no = find_applet_by_name(argv[0]);
+						TRACE(("NOEXEC: real_path of cmdname is path to busybox, getting applet_no = %d\n", applet_no));
+					}
+				}
+#endif
+				tryexec(IF_FEATURE_SH_STANDALONE_OR_FEATURE_SH_PATH_BEFORE_NOEXEC(applet_no,) cmdname, argv, envp);
 				if (errno != ENOENT && errno != ENOTDIR)
 					e = errno;
 			}
@@ -9407,6 +9459,7 @@ evalcommand(union node *cmd, int flags)
 		if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) {
 			listsetvar(varlist.list, VEXPORT|VSTACK);
 			/* run <applet>_main() */
+			TRACE(("NOFORK: doing NOFORK for applet %d\n", applet_no));
 			exitstatus = run_nofork_applet(applet_no, argv);
 			break;
 		}
@@ -12473,6 +12526,35 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
 			goto success;
 		}
 		TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
+#if ENABLE_FEATURE_SH_PATH_BEFORE_NOEXEC
+		{
+			int applet_no = -1;
+			int res=0;
+			char real_path[PATH_MAX] = {0};
+			realpath(fullname, real_path);
+			if(!rp_bb){
+				res = fill_in_bb_path();
+				if(res!=0){
+					fprintf(stderr, "could not get path to busybox_executable, got errno %d\n",res);
+				}
+			}
+			//check if the absolute path is the path to the busybox executable
+			//if it is, we can do the NOEXEC trick when we are ececuting an applet
+			TRACE(("NOEXEC: real_path = \"%s\" , fullname = \"%s\", real_path_bb = \"%s\"\n", real_path, fullname, real_path_bb));
+			if(strncmp(&real_path[0], &real_path_bb[0], real_path_bb_len)==0){
+				applet_no = find_applet_by_name(name);
+				TRACE(("NOEXEC: real_path of fullname is path to busybox, getting applet_no: %d\n", applet_no));
+				if (applet_no >= 0) {
+					TRACE(("NOEXEC: applet was present in busybox\n"));
+					entry->cmdtype = CMDNORMAL;
+					entry->u.index = -2 - applet_no;
+					return;
+				}else{
+					TRACE(("NOEXEC: applet not present in busybox\n"));
+				}
+			}
+		}
+#endif
 		if (!updatetbl) {
 			entry->cmdtype = CMDNORMAL;
 			entry->u.index = idx;
-- 
1.8.3.1

