On Wed, Jul 01, 2020 at 10:55:37AM +0200, Christian Brauner wrote: > On Wed, Jul 01, 2020 at 08:49:06AM +0200, Adrian Reber wrote: > > From: Nicolas Viennot <Nicolas.Viennot@xxxxxxxxxxxx> > > > > Previously, the current process could only change the /proc/self/exe > > link with local CAP_SYS_ADMIN. > > This commit relaxes this restriction by permitting such change with > > CAP_CHECKPOINT_RESTORE, and the ability to use ptrace. > > > > With access to ptrace facilities, a process can do the following: fork a > > child, execve() the target executable, and have the child use ptrace() > > to replace the memory content of the current process. This technique > > makes it possible to masquerade an arbitrary program as any executable, > > even setuid ones. > > > > Signed-off-by: Nicolas Viennot <Nicolas.Viennot@xxxxxxxxxxxx> > > Signed-off-by: Adrian Reber <areber@xxxxxxxxxx> > > --- > > include/linux/lsm_hook_defs.h | 1 + > > include/linux/security.h | 6 ++++++ > > kernel/sys.c | 12 ++++-------- > > security/commoncap.c | 26 ++++++++++++++++++++++++++ > > security/security.c | 5 +++++ > > security/selinux/hooks.c | 14 ++++++++++++++ > > 6 files changed, 56 insertions(+), 8 deletions(-) > > > > diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h > > index 0098852bb56a..90e51d5e093b 100644 > > --- a/include/linux/lsm_hook_defs.h > > +++ b/include/linux/lsm_hook_defs.h > > @@ -211,6 +211,7 @@ LSM_HOOK(int, 0, task_kill, struct task_struct *p, struct kernel_siginfo *info, > > int sig, const struct cred *cred) > > LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2, > > unsigned long arg3, unsigned long arg4, unsigned long arg5) > > +LSM_HOOK(int, 0, prctl_set_mm_exe_file, struct file *exe_file) > > LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p, > > struct inode *inode) > > LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag) > > diff --git a/include/linux/security.h b/include/linux/security.h > > index 2797e7f6418e..0f594eb7e766 100644 > > --- a/include/linux/security.h > > +++ b/include/linux/security.h > > @@ -412,6 +412,7 @@ int security_task_kill(struct task_struct *p, struct kernel_siginfo *info, > > int sig, const struct cred *cred); > > int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, > > unsigned long arg4, unsigned long arg5); > > +int security_prctl_set_mm_exe_file(struct file *exe_file); > > void security_task_to_inode(struct task_struct *p, struct inode *inode); > > int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag); > > void security_ipc_getsecid(struct kern_ipc_perm *ipcp, u32 *secid); > > @@ -1124,6 +1125,11 @@ static inline int security_task_prctl(int option, unsigned long arg2, > > return cap_task_prctl(option, arg2, arg3, arg4, arg5); > > } > > > > +static inline int security_prctl_set_mm_exe_file(struct file *exe_file) > > +{ > > + return cap_prctl_set_mm_exe_file(exe_file); > > +} > > + > > static inline void security_task_to_inode(struct task_struct *p, struct inode *inode) > > { } > > > > diff --git a/kernel/sys.c b/kernel/sys.c > > index 00a96746e28a..bb53e8408c63 100644 > > --- a/kernel/sys.c > > +++ b/kernel/sys.c > > @@ -1851,6 +1851,10 @@ static int prctl_set_mm_exe_file(struct mm_struct *mm, unsigned int fd) > > if (err) > > goto exit; > > > > + err = security_prctl_set_mm_exe_file(exe.file); > > + if (err) > > + goto exit; > > + > > /* > > * Forbid mm->exe_file change if old file still mapped. > > */ > > @@ -2006,14 +2010,6 @@ static int prctl_set_mm_map(int opt, const void __user *addr, unsigned long data > > } > > > > if (prctl_map.exe_fd != (u32)-1) { > > - /* > > - * Make sure the caller has the rights to > > - * change /proc/pid/exe link: only local sys admin should > > - * be allowed to. > > - */ > > - if (!ns_capable(current_user_ns(), CAP_SYS_ADMIN)) > > - return -EINVAL; > > - > > error = prctl_set_mm_exe_file(mm, prctl_map.exe_fd); > > if (error) > > return error; > > diff --git a/security/commoncap.c b/security/commoncap.c > > index 59bf3c1674c8..663d00fe2ecc 100644 > > --- a/security/commoncap.c > > +++ b/security/commoncap.c > > @@ -1291,6 +1291,31 @@ int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3, > > } > > } > > > > +/** > > + * cap_prctl_set_mm_exe_file - Determine whether /proc/self/exe can be changed > > + * by the current process. > > + * @exe_file: The new exe file > > + * Returns 0 if permission is granted, -ve if denied. > > + * > > + * The current process is permitted to change its /proc/self/exe link via two policies: > > + * 1) The current user can do checkpoint/restore. At the time of this writing, > > + * this means CAP_SYS_ADMIN or CAP_CHECKPOINT_RESTORE capable. > > + * 2) The current user can use ptrace. > > + * > > + * With access to ptrace facilities, a process can do the following: > > + * fork a child, execve() the target executable, and have the child use > > + * ptrace() to replace the memory content of the current process. > > + * This technique makes it possible to masquerade an arbitrary program as the > > + * target executable, even if it is setuid. > > + */ > > +int cap_prctl_set_mm_exe_file(struct file *exe_file) > > +{ > > + if (checkpoint_restore_ns_capable(current_user_ns())) > > + return 0; > > + > > + return security_ptrace_access_check(current, PTRACE_MODE_ATTACH_REALCREDS); > > +} > > What is the reason for having this be a new security hook? Doesn't look > like it needs to be unless I'm missing something. This just seems more > complex than it needs to be. Yeah, agreed, and that in turn risks actually making it less safe. Is there a good reason not to do what Christian suggests? > I might be wrong here but if you look at the callsites for > security_ptrace_access_check() right now, you'll see that it's only > called from kernel/ptrace.c in __ptrace_may_access() and that function > checks right at the top: > > /* Don't let security modules deny introspection */ > if (same_thread_group(task, current)) > return 0; > > since you're passing in same_thread_group(current, current) you're > passing this check and never even hitting > security_ptrace_access_check(). So the contract seems to be (as is > obvious from the comment) that a task can't be denied ptrace > introspection. But if you're using security_ptrace_access_check(current) > here and _if_ there would be any lsm that would deny ptrace > introspection to current you'd suddenly introduce a callsite where > ptrace introspection is denied. That seems wrong. So either you meant to > do something else here or you really just want: > > checkpoint_restore_ns_capable(current_user_ns()) > > and none of the rest. But I might be missing the big picture in this > patch. > > > + if (checkpoint_restore_ns_capable(current_user_ns())) > > + return 0; > > + > > /** > > * cap_vm_enough_memory - Determine whether a new virtual mapping is permitted > > * @mm: The VM space in which the new mapping is to be made > > @@ -1356,6 +1381,7 @@ static struct security_hook_list capability_hooks[] __lsm_ro_after_init = { > > LSM_HOOK_INIT(mmap_file, cap_mmap_file), > > LSM_HOOK_INIT(task_fix_setuid, cap_task_fix_setuid), > > LSM_HOOK_INIT(task_prctl, cap_task_prctl), > > + LSM_HOOK_INIT(prctl_set_mm_exe_file, cap_prctl_set_mm_exe_file), > > LSM_HOOK_INIT(task_setscheduler, cap_task_setscheduler), > > LSM_HOOK_INIT(task_setioprio, cap_task_setioprio), > > LSM_HOOK_INIT(task_setnice, cap_task_setnice), > > diff --git a/security/security.c b/security/security.c > > index 2bb912496232..13a1ed32f9e3 100644 > > --- a/security/security.c > > +++ b/security/security.c > > @@ -1790,6 +1790,11 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, > > return rc; > > } > > > > +int security_prctl_set_mm_exe_file(struct file *exe_file) > > +{ > > + return call_int_hook(prctl_set_mm_exe_file, 0, exe_file); > > +} > > + > > void security_task_to_inode(struct task_struct *p, struct inode *inode) > > { > > call_void_hook(task_to_inode, p, inode); > > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c > > index ca901025802a..fca5581392b8 100644 > > --- a/security/selinux/hooks.c > > +++ b/security/selinux/hooks.c > > @@ -4156,6 +4156,19 @@ static int selinux_task_kill(struct task_struct *p, struct kernel_siginfo *info, > > secid, task_sid(p), SECCLASS_PROCESS, perm, NULL); > > } > > > > +static int selinux_prctl_set_mm_exe_file(struct file *exe_file) > > +{ > > + u32 sid = current_sid(); > > + > > + struct common_audit_data ad = { > > + .type = LSM_AUDIT_DATA_FILE, > > + .u.file = exe_file, > > + }; > > + > > + return avc_has_perm(&selinux_state, sid, sid, > > + SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad); > > +} > > + > > static void selinux_task_to_inode(struct task_struct *p, > > struct inode *inode) > > { > > @@ -7057,6 +7070,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { > > LSM_HOOK_INIT(task_getscheduler, selinux_task_getscheduler), > > LSM_HOOK_INIT(task_movememory, selinux_task_movememory), > > LSM_HOOK_INIT(task_kill, selinux_task_kill), > > + LSM_HOOK_INIT(prctl_set_mm_exe_file, selinux_prctl_set_mm_exe_file), > > LSM_HOOK_INIT(task_to_inode, selinux_task_to_inode), > > > > LSM_HOOK_INIT(ipc_permission, selinux_ipc_permission), > > -- > > 2.26.2 > >