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
EXEC & BINFMT API, ELF
M: Kees Cook <kees@kernel.org>
R: Eric Biederman <ebiederm@xmission.com>
R: Kees Cook <kees@kernel.org>
L: linux-mm@kvack.org
S: Supported
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: include/linux/binfmts.h
F: include/linux/elf.h
F: include/uapi/linux/auxvec.h
F: include/uapi/linux/binfmts.h
F: include/uapi/linux/elf.h
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
* 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.
*/
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
* so that a new one can be started
* This is unlocked -- the string will always be NUL-terminated, but
* may show overlapping contents if racing concurrent reads.
*/
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);
strscpy_pad(tsk->comm, buf, sizeof(tsk->comm));
task_unlock(tsk);
memcpy(tsk->comm, buf, len);
memset(&tsk->comm[len], 0, sizeof(tsk->comm) - len);
perf_event_comm(tsk, exec);
}
@ -1342,7 +1342,28 @@ int begin_new_exec(struct linux_binprm * bprm)
set_dumpable(current->mm, SUID_DUMP_USER);
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
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] == '/') {
bprm->filename = filename->name;
} else {
if (filename->name[0] == '\0')
if (filename->name[0] == '\0') {
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",
fd, filename->name);
}
if (!bprm->fdpath)
goto out_free;
@ -1735,13 +1758,11 @@ int remove_arg_zero(struct linux_binprm *bprm)
}
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
*/
static int search_binary_handler(struct linux_binprm *bprm)
{
bool need_retry = IS_ENABLED(CONFIG_MODULES);
struct linux_binfmt *fmt;
int retval;
@ -1753,8 +1774,6 @@ static int search_binary_handler(struct linux_binprm *bprm)
if (retval)
return retval;
retval = -ENOENT;
retry:
read_lock(&binfmt_lock);
list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
@ -1772,17 +1791,7 @@ static int search_binary_handler(struct linux_binprm *bprm)
}
read_unlock(&binfmt_lock);
if (need_retry) {
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;
return -ENOEXEC;
}
/* 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);
if (retval == 0)
pr_warn_once("process '%s' launched '%s' with NULL argv: empty string added\n",
current->comm, bprm->filename);
if (retval < 0)
goto out_free;
bprm->argc = retval;
@ -1960,6 +1966,9 @@ static int do_execveat_common(int fd, struct filename *filename,
if (retval < 0)
goto out_free;
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);

View File

@ -47,7 +47,9 @@ struct linux_binprm {
* Set by user space to check executability according to the
* 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 *interpreter;
struct file *file;

View File

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

View File

@ -1936,11 +1936,10 @@ static inline void kick_process(struct task_struct *tsk) { }
#endif
extern void __set_task_comm(struct task_struct *tsk, const char *from, bool exec);
static inline void set_task_comm(struct task_struct *tsk, const char *from)
{
__set_task_comm(tsk, from, false);
}
#define set_task_comm(tsk, from) ({ \
BUILD_BUG_ON(sizeof(from) != TASK_COMM_LEN); \
__set_task_comm(tsk, from, false); \
})
/*
* - 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 *wq = worker->wq;
bool exit_mask = false, last_timeout = false;
char buf[TASK_COMM_LEN];
char buf[TASK_COMM_LEN] = {};
set_mask_bits(&worker->flags, 0,
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 rusage start;
unsigned long timeout = 0;
char buf[TASK_COMM_LEN];
char buf[TASK_COMM_LEN] = {};
DEFINE_WAIT(wait);
/* offload context creation failed, just exit */

View File

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

View File

@ -23,9 +23,11 @@
#include "../kselftest.h"
#define TESTS_EXPECTED 51
#define TESTS_EXPECTED 54
#define TEST_NAME_LEN (PATH_MAX * 4)
#define CHECK_COMM "CHECK_COMM"
static char longpath[2 * PATH_MAX] = "";
static char *envp[] = { "IN_TEST=yes", NULL, 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;
}
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)
{
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, "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;
}
@ -415,9 +448,13 @@ int main(int argc, char **argv)
int ii;
int rc;
const char *verbose = getenv("VERBOSE");
const char *check_comm = getenv(CHECK_COMM);
if (argc >= 2) {
/* If we are invoked with an argument, don't run tests. */
if (argc >= 2 || check_comm) {
/*
* 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");
if (verbose) {
@ -426,6 +463,38 @@ int main(int argc, char **argv)
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. */
if (!in_test || strcmp(in_test, "yes") != 0) {
ksft_print_msg("no IN_TEST=yes in env\n");