prctl: add PR_[GS]ET_PDEATHSIG_PROC
Subsystems (8)
PATCH 1/1 prctl: add PR_[GS]ET_PDEATHSIG_PROC
PR_SET_PDEATHSIG sets a parent death signal that the calling process will get when its parent thread dies, even when the result of getppid() doesn't change because the calling process is reparented to a different thread in the same parent process. When managing multiple processes, a process-based parent death signal is much more useful. E.g., to avoid stray child processes.
PR_SET_PDEATHSIG_PROC sets a process-based death signal. Unlike PR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.
This can be used for sandboxing when combined with a seccomp filter.
There have been previous attempts to support this by changing the behavior of PR_SET_PDEATHSIG. However, that would break existing applications. See https://marc.info/?l=linux-kernel&m=117621804801689
and https://bugzilla.kernel.org/show_bug.cgi?id=43300
--- fs/exec.c | 1 + include/linux/sched/signal.h | 3 +++ include/uapi/linux/prctl.h | 4 ++++ kernel/cred.c | 1 + kernel/exit.c | 4 ++++ kernel/fork.c | 2 ++ kernel/sys.c | 11 +++++++++++ security/apparmor/lsm.c | 1 + security/selinux/hooks.c | 1 + 9 files changed, 28 insertions(+)
fs/exec.c +1 -0
@@ -1353,6 +1353,7 @@ void setup_new_exec(struct linux_binprm * bprm)if (bprm->secureexec) {/* Make sure parent cannot signal privileged process. */ current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* * For secureexec, reset the stack limit to sane default to
include/linux/sched/signal.h +3 -0
@@ -103,6 +103,9 @@ struct signal_struct {int group_stop_count;unsigned int flags;/* see SIGNAL_* flags below */ +/* The signal sent when the parent dies: */ +int pdeath_signal_proc; +/* * PR_SET_CHILD_SUBREAPER marks a process, like a service * manager, to re-parent orphan (double-forking) child processes
include/uapi/linux/prctl.h +4 -0
@@ -197,4 +197,8 @@ struct prctl_mm_map {# define PR_CAP_AMBIENT_LOWER 3# define PR_CAP_AMBIENT_CLEAR_ALL 4 +/* Process-based variant of PDEATHSIG */ +#define PR_SET_PDEATHSIG_PROC 48 +#define PR_GET_PDEATHSIG_PROC 49 +#endif /* _LINUX_PRCTL_H */
kernel/cred.c +1 -0
@@ -448,6 +448,7 @@ int commit_creds(struct cred *new)if (task->mm)set_dumpable (task->mm, suid_dumpable); task->pdeath_signal =0 ; + task->signal->pdeath_signal_proc =0 ;smp_wmb (); }
kernel/exit.c +4 -0
@@ -635,6 +635,10 @@ static void reparent_leader(struct task_struct *father, struct task_struct *p,if (unlikely (p->exit_state ==EXIT_DEAD ))return ; +if (p->signal->pdeath_signal_proc) +group_send_sig_info (p->signal->pdeath_signal_proc, +SEND_SIG_NOINFO , p); +/* We don't want people slaying init. */ p->exit_signal =SIGCHLD ;
kernel/fork.c +2 -0
@@ -1412,6 +1412,8 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)mutex_init (&sig->cred_guard_mutex); + sig->pdeath_signal_proc = current->signal->pdeath_signal_proc; +return 0 ; }
kernel/sys.c +11 -0
@@ -2210,6 +2210,17 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,case PR_GET_PDEATHSIG : error =put_user (me->pdeath_signal, (int __user *)arg2);break ; +case PR_SET_PDEATHSIG_PROC : +if (!valid_signal (arg2)) { + error = -EINVAL ; +break ; + } + me->signal->pdeath_signal_proc = arg2; +break ; +case PR_GET_PDEATHSIG_PROC : + error =put_user (me->signal->pdeath_signal_proc, + (int __user *)arg2); +break ;case PR_GET_DUMPABLE : error =get_dumpable (me->mm);break ;
security/apparmor/lsm.c +1 -0
@@ -628,6 +628,7 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm)aa_inherit_files (bprm->cred, current->files); current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* reset soft limits and set hard limits for the new label */ __aa_transition_rlimits (label, new_ctx->label);
security/selinux/hooks.c +1 -0
@@ -2527,6 +2527,7 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm)/* Always clear parent death signal on SID transitions. */ current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* Check whether the new SID can inherit resource limits from the old * SID. If not, reset all soft limits to the lower of the current
Signature · 1 line
2.14.1
Quoted 2 lines · Jürg Billeter
PR_SET_PDEATHSIG_PROCsets a process-based death signal.
I think the patch is technically correct,
Quoted 3 lines
Unlike
PR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.
but I am still not sure this is right... at least I can't understand the "without race conditions" above.
IOW, the child can do prctl(PR_SET_PDEATHSIG_PROC, SIGKILL) right after fork(), why this is not enough to kill a whole subtree without race conditions?
OTOH. If you want to kill a whole sub-tree then perhaps the exiting process should simply send the ->pdeath_signal_proc to the whole sub-tree? Not that I really think this makes more sense, but if we add the new API we should discuss everything we can.
Say, CLONE_PARENT. Should it succeed if ->pdeath_signal_proc != 0 ?
Anyway, I think this patch needs more reviewers. Let me add Linus and Michael. Again, I am not worried about correctness, the patch is simple, but the new API always needs a thorough discussion.
Oleg.
Quoted 148 lines
This can be used for sandboxing when combined with a seccomp filter.
There have been previous attempts to support this by changing the behavior of
PR_SET_PDEATHSIG. However, that would break existing applications. See https://marc.info/?l=linux-kernel&m=117621804801689
and https://bugzilla.kernel.org/show_bug.cgi?id=43300Signed-off-by Jürg Billeter <[email protected]>--- fs/exec.c | 1 + include/linux/sched/signal.h | 3 +++ include/uapi/linux/prctl.h | 4 ++++ kernel/cred.c | 1 + kernel/exit.c | 4 ++++ kernel/fork.c | 2 ++ kernel/sys.c | 11 +++++++++++ security/apparmor/lsm.c | 1 + security/selinux/hooks.c | 1 + 9 files changed, 28 insertions(+)fs/exec.c +1
-0@@ -1353,6 +1353,7 @@ void setup_new_exec(struct linux_binprm * bprm)if (bprm->secureexec) {/* Make sure parent cannot signal privileged process. */ current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* * For secureexec, reset the stack limit to sane default to include/linux/sched/signal.h +3
-0@@ -103,6 +103,9 @@ struct signal_struct {int group_stop_count;unsigned int flags;/* see SIGNAL_* flags below */ +/* The signal sent when the parent dies: */ +int pdeath_signal_proc; +/* * PR_SET_CHILD_SUBREAPER marks a process, like a service * manager, to re-parent orphan (double-forking) child processes include/uapi/linux/prctl.h +4
-0@@ -197,4 +197,8 @@ struct prctl_mm_map {# define PR_CAP_AMBIENT_LOWER 3# define PR_CAP_AMBIENT_CLEAR_ALL 4 +/* Process-based variant of PDEATHSIG */ +#define PR_SET_PDEATHSIG_PROC 48 +#define PR_GET_PDEATHSIG_PROC 49 +#endif /* _LINUX_PRCTL_H */ kernel/cred.c +1
-0@@ -448,6 +448,7 @@ int commit_creds(struct cred *new)if (task->mm)set_dumpable (task->mm, suid_dumpable); task->pdeath_signal =0 ; + task->signal->pdeath_signal_proc =0 ;smp_wmb (); }kernel/exit.c +4
-0@@ -635,6 +635,10 @@ static void reparent_leader(struct task_struct *father, struct task_struct *p,if (unlikely (p->exit_state ==EXIT_DEAD ))return ; +if (p->signal->pdeath_signal_proc) +group_send_sig_info (p->signal->pdeath_signal_proc, +SEND_SIG_NOINFO , p); +/* We don't want people slaying init. */ p->exit_signal =SIGCHLD ;kernel/fork.c +2
-0@@ -1412,6 +1412,8 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)mutex_init (&sig->cred_guard_mutex); + sig->pdeath_signal_proc = current->signal->pdeath_signal_proc; +return 0 ; }kernel/sys.c +11
-0@@ -2210,6 +2210,17 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,case PR_GET_PDEATHSIG : error =put_user (me->pdeath_signal, (int __user *)arg2);break ; +case PR_SET_PDEATHSIG_PROC : +if (!valid_signal (arg2)) { + error = -EINVAL ; +break ; + } + me->signal->pdeath_signal_proc = arg2; +break ; +case PR_GET_PDEATHSIG_PROC : + error =put_user (me->signal->pdeath_signal_proc, + (int __user *)arg2); +break ;case PR_GET_DUMPABLE : error =get_dumpable (me->mm);break ;security/apparmor/lsm.c +1
-0@@ -628,6 +628,7 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm)aa_inherit_files (bprm->cred, current->files); current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* reset soft limits and set hard limits for the new label */ __aa_transition_rlimits (label, new_ctx->label);security/selinux/hooks.c +1
-0@@ -2527,6 +2527,7 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm)/* Always clear parent death signal on SID transitions. */ current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* Check whether the new SID can inherit resource limits from the old * SID. If not, reset all soft limits to the lower of the current Signature · 1 line
2.14.1
Hi Oleg,
Thanks for the review.
Quoted 10 lines · Oleg Nesterov
Quoted 3 lines · Jürg Billeter
Unlike
PR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.but I am still not sure this is right... at least I can't understand the "without race conditions" above.
IOW, the child can do
prctl(PR_SET_PDEATHSIG_PROC, SIGKILL)right afterfork(), why this is not enough to kill a whole subtree without race conditions?
What if the parent dies between fork() and prctl()? Besides avoiding this race condition, it also makes it relatively easy to enforce PDEATHSIG_PROC for all descendants of a process. You simply set PDEATHSIG_PROC and then block further changes using seccomp (and set no_new_privs) to avoid runaway children.
Quoted 4 lines
OTOH. If you want to kill a whole sub-tree then perhaps the exiting process should simply send the ->pdeath_signal_proc to the whole sub-tree? Not that I really think this makes more sense, but if we add the new API we should discuss everything we can.
While this would likely work for my use case of avoiding runaway processes, I don't think it would make sense for non-SIGKILL use cases of cooperating processes. Inheritance across fork still allows resetting PDEATHSIG_PROC in the child after fork and I don't expect the parent death race to be a significant issue in the case of cooperating processes.
Quoted 1 line
Say,
CLONE_PARENT. Should it succeed if ->pdeath_signal_proc != 0 ?
Yes, I don't see an issue with that. The new process will be a sibling and inheriting pdeath_signal_proc seems sensible to me for this.
Jürg
Quoted 7 lines · Jürg Billeter
Quoted 5 lines · Oleg Nesterov
Quoted 3 lines · Jürg Billeter
Unlike
PR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.
Quoted 7 lines
Quoted 5 lines
but I am still not sure this is right... at least I can't understand the "without race conditions" above.
IOW, the child can do
prctl(PR_SET_PDEATHSIG_PROC, SIGKILL)right afterfork(), why this is not enough to kill a whole subtree without race conditions?What if the parent dies between
fork()andprctl()?
The child will be killed? Sorry, can't understand...
Quoted 2 lines
it also makes it relatively easy to enforce
PDEATHSIG_PROCfor all descendants of a process.
this is clear,
Quoted 4 lines
Quoted 1 line
Say,
CLONE_PARENT. Should it succeed if ->pdeath_signal_proc != 0 ?Yes, I don't see an issue with that. The new process will be a sibling and inheriting
pdeath_signal_procseems sensible to me for this.
I meant, the process created by clone(CLONE_PARENT) won't be killed by pdeath_signal if the creator process exits, exactly because it won't be its child. Not that I think this is wrong.
Oleg.
Quoted 11 lines · Oleg Nesterov
Quoted 10 lines · Jürg Billeter
Quoted 8 lines · Oleg Nesterov
Quoted 3 lines · Jürg Billeter
Unlike
PR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.but I am still not sure this is right... at least I can't understand the "without race conditions" above.
Quoted 6 lines
Quoted 4 lines
Quoted 2 lines
IOW, the child can do
prctl(PR_SET_PDEATHSIG_PROC, SIGKILL)right afterfork(), why this is not enough to kill a whole subtree without race conditions?What if the parent dies between
fork()andprctl()?The child will be killed? Sorry, can't understand...
If PR_SET_PDEATHSIG_PROC was not inherited across fork and the parent died between fork() and prctl(PR_SET_PDEATHSIG_PROC, SIGKILL) in the child, the child would not be killed. It would be reparented to init(1) or a subreaper, i.e., you end up with a runaway process. It would be possible to safe guard against this race condition in other ways but inheriting the setting avoids it nicely, and makes it easy to apply/enforce PDEATHSIG_PROC for all descendants.
Quoted 8 lines
Quoted 4 lines
Quoted 1 line
Say,
CLONE_PARENT. Should it succeed if ->pdeath_signal_proc != 0 ?Yes, I don't see an issue with that. The new process will be a sibling and inheriting
pdeath_signal_procseems sensible to me for this.I meant, the process created by
clone(CLONE_PARENT)won't be killed bypdeath_signalif the creator process exits, exactly because it won't be its child. Not that I think this is wrong.
Right, creator and parent won't be the same.
Jürg
Quoted 16 lines · Jürg Billeter
Quoted 14 lines · Oleg Nesterov
Quoted 13 lines · Jürg Billeter
Quoted 10 lines · Oleg Nesterov
Quoted 3 lines · Jürg Billeter
Unlike
PR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.but I am still not sure this is right... at least I can't understand the "without race conditions" above.
IOW, the child can do
prctl(PR_SET_PDEATHSIG_PROC, SIGKILL)right afterfork(), why this is not enough to kill a whole subtree without race conditions?
Quoted 7 lines
Quoted 3 lines
Quoted 1 line
What if the parent dies between
fork()andprctl()?The child will be killed? Sorry, can't understand...
If
PR_SET_PDEATHSIG_PROCwas not inherited across fork and the parent died betweenfork()andprctl(PR_SET_PDEATHSIG_PROC, SIGKILL)in the child, the child would not be killed.
Aah, sorry. I forgot about another oddity of pdeath_signal API...
Somehow I misread this patch as if reparent_leader() looks at current->signal->pdeath_signal_proc, not child->signal->pdeath_signal_proc. And to me the former makes more sense. But I won't insist.
Oleg.
PATCH RESEND 1/1 prctl: add PR_[GS]ET_PDEATHSIG_PROC
In reply to Jürg BilleterPR_SET_PDEATHSIG sets a parent death signal that the calling process will get when its parent thread dies, even when the result of getppid() doesn't change because the calling process is reparented to a different thread in the same parent process. When managing multiple processes, a process-based parent death signal is much more useful. E.g., to avoid stray child processes.
PR_SET_PDEATHSIG_PROC sets a process-based death signal. Unlike PR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.
This can be used for sandboxing when combined with a seccomp filter.
There have been previous attempts to support this by changing the behavior of PR_SET_PDEATHSIG. However, that would break existing applications. See https://marc.info/?l=linux-kernel&m=117621804801689
and https://bugzilla.kernel.org/show_bug.cgi?id=43300
---
Previous discussion: https://patchwork.kernel.org/patch/9945315/
fs/exec.c | 1 + include/linux/sched/signal.h | 3 +++ include/uapi/linux/prctl.h | 4 ++++ kernel/cred.c | 1 + kernel/exit.c | 4 ++++ kernel/fork.c | 2 ++ kernel/sys.c | 11 +++++++++++ security/apparmor/lsm.c | 1 + security/selinux/hooks.c | 1 + 9 files changed, 28 insertions(+)
fs/exec.c +1 -0
@@ -1334,6 +1334,7 @@ void setup_new_exec(struct linux_binprm * bprm)if (bprm->secureexec) {/* Make sure parent cannot signal privileged process. */ current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* * For secureexec, reset the stack limit to sane default to
include/linux/sched/signal.h +3 -0
@@ -103,6 +103,9 @@ struct signal_struct {int group_stop_count;unsigned int flags;/* see SIGNAL_* flags below */ +/* The signal sent when the parent dies: */ +int pdeath_signal_proc; +/* * PR_SET_CHILD_SUBREAPER marks a process, like a service * manager, to re-parent orphan (double-forking) child processes
include/uapi/linux/prctl.h +4 -0
@@ -197,4 +197,8 @@ struct prctl_mm_map {# define PR_CAP_AMBIENT_LOWER 3# define PR_CAP_AMBIENT_CLEAR_ALL 4 +/* Process-based variant of PDEATHSIG */ +#define PR_SET_PDEATHSIG_PROC 48 +#define PR_GET_PDEATHSIG_PROC 49 +#endif /* _LINUX_PRCTL_H */
kernel/cred.c +1 -0
@@ -448,6 +448,7 @@ int commit_creds(struct cred *new)if (task->mm)set_dumpable (task->mm, suid_dumpable); task->pdeath_signal =0 ; + task->signal->pdeath_signal_proc =0 ;smp_wmb (); }
kernel/exit.c +4 -0
@@ -635,6 +635,10 @@ static void reparent_leader(struct task_struct *father, struct task_struct *p,if (unlikely (p->exit_state ==EXIT_DEAD ))return ; +if (p->signal->pdeath_signal_proc) +group_send_sig_info (p->signal->pdeath_signal_proc, +SEND_SIG_NOINFO , p); +/* We don't want people slaying init. */ p->exit_signal =SIGCHLD ;
kernel/fork.c +2 -0
@@ -1415,6 +1415,8 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)mutex_init (&sig->cred_guard_mutex); + sig->pdeath_signal_proc = current->signal->pdeath_signal_proc; +return 0 ; }
kernel/sys.c +11 -0
@@ -2206,6 +2206,17 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,case PR_GET_PDEATHSIG : error =put_user (me->pdeath_signal, (int __user *)arg2);break ; +case PR_SET_PDEATHSIG_PROC : +if (!valid_signal (arg2)) { + error = -EINVAL ; +break ; + } + me->signal->pdeath_signal_proc = arg2; +break ; +case PR_GET_PDEATHSIG_PROC : + error =put_user (me->signal->pdeath_signal_proc, + (int __user *)arg2); +break ;case PR_GET_DUMPABLE : error =get_dumpable (me->mm);break ;
security/apparmor/lsm.c +1 -0
@@ -689,6 +689,7 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm)aa_inherit_files (bprm->cred, current->files); current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* reset soft limits and set hard limits for the new label */ __aa_transition_rlimits (label, new_ctx->label);
security/selinux/hooks.c +1 -0
@@ -2547,6 +2547,7 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm)/* Always clear parent death signal on SID transitions. */ current->pdeath_signal =0 ; + current->signal->pdeath_signal_proc =0 ;/* Check whether the new SID can inherit resource limits from the old * SID. If not, reset all soft limits to the lower of the current
Signature · 1 line
2.14.1
Andrew Morton
maintainerQuoted 11 lines · On Fri, 29 Sep 2017 14:30:58 +0200 Jürg Billeter <[email protected]> wrote:
PR_SET_PDEATHSIGsets a parent death signal that the calling process will get when its parent thread dies, even when the result ofgetppid()doesn't change because the calling process is reparented to a different thread in the same parent process. When managing multiple processes, a process-based parent death signal is much more useful. E.g., to avoid stray child processes.
PR_SET_PDEATHSIG_PROCsets a process-based death signal. UnlikePR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.
Quoted 6 lines
This can be used for sandboxing when combined with a seccomp filter.
There have been previous attempts to support this by changing the behavior of
PR_SET_PDEATHSIG. However, that would break existing applications. See https://marc.info/?l=linux-kernel&m=117621804801689
and https://bugzilla.kernel.org/show_bug.cgi?id=43300
Are Eric and Oleg OK with this?
A prctl manpage update will be needed, please (cc linux-api).
Quoted 15 lines · Andrew Morton
Quoted 13 lines · On Fri, 29 Sep 2017 14:30:58 +0200 Jürg Billeter <[email protected]> wrote:
PR_SET_PDEATHSIGsets a parent death signal that the calling process will get when its parent thread dies, even when the result ofgetppid()doesn't change because the calling process is reparented to a different thread in the same parent process. When managing multiple processes, a process-based parent death signal is much more useful. E.g., to avoid stray child processes.
PR_SET_PDEATHSIG_PROCsets a process-based death signal. UnlikePR_SET_PDEATHSIG, this is inherited across fork to allow killing a whole subtree without race conditions.This can be used for sandboxing when combined with a seccomp filter.
Quoted 8 lines
Quoted 4 lines
There have been previous attempts to support this by changing the behavior of
PR_SET_PDEATHSIG. However, that would break existing applications. See https://marc.info/?l=linux-kernel&m=117621804801689
and https://bugzilla.kernel.org/show_bug.cgi?id=43300Are Eric and Oleg OK with this?
A prctl manpage update will be needed, please (cc linux-api).
It makes for an interesting way of killing a process tree. The domino effect.
I believe the rational for adding a new prctl.
The code where it calls group_send_sig_info is buggy for pdeath_signal. And it no less buggy for this new case. There is no point to check permissions when sending a signal to yourself. Especially this signal gets cleared during exec with a change of permissions.
I would recommend using:
do_send_sig_info(p->signal->pdeath_signal_proc, SEND_SIG_NOINFO, p, true);
Perhaps with a comment saying that no permission check is needed when sending a signal to yourself.
I don't know what I think about inherit over fork, and the whole tree killing thing. Except when the signal is SIGKILL I don't know if that code does what is intended. So I am a little leary of it.
Eric
Quoted 11 lines · Eric W. Biederman
The code where it calls
group_send_sig_infois buggy forpdeath_signal. And it no less buggy for this new case. There is no point to check permissions when sending a signal to yourself. Especially this signal gets cleared during exec with a change of permissions.I would recommend using:
do_send_sig_info(p->signal->pdeath_signal_proc, SEND_SIG_NOINFO, p, true);Perhaps with a comment saying that no permission check is needed when sending a signal to yourself.
Depending on how you look at it, one could also argue that the dying parent sends the signal. However, I'm fine with dropping the permission check in v2. I'll also send a patch to change this for the existing pdeath_signal.
Quoted 3 lines
I don't know what I think about inherit over fork, and the whole tree killing thing. Except when the signal is SIGKILL I don't know if that code does what is intended. So I am a little leary of it.
I agree that inheritance across fork is mainly useful for SIGKILL. While non-SIGKILL users could clear the setting after fork(), another option would be to allow the caller to specify whether the setting should be inherited using prctl arg3.
This would allow both, the exact process-based equivalent to pdeath_signal (no inheritance) as well as the interesting SIGKILL case for killing a process tree. Does this sound sensible? I'd be happy to add this to v2.
Jürg
Quoted 10 lines · Jürg Billeter
Quoted 9 lines · Eric W. Biederman
The code where it calls
group_send_sig_infois buggy forpdeath_signal. And it no less buggy for this new case. There is no point to check permissions when sending a signal to yourself. Especially this signal gets cleared during exec with a change of permissions.I would recommend using:
do_send_sig_info(p->signal->pdeath_signal_proc, SEND_SIG_NOINFO, p, true);
Quoted 7 lines
Quoted 2 lines
Perhaps with a comment saying that no permission check is needed when sending a signal to yourself.
Depending on how you look at it, one could also argue that the dying parent sends the signal. However, I'm fine with dropping the permission check in v2. I'll also send a patch to change this for the existing
pdeath_signal.
The process that requests the signal be sent is the process that is receiving the signal. I can see a theoretical need for a permission check in there somewhere (especially as this persists over fork).
That is what matters for a permission check.
I tried fixing this once along with a bunch of other changes, and apparently I did not do it obviously enough. So I think this needs to happen but let's make it very clear.
Quoted 10 lines
Quoted 3 lines
I don't know what I think about inherit over fork, and the whole tree killing thing. Except when the signal is SIGKILL I don't know if that code does what is intended. So I am a little leary of it.
I agree that inheritance across fork is mainly useful for SIGKILL. While non-SIGKILL users could clear the setting after
fork(), another option would be to allow the caller to specify whether the setting should be inherited using prctl arg3.
Quoted 4 lines
This would allow both, the exact process-based equivalent to
pdeath_signal(no inheritance) as well as the interesting SIGKILL case for killing a process tree. Does this sound sensible? I'd be happy to add this to v2.
Possibly.
There is a general need to find out about the death of other processes,
if you are not the parent of the process. I would be inclined to call
it waitfd. Something that you give a pid. It performs a permission check and the pid becomes readable when the process dies. With poll working on the fd, and the fd returning wstatus of the dead child.
Support SIGIO on the fd and you have a signal delivery mechanism, if you want it.
For the kill all children when the parent dies the mechanism you are proposing is escapable. We already have an inescapable version of it with init in a pid namespace. We already have an escapable version of it with orphaned process groups and SIGHUP.
So I would really appreciate a very clear use case for what we are building here. As it appears the killing of children can already be done another way, and that the waiting for the parent can be done better another way.
At the end of the day I would prefer to support something that has a good solid definition and a clear use case.
I may be missing something and there is a good reason to extend pdeath signal, but I don't see it right now.
Eric
Quoted 4 lines · Eric W. Biederman
The process that requests the signal be sent is the process that is receiving the signal. I can see a theoretical need for a permission check in there somewhere (especially as this persists over fork).
Note that it also persists over not just fork, but execve() too.
Yes, the signal is cleared if the e[ug]id/fs[ug]id is changed by exec, but not (for example) if just uid is changed.
Does that matter? Probably not. But signal handling does actually check uid, so it does actually affect signal permission checks across execve.
I'm not entirely convinced about this patch. The parent death signal has been problematic before, I'm not sure we want to add to the whole situation without very strong arguments.
Linus
Quoted 10 lines · Linus Torvalds
Quoted 4 lines · Eric W. Biederman
The process that requests the signal be sent is the process that is receiving the signal. I can see a theoretical need for a permission check in there somewhere (especially as this persists over fork).
Note that it also persists over not just fork, but
execve()too.Yes, the signal is cleared if the e[ug]id/fs[ug]id is changed by exec, but not (for example) if just uid is changed.
*Scratches head*pdeath_signal is cleared during exec if bprm->cap_elevated.
bprm->cap_elevated is set if we are not root and we gain caps during the exec. bprm->cap_elevated is set if is_setid is true.is_setid is set if the uid != eid or gid != egid.
So looking at that I am not exactly wild about the name cap_elevated, but it seems to clear pdeath_signal if the jus the uid is changed during exec.
Quoted 3 lines
Does that matter? Probably not. But signal handling does actually check uid, so it does actually affect signal permission checks across execve.
I don't think there is anything in exec in this case to worry about.
Of course there is the completely bizarre case that if the parent execs or calls setresuid it is possible that the signal won't send because it
is the parent's permission that are checked. I think that is probably
a bug.
I can understand not sending to our future self if our future self has different credentials than our present self. But not sending to our future self because someone else changed seems completely bizarre to me.
Eric
Quoted 3 lines · Eric W. Biederman
*Scratches head*
pdeath_signalis cleared during exec ifbprm->cap_elevated.
It's not cleared if we are *releasing* capabilities, which is exactly what might cause a "we can no longer send a signal"
Quoted 1 line
is_setidis set if the uid != eid or gid != egid.
Again, that may be exactly what changes - the original process may have uid != euid, and now we're going from an "we still had a root uid/suid" to "dropping everything to euid".
IOW, we're _dropping_ capabilities, not adding them. Maybe we don't want to allow signaling the original parent any more.
That said, as mentioned, I actually don't think it's a real problem. The real problem is entirely conceptual: yet more complexity in an area that we've already had problems in before.
Linus
Quoted 7 lines · Linus Torvalds
Quoted 3 lines · Eric W. Biederman
*Scratches head*
pdeath_signalis cleared during exec ifbprm->cap_elevated.It's not cleared if we are *releasing* capabilities, which is exactly what might cause a "we can no longer send a signal"
*Doh* Now I see where you are coming from.
Quoted 8 lines
Quoted 1 line
is_setidis set if the uid != eid or gid != egid.Again, that may be exactly what changes - the original process may have uid != euid, and now we're going from an "we still had a root uid/suid" to "dropping everything to euid".
IOW, we're _dropping_ capabilities, not adding them. Maybe we don't want to allow signaling the original parent any more.
We never signal the orignal parent. We signal the child that requested the pdeath_signal when the original parent dies.
The permission check is:
Does the parent have permission to signal the child that requested the signal.
So the child dropping permissions won't change the result of that permission check one iota.
The parent dropping permissions may change the result of that permission check.
I care about this case because we already have the pdeath_signal and the permission check doesn't make any sense to me, and appears not to be at all useful.
Plus it is the only caller of group_send_sig_info outside of kernel/signal.c so I think the kernel would be more maintainable if we could simplify this piece of code.
Quoted 5 lines
That said, as mentioned, I actually don't think it's a real problem. The real problem is entirely conceptual: yet more complexity in an area that we've already had problems in before.
From the conversation so far it does appear we can do better.
Eric
Quoted 3 lines · Eric W. Biederman
We never signal the orignal parent. We signal the child that requested the
pdeath_signalwhen the original parent dies.
Yeah, I keep making that mistake, because I always confuse this with the exit_signal handling.
Just mentally kick me next time I do that: "Christ, Linus, not *again*! Take your damn meds"
Anyway, it's more the "another confusing and fragile special case that will probably not be used very widely and cause confusion because it lacks any test coverage" thing I worry about most.
Linus
Quoted 9 lines · Linus Torvalds
Quoted 3 lines · Eric W. Biederman
We never signal the orignal parent. We signal the child that requested the
pdeath_signalwhen the original parent dies.Yeah, I keep making that mistake, because I always confuse this with the
exit_signalhandling.
Quoted 6 lines
Just mentally kick me next time I do that: "Christ, Linus, not *again*! Take your damn meds"
Anyway, it's more the "another confusing and fragile special case that will probably not be used very widely and cause confusion because it lacks any test coverage" thing I worry about most.
Agreed. That makes a more general solution perferable.
Eric
Quoted 8 lines · Eric W. Biederman
There is a general need to find out about the death of other processes,
if you are not the parent of the process. I would be inclined to call
it waitfd. Something that you give a pid. It performs a permission check and the pid becomes readable when the process dies. With poll working on the fd, and the fd returning wstatus of the dead child.Support SIGIO on the fd and you have a signal delivery mechanism, if you want it.
File descriptors for processes (waitfd/clonefd) are definitely interesting. Especially if reaping the process (and reparenting its children) is delayed until the last process file descriptor is closed. However, this would be a much larger addition and also less intuitive to use if all you want is killing the process tree.
Quoted 9 lines
For the kill all children when the parent dies the mechanism you are proposing is escapable. We already have an inescapable version of it with init in a pid namespace. We already have an escapable version of it with orphaned process groups and SIGHUP.
So I would really appreciate a very clear use case for what we are building here. As it appears the killing of children can already be done another way, and that the waiting for the parent can be done better another way.
My use case is to provide a way for a process to spawn a child and ensure that no descendants survive when that child dies. Avoiding runaway processes is desirable in many situations. My motivation is very lightweight (nested) sandboxing (every process is potentially sandboxed).
I.e., pid namespaces would be a pretty good fit (assuming they are sufficiently lightweight) but CLONE_NEWPID requires CAP_SYS_ADMIN. User namespaces can help here, but creating tons of user namespaces just for this doesn't sound sensible. MAX_PID_NS_LEVEL could be an issue as well at some point but 32 levels are likely fine in practice.
For my particular scenario I may actually be able to create a single user namespace, run all processes with (namespaced) CAP_SYS_ADMIN and use CLONE_NEWPID for every process. However, I would prefer not requiring CAP_SYS_ADMIN and a regular application that wants to avoid runaway processes for a spawned helper process cannot rely on CAP_SYS_ADMIN.
My plan was to use PR_SET_PDEATHSIG_PROC with PR_NO_NEW_PRIVS and a suitable seccomp filter to prevent changes to pdeath_signal_proc. For my SIGKILL use case it would be even better to simply require PR_NO_NEW_PRIVS and make pdeath_signal_proc sticky, avoiding the need for seccomp. I wanted to keep the differences to the existing PR_SET_PDEATHSIG minimal but if we argue that the non-SIGKILL use case is better solved with waitfd (or maybe the process events connector), we could tailor the prctl for the SIGKILL use case (or support both via prctl arg3).
I have another small patch locally that adds a prctl that restricts kill(2) to direct children of the current thread group for lightweight sandboxing. That would also be redundant if it was possible to use CLONE_NEWPID for every process.
What's actually the reason that CLONE_NEWPID requires CAP_SYS_ADMIN? Does CLONE_NEWPID pose any risks that don't exist for CLONE_NEWUSER|CLONE_NEWPID? Assuming we can't simply drop the CAP_SYS_ADMIN requirement, do you see a better solution for this use case?
Jürg
Quoted 55 lines · Jürg Billeter
Quoted 8 lines · Eric W. Biederman
There is a general need to find out about the death of other processes,
if you are not the parent of the process. I would be inclined to call
it waitfd. Something that you give a pid. It performs a permission check and the pid becomes readable when the process dies. With poll working on the fd, and the fd returning wstatus of the dead child.Support SIGIO on the fd and you have a signal delivery mechanism, if you want it.
File descriptors for processes (waitfd/clonefd) are definitely interesting. Especially if reaping the process (and reparenting its children) is delayed until the last process file descriptor is closed. However, this would be a much larger addition and also less intuitive to use if all you want is killing the process tree.
Quoted 9 lines
For the kill all children when the parent dies the mechanism you are proposing is escapable. We already have an inescapable version of it with init in a pid namespace. We already have an escapable version of it with orphaned process groups and SIGHUP.
So I would really appreciate a very clear use case for what we are building here. As it appears the killing of children can already be done another way, and that the waiting for the parent can be done better another way.
My use case is to provide a way for a process to spawn a child and ensure that no descendants survive when that child dies. Avoiding runaway processes is desirable in many situations. My motivation is very lightweight (nested) sandboxing (every process is potentially sandboxed).
I.e., pid namespaces would be a pretty good fit (assuming they are sufficiently lightweight) but
CLONE_NEWPIDrequiresCAP_SYS_ADMIN. User namespaces can help here, but creating tons of user namespaces just for this doesn't sound sensible.MAX_PID_NS_LEVELcould be an issue as well at some point but 32 levels are likely fine in practice.For my particular scenario I may actually be able to create a single user namespace, run all processes with (namespaced)
CAP_SYS_ADMINand useCLONE_NEWPIDfor every process. However, I would prefer not requiringCAP_SYS_ADMINand a regular application that wants to avoid runaway processes for a spawned helper process cannot rely onCAP_SYS_ADMIN.My plan was to use
PR_SET_PDEATHSIG_PROCwithPR_NO_NEW_PRIVSand a suitable seccomp filter to prevent changes topdeath_signal_proc. For my SIGKILL use case it would be even better to simply requirePR_NO_NEW_PRIVSand makepdeath_signal_procsticky, avoiding the need for seccomp. I wanted to keep the differences to the existingPR_SET_PDEATHSIGminimal but if we argue that the non-SIGKILL use case is better solved with waitfd (or maybe the process events connector), we could tailor the prctl for the SIGKILL use case (or support both via prctl arg3).
Quoted 4 lines
I have another small patch locally that adds a prctl that restricts kill(2) to direct children of the current thread group for lightweight sandboxing. That would also be redundant if it was possible to use
CLONE_NEWPIDfor every process.
I believe the current default limits allow using CLONE_NEWPID for every process. The data structures seem light enough as well.
Quoted 5 lines
What's actually the reason that
CLONE_NEWPIDrequiresCAP_SYS_ADMIN? DoesCLONE_NEWPIDpose any risks that don't exist for CLONE_NEWUSER|CLONE_NEWPID? Assuming we can't simply drop theCAP_SYS_ADMINrequirement, do you see a better solution for this use case?
CLONE_NEWPID without a permission check would allow runing a setuid root application in a pid namespace. Off the top of my head I can't think of a really good exploit. But when you mess up pid files, and hide information from a privileged application I can completely imagine forcing that application to misbehave in ways the attacker can control. Leading to bad things.
Eric
Quoted 7 lines · Eric W. Biederman
Quoted 5 lines · Jürg Billeter
What's actually the reason that
CLONE_NEWPIDrequiresCAP_SYS_ADMIN? DoesCLONE_NEWPIDpose any risks that don't exist for CLONE_NEWUSER|CLONE_NEWPID? Assuming we can't simply drop theCAP_SYS_ADMINrequirement, do you see a better solution for this use case?
Quoted 6 lines
CLONE_NEWPIDwithout a permission check would allow runing a setuid root application in a pid namespace. Off the top of my head I can't think of a really good exploit. But when you mess up pid files, and hide information from a privileged application I can completely imagine forcing that application to misbehave in ways the attacker can control. Leading to bad things.
Could we allow unprivileged CLONE_NEWPID if the no_new_privs bit is set?
Jürg
Quoted 15 lines · Jürg Billeter
Quoted 7 lines · Eric W. Biederman
Quoted 5 lines · Jürg Billeter
What's actually the reason that
CLONE_NEWPIDrequiresCAP_SYS_ADMIN? DoesCLONE_NEWPIDpose any risks that don't exist for CLONE_NEWUSER|CLONE_NEWPID? Assuming we can't simply drop theCAP_SYS_ADMINrequirement, do you see a better solution for this use case?Quoted 6 lines
CLONE_NEWPIDwithout a permission check would allow runing a setuid root application in a pid namespace. Off the top of my head I can't think of a really good exploit. But when you mess up pid files, and hide information from a privileged application I can completely imagine forcing that application to misbehave in ways the attacker can control. Leading to bad things.
Quoted 2 lines
Could we allow unprivileged
CLONE_NEWPIDif theno_new_privsbit is set?
We discussed this early on, and the decision was that no_new_privs would be kept simple and the user namespace would be what enabled additional functionality.
Given how much of a challenge dealing with the additional attack surface of enabling additional functionality in the kernel I think that was the right call. That has been the difference between no_new_privs being done and user namespaces interesting since they have been merged.
Eric
Quoted 9 lines · Jürg Billeter
My use case is to provide a way for a process to spawn a child and ensure that no descendants survive when that child dies. Avoiding runaway processes is desirable in many situations. My motivation is very lightweight (nested) sandboxing (every process is potentially sandboxed).
I.e., pid namespaces would be a pretty good fit (assuming they are sufficiently lightweight) but
CLONE_NEWPID
sorry if this was already discussed, I didn't read this thread yet...
if CLONE_NEWPID is not suitable for any reason. We already have PR_SET_CHILD_SUBREAPER. Perhaps we can simply add another PR_SET_KILL_ALL_DESCEDANTS_ON_EXIT? we can use walk_process_tree() to send SIGKILL.
Oleg.
Quoted 11 lines · Oleg Nesterov
Quoted 9 lines · Jürg Billeter
My use case is to provide a way for a process to spawn a child and ensure that no descendants survive when that child dies. Avoiding runaway processes is desirable in many situations. My motivation is very lightweight (nested) sandboxing (every process is potentially sandboxed).
I.e., pid namespaces would be a pretty good fit (assuming they are sufficiently lightweight) but
CLONE_NEWPID
Quoted 6 lines
sorry if this was already discussed, I didn't read this thread yet...
if
CLONE_NEWPIDis not suitable for any reason. We already havePR_SET_CHILD_SUBREAPER. Perhaps we can simply add anotherPR_SET_KILL_ALL_DESCEDANTS_ON_EXIT? we can usewalk_process_tree()to send SIGKILL.
Yes, this is an option. However, after the discussion in this thread I believe it would be better to drop the CAP_SYS_ADMIN requirement for CLONE_NEWPID (when no_new_privs is set) as this would avoid adding another API and code path for a similar effect. I'm interested in possible security concerns about such a change. Adding Andy Lutomirski to cc.
Jürg
Quoted 19 lines · Jürg Billeter
Quoted 11 lines · Oleg Nesterov
Quoted 9 lines · Jürg Billeter
My use case is to provide a way for a process to spawn a child and ensure that no descendants survive when that child dies. Avoiding runaway processes is desirable in many situations. My motivation is very lightweight (nested) sandboxing (every process is potentially sandboxed).
I.e., pid namespaces would be a pretty good fit (assuming they are sufficiently lightweight) but
CLONE_NEWPIDQuoted 6 lines
sorry if this was already discussed, I didn't read this thread yet...
if
CLONE_NEWPIDis not suitable for any reason. We already havePR_SET_CHILD_SUBREAPER. Perhaps we can simply add anotherPR_SET_KILL_ALL_DESCEDANTS_ON_EXIT? we can usewalk_process_tree()to send SIGKILL.
Quoted 6 lines
Yes, this is an option. However, after the discussion in this thread I believe it would be better to drop the
CAP_SYS_ADMINrequirement forCLONE_NEWPID(whenno_new_privsis set) as this would avoid adding another API and code path for a similar effect. I'm interested in possible security concerns about such a change. Adding Andy Lutomirski to cc.
Absolutely not. no_new_privs does not need the headache of being increasing the kernel attack surface.
User namespaces are cheap, use one. Let the people using no_new_privs sleep easy. We don't need to transform no_new_privs into a user namespace.
Eric