mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-22 07:53:11 -05:00
execve updates for v6.14-rc1
- exec: fix up /proc/pid/comm in the execveat(AT_EMPTY_PATH) case (Tycho Andersen, Kees Cook) - binfmt_misc: Fix comment typos (Christophe JAILLET) - exec: move empty argv[0] warning closer to actual logic (Nir Lichtman) - exec: remove legacy custom binfmt modules autoloading (Nir Lichtman) - binfmt_flat: Fix integer overflow bug on 32 bit systems (Dan Carpenter) - exec: Make sure set_task_comm() always NUL-terminates - coredump: Do not lock when copying "comm" - MAINTAINERS: add auxvec.h and set myself as maintainer -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQRSPkdeREjth1dHnSE2KwveOeQkuwUCZ4hNmQAKCRA2KwveOeQk u0/nAQCTGU0zqhdO6t7ABsL3p9kJ2jVRA5njAoX7A/9jGPSWEQD/boRMqZuUpthV nMevcQ2F4u0A7kJJBMK05YdXWHkYqgk= =49Di -----END PGP SIGNATURE----- Merge tag 'execve-v6.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux Pull execve updates from Kees Cook: - fix up /proc/pid/comm in the execveat(AT_EMPTY_PATH) case (Tycho Andersen, Kees Cook) - binfmt_misc: Fix comment typos (Christophe JAILLET) - move empty argv[0] warning closer to actual logic (Nir Lichtman) - remove legacy custom binfmt modules autoloading (Nir Lichtman) - Make sure set_task_comm() always NUL-terminates - binfmt_flat: Fix integer overflow bug on 32 bit systems (Dan Carpenter) - coredump: Do not lock when copying "comm" - MAINTAINERS: add auxvec.h and set myself as maintainer * tag 'execve-v6.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux: binfmt_flat: Fix integer overflow bug on 32 bit systems selftests/exec: add a test for execveat()'s comm exec: fix up /proc/pid/comm in the execveat(AT_EMPTY_PATH) case exec: Make sure task->comm is always NUL-terminated exec: remove legacy custom binfmt modules autoloading exec: move warning of null argv to be next to the relevant code fs: binfmt: Fix a typo MAINTAINERS: exec: Mark Kees as maintainer MAINTAINERS: exec: Add auxvec.h UAPI coredump: Do not lock during 'comm' reporting
This commit is contained in:
commit
fadc3ed9ce
11 changed files with 125 additions and 44 deletions
|
@ -8548,8 +8548,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
|
||||
|
@ -8561,6 +8561,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/
|
||||
|
|
|
@ -478,7 +478,7 @@ static int load_flat_file(struct linux_binprm *bprm,
|
|||
* 28 bits (256 MB) is way more than reasonable in this case.
|
||||
* If some top bits are set we have probable binary corruption.
|
||||
*/
|
||||
if ((text_len | data_len | bss_len | stack_len | full_data) >> 28) {
|
||||
if ((text_len | data_len | bss_len | stack_len | relocs | full_data) >> 28) {
|
||||
pr_err("bad header\n");
|
||||
ret = -ENOEXEC;
|
||||
goto err;
|
||||
|
|
|
@ -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);
|
||||
|
|
63
fs/exec.c
63
fs/exec.c
|
@ -1194,16 +1194,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);
|
||||
}
|
||||
|
||||
|
@ -1341,7 +1341,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 */
|
||||
|
@ -1517,11 +1538,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;
|
||||
|
||||
|
@ -1719,13 +1742,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;
|
||||
|
||||
|
@ -1737,8 +1758,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))
|
||||
|
@ -1756,17 +1775,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. */
|
||||
|
@ -1904,9 +1913,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;
|
||||
|
@ -1944,6 +1950,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);
|
||||
|
|
|
@ -42,7 +42,9 @@ struct linux_binprm {
|
|||
* Set when errors can no longer be returned to the
|
||||
* original userspace.
|
||||
*/
|
||||
point_of_no_return:1;
|
||||
point_of_no_return: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;
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -1944,11 +1944,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()?
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue