This commit implements proxy functions for the exec family of functions. bb_execv, bb_execve and bb_execvp were implemented as proxy functions to their libc counterparts.
applet_execve was implemented as an internal utility function and is used by bb_execve and bb_execvp to handle applet invocations. When called, applet_execve looks for an applet with a matching name (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_execXX functions will fail if an applet is not found, and only applets that are compiled into the program will be able to run. When FEATURE_TRY_BASENAME_APPLETS is enabled, applet_execve will try to find the applet by it's basename (essentially faking a PATH lookup). This feature, in combination with FEATURE_FORCE_APPLETS, can be used to force applet execution while mostly maintaining full functionality in applets that try to execute shells. These changes allow for better control over external programs executed by busybox, and provide a unified interface that can be used in the future to implement further execution limitations. Moreover, having applet execution logic in one function reduces code duplication in several applets that attempt to implement this logic. 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 limitations. Signed-off-by: Nadav Tasher <tasherna...@gmail.com> --- Config.in | 20 +++++++ docs/nofork_noexec.txt | 17 ++++-- include/libbb.h | 41 +++++++++------ libbb/executable.c | 117 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 165 insertions(+), 30 deletions(-) diff --git a/Config.in b/Config.in index ad0cd1e26..bdf55b412 100644 --- a/Config.in +++ b/Config.in @@ -310,6 +310,26 @@ 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_TRY_BASENAME_APPLETS + bool "fake PATH lookup using basename" + default n + depends on FEATURE_PREFER_APPLETS + help + This makes applet lookup try the basename of the applet name given, + essentially allowing applet execution by absolute path. + + This feature extends the "exec prefers applets" feature. + +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..6294371d9 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,20 @@ 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_TRY_BASENAME_APPLETS + Makes bb_execXX functions try the basename of the pathname given, + essentially faking a PATH lookup for applets. + +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..e482cdd12 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1235,22 +1235,31 @@ 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; + +/* 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..cadc62fe9 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,114 @@ 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[]) +int FAST_FUNC applet_execve(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); +#if ENABLE_FEATURE_PREFER_APPLETS + /* used to copy argv to heap */ + char **copied_argv; + +# if ENABLE_FEATURE_TRY_BASENAME_APPLETS + /* find applet by basename */ + int applet = find_applet_by_name(bb_basename(name)); +# else + /* find applet by given name*/ + int applet = find_applet_by_name(name); +# endif + + /* no matching applet was found */ + if (applet < 0) { + errno = ENOENT; + return -1; + } + + /* 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. + * these applets must be executed using the exec syscall. */ + if (!BB_MMU || !APPLET_IS_NOEXEC(applet)) + return execve(bb_busybox_exec_path, argv, envp); + + /* since run_noexec_applet_and_exit takes char **argv, + * we need to copy argv to a new heap-allocated array. */ + 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); + + /* free duplicated argv array */ + while (*copied_argv) + free(*copied_argv++); + + /* if this is reached, error out */ + errno = ENOEXEC; + return -1; +#else + /* applets are not prefered */ + return -1; +#endif } + +/* 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[]) +{ + /* try executing using applet_execve */ + int status = applet_execve(pathname, argv, envp); + + /* if status is valid, should not fall back */ + if (status >= 0) + return status; + +#if ENABLE_FEATURE_FORCE_APPLETS + /* no external programs are allowed, error out */ + errno = ENOENT; + return -1; #endif -void FAST_FUNC BB_EXECVP_or_die(char **argv) + /* 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[]) { - 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]); + return bb_execve(pathname, argv, environ); +} + +/* just like bb_execve, but we pass the basename of file and fall back to execvp */ +int FAST_FUNC bb_execvp(const char *file, char *const argv[]) +{ + /* try executing using applet_execve with the basename of file. + * this emulates a PATH search, since applet names are always basenames. */ + int status = applet_execve(bb_basename(file), argv, environ); + + /* if status is valid, should not fall back */ + if (status >= 0) + return status; + +#if ENABLE_FEATURE_FORCE_APPLETS + /* no external programs are allowed, error out */ + errno = ENOENT; + return -1; +#endif + + /* fall back to execvp */ + return execvp(file, argv); } -- 2.34.1 _______________________________________________ busybox mailing list busybox@busybox.net https://lists.busybox.net/mailman/listinfo/busybox