[PATCH v4 3/8] executable.c: introduce BB_EXECVPE function to handle applet execution and replace BB_EXECVP with a macro

Nadav Tasher tashernadav at gmail.com
Mon Jan 27 23:04:16 UTC 2025


This patch makes BB_EXECVPE the gateway to the exec syscall
family.

When called, it first looks for a matching applet, and
executes it directly of indirectly by re-executing busybox
binary. This feature takes NOEXEC definitions into account
by checking NOEXEC with the APPLET_IS_NOEXEC function.

When FEATURE_FORCE_APPLETS is enabled, BB_EXECVPE will
fail when trying to execute external binaries, and only
pre-compiled applets will be able to run. This allows for
better control over the programs executed by busybox.

BB_EXECVP is redefined as a macro to BB_EXECVPE that uses
environ as the default envp argument.

Signed-off-by: Nadav Tasher <tashernadav at gmail.com>
---
 Config.in          | 10 +++++++++
 include/libbb.h    | 28 +++++++++++--------------
 libbb/executable.c | 51 +++++++++++++++++++++++++++++++++++++---------
 3 files changed, 63 insertions(+), 26 deletions(-)

diff --git a/Config.in b/Config.in
index dfab102bb..9d40431ee 100644
--- a/Config.in
+++ b/Config.in
@@ -311,6 +311,16 @@ config FEATURE_PREFER_APPLETS
 	problems in chroot jails without mounted /proc and with ps/top
 	(command name can be shown as 'exe' for applets started this way).
 
+config FEATURE_FORCE_APPLETS
+	bool "only use applets"
+	default n
+	depends on FEATURE_PREFER_APPLETS
+	help
+	This is an experimental option which makes exec calls fail when trying
+	to execute external binaries that are not part of busybox.
+
+	This feature extends the "exec prefers applets" feature.
+
 config BUSYBOX_EXEC_PATH
 	string "Path to busybox executable"
 	default "/proc/self/exe"
diff --git a/include/libbb.h b/include/libbb.h
index 4d6193795..39ca6d811 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1230,22 +1230,18 @@ int file_is_executable(const char *name) FAST_FUNC;
 char *find_executable(const char *filename, const char **PATHp) FAST_FUNC;
 int executable_exists(const char *filename) FAST_FUNC;
 
-/* BB_EXECxx always execs (it's not doing NOFORK/NOEXEC stuff),
- * but it may exec busybox and call applet instead of searching PATH.
- */
-#if ENABLE_FEATURE_PREFER_APPLETS
-int BB_EXECVP(const char *file, char *const argv[]) FAST_FUNC;
-#define BB_EXECLP(prog,cmd,...) \
-	do { \
-		if (find_applet_by_name(prog) >= 0) \
-			execlp(bb_busybox_exec_path, cmd, __VA_ARGS__); \
-		execlp(prog, cmd, __VA_ARGS__); \
-	} while (0)
-#else
-#define BB_EXECVP(prog,cmd)     execvp(prog,cmd)
-#define BB_EXECLP(prog,cmd,...) execlp(prog,cmd,__VA_ARGS__)
-#endif
-void BB_EXECVP_or_die(char **argv) NORETURN FAST_FUNC;
+/* BB_EXECVP is just a macro to use BB_EXECVPE
+ * with a default environ argument. */
+#define BB_EXECVP(file, argv) BB_EXECVPE(file, argv, environ)
+#define BB_EXECVP_or_die(argv) BB_EXECVP_or_die_msg(argv, "can't execute '%s'")
+
+/* BB_EXECVPE is called instead of using exec directly.
+ * this allows implementation of NOEXEC applet logic. */
+int BB_EXECVPE(const char *file, char *const argv[], char *const envp[]) FAST_FUNC;
+
+/* BB_EXECVP_or_die is commonly used in functions that must
+ * exit if the execution of the desired program fails. */
+void BB_EXECVP_or_die_msg(char **argv, const char *msg) NORETURN FAST_FUNC;
 
 /* xvfork() can't be a _function_, return after vfork in child mangles stack
  * in the parent. It must be a macro. */
diff --git a/libbb/executable.c b/libbb/executable.c
index 09bed1eaf..2f27cb9fa 100644
--- a/libbb/executable.c
+++ b/libbb/executable.c
@@ -7,6 +7,7 @@
  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
 #include "libbb.h"
+#include "busybox.h" /* for APPLET_IS_NOEXEC */
 
 /* check if path points to an executable file;
  * return 1 if found;
@@ -78,20 +79,50 @@ int FAST_FUNC executable_exists(const char *name)
 	return ret != NULL;
 }
 
-#if ENABLE_FEATURE_PREFER_APPLETS
-/* just like the real execvp, but try to launch an applet named 'file' first */
-int FAST_FUNC BB_EXECVP(const char *file, char *const argv[])
+/* just like the real execvpe, but we might try to launch an applet named 'file' first */
+int FAST_FUNC BB_EXECVPE(const char *file, char *const argv[], char *const envp[])
 {
-	if (find_applet_by_name(file) >= 0)
-		execvp(bb_busybox_exec_path, argv);
-	return execvp(file, argv);
-}
+#if ENABLE_FEATURE_PREFER_APPLETS
+	int applet = find_applet_by_name(file);
+	if (applet >= 0) {
+		if (APPLET_IS_NOEXEC(applet)) {
+			/* if non-default environ was passed, replace environ */
+			if (envp != environ) {
+				clearenv();
+
+				/* envp is NULL terminated. */
+				while (*envp)
+					putenv(*envp++);
+			}
+
+			/* this should never return. */
+			run_noexec_applet_and_exit(applet, file, (char **) argv);
+
+			/* if this is reached, error out */
+			errno = ENOEXEC;
+			return -1;
+		} else {
+			/* applet has to be executed using an exec syscall */
+			return execvpe(bb_busybox_exec_path, argv, envp);
+		}
+	}
+# if ENABLE_FEATURE_FORCE_APPLETS
+	else {
+		/* no external programs are allowed, error out */
+		errno = ENOENT;
+		return -1;
+	}
+# endif
 #endif
 
-void FAST_FUNC BB_EXECVP_or_die(char **argv)
+	/* we can always fall back to execvpe */
+	return execvpe(file, argv, envp);
+}
+
+void FAST_FUNC BB_EXECVP_or_die_msg(char **argv, const char *msg)
 {
 	BB_EXECVP(argv[0], argv);
 	/* SUSv3-mandated exit codes */
 	xfunc_error_retval = (errno == ENOENT) ? 127 : 126;
-	bb_perror_msg_and_die("can't execute '%s'", argv[0]);
-}
+	bb_perror_msg_and_die(msg, argv[0]);
+}
\ No newline at end of file
-- 
2.43.0



More information about the busybox mailing list