[PATCH v8 05/14] libbb: implement bb_execXX function family to handle applet executions

Nadav Tasher tashernadav at gmail.com
Sun Mar 9 23:55:27 UTC 2025


This commit implements proxy functions for the exec family of
functions.

bb_execv, bb_execve, bb_execvp and bb_execvpe were implemented as
proxy functions to their libc counterparts.

applet_execve and applet_execvpe were implemented as internal utility
functions and are used by bb_exec function family to handle applet
executions.

When called, the bb_execXX functions use the applet_execXX functions
to look for a matching applet (assuming FEATURE_PREFER_APPLETS is
enabled).
When an applet is found, it is executed in one of two ways:
1. If the applet is marked NOEXEC in applets.h, it is
executed using run_noexec_applet_and_exit(). This requires manual
bootstrapping, to mimic the behaviour of the actual exec syscall.
2. If the applet is not marked NOEXEC in applets.h, it is executed
by re-executing the busybox executable with new argv and envp.

When FEATURE_FORCE_APPLETS is enabled, the bb_exec function family
will fail if an applet is not found, and only included applets will
be executed.

These changes allow for better control over programs executed by
busybox, and provide a unified interface that can be used in the
future to implement further execution validations.
Moreover, having applet execution logic in one function reduces
code duplication in several applets that attempt to execute
applets directly.

Notes:
1. applet_execve copies argv to a new heap-allocated array because
applets are able to modify argv strings. This avoids potential
memory corruptions.
2. applet_execve manually closes FDs marked with FD_CLOEXEC and
resets custom signal handlers to mimic the behaviour of the exec
syscall.
3. BB_EXECVP_or_die was replaced with the bb_execvp_or_die macro.
4. NOMMU targets do not support NOEXEC due to vfork restrictions.

Signed-off-by: Nadav Tasher <tashernadav at gmail.com>
---
 Config.in              |  10 ++++
 docs/nofork_noexec.txt |  13 +++--
 include/libbb.h        |  42 ++++++++++------
 libbb/executable.c     | 111 +++++++++++++++++++++++++++++++++++++----
 4 files changed, 147 insertions(+), 29 deletions(-)

diff --git a/Config.in b/Config.in
index ad0cd1e26..b1dfe98c1 100644
--- a/Config.in
+++ b/Config.in
@@ -310,6 +310,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/docs/nofork_noexec.txt b/docs/nofork_noexec.txt
index 9d210a1c9..ff26a06a0 100644
--- a/docs/nofork_noexec.txt
+++ b/docs/nofork_noexec.txt
@@ -14,7 +14,8 @@ Applet will be subject to NOFORK/NOEXEC tricks only if it is marked
 as such in applets.src.h or in their inline "//applet:" directives.
 
 In C, if you want to call a program and wait for it, use
-spawn_and_wait(argv), BB_EXECVP(prog,argv) or BB_EXECLP(prog,argv0,...).
+spawn_and_wait(argv), bb_system(command), bb_execv(path,argv),
+execve(path,argv,envp), bb_execvp(prog,argv) or bb_execvpe(prog,argv,envp).
 They check whether program name is an applet name and optionally
 do NOFORK/NOEXEC thing depending on configuration.
 
@@ -24,10 +25,16 @@ do NOFORK/NOEXEC thing depending on configuration.
 FEATURE_PREFER_APPLETS
   Globally enables NOFORK/NOEXEC tricks for such programs as xargs
   and find:
-  BB_EXECVP(cmd, argv) will try to exec /proc/self/exe
-  if command's name matches some applet name;
+  bb_execXX functions will try to exec /proc/self/exe or execute the main
+  function of the applet directly when possible, if command's name
+  matches some applet name.
   spawn_and_wait(argv) will do NOFORK/NOEXEC tricks
 
+FEATURE_FORCE_APPLETS
+  Globally disables the fallback to exec in all bb_execXX functions.
+  This makes busybox program executions constrained to applets,
+  essentially making busybox some kind of sandbox.
+
 //TODO: the above two things probably should have separate options?
 
 FEATURE_SH_STANDALONE
diff --git a/include/libbb.h b/include/libbb.h
index 11d2c27ec..c8814ce50 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1235,22 +1235,32 @@ 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;
+
+/* when FEATURE_PREFER_APPLETS is enabled, these functions act as a way
+ * to "exec" a built-in applet, either by NOEXEC or by re-exec. */
+int applet_execve(const char *name, char *const argv[], char *const envp[]) FAST_FUNC;
+int applet_execvpe(const char *name, char *const argv[], char *const envp[]) FAST_FUNC;
+
+/* these functions act as proxies to execve and execvpe, allowing for the
+ * use of bb_applet_execve and bb_applet_execvpe when required. */
+int bb_execv(const char *pathname, char *const argv[]) FAST_FUNC;
+int bb_execve(const char *pathname, char *const argv[], char *const envp[]) FAST_FUNC;
+int bb_execvp(const char *file, char *const argv[]) FAST_FUNC;
+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. */
+#define bb_execvp_or_die_msg(argv,msg) \
+({ \
+	bb_execvp((argv)[0], (argv)); \
+	/* SUSv3-mandated exit codes */ \
+	xfunc_error_retval = 2; \
+	if (errno == EACCES) xfunc_error_retval = 126; \
+	if (errno == ENOENT) xfunc_error_retval = 127; \
+	bb_perror_msg_and_die(msg, (argv)[0]); \
+})
+#define bb_execvp_or_die(argv) \
+	bb_execvp_or_die_msg(argv, "can't execute '%s'")
 
 #if !BB_MMU
 /* xvfork() can't be a _function_, return after vfork in child mangles stack
diff --git a/libbb/executable.c b/libbb/executable.c
index 09bed1eaf..dcd2613f0 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,110 @@ int FAST_FUNC executable_exists(const char *name)
 	return ret != NULL;
 }
 
+int FAST_FUNC applet_execve(const char *name, char *const argv[], char *const envp[])
+{
 #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[])
+	int applet = find_applet_by_name(name);
+	if (applet >= 0) {
+		/* NOMMU targets only support vfork(). 
+		 * since vfork() requires the child to exec() or _exit() for the
+		 * parent to resume, running applets with NOEXEC and vfork()
+		 * may result in deadlocks, as exec() will never be called. */
+		if (BB_MMU && APPLET_IS_NOEXEC(applet)) {
+			/* since run_noexec_applet_and_exit takes char **argv,
+			 * we need to copy argv to a new heap-allocated array. */
+			char **copied_argv = clone_string_array(argv);
+			
+			/* since exec will not be called, we need to manually
+			 * reset some signal handlers. */
+			reset_all_signals();
+
+			/* since exec will not be called, we then need to close
+			 * all FDs with FD_CLOEXEC manually. */
+			close_cloexec_fds();
+
+			/* 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, name, copied_argv);
+
+			/* if this is reached, error out */
+			errno = ENOEXEC;
+			return -1;
+		} else {
+			/* applet has to be executed using an exec syscall */
+			return execve(bb_busybox_exec_path, argv, envp);
+		}
+	}
+
+	/* no matching applet was found */
+	errno = ENOENT;
+	return -1;
+#else
+	/* applets are not prefered */
+	return -1;
+#endif
+}
+
+int FAST_FUNC applet_execvpe(const char *name, char *const argv[], char *const envp[])
 {
-	if (find_applet_by_name(file) >= 0)
-		execvp(bb_busybox_exec_path, argv);
-	return execvp(file, argv);
+	/* we try calling bb_applet_execve with the given name. */
+	int error = applet_execve(name, argv, envp);
+
+	/* since this is function is supposed to emulate a PATH
+	 * search, we try executing with the basename too. */
+	if (error < 0)
+		error = applet_execve(bb_basename(name), argv, envp);
+
+	return error;
 }
+
+/* just like the real execve, but we might try to launch an applet named 'pathname' first */
+int FAST_FUNC bb_execve(const char *pathname, char *const argv[], char *const envp[])
+{
+	if (applet_execve(pathname, argv, envp) < 0) {
+#if ENABLE_FEATURE_FORCE_APPLETS
+		/* no external programs are allowed, error out */
+		errno = ENOENT;
+		return -1;
 #endif
+	}
+
+	/* fall back to execve */
+	return execve(pathname, argv, envp);
+}
+
+/* just like bb_execve, but we keep the existing environment by passing environ */
+int FAST_FUNC bb_execv(const char *pathname, char *const argv[])
+{
+	return bb_execve(pathname, argv, environ);
+}
+
 
-void FAST_FUNC BB_EXECVP_or_die(char **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[])
 {
-	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]);
+	if (applet_execvpe(file, argv, envp) < 0) {
+#if ENABLE_FEATURE_FORCE_APPLETS
+		/* no external programs are allowed, error out */
+		errno = ENOENT;
+		return -1;
+#endif
+	}
+
+	/* fall back to execvpe */
+	return execvpe(file, argv, envp);
 }
+
+/* just like bb_execvpe, but we keep the existing environment by passing environ */
+int FAST_FUNC bb_execvp(const char *file, char *const argv[])
+{
+	return bb_execvpe(file, argv, environ);
+}
\ No newline at end of file
-- 
2.43.0



More information about the busybox mailing list