[PATCH v3.1 2/3] Limit dump_pipe program's permission to init for container

From: Cao Shufeng
Date: Tue Oct 18 2016 - 06:06:56 EST


From: Zhao Lei <zhaolei@xxxxxxxxxxxxxx>

Currently when we set core_pattern to a pipe, the pipe program is
forked by kthread running with root's permission, and write dumpfile
into host's filesystem.
Same thing happened for container, the dumper and dumpfile are also
in host(not in container).

It have following program:
1: Not consistent with file_type core_pattern
When we set core_pattern to a file, the container will write dump
into container's filesystem instead of host.
2: Not safe for privileged container
In a privileged container, user can destroy host system by following
command:
# # In a container
# echo "|/bin/dd of=/boot/vmlinuz" >/proc/sys/kernel/core_pattern
# make_dump

This patch switch dumper program's environment to init task, so, for
container, dumper program have same environment with init task in
container, which make dumper program put in container's filesystem, and
write coredump into container's filesystem.
The dumper's permission is also limited into subset of container's init
process.

Suggested-by: Eric W. Biederman <ebiederm@xxxxxxxxxxxx>
Suggested-by: KOSAKI Motohiro <kosaki.motohiro@xxxxxxxxxxxxxx>

Signed-off-by: Zhao Lei <zhaolei@xxxxxxxxxxxxxx>
Co-Authored-By: Cao ShuFeng<caosf.fnst@xxxxxxxxxxxxxx>
---
fs/coredump.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++-
include/linux/binfmts.h | 1 +
2 files changed, 102 insertions(+), 2 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index ceb0ee8..8ea8bc1 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -501,6 +501,23 @@ static void wait_for_dump_helpers(struct file *file)
pipe_unlock(pipe);
}

+static int umh_ns_setup(struct subprocess_info *info)
+{
+ struct coredump_params *cp = (struct coredump_params *)info->data;
+ struct task_struct *base_task = cp->base_task;
+
+ if (base_task) {
+ /* Set namespaces to base_task */
+ get_nsproxy(base_task->nsproxy);
+ switch_task_namespaces(current, base_task->nsproxy);
+
+ /* Return -EAGAIN to notice caller to refork */
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
/*
* umh_pipe_setup
* helper function to customize the process used
@@ -516,6 +533,8 @@ static int umh_pipe_setup(struct subprocess_info *info, struct cred *new)
{
struct file *files[2];
struct coredump_params *cp = (struct coredump_params *)info->data;
+ struct task_struct *base_task;
+
int err = create_pipe_files(files, 0);
if (err)
return err;
@@ -524,10 +543,76 @@ static int umh_pipe_setup(struct subprocess_info *info, struct cred *new)

err = replace_fd(0, files[0], 0);
fput(files[0]);
+ if (err)
+ return err;
+
/* and disallow core files too */
current->signal->rlim[RLIMIT_CORE] = (struct rlimit){1, 1};

- return err;
+ base_task = cp->base_task;
+ if (base_task) {
+ const struct cred *base_cred;
+
+ /* Set fs_root to base_task */
+ spin_lock(&base_task->fs->lock);
+ set_fs_root(current->fs, &base_task->fs->root);
+ set_fs_pwd(current->fs, &base_task->fs->pwd);
+ spin_unlock(&base_task->fs->lock);
+
+ /* Set cgroup to base_task */
+ current->flags &= ~PF_NO_SETAFFINITY;
+ err = cgroup_attach_task_all(base_task, current);
+ if (err < 0)
+ return err;
+
+ /* Set cred to base_task */
+ base_cred = get_task_cred(base_task);
+
+ new->uid = base_cred->uid;
+ new->gid = base_cred->gid;
+ new->suid = base_cred->suid;
+ new->sgid = base_cred->sgid;
+ new->euid = base_cred->euid;
+ new->egid = base_cred->egid;
+ new->fsuid = base_cred->fsuid;
+ new->fsgid = base_cred->fsgid;
+
+ new->securebits = base_cred->securebits;
+
+ new->cap_inheritable = base_cred->cap_inheritable;
+ new->cap_permitted = base_cred->cap_permitted;
+ new->cap_effective = base_cred->cap_effective;
+ new->cap_bset = base_cred->cap_bset;
+ new->cap_ambient = base_cred->cap_ambient;
+
+ security_cred_free(new);
+#ifdef CONFIG_SECURITY
+ new->security = NULL;
+#endif
+ err = security_prepare_creds(new, base_cred, GFP_KERNEL);
+ if (err < 0) {
+ put_cred(base_cred);
+ return err;
+ }
+
+ free_uid(new->user);
+ new->user = base_cred->user;
+ get_uid(new->user);
+
+ put_user_ns(new->user_ns);
+ new->user_ns = base_cred->user_ns;
+ get_user_ns(new->user_ns);
+
+ put_group_info(new->group_info);
+ new->group_info = base_cred->group_info;
+ get_group_info(new->group_info);
+
+ put_cred(base_cred);
+
+ validate_creds(new);
+ }
+
+ return 0;
}

void do_coredump(const siginfo_t *siginfo)
@@ -590,6 +675,7 @@ void do_coredump(const siginfo_t *siginfo)

if (ispipe) {
int dump_count;
+ struct task_struct *vinit_task;
char **helper_argv;
struct subprocess_info *sub_info;

@@ -631,6 +717,14 @@ void do_coredump(const siginfo_t *siginfo)
goto fail_dropcount;
}

+ rcu_read_lock();
+ vinit_task = find_task_by_vpid(1);
+ rcu_read_unlock();
+ if (!vinit_task) {
+ printk(KERN_WARNING "failed getting init task info, skipping core dump\n");
+ goto fail_dropcount;
+ }
+
helper_argv = argv_split(GFP_KERNEL, cn.corename, NULL);
if (!helper_argv) {
printk(KERN_WARNING "%s failed to allocate memory\n",
@@ -638,15 +732,20 @@ void do_coredump(const siginfo_t *siginfo)
goto fail_dropcount;
}

+ get_task_struct(vinit_task);
+
+ cprm.base_task = vinit_task;
+
retval = -ENOMEM;
sub_info = call_usermodehelper_setup(helper_argv[0],
helper_argv, NULL, GFP_KERNEL,
- NULL, umh_pipe_setup,
+ umh_ns_setup, umh_pipe_setup,
NULL, &cprm);
if (sub_info)
retval = call_usermodehelper_exec(sub_info,
UMH_WAIT_EXEC);

+ put_task_struct(vinit_task);
argv_free(helper_argv);
if (retval) {
printk(KERN_INFO "Core dump to |%s pipe failed\n",
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 1303b57..193d269 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -59,6 +59,7 @@ struct linux_binprm {

/* Function parameter for binfmt->coredump */
struct coredump_params {
+ struct task_struct *base_task;
const siginfo_t *siginfo;
struct pt_regs *regs;
struct file *file;
--
2.7.4