Merge branch 'for-next/topic/execve/core' into for-next/execve

This commit is contained in:
Kees Cook 2024-12-18 17:01:53 -08:00
commit c7c1167fcb
10 changed files with 124 additions and 43 deletions

View File

@ -8551,8 +8551,8 @@ F: rust/kernel/net/phy.rs
F: rust/kernel/net/phy/reg.rs F: rust/kernel/net/phy/reg.rs
EXEC & BINFMT API, ELF EXEC & BINFMT API, ELF
M: Kees Cook <kees@kernel.org>
R: Eric Biederman <ebiederm@xmission.com> R: Eric Biederman <ebiederm@xmission.com>
R: Kees Cook <kees@kernel.org>
L: linux-mm@kvack.org L: linux-mm@kvack.org
S: Supported S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/execve T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/execve
@ -8564,6 +8564,7 @@ F: fs/tests/binfmt_*_kunit.c
F: fs/tests/exec_kunit.c F: fs/tests/exec_kunit.c
F: include/linux/binfmts.h F: include/linux/binfmts.h
F: include/linux/elf.h F: include/linux/elf.h
F: include/uapi/linux/auxvec.h
F: include/uapi/linux/binfmts.h F: include/uapi/linux/binfmts.h
F: include/uapi/linux/elf.h F: include/uapi/linux/elf.h
F: tools/testing/selftests/exec/ F: tools/testing/selftests/exec/

View File

@ -1001,7 +1001,7 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
/* /*
* If it turns out that most user namespaces actually want to * If it turns out that most user namespaces actually want to
* register their own binary type handler and therefore all * register their own binary type handler and therefore all
* create their own separate binfm_misc mounts we should * create their own separate binfmt_misc mounts we should
* consider turning this into a kmem cache. * consider turning this into a kmem cache.
*/ */
misc = kzalloc(sizeof(struct binfmt_misc), GFP_KERNEL); misc = kzalloc(sizeof(struct binfmt_misc), GFP_KERNEL);

View File

@ -1195,16 +1195,16 @@ static int unshare_sighand(struct task_struct *me)
} }
/* /*
* These functions flushes out all traces of the currently running executable * This is unlocked -- the string will always be NUL-terminated, but
* so that a new one can be started * may show overlapping contents if racing concurrent reads.
*/ */
void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec) void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec)
{ {
task_lock(tsk); size_t len = min(strlen(buf), sizeof(tsk->comm) - 1);
trace_task_rename(tsk, buf); trace_task_rename(tsk, buf);
strscpy_pad(tsk->comm, buf, sizeof(tsk->comm)); memcpy(tsk->comm, buf, len);
task_unlock(tsk); memset(&tsk->comm[len], 0, sizeof(tsk->comm) - len);
perf_event_comm(tsk, exec); perf_event_comm(tsk, exec);
} }
@ -1342,7 +1342,28 @@ int begin_new_exec(struct linux_binprm * bprm)
set_dumpable(current->mm, SUID_DUMP_USER); set_dumpable(current->mm, SUID_DUMP_USER);
perf_event_exec(); perf_event_exec();
__set_task_comm(me, kbasename(bprm->filename), true);
/*
* If the original filename was empty, alloc_bprm() made up a path
* that will probably not be useful to admins running ps or similar.
* Let's fix it up to be something reasonable.
*/
if (bprm->comm_from_dentry) {
/*
* Hold RCU lock to keep the name from being freed behind our back.
* Use acquire semantics to make sure the terminating NUL from
* __d_alloc() is seen.
*
* Note, we're deliberately sloppy here. We don't need to care about
* detecting a concurrent rename and just want a terminated name.
*/
rcu_read_lock();
__set_task_comm(me, smp_load_acquire(&bprm->file->f_path.dentry->d_name.name),
true);
rcu_read_unlock();
} else {
__set_task_comm(me, kbasename(bprm->filename), true);
}
/* An exec changes our domain. We are no longer part of the thread /* An exec changes our domain. We are no longer part of the thread
group */ group */
@ -1518,11 +1539,13 @@ static struct linux_binprm *alloc_bprm(int fd, struct filename *filename, int fl
if (fd == AT_FDCWD || filename->name[0] == '/') { if (fd == AT_FDCWD || filename->name[0] == '/') {
bprm->filename = filename->name; bprm->filename = filename->name;
} else { } else {
if (filename->name[0] == '\0') if (filename->name[0] == '\0') {
bprm->fdpath = kasprintf(GFP_KERNEL, "/dev/fd/%d", fd); bprm->fdpath = kasprintf(GFP_KERNEL, "/dev/fd/%d", fd);
else bprm->comm_from_dentry = 1;
} else {
bprm->fdpath = kasprintf(GFP_KERNEL, "/dev/fd/%d/%s", bprm->fdpath = kasprintf(GFP_KERNEL, "/dev/fd/%d/%s",
fd, filename->name); fd, filename->name);
}
if (!bprm->fdpath) if (!bprm->fdpath)
goto out_free; goto out_free;
@ -1735,13 +1758,11 @@ int remove_arg_zero(struct linux_binprm *bprm)
} }
EXPORT_SYMBOL(remove_arg_zero); EXPORT_SYMBOL(remove_arg_zero);
#define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e))
/* /*
* cycle the list of binary formats handler, until one recognizes the image * cycle the list of binary formats handler, until one recognizes the image
*/ */
static int search_binary_handler(struct linux_binprm *bprm) static int search_binary_handler(struct linux_binprm *bprm)
{ {
bool need_retry = IS_ENABLED(CONFIG_MODULES);
struct linux_binfmt *fmt; struct linux_binfmt *fmt;
int retval; int retval;
@ -1753,8 +1774,6 @@ static int search_binary_handler(struct linux_binprm *bprm)
if (retval) if (retval)
return retval; return retval;
retval = -ENOENT;
retry:
read_lock(&binfmt_lock); read_lock(&binfmt_lock);
list_for_each_entry(fmt, &formats, lh) { list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module)) if (!try_module_get(fmt->module))
@ -1772,17 +1791,7 @@ static int search_binary_handler(struct linux_binprm *bprm)
} }
read_unlock(&binfmt_lock); read_unlock(&binfmt_lock);
if (need_retry) { return -ENOEXEC;
if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
printable(bprm->buf[2]) && printable(bprm->buf[3]))
return retval;
if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)
return retval;
need_retry = false;
goto retry;
}
return retval;
} }
/* binfmt handlers will call back into begin_new_exec() on success. */ /* binfmt handlers will call back into begin_new_exec() on success. */
@ -1920,9 +1929,6 @@ static int do_execveat_common(int fd, struct filename *filename,
} }
retval = count(argv, MAX_ARG_STRINGS); retval = count(argv, MAX_ARG_STRINGS);
if (retval == 0)
pr_warn_once("process '%s' launched '%s' with NULL argv: empty string added\n",
current->comm, bprm->filename);
if (retval < 0) if (retval < 0)
goto out_free; goto out_free;
bprm->argc = retval; bprm->argc = retval;
@ -1960,6 +1966,9 @@ static int do_execveat_common(int fd, struct filename *filename,
if (retval < 0) if (retval < 0)
goto out_free; goto out_free;
bprm->argc = 1; bprm->argc = 1;
pr_warn_once("process '%s' launched '%s' with NULL argv: empty string added\n",
current->comm, bprm->filename);
} }
retval = bprm_execve(bprm); retval = bprm_execve(bprm);

View File

@ -47,7 +47,9 @@ struct linux_binprm {
* Set by user space to check executability according to the * Set by user space to check executability according to the
* caller's environment. * caller's environment.
*/ */
is_check:1; is_check:1,
/* Set when "comm" must come from the dentry. */
comm_from_dentry:1;
struct file *executable; /* Executable to pass to the interpreter */ struct file *executable; /* Executable to pass to the interpreter */
struct file *interpreter; struct file *interpreter;
struct file *file; struct file *file;

View File

@ -52,8 +52,8 @@ extern void do_coredump(const kernel_siginfo_t *siginfo);
#define __COREDUMP_PRINTK(Level, Format, ...) \ #define __COREDUMP_PRINTK(Level, Format, ...) \
do { \ do { \
char comm[TASK_COMM_LEN]; \ char comm[TASK_COMM_LEN]; \
\ /* This will always be NUL terminated. */ \
get_task_comm(comm, current); \ memcpy(comm, current->comm, sizeof(comm)); \
printk_ratelimited(Level "coredump: %d(%*pE): " Format "\n", \ printk_ratelimited(Level "coredump: %d(%*pE): " Format "\n", \
task_tgid_vnr(current), (int)strlen(comm), comm, ##__VA_ARGS__); \ task_tgid_vnr(current), (int)strlen(comm), comm, ##__VA_ARGS__); \
} while (0) \ } while (0) \

View File

@ -1936,11 +1936,10 @@ static inline void kick_process(struct task_struct *tsk) { }
#endif #endif
extern void __set_task_comm(struct task_struct *tsk, const char *from, bool exec); extern void __set_task_comm(struct task_struct *tsk, const char *from, bool exec);
#define set_task_comm(tsk, from) ({ \
static inline void set_task_comm(struct task_struct *tsk, const char *from) BUILD_BUG_ON(sizeof(from) != TASK_COMM_LEN); \
{ __set_task_comm(tsk, from, false); \
__set_task_comm(tsk, from, false); })
}
/* /*
* - Why not use task_lock()? * - Why not use task_lock()?

View File

@ -634,7 +634,7 @@ static int io_wq_worker(void *data)
struct io_wq_acct *acct = io_wq_get_acct(worker); struct io_wq_acct *acct = io_wq_get_acct(worker);
struct io_wq *wq = worker->wq; struct io_wq *wq = worker->wq;
bool exit_mask = false, last_timeout = false; bool exit_mask = false, last_timeout = false;
char buf[TASK_COMM_LEN]; char buf[TASK_COMM_LEN] = {};
set_mask_bits(&worker->flags, 0, set_mask_bits(&worker->flags, 0,
BIT(IO_WORKER_F_UP) | BIT(IO_WORKER_F_RUNNING)); BIT(IO_WORKER_F_UP) | BIT(IO_WORKER_F_RUNNING));

View File

@ -264,7 +264,7 @@ static int io_sq_thread(void *data)
struct io_ring_ctx *ctx; struct io_ring_ctx *ctx;
struct rusage start; struct rusage start;
unsigned long timeout = 0; unsigned long timeout = 0;
char buf[TASK_COMM_LEN]; char buf[TASK_COMM_LEN] = {};
DEFINE_WAIT(wait); DEFINE_WAIT(wait);
/* offload context creation failed, just exit */ /* offload context creation failed, just exit */

View File

@ -738,10 +738,11 @@ EXPORT_SYMBOL(kthread_stop_put);
int kthreadd(void *unused) int kthreadd(void *unused)
{ {
static const char comm[TASK_COMM_LEN] = "kthreadd";
struct task_struct *tsk = current; struct task_struct *tsk = current;
/* Setup a clean context for our children to inherit. */ /* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd"); set_task_comm(tsk, comm);
ignore_signals(tsk); ignore_signals(tsk);
set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_TYPE_KTHREAD)); set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_TYPE_KTHREAD));
set_mems_allowed(node_states[N_MEMORY]); set_mems_allowed(node_states[N_MEMORY]);

View File

@ -23,9 +23,11 @@
#include "../kselftest.h" #include "../kselftest.h"
#define TESTS_EXPECTED 51 #define TESTS_EXPECTED 54
#define TEST_NAME_LEN (PATH_MAX * 4) #define TEST_NAME_LEN (PATH_MAX * 4)
#define CHECK_COMM "CHECK_COMM"
static char longpath[2 * PATH_MAX] = ""; static char longpath[2 * PATH_MAX] = "";
static char *envp[] = { "IN_TEST=yes", NULL, NULL }; static char *envp[] = { "IN_TEST=yes", NULL, NULL };
static char *argv[] = { "execveat", "99", NULL }; static char *argv[] = { "execveat", "99", NULL };
@ -237,6 +239,29 @@ static int check_execveat_pathmax(int root_dfd, const char *src, int is_script)
return fail; return fail;
} }
static int check_execveat_comm(int fd, char *argv0, char *expected)
{
char buf[128], *old_env, *old_argv0;
int ret;
snprintf(buf, sizeof(buf), CHECK_COMM "=%s", expected);
old_env = envp[1];
envp[1] = buf;
old_argv0 = argv[0];
argv[0] = argv0;
ksft_print_msg("Check execveat(AT_EMPTY_PATH)'s comm is %s\n",
expected);
ret = check_execveat_invoked_rc(fd, "", AT_EMPTY_PATH, 0, 0);
envp[1] = old_env;
argv[0] = old_argv0;
return ret;
}
static int run_tests(void) static int run_tests(void)
{ {
int fail = 0; int fail = 0;
@ -389,6 +414,14 @@ static int run_tests(void)
fail += check_execveat_pathmax(root_dfd, "execveat", 0); fail += check_execveat_pathmax(root_dfd, "execveat", 0);
fail += check_execveat_pathmax(root_dfd, "script", 1); fail += check_execveat_pathmax(root_dfd, "script", 1);
/* /proc/pid/comm gives filename by default */
fail += check_execveat_comm(fd, "sentinel", "execveat");
/* /proc/pid/comm gives argv[0] when invoked via link */
fail += check_execveat_comm(fd_symlink, "sentinel", "execveat");
/* /proc/pid/comm gives filename if NULL is passed */
fail += check_execveat_comm(fd, NULL, "execveat");
return fail; return fail;
} }
@ -415,9 +448,13 @@ int main(int argc, char **argv)
int ii; int ii;
int rc; int rc;
const char *verbose = getenv("VERBOSE"); const char *verbose = getenv("VERBOSE");
const char *check_comm = getenv(CHECK_COMM);
if (argc >= 2) { if (argc >= 2 || check_comm) {
/* If we are invoked with an argument, don't run tests. */ /*
* If we are invoked with an argument, or no arguments but a
* command to check, don't run tests.
*/
const char *in_test = getenv("IN_TEST"); const char *in_test = getenv("IN_TEST");
if (verbose) { if (verbose) {
@ -426,6 +463,38 @@ int main(int argc, char **argv)
ksft_print_msg("\t[%d]='%s\n'", ii, argv[ii]); ksft_print_msg("\t[%d]='%s\n'", ii, argv[ii]);
} }
/* If the tests wanted us to check the command, do so. */
if (check_comm) {
/* TASK_COMM_LEN == 16 */
char buf[32];
int fd, ret;
fd = open("/proc/self/comm", O_RDONLY);
if (fd < 0) {
ksft_perror("open() comm failed");
exit(1);
}
ret = read(fd, buf, sizeof(buf));
if (ret < 0) {
ksft_perror("read() comm failed");
close(fd);
exit(1);
}
close(fd);
// trim off the \n
buf[ret-1] = 0;
if (strcmp(buf, check_comm)) {
ksft_print_msg("bad comm, got: %s expected: %s\n",
buf, check_comm);
exit(1);
}
exit(0);
}
/* Check expected environment transferred. */ /* Check expected environment transferred. */
if (!in_test || strcmp(in_test, "yes") != 0) { if (!in_test || strcmp(in_test, "yes") != 0) {
ksft_print_msg("no IN_TEST=yes in env\n"); ksft_print_msg("no IN_TEST=yes in env\n");