Merge patch series "coredump: further cleanups"

Christian Brauner <brauner@kernel.org> says:

Continue reworking the coredump code so it's easier to follow and modify
in the future.

* Each method is moved into a separate helper.
* The cleanup code is simplified and unified.
* Entangle the dependency between the pipe coredump rate limiting and
  the common exit path.

It's likely that there'll be more.

* patches from https://lore.kernel.org/20250612-work-coredump-massage-v1-0-315c0c34ba94@kernel.org: (24 commits)
  coredump: add coredump_skip() helper
  coredump: avoid pointless variable
  coredump: order auto cleanup variables at the top
  coredump: add coredump_cleanup()
  coredump: auto cleanup prepare_creds()
  cred: add auto cleanup method
  coredump: directly return
  coredump: auto cleanup argv
  coredump: add coredump_write()
  coredump: use a single helper for the socket
  coredump: move pipe specific file check into coredump_pipe()
  coredump: split pipe coredumping into coredump_pipe()
  coredump: move core_pipe_count to global variable
  coredump: prepare to simplify exit paths
  coredump: split file coredumping into coredump_file()
  coredump: rename do_coredump() to vfs_coredump()
  selftests/coredump: make sure invalid paths are rejected
  coredump: validate socket path in coredump_parse()
  coredump: don't allow ".." in coredump socket path
  fs: move name_contains_dotdot() to header
  ...

Link: https://lore.kernel.org/20250612-work-coredump-massage-v1-0-315c0c34ba94@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2025-06-16 17:01:31 +02:00
commit a6ed5691b2
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
9 changed files with 366 additions and 279 deletions

View File

@ -555,5 +555,5 @@ the VFS, and that can be done by calling into such as ``vfs_mkdir()`` with a
different set of credentials. This is done in the following places:
* ``sys_faccessat()``.
* ``do_coredump()``.
* ``vfs_coredump()``.
* nfs4recover.c.

View File

@ -475,5 +475,5 @@ const指针上操作因此不需要进行类型转换但需要临时放弃
``vfs_mkdir()`` 来实现。以下是一些进行此操作的位置:
* ``sys_faccessat()``.
* ``do_coredump()``.
* ``vfs_coredump()``.
* nfs4recover.c.

View File

@ -822,26 +822,6 @@ static void fw_log_firmware_info(const struct firmware *fw, const char *name,
{}
#endif
/*
* Reject firmware file names with ".." path components.
* There are drivers that construct firmware file names from device-supplied
* strings, and we don't want some device to be able to tell us "I would like to
* be sent my firmware from ../../../etc/shadow, please".
*
* Search for ".." surrounded by either '/' or start/end of string.
*
* This intentionally only looks at the firmware name, not at the firmware base
* directory or at symlink contents.
*/
static bool name_contains_dotdot(const char *name)
{
size_t name_len = strlen(name);
return strcmp(name, "..") == 0 || strncmp(name, "../", 3) == 0 ||
strstr(name, "/../") != NULL ||
(name_len >= 3 && strcmp(name+name_len-3, "/..") == 0);
}
/* called from request_firmware() and request_firmware_work_func() */
static int
_request_firmware(const struct firmware **firmware_p, const char *name,
@ -862,6 +842,17 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
goto out;
}
/*
* Reject firmware file names with ".." path components.
* There are drivers that construct firmware file names from
* device-supplied strings, and we don't want some device to be
* able to tell us "I would like to be sent my firmware from
* ../../../etc/shadow, please".
*
* This intentionally only looks at the firmware name, not at
* the firmware base directory or at symlink contents.
*/
if (name_contains_dotdot(name)) {
dev_warn(device,
"Firmware load for '%s' refused, path contains '..' component\n",

View File

@ -82,6 +82,7 @@ static unsigned int core_sort_vma;
static char core_pattern[CORENAME_MAX_SIZE] = "core";
static int core_name_size = CORENAME_MAX_SIZE;
unsigned int core_file_note_size_limit = CORE_FILE_NOTE_SIZE_DEFAULT;
static atomic_t core_pipe_count = ATOMIC_INIT(0);
enum coredump_type_t {
COREDUMP_FILE = 1,
@ -93,6 +94,8 @@ enum coredump_type_t {
struct core_name {
char *corename;
int used, size;
unsigned int core_pipe_limit;
bool core_dumped;
enum coredump_type_t core_type;
u64 mask;
};
@ -225,11 +228,12 @@ static int cn_print_exe_file(struct core_name *cn, bool name_only)
return ret;
}
/* format_corename will inspect the pattern parameter, and output a
* name into corename, which must have space for at least
* CORENAME_MAX_SIZE bytes plus one byte for the zero terminator.
/*
* coredump_parse will inspect the pattern parameter, and output a name
* into corename, which must have space for at least CORENAME_MAX_SIZE
* bytes plus one byte for the zero terminator.
*/
static int format_corename(struct core_name *cn, struct coredump_params *cprm,
static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm,
size_t **argv, int *argc)
{
const struct cred *cred = current_cred();
@ -243,6 +247,8 @@ static int format_corename(struct core_name *cn, struct coredump_params *cprm,
cn->mask |= COREDUMP_WAIT;
cn->used = 0;
cn->corename = NULL;
cn->core_pipe_limit = 0;
cn->core_dumped = false;
if (*pat_ptr == '|')
cn->core_type = COREDUMP_PIPE;
else if (*pat_ptr == '@')
@ -250,7 +256,7 @@ static int format_corename(struct core_name *cn, struct coredump_params *cprm,
else
cn->core_type = COREDUMP_FILE;
if (expand_corename(cn, core_name_size))
return -ENOMEM;
return false;
cn->corename[0] = '\0';
switch (cn->core_type) {
@ -258,33 +264,33 @@ static int format_corename(struct core_name *cn, struct coredump_params *cprm,
int argvs = sizeof(core_pattern) / 2;
(*argv) = kmalloc_array(argvs, sizeof(**argv), GFP_KERNEL);
if (!(*argv))
return -ENOMEM;
return false;
(*argv)[(*argc)++] = 0;
++pat_ptr;
if (!(*pat_ptr))
return -ENOMEM;
return false;
break;
}
case COREDUMP_SOCK: {
/* skip the @ */
pat_ptr++;
if (!(*pat_ptr))
return -ENOMEM;
return false;
if (*pat_ptr == '@') {
pat_ptr++;
if (!(*pat_ptr))
return -ENOMEM;
return false;
cn->core_type = COREDUMP_SOCK_REQ;
}
err = cn_printf(cn, "%s", pat_ptr);
if (err)
return err;
return false;
/* Require absolute paths. */
if (cn->corename[0] != '/')
return -EINVAL;
return false;
/*
* Ensure we can uses spaces to indicate additional
@ -292,7 +298,18 @@ static int format_corename(struct core_name *cn, struct coredump_params *cprm,
*/
if (strchr(cn->corename, ' ')) {
coredump_report_failure("Coredump socket may not %s contain spaces", cn->corename);
return -EINVAL;
return false;
}
/* Must not contain ".." in the path. */
if (name_contains_dotdot(cn->corename)) {
coredump_report_failure("Coredump socket may not %s contain '..' spaces", cn->corename);
return false;
}
if (strlen(cn->corename) >= UNIX_PATH_MAX) {
coredump_report_failure("Coredump socket path %s too long", cn->corename);
return false;
}
/*
@ -302,13 +319,13 @@ static int format_corename(struct core_name *cn, struct coredump_params *cprm,
* via /proc/<pid>, using the SO_PEERPIDFD to guard
* against pid recycling when opening /proc/<pid>.
*/
return 0;
return true;
}
case COREDUMP_FILE:
break;
default:
WARN_ON_ONCE(true);
return -EINVAL;
return false;
}
/* Repeat as long as we have more pattern to process and more output
@ -446,7 +463,7 @@ static int format_corename(struct core_name *cn, struct coredump_params *cprm,
}
if (err)
return err;
return false;
}
out:
@ -456,9 +473,9 @@ static int format_corename(struct core_name *cn, struct coredump_params *cprm,
* and core_uses_pid is set, then .%pid will be appended to
* the filename. Do not do this for piped commands. */
if (cn->core_type == COREDUMP_FILE && !pid_in_pattern && core_uses_pid)
return cn_printf(cn, ".%d", task_tgid_vnr(current));
return cn_printf(cn, ".%d", task_tgid_vnr(current)) == 0;
return 0;
return true;
}
static int zap_process(struct signal_struct *signal, int exit_code)
@ -840,34 +857,250 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
cn->mask = ack.mask;
return coredump_sock_mark(cprm->file, COREDUMP_MARK_REQACK);
}
#else
static bool coredump_sock_connect(struct core_name *cn,
struct coredump_params *cprm)
static bool coredump_socket(struct core_name *cn, struct coredump_params *cprm)
{
coredump_report_failure("Core dump socket support %s disabled", cn->corename);
return false;
if (!coredump_sock_connect(cn, cprm))
return false;
return coredump_sock_request(cn, cprm);
}
static bool coredump_sock_request(struct core_name *cn,
struct coredump_params *cprm) { return false; }
#else
static inline void coredump_sock_wait(struct file *file) { }
static inline void coredump_sock_shutdown(struct file *file) { }
static inline bool coredump_socket(struct core_name *cn, struct coredump_params *cprm) { return false; }
#endif
void do_coredump(const kernel_siginfo_t *siginfo)
/* cprm->mm_flags contains a stable snapshot of dumpability flags. */
static inline bool coredump_force_suid_safe(const struct coredump_params *cprm)
{
/* Require nonrelative corefile path and be extra careful. */
return __get_dumpable(cprm->mm_flags) == SUID_DUMP_ROOT;
}
static bool coredump_file(struct core_name *cn, struct coredump_params *cprm,
const struct linux_binfmt *binfmt)
{
struct mnt_idmap *idmap;
struct inode *inode;
struct file *file __free(fput) = NULL;
int open_flags = O_CREAT | O_WRONLY | O_NOFOLLOW | O_LARGEFILE | O_EXCL;
if (cprm->limit < binfmt->min_coredump)
return false;
if (coredump_force_suid_safe(cprm) && cn->corename[0] != '/') {
coredump_report_failure("this process can only dump core to a fully qualified path, skipping core dump");
return false;
}
/*
* Unlink the file if it exists unless this is a SUID
* binary - in that case, we're running around with root
* privs and don't want to unlink another user's coredump.
*/
if (!coredump_force_suid_safe(cprm)) {
/*
* If it doesn't exist, that's fine. If there's some
* other problem, we'll catch it at the filp_open().
*/
do_unlinkat(AT_FDCWD, getname_kernel(cn->corename));
}
/*
* There is a race between unlinking and creating the
* file, but if that causes an EEXIST here, that's
* fine - another process raced with us while creating
* the corefile, and the other process won. To userspace,
* what matters is that at least one of the two processes
* writes its coredump successfully, not which one.
*/
if (coredump_force_suid_safe(cprm)) {
/*
* Using user namespaces, normal user tasks can change
* their current->fs->root to point to arbitrary
* directories. Since the intention of the "only dump
* with a fully qualified path" rule is to control where
* coredumps may be placed using root privileges,
* current->fs->root must not be used. Instead, use the
* root directory of init_task.
*/
struct path root;
task_lock(&init_task);
get_fs_root(init_task.fs, &root);
task_unlock(&init_task);
file = file_open_root(&root, cn->corename, open_flags, 0600);
path_put(&root);
} else {
file = filp_open(cn->corename, open_flags, 0600);
}
if (IS_ERR(file))
return false;
inode = file_inode(file);
if (inode->i_nlink > 1)
return false;
if (d_unhashed(file->f_path.dentry))
return false;
/*
* AK: actually i see no reason to not allow this for named
* pipes etc, but keep the previous behaviour for now.
*/
if (!S_ISREG(inode->i_mode))
return false;
/*
* Don't dump core if the filesystem changed owner or mode
* of the file during file creation. This is an issue when
* a process dumps core while its cwd is e.g. on a vfat
* filesystem.
*/
idmap = file_mnt_idmap(file);
if (!vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), current_fsuid())) {
coredump_report_failure("Core dump to %s aborted: cannot preserve file owner", cn->corename);
return false;
}
if ((inode->i_mode & 0677) != 0600) {
coredump_report_failure("Core dump to %s aborted: cannot preserve file permissions", cn->corename);
return false;
}
if (!(file->f_mode & FMODE_CAN_WRITE))
return false;
if (do_truncate(idmap, file->f_path.dentry, 0, 0, file))
return false;
cprm->file = no_free_ptr(file);
return true;
}
static bool coredump_pipe(struct core_name *cn, struct coredump_params *cprm,
size_t *argv, int argc)
{
int argi;
char **helper_argv __free(kfree) = NULL;
struct subprocess_info *sub_info;
if (cprm->limit == 1) {
/* See umh_coredump_setup() which sets RLIMIT_CORE = 1.
*
* Normally core limits are irrelevant to pipes, since
* we're not writing to the file system, but we use
* cprm.limit of 1 here as a special value, this is a
* consistent way to catch recursive crashes.
* We can still crash if the core_pattern binary sets
* RLIM_CORE = !1, but it runs as root, and can do
* lots of stupid things.
*
* Note that we use task_tgid_vnr here to grab the pid
* of the process group leader. That way we get the
* right pid if a thread in a multi-threaded
* core_pattern process dies.
*/
coredump_report_failure("RLIMIT_CORE is set to 1, aborting core");
return false;
}
cprm->limit = RLIM_INFINITY;
cn->core_pipe_limit = atomic_inc_return(&core_pipe_count);
if (core_pipe_limit && (core_pipe_limit < cn->core_pipe_limit)) {
coredump_report_failure("over core_pipe_limit, skipping core dump");
return false;
}
helper_argv = kmalloc_array(argc + 1, sizeof(*helper_argv), GFP_KERNEL);
if (!helper_argv) {
coredump_report_failure("%s failed to allocate memory", __func__);
return false;
}
for (argi = 0; argi < argc; argi++)
helper_argv[argi] = cn->corename + argv[argi];
helper_argv[argi] = NULL;
sub_info = call_usermodehelper_setup(helper_argv[0], helper_argv, NULL,
GFP_KERNEL, umh_coredump_setup,
NULL, cprm);
if (!sub_info)
return false;
if (call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC)) {
coredump_report_failure("|%s pipe failed", cn->corename);
return false;
}
/*
* umh disabled with CONFIG_STATIC_USERMODEHELPER_PATH="" would
* have this set to NULL.
*/
if (!cprm->file) {
coredump_report_failure("Core dump to |%s disabled", cn->corename);
return false;
}
return true;
}
static bool coredump_write(struct core_name *cn,
struct coredump_params *cprm,
struct linux_binfmt *binfmt)
{
if (dump_interrupted())
return true;
if (!dump_vma_snapshot(cprm))
return false;
file_start_write(cprm->file);
cn->core_dumped = binfmt->core_dump(cprm);
/*
* Ensures that file size is big enough to contain the current
* file postion. This prevents gdb from complaining about
* a truncated file if the last "write" to the file was
* dump_skip.
*/
if (cprm->to_skip) {
cprm->to_skip--;
dump_emit(cprm, "", 1);
}
file_end_write(cprm->file);
free_vma_snapshot(cprm);
return true;
}
static void coredump_cleanup(struct core_name *cn, struct coredump_params *cprm)
{
if (cprm->file)
filp_close(cprm->file, NULL);
if (cn->core_pipe_limit) {
VFS_WARN_ON_ONCE(cn->core_type != COREDUMP_PIPE);
atomic_dec(&core_pipe_count);
}
kfree(cn->corename);
coredump_finish(cn->core_dumped);
}
static inline bool coredump_skip(const struct coredump_params *cprm,
const struct linux_binfmt *binfmt)
{
if (!binfmt)
return true;
if (!binfmt->core_dump)
return true;
if (!__get_dumpable(cprm->mm_flags))
return true;
return false;
}
void vfs_coredump(const kernel_siginfo_t *siginfo)
{
struct cred *cred __free(put_cred) = NULL;
size_t *argv __free(kfree) = NULL;
struct core_state core_state;
struct core_name cn;
struct mm_struct *mm = current->mm;
struct linux_binfmt * binfmt;
struct linux_binfmt *binfmt = mm->binfmt;
const struct cred *old_cred;
struct cred *cred;
int retval = 0;
size_t *argv = NULL;
int argc = 0;
/* require nonrelative corefile path and be extra careful */
bool need_suid_safe = false;
bool core_dumped = false;
static atomic_t core_dump_count = ATOMIC_INIT(0);
struct coredump_params cprm = {
.siginfo = siginfo,
.limit = rlimit(RLIMIT_CORE),
@ -883,201 +1116,44 @@ void do_coredump(const kernel_siginfo_t *siginfo)
audit_core_dumps(siginfo->si_signo);
binfmt = mm->binfmt;
if (!binfmt || !binfmt->core_dump)
goto fail;
if (!__get_dumpable(cprm.mm_flags))
goto fail;
if (coredump_skip(&cprm, binfmt))
return;
cred = prepare_creds();
if (!cred)
goto fail;
return;
/*
* We cannot trust fsuid as being the "true" uid of the process
* nor do we know its entire history. We only know it was tainted
* so we dump it as root in mode 2, and only into a controlled
* environment (pipe handler or fully qualified path).
*/
if (__get_dumpable(cprm.mm_flags) == SUID_DUMP_ROOT) {
/* Setuid core dump mode */
cred->fsuid = GLOBAL_ROOT_UID; /* Dump root private */
need_suid_safe = true;
}
if (coredump_force_suid_safe(&cprm))
cred->fsuid = GLOBAL_ROOT_UID;
retval = coredump_wait(siginfo->si_signo, &core_state);
if (retval < 0)
goto fail_creds;
if (coredump_wait(siginfo->si_signo, &core_state) < 0)
return;
old_cred = override_creds(cred);
retval = format_corename(&cn, &cprm, &argv, &argc);
if (retval < 0) {
if (!coredump_parse(&cn, &cprm, &argv, &argc)) {
coredump_report_failure("format_corename failed, aborting core");
goto fail_unlock;
goto close_fail;
}
switch (cn.core_type) {
case COREDUMP_FILE: {
struct mnt_idmap *idmap;
struct inode *inode;
int open_flags = O_CREAT | O_WRONLY | O_NOFOLLOW |
O_LARGEFILE | O_EXCL;
if (cprm.limit < binfmt->min_coredump)
goto fail_unlock;
if (need_suid_safe && cn.corename[0] != '/') {
coredump_report_failure(
"this process can only dump core to a fully qualified path, skipping core dump");
goto fail_unlock;
}
/*
* Unlink the file if it exists unless this is a SUID
* binary - in that case, we're running around with root
* privs and don't want to unlink another user's coredump.
*/
if (!need_suid_safe) {
/*
* If it doesn't exist, that's fine. If there's some
* other problem, we'll catch it at the filp_open().
*/
do_unlinkat(AT_FDCWD, getname_kernel(cn.corename));
}
/*
* There is a race between unlinking and creating the
* file, but if that causes an EEXIST here, that's
* fine - another process raced with us while creating
* the corefile, and the other process won. To userspace,
* what matters is that at least one of the two processes
* writes its coredump successfully, not which one.
*/
if (need_suid_safe) {
/*
* Using user namespaces, normal user tasks can change
* their current->fs->root to point to arbitrary
* directories. Since the intention of the "only dump
* with a fully qualified path" rule is to control where
* coredumps may be placed using root privileges,
* current->fs->root must not be used. Instead, use the
* root directory of init_task.
*/
struct path root;
task_lock(&init_task);
get_fs_root(init_task.fs, &root);
task_unlock(&init_task);
cprm.file = file_open_root(&root, cn.corename,
open_flags, 0600);
path_put(&root);
} else {
cprm.file = filp_open(cn.corename, open_flags, 0600);
}
if (IS_ERR(cprm.file))
goto fail_unlock;
inode = file_inode(cprm.file);
if (inode->i_nlink > 1)
goto close_fail;
if (d_unhashed(cprm.file->f_path.dentry))
goto close_fail;
/*
* AK: actually i see no reason to not allow this for named
* pipes etc, but keep the previous behaviour for now.
*/
if (!S_ISREG(inode->i_mode))
goto close_fail;
/*
* Don't dump core if the filesystem changed owner or mode
* of the file during file creation. This is an issue when
* a process dumps core while its cwd is e.g. on a vfat
* filesystem.
*/
idmap = file_mnt_idmap(cprm.file);
if (!vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode),
current_fsuid())) {
coredump_report_failure("Core dump to %s aborted: "
"cannot preserve file owner", cn.corename);
goto close_fail;
}
if ((inode->i_mode & 0677) != 0600) {
coredump_report_failure("Core dump to %s aborted: "
"cannot preserve file permissions", cn.corename);
goto close_fail;
}
if (!(cprm.file->f_mode & FMODE_CAN_WRITE))
goto close_fail;
if (do_truncate(idmap, cprm.file->f_path.dentry,
0, 0, cprm.file))
case COREDUMP_FILE:
if (!coredump_file(&cn, &cprm, binfmt))
goto close_fail;
break;
}
case COREDUMP_PIPE: {
int argi;
int dump_count;
char **helper_argv;
struct subprocess_info *sub_info;
if (cprm.limit == 1) {
/* See umh_coredump_setup() which sets RLIMIT_CORE = 1.
*
* Normally core limits are irrelevant to pipes, since
* we're not writing to the file system, but we use
* cprm.limit of 1 here as a special value, this is a
* consistent way to catch recursive crashes.
* We can still crash if the core_pattern binary sets
* RLIM_CORE = !1, but it runs as root, and can do
* lots of stupid things.
*
* Note that we use task_tgid_vnr here to grab the pid
* of the process group leader. That way we get the
* right pid if a thread in a multi-threaded
* core_pattern process dies.
*/
coredump_report_failure("RLIMIT_CORE is set to 1, aborting core");
goto fail_unlock;
}
cprm.limit = RLIM_INFINITY;
dump_count = atomic_inc_return(&core_dump_count);
if (core_pipe_limit && (core_pipe_limit < dump_count)) {
coredump_report_failure("over core_pipe_limit, skipping core dump");
goto fail_dropcount;
}
helper_argv = kmalloc_array(argc + 1, sizeof(*helper_argv),
GFP_KERNEL);
if (!helper_argv) {
coredump_report_failure("%s failed to allocate memory", __func__);
goto fail_dropcount;
}
for (argi = 0; argi < argc; argi++)
helper_argv[argi] = cn.corename + argv[argi];
helper_argv[argi] = NULL;
retval = -ENOMEM;
sub_info = call_usermodehelper_setup(helper_argv[0],
helper_argv, NULL, GFP_KERNEL,
umh_coredump_setup, NULL, &cprm);
if (sub_info)
retval = call_usermodehelper_exec(sub_info,
UMH_WAIT_EXEC);
kfree(helper_argv);
if (retval) {
coredump_report_failure("|%s pipe failed", cn.corename);
case COREDUMP_PIPE:
if (!coredump_pipe(&cn, &cprm, argv, argc))
goto close_fail;
}
break;
}
case COREDUMP_SOCK_REQ:
fallthrough;
case COREDUMP_SOCK:
if (!coredump_sock_connect(&cn, &cprm))
goto close_fail;
if (!coredump_sock_request(&cn, &cprm))
if (!coredump_socket(&cn, &cprm))
goto close_fail;
break;
default:
@ -1091,43 +1167,17 @@ void do_coredump(const kernel_siginfo_t *siginfo)
/* get us an unshared descriptor table; almost always a no-op */
/* The cell spufs coredump code reads the file descriptor tables */
retval = unshare_files();
if (retval)
if (unshare_files())
goto close_fail;
if ((cn.mask & COREDUMP_KERNEL) && !dump_interrupted()) {
/*
* umh disabled with CONFIG_STATIC_USERMODEHELPER_PATH="" would
* have this set to NULL.
*/
if (!cprm.file) {
coredump_report_failure("Core dump to |%s disabled", cn.corename);
goto close_fail;
}
if (!dump_vma_snapshot(&cprm))
goto close_fail;
file_start_write(cprm.file);
core_dumped = binfmt->core_dump(&cprm);
/*
* Ensures that file size is big enough to contain the current
* file postion. This prevents gdb from complaining about
* a truncated file if the last "write" to the file was
* dump_skip.
*/
if (cprm.to_skip) {
cprm.to_skip--;
dump_emit(&cprm, "", 1);
}
file_end_write(cprm.file);
free_vma_snapshot(&cprm);
}
if ((cn.mask & COREDUMP_KERNEL) && !coredump_write(&cn, &cprm, binfmt))
goto close_fail;
coredump_sock_shutdown(cprm.file);
/* Let the parent know that a coredump was generated. */
if (cn.mask & COREDUMP_USERSPACE)
core_dumped = true;
cn.core_dumped = true;
/*
* When core_pipe_limit is set we wait for the coredump server
@ -1150,19 +1200,8 @@ void do_coredump(const kernel_siginfo_t *siginfo)
}
close_fail:
if (cprm.file)
filp_close(cprm.file, NULL);
fail_dropcount:
if (cn.core_type == COREDUMP_PIPE)
atomic_dec(&core_dump_count);
fail_unlock:
kfree(argv);
kfree(cn.corename);
coredump_finish(core_dumped);
coredump_cleanup(&cn, &cprm);
revert_creds(old_cred);
fail_creds:
put_cred(cred);
fail:
return;
}
@ -1388,6 +1427,8 @@ void validate_coredump_safety(void)
static inline bool check_coredump_socket(void)
{
const char *p;
if (core_pattern[0] != '@')
return true;
@ -1399,8 +1440,25 @@ static inline bool check_coredump_socket(void)
if (current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns)
return false;
/* Must be an absolute path or the socket request. */
if (*(core_pattern + 1) != '/' && *(core_pattern + 1) != '@')
/* Must be an absolute path... */
if (core_pattern[1] != '/') {
/* ... or the socket request protocol... */
if (core_pattern[1] != '@')
return false;
/* ... and if so must be an absolute path. */
if (core_pattern[2] != '/')
return false;
p = &core_pattern[2];
} else {
p = &core_pattern[1];
}
/* The path obviously cannot exceed UNIX_PATH_MAX. */
if (strlen(p) >= UNIX_PATH_MAX)
return false;
/* Must not contain ".." in the path. */
if (name_contains_dotdot(core_pattern))
return false;
return true;

View File

@ -43,7 +43,7 @@ extern int dump_emit(struct coredump_params *cprm, const void *addr, int nr);
extern int dump_align(struct coredump_params *cprm, int align);
int dump_user_range(struct coredump_params *cprm, unsigned long start,
unsigned long len);
extern void do_coredump(const kernel_siginfo_t *siginfo);
extern void vfs_coredump(const kernel_siginfo_t *siginfo);
/*
* Logging for the coredump code, ratelimited.
@ -63,7 +63,7 @@ extern void do_coredump(const kernel_siginfo_t *siginfo);
#define coredump_report_failure(fmt, ...) __COREDUMP_PRINTK(KERN_WARNING, fmt, ##__VA_ARGS__)
#else
static inline void do_coredump(const kernel_siginfo_t *siginfo) {}
static inline void vfs_coredump(const kernel_siginfo_t *siginfo) {}
#define coredump_report(...)
#define coredump_report_failure(...)

View File

@ -263,6 +263,8 @@ static inline void put_cred(const struct cred *cred)
put_cred_many(cred, 1);
}
DEFINE_FREE(put_cred, struct cred *, if (!IS_ERR_OR_NULL(_T)) put_cred(_T))
/**
* current_cred - Access the current task's subjective credentials
*

View File

@ -3264,6 +3264,22 @@ static inline bool is_dot_dotdot(const char *name, size_t len)
(len == 1 || (len == 2 && name[1] == '.'));
}
/**
* name_contains_dotdot - check if a file name contains ".." path components
*
* Search for ".." surrounded by either '/' or start/end of string.
*/
static inline bool name_contains_dotdot(const char *name)
{
size_t name_len;
name_len = strlen(name);
return strcmp(name, "..") == 0 ||
strncmp(name, "../", 3) == 0 ||
strstr(name, "/../") != NULL ||
(name_len >= 3 && strcmp(name + name_len - 3, "/..") == 0);
}
#include <linux/err.h>
/* needed for stackable file system support */

View File

@ -3016,7 +3016,7 @@ bool get_signal(struct ksignal *ksig)
* first and our do_group_exit call below will use
* that value and ignore the one we pass it.
*/
do_coredump(&ksig->info);
vfs_coredump(&ksig->info);
}
/*

View File

@ -241,16 +241,19 @@ static int create_and_listen_unix_socket(const char *path)
static bool set_core_pattern(const char *pattern)
{
FILE *file;
int ret;
int fd;
ssize_t ret;
file = fopen("/proc/sys/kernel/core_pattern", "w");
if (!file)
fd = open("/proc/sys/kernel/core_pattern", O_WRONLY | O_CLOEXEC);
if (fd < 0)
return false;
ret = fprintf(file, "%s", pattern);
fclose(file);
ret = write(fd, pattern, strlen(pattern));
close(fd);
if (ret < 0)
return false;
fprintf(stderr, "Set core_pattern to '%s' | %zu == %zu\n", pattern, ret, strlen(pattern));
return ret == strlen(pattern);
}
@ -1804,4 +1807,21 @@ TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps_epoll_workers, 500)
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
TEST_F(coredump, socket_invalid_paths)
{
ASSERT_FALSE(set_core_pattern("@ /tmp/coredump.socket"));
ASSERT_FALSE(set_core_pattern("@/tmp/../coredump.socket"));
ASSERT_FALSE(set_core_pattern("@../coredump.socket"));
ASSERT_FALSE(set_core_pattern("@/tmp/coredump.socket/.."));
ASSERT_FALSE(set_core_pattern("@.."));
ASSERT_FALSE(set_core_pattern("@@ /tmp/coredump.socket"));
ASSERT_FALSE(set_core_pattern("@@/tmp/../coredump.socket"));
ASSERT_FALSE(set_core_pattern("@@../coredump.socket"));
ASSERT_FALSE(set_core_pattern("@@/tmp/coredump.socket/.."));
ASSERT_FALSE(set_core_pattern("@@.."));
ASSERT_FALSE(set_core_pattern("@@@/tmp/coredump.socket"));
}
TEST_HARNESS_MAIN