fork: Add the ability to create tasks with given pids
Subsystems (5)
cover fork: Add the ability to create tasks with given pids
Gentlemen, please, find some time for this, your ACK/NACK on the API proposal is required badly.
There's currently a work in progress with checkpoint-restore functionality in the userspace. Most of the API for doing this kernel already provides, but sometimes it's not enough. One of the required things is the ability to create a process with its pids (in different pid namespaces) set to some given values, rather than generated. Currently kernel doesn't allow for this, so an API extension is required.
The proposal is to introduce the CLONE_CHILD_USEPIDS flag for clone() syscall and pass the pids values in the child_tidptr. In order not to introduce the hole for the pid-reuse attack, using this flag will result in EPERM in case the pid namespace we're trying to create pid in has at least one pid (except for the init's one) generated with regular fork()/clone().
Currently Tejun and Oleg are worrying only about the intrusiveness of this approach, although Oleg agrees, that it solves all the problems it should. The previous attempts to implement the similar stuff stopped, but no objections against this were expressed. So the decision of whether it's OK to go this way or not is required.
The API will be used like in the code below
/* restore new pid namespace with an init in it */ pid = clone(CLONE_NEWPID); if (pid) return 0; /* * init of a new pid namespace. * recreate the process tree */
restore_children:
while (1) {
pid = next_pid_from_image();
if (!pid)
/* no more children */
break;
pid = clone(CLONE_CHILD_USEPIDS, &pid);
if (pid == 0)
goto restore_children;
}
/*
* the process tree is recreated, can proceed with restoring
* other stuff
*/Thanks,
Pavel
PATCH 1/3 pids: Make alloc_pid return error
Currently the alloc_pid just reports the pid allocation failre and this gets reported as ENOMEM to user. With the clone-with-pid ability the error codes set will be extended and this patch eases this extension.
--- kernel/fork.c | 5 +++-- kernel/pid.c | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-)
kernel/fork.c +3 -2
@@ -1253,10 +1253,11 @@ static struct task_struct *copy_process(unsigned long clone_flags, goto bad_fork_cleanup_io;if (pid != &init_struct_pid) { - retval = -ENOMEM ; pid =alloc_pid (p->nsproxy->pid_ns); -if (!pid) +if (IS_ERR (pid)) { + retval =PTR_ERR (pid); goto bad_fork_cleanup_io; + } } p->pid =pid_nr (pid);
kernel/pid.c +4 -5
@@ -214,7 +214,7 @@ static int alloc_pidmap(struct pid_namespace *pid_ns) } pid =mk_pid (pid_ns, map, offset); } -return -1 ; +return -ENOMEM ; }int next_pidmap (struct pid_namespace *pid_ns,unsigned int last)
@@ -281,7 +281,7 @@ struct pid *alloc_pid(struct pid_namespace *ns) {struct pid *pid;enum pid_type type; -int i, nr; +int i, nr = -ENOMEM ;struct pid_namespace *tmp;struct upid *upid;
@@ -313,7 +313,6 @@ struct pid *alloc_pid(struct pid_namespace *ns) &pid_hash[pid_hashfn(upid->nr, upid->ns)]);spin_unlock_irq (&pidmap_lock); -out:return pid; out_free:
@@ -321,8 +320,8 @@ out_free:free_pidmap (pid->numbers + i);kmem_cache_free (ns->pid_cachep, pid); - pid =NULL ; - goto out; +out: +return ERR_PTR (nr); }struct pid *find_pid_ns (int nr,struct pid_namespace *ns)
Signature · 1 line
1.5.5.6
PATCH 2/3 pids: Split alloc_pidmap into parts
In reply to Pavel EmelyanovThe map's page allocation code is moved to separate function to make clone-with-pids patching simpler.
--- kernel/pid.c | 39 +++++++++++++++++++++++---------------- 1 files changed, 23 insertions(+), 16 deletions(-)
kernel/pid.c +23 -16
@@ -159,6 +159,26 @@ static void set_last_pid(struct pid_namespace *pid_ns, int base, int pid) }while ((prev != last_write) && (pid_before (base, last_write, pid))); } +static int alloc_pidmap_page (struct pidmap *map) +{ +if (unlikely (!map->page)) { +void *page =kzalloc (PAGE_SIZE ,GFP_KERNEL ); +/* +* Free the page if someone raced with us +* installing it: +*/ +spin_lock_irq (&pidmap_lock); +if (!map->page) { + map->page = page; + page =NULL ; + } +spin_unlock_irq (&pidmap_lock); +kfree (page); + } + +return !map->page; +} +static int alloc_pidmap (struct pid_namespace *pid_ns) {int i, offset, max_scan, pid, last = pid_ns->last_pid;
@@ -176,22 +196,9 @@ static int alloc_pidmap(struct pid_namespace *pid_ns)*/ max_scan =DIV_ROUND_UP (pid_max,BITS_PER_PAGE ) - !offset;for (i =0 ; i <= max_scan; ++i) { -if (unlikely (!map->page)) { -void *page =kzalloc (PAGE_SIZE ,GFP_KERNEL ); -/* -* Free the page if someone raced with us -* installing it: -*/ -spin_lock_irq (&pidmap_lock); -if (!map->page) { - map->page = page; - page =NULL ; - } -spin_unlock_irq (&pidmap_lock); -kfree (page); -if (unlikely (!map->page)) -break ; - } +if (alloc_pidmap_page (map)) +break ; +if (likely (atomic_read (&map->nr_free))) { do { if (!test_and_set_bit(offset, map->page)) {
Signature · 1 line
1.5.5.6
PATCH 3/3 pids: Make it possible to clone tasks with given pids
In reply to Pavel EmelyanovWhen restoring a task (or a set of tasks) we need to recreate them with exactly the same pid(s) as they had before. Thus we need the ability to create a task with specified pid. The proposal is to reuse the already free CLONE_STOPPED clone flag, introduce the new one called CLONE_CHILD_USEPIDS and point to the desired pids with the child_tidptr.
The child_tidptr points to an array of pids for current namespace and its ancestors. When 0 is met in this array the pid number for the corresponding namespace is generated, rather than set.
For security reasons after a regular clone/fork is done in a namespace further cloning with predefined pid is not allowed.
--- include/linux/pid.h | 2 +- include/linux/sched.h | 3 +- kernel/fork.c | 4 ++- kernel/pid.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 53 insertions(+), 6 deletions(-)
include/linux/pid.h +1 -1
@@ -119,7 +119,7 @@ extern struct pid *find_get_pid(int nr);extern struct pid *find_ge_pid (int nr,struct pid_namespace *);int next_pidmap (struct pid_namespace *pid_ns,unsigned int last); -extern struct pid *alloc_pid (struct pid_namespace *ns); +extern struct pid *alloc_pid (struct pid_namespace *ns,int __user *want_pids);extern void free_pid (struct pid *pid);/*
include/linux/sched.h +1 -2
@@ -21,8 +21,7 @@#define CLONE_DETACHED 0x00400000/* Unused, ignored */ #define CLONE_UNTRACED 0x00800000/* set if the tracing process can't force CLONE_PTRACE on this clone */ #define CLONE_CHILD_SETTID 0x01000000/* set the TID in the child */ -/* 0x02000000 was previously the unused CLONE_STOPPED (Start in stopped state) -and is now available for re-use. */ +#define CLONE_CHILD_USEPIDS 0x02000000/* use the pids given by user */ #define CLONE_NEWUTS 0x04000000/* New utsname group? */ #define CLONE_NEWIPC 0x08000000/* New ipcs */ #define CLONE_NEWUSER 0x10000000/* New user namespace */
kernel/fork.c +3 -1
@@ -1253,7 +1253,9 @@ static struct task_struct *copy_process(unsigned long clone_flags, goto bad_fork_cleanup_io;if (pid != &init_struct_pid) { - pid =alloc_pid (p->nsproxy->pid_ns); + pid =alloc_pid (p->nsproxy->pid_ns, + (clone_flags &CLONE_CHILD_USEPIDS ) ? + child_tidptr :NULL );if (IS_ERR (pid)) { retval =PTR_ERR (pid); goto bad_fork_cleanup_io;
kernel/pid.c +48 -2
68 lines not on this page · raw message
Signature · 1 line
1.5.5.6
Re: [PATCH 3/3] pids: Make it possible to clone tasks with given pids
kernel/pid.c static int alloc_pidmap(struct pid_namespace *pid_ns) · line 237
Quoted 12 lines · Pavel Emelyanov
kernel/pid.c +11
-0+static int set_pidmap (struct pid_namespace *pid_ns,int pid) +{ +int offset; +struct pidmap *map; + +/* +* When creating a new pid namespace we must make its init +* have pid == 1 in it. +*/ +if (pid_ns->child_reaper ==NULL ) +return 0 ;
Do we really need this check? please see below...
kernel/pid.c static int alloc_pidmap(struct pid_namespace *pid_ns) · line 245
Quoted 7 lines
kernel/pid.c +7
-0+/* +* Don't allow to create a task with a pid which has recently +* belonged to some other (dead already) task. Only init (of +* a freshly created namespace) and his clones can do this. +*/ +if (pid_ns->last_pid !=1 ) +return -EPERM ;
->last_pid == 1. This means that pid_nr == 1 was already created in this namespace via CLONE_NEWPID, and the child with this pid must be ->child_reaper, no?
IOW. if copy_process() allocs the first pid in the new pid_ns, it always sets ->child_reaper.
Cough. I really think 45a68628 should be reverted ;) IMHO it complicates the understanding of CLONE_NEWPID logic.
Oleg.
Re: [PATCH 3/3] pids: Make it possible to clone tasks with given pids
Quoted 16 lines · Oleg Nesterov
kernel/pid.c static int alloc_pidmap(struct pid_namespace *pid_ns) · line 237
Quoted 12 lines · Pavel Emelyanov
kernel/pid.c +11
-0+static int set_pidmap (struct pid_namespace *pid_ns,int pid) +{ +int offset; +struct pidmap *map; + +/* +* When creating a new pid namespace we must make its init +* have pid == 1 in it. +*/ +if (pid_ns->child_reaper ==NULL ) +return 0 ;Do we really need this check? please see below...
Quoted 11 lines
kernel/pid.c static int alloc_pidmap(struct pid_namespace *pid_ns) · line 245
Quoted 7 lines
kernel/pid.c +7
-0+/* +* Don't allow to create a task with a pid which has recently +* belonged to some other (dead already) task. Only init (of +* a freshly created namespace) and his clones can do this. +*/ +if (pid_ns->last_pid !=1 ) +return -EPERM ;->last_pid == 1. This means that
pid_nr== 1 was already created in this namespace viaCLONE_NEWPID, and the child with this pid must be ->child_reaper, no?
If you use the CLONE_NEWPID | CLONE_CHILD_USEPIDS then you should provide the 1st pid in array is 1. Otherwise init in this new pid namespace will have pid != 1 and the child_reaper assignment (yes, the 45a68628 commit) will be lost :(
Quoted 2 lines
IOW. if
copy_process()allocs the first pid in the newpid_ns, it always sets ->child_reaper.
Fixup - if it allocates pid == 1, then it sets the child reaper.
Quoted 2 lines
Cough. I really think
45a68628should be reverted ;) IMHO it complicates the understanding ofCLONE_NEWPIDlogic.
If we remove it, then it's OK to remove the check above, but in this case we make it possible to have an init with pid != 1. This is flexible, but ... strange.
Quoted 4 lines
Oleg.
.
Re: [PATCH 3/3] pids: Make it possible to clone tasks with given pids
Quoted 30 lines · Pavel Emelyanov
Quoted 16 lines · Oleg Nesterov
kernel/pid.c static int alloc_pidmap(struct pid_namespace *pid_ns) · line 237
Quoted 12 lines · Pavel Emelyanov
kernel/pid.c +11
-0+static int set_pidmap (struct pid_namespace *pid_ns,int pid) +{ +int offset; +struct pidmap *map; + +/* +* When creating a new pid namespace we must make its init +* have pid == 1 in it. +*/ +if (pid_ns->child_reaper ==NULL ) +return 0 ;Do we really need this check? please see below...
Quoted 11 lines
kernel/pid.c static int alloc_pidmap(struct pid_namespace *pid_ns) · line 245
Quoted 7 lines
kernel/pid.c +7
-0+/* +* Don't allow to create a task with a pid which has recently +* belonged to some other (dead already) task. Only init (of +* a freshly created namespace) and his clones can do this. +*/ +if (pid_ns->last_pid !=1 ) +return -EPERM ;->last_pid == 1. This means that
pid_nr== 1 was already created in this namespace viaCLONE_NEWPID, and the child with this pid must be ->child_reaper, no?
Quoted 1 line
If you use the
CLONE_NEWPID|CLONE_CHILD_USEPIDS
Ah wait... I misread the check above as if it returns the error if ->child_reaper == NULL.
So, CLONE_NEWPID simply ignores child_tidptr[0], alloc_pid() fallbacks to alloc_pidmap() after set_pidmap() returns 0.
Quoted 5 lines
Quoted 2 lines
Cough. I really think
45a68628should be reverted ;) IMHO it complicates the understanding ofCLONE_NEWPIDlogic.If we remove it, then it's OK to remove the check above, but in this case we make it possible to have an init with pid != 1. This is flexible, but ... strange.
No, I didn't mean we should allow ->child_reaper with pid != 1, sorry for confusion.
Oleg.
Re: [PATCH 3/3] pids: Make it possible to clone tasks with given pids
In reply to Pavel EmelyanovOk. Patches 1-2 look sane on their own. I think we can merge them regardless in the next cycle if Oleg & co agree. Oleg?
Patch 3 obviously is generating discussion and actually introduces new functionality, so this one is the contentious one..
Linus
Quoted 3 lines · Pavel Emelyanov
When restoring a task (or a set of tasks) we need to recreate them with exactly the same pid(s) as they had before. [...]
Re: [PATCH 3/3] pids: Make it possible to clone tasks with given pids
Quoted 3 lines · Linus Torvalds
Ok. Patches 1-2 look sane on their own. I think we can merge them regardless in the next cycle if Oleg & co agree. Oleg?
I am not sure 1/3 really makes sense without 3/3... although it cleanups the "goto out" logic in alloc_pid().
Anyway I agree, the patches look fine.
Quoted 9 lines
Patch 3 obviously is generating discussion and actually introduces new functionality, so this one is the contentious one..
Linus
Quoted 3 lines · Pavel Emelyanov
When restoring a task (or a set of tasks) we need to recreate them with exactly the same pid(s) as they had before. [...]
Yes.
Just in case, I believe technically 3/3 is correct too, modulo the small problem with the unchecked access to ->pidmap[] (unless I missed something again).
I am not sure about pid_max... probably we do not care in this case?
Oleg.
Re: [PATCH 3/3] pids: Make it possible to clone tasks with given pids
In reply to Pavel EmelyanovMy previous objection was wrong, I should try to find something else ;)
Quoted 12 lines · Pavel Emelyanov
kernel/pid.c +11
-0+static int set_pidmap (struct pid_namespace *pid_ns,int pid) +{ +int offset; +struct pidmap *map; + +/* +* When creating a new pid namespace we must make its init +* have pid == 1 in it. +*/ +if (pid_ns->child_reaper ==NULL ) +return 0 ;
kernel/pid.c static int alloc_pidmap(struct pid_namespace *pid_ns) · line 247
Quoted 10 lines
kernel/pid.c +10
-0+ +/* +* Don't allow to create a task with a pid which has recently +* belonged to some other (dead already) task. Only init (of +* a freshly created namespace) and his clones can do this. +*/ +if (pid_ns->last_pid !=1 ) +return -EPERM ; + + map = &pid_ns->pidmap[pid/BITS_PER_PAGE ];
probably we should check that map < PIDMAP_ENTRIES ?
Oleg.
Re: [PATCH 3/3] pids: Make it possible to clone tasks with given pids
Quoted 19 lines · Oleg Nesterov
My previous objection was wrong, I should try to find something else ;)
Quoted 16 lines · Pavel Emelyanov
kernel/pid.c +15
-0+static int set_pidmap (struct pid_namespace *pid_ns,int pid) +{ +int offset; +struct pidmap *map; + +/* +* When creating a new pid namespace we must make its init +* have pid == 1 in it. +*/ +if (pid_ns->child_reaper ==NULL ) +return 0 ; + +/* +* Don't allow to create a task with a pid which has recently +* belonged to some other (dead already) task. Only init (of
Quoted 8 lines
kernel/pid.c static int alloc_pidmap(struct pid_namespace *pid_ns) · line 247
Quoted 6 lines
kernel/pid.c +6
-0+* a freshly created namespace) and his clones can do this. +*/ +if (pid_ns->last_pid !=1 ) +return -EPERM ; + + map = &pid_ns->pidmap[pid/BITS_PER_PAGE ];probably we should check that map <
PIDMAP_ENTRIES?
Yes, you're right here. The checks for given pid being correct are required. Will fix.
Quoted 1 line
Oleg.
Thanks,
Pavel
Quoted 3 lines · Pavel Emelyanov
Gentlemen, please, find some time for this, your ACK/NACK on the API proposal is required badly.
Please.
Quoted 11 lines
The proposal is to introduce the
CLONE_CHILD_USEPIDSflag forclone()syscall and pass the pids values in thechild_tidptr. In order not to introduce the hole for the pid-reuse attack, using this flag will result in EPERM in case the pid namespace we're trying to create pid in has at least one pid (except for the init's one) generated with regular fork()/clone().Currently Tejun and Oleg are worrying only about the intrusiveness of this approach, although Oleg agrees, that it solves all the problems it should. The previous attempts to implement the similar stuff stopped, but no objections against this were expressed. So the decision of whether it's OK to go this way or not is required.
Yes, personally I'd prefer /proc/set_last_pid (or something similar) which simply writes to pid_ns->last_pid. Perhaps it is less convenient from the user-space pov (serialization, security) but it is much simpler.
OTOH, I do not pretend I understand the user-space needs, so I won't argue. This series seems correct, the bugs we discussed are fixed.
But. Speaking of API, it differs a bit compared to the previous version...
Quoted 4 lines
The API will be used like in the code below
/* restore new pid namespace with an init in it */ pid = clone(CLONE_NEWPID);
Yes, CLONE_NEWPID | CLONE_CHILD_USEPIDS is not possible.
Then how the array of pids in child_tidptr[] can be useful? If CLONE_NEWPID can't restore the pid_nr's in the parent namespaces, then probably this doesn't makes sense at all?
IOW. I think we should either allow CLONE_NEWPID | CLONE_CHILD_USEPIDS (with additional check in set_pidmap() to ensure that CLONE_NEWPID
comes with child_tidptr[0] == 1), or we should treat the "overloaded"child_tidptr as a simple pid_t.
Again, I won't insist. Just I want to be sure we do not miss something adding the new API.
Oleg.
Quoted 19 lines · Oleg Nesterov
Quoted 3 lines · Pavel Emelyanov
Gentlemen, please, find some time for this, your ACK/NACK on the API proposal is required badly.
Please.
Quoted 11 lines
The proposal is to introduce the
CLONE_CHILD_USEPIDSflag forclone()syscall and pass the pids values in thechild_tidptr. In order not to introduce the hole for the pid-reuse attack, using this flag will result in EPERM in case the pid namespace we're trying to create pid in has at least one pid (except for the init's one) generated with regular fork()/clone().Currently Tejun and Oleg are worrying only about the intrusiveness of this approach, although Oleg agrees, that it solves all the problems it should. The previous attempts to implement the similar stuff stopped, but no objections against this were expressed. So the decision of whether it's OK to go this way or not is required.
Quoted 3 lines
Yes, personally I'd prefer
/proc/set_last_pid(or something similar) which simply writes topid_ns->last_pid. Perhaps it is less convenient from the user-space pov (serialization, security) but it is much simpler.
Yes, this is also possible. I have a working prototype of /proc/sys/kernel/ns_last_pid with the security issue solved, but setting sysctl then cloning seems more obfuscating to me than just passing an array of pids to clone.
Quoted 11 lines
OTOH, I do not pretend I understand the user-space needs, so I won't argue. This series seems correct, the bugs we discussed are fixed.
But. Speaking of API, it differs a bit compared to the previous version...
Quoted 4 lines
The API will be used like in the code below
/* restore new pid namespace with an init in it */ pid = clone(CLONE_NEWPID);Yes,
CLONE_NEWPID|CLONE_CHILD_USEPIDSis not possible.
It should be. If we (in theory, but) restore two pid namespaces with one being a child of another we will have to create an init of the child ns with predefined pid in the parent ns.
Quoted 8 lines
Then how the array of pids in child_tidptr[] can be useful? If
CLONE_NEWPIDcan't restore the pid_nr's in the parent namespaces, then probably this doesn't makes sense at all?IOW. I think we should either allow
CLONE_NEWPID|CLONE_CHILD_USEPIDS(with additional check inset_pidmap()to ensure thatCLONE_NEWPID
comes with child_tidptr[0] == 1), or we should treat the "overloaded"child_tidptras a simplepid_t.
The child_tidptr[0] == 1 check will also work. Currently I check for the ns->child_reaper being NULL instead.
Quoted 4 lines
Again, I won't insist. Just I want to be sure we do not miss something adding the new API.
Oleg.
Quoted 5 lines · Pavel Emelyanov
Quoted 3 lines · Oleg Nesterov
But. Speaking of API, it differs a bit compared to the previous version...
Quoted 8 lines
Quoted 6 lines
Quoted 4 lines
The API will be used like in the code below
/* restore new pid namespace with an init in it */ pid = clone(CLONE_NEWPID);Yes,
CLONE_NEWPID|CLONE_CHILD_USEPIDSis not possible.It should be.
Yes, I was wrong, sorry. I misread the ->child_reaper == NULL in 3/3.
We simply ignore child_tidptr[0] in this case. This should work.
Oleg.
Hello,
Quoted 7 lines · Pavel Emelyanov
Quoted 3 lines
Yes, personally I'd prefer
/proc/set_last_pid(or something similar) which simply writes topid_ns->last_pid. Perhaps it is less convenient from the user-space pov (serialization, security) but it is much simpler.Yes, this is also possible. I have a working prototype of
/proc/sys/kernel/ns_last_pidwith the security issue solved, but setting sysctl then cloning seems more obfuscating to me than just passing an array of pids to clone.
Do you mind sharing the patch? It doesn't have to be perfect. I'm just curious how it looks. IMHO the suggested pid array passing is good enough and not too intrusive but, if there's something simpler from kernel side, given that this is a very specialized interface, I think we definitely need to consider that.
Thank you.
Signature · 1 line
tejun
Quoted 7 lines · Tejun Heo
Hello,
Quoted 4 lines · Pavel Emelyanov
Quoted 3 lines
Yes, personally I'd prefer
/proc/set_last_pid(or something similar) which simply writes topid_ns->last_pid. Perhaps it is less convenient from the user-space pov (serialization, security) but it is much simpler.
Quoted 5 lines
Quoted 3 lines
Yes, this is also possible. I have a working prototype of
/proc/sys/kernel/ns_last_pidwith the security issue solved, but setting sysctl then cloning seems more obfuscating to me than just passing an array of pids to clone.Do you mind sharing the patch?
Sure! First of all, we need to change the ctl_table_root->permission callback to pass the required operations (MAY_XXX) into it, rather than just getting the mode allowed. The API change it like in the patch below (plus we need to patch the net/ sysctl's, since they use this API):
include/linux/sysctl.h +6 -8
@@ -1052,10 +1052,12 @@ struct ctl_table_root {struct ctl_table_set default_set;struct ctl_table_set *(*lookup)(struct ctl_table_root *root,struct nsproxy *namespaces); -int (*permissions)(struct ctl_table_root *root, -struct nsproxy *namespaces,struct ctl_table *table); +int (*permissions)(struct nsproxy *namespaces, +struct ctl_table *table,int op); };/* struct ctl_table_header is used to maintain dynamic lists of --- a/kernel/sysctl.c +++ b/kernel/sysctl.c
@@ -1706,7 +1706,7 @@ void register_sysctl_root(struct ctl_table_root *root)* some sysctl variables are readonly even to root. */ -static int test_perm (int mode,int op) +int sysctl_test_perm (int mode,int op) {if (!current_euid ()) mode >>=6 ;
@@ -1722,11 +1722,9 @@ int sysctl_perm(struct ctl_table_root *root, struct ctl_table *table, int op)int mode;if (root->permissions) - mode = root->permissions (root, current->nsproxy, table); +return root->permissions (current->nsproxy, table, op);else - mode = table->mode; - -return test_perm (mode, op); +return sysctl_test_perm (table->mode, op); }static void sysctl_set_parent (struct ctl_table *parent,struct ctl_table *table)
---
Then I introduce the kernel.ns_last_pid sysctl that is allows for MAY_OPEN | MAP_WRITE for the namespace's init only and allows for MAY_WRITE for anyone else. Thus, if we want to write to this file from non-init task it must have the respective fd inherited from the init on fork. It works OK for checkpoint/restore.
The patch is:
kernel/pid_namespace.c +52 -0
61 lines not on this page · raw message
static __init int pid_namespaces_init(void)
{
pid_ns_cachep = KMEM_CACHE(pid_namespace, SLAB_PANIC);
+
+ setup_sysctl_set(&pid_ns_root.default_set, NULL, NULL);
+ register_sysctl_root(&pid_ns_root);
+ __register_sysctl_paths(&pid_ns_root, current->nsproxy,
+ kern_path, pid_ns_ctl_table);
+
return 0;
}
Quoted 4 lines
It doesn't have to be perfect. I'm just curious how it looks. IMHO the suggested pid array passing is good enough and not too intrusive but, if there's something simpler from kernel side, given that this is a very specialized interface, I think we definitely need to consider that.
Well, after a bit more thinking I found one more pros for this sysctl - when restoring a container we'll have the possibility to set the last_pid to what we want to prevent the pids reuse after the restore.
Quoted 2 lines
Thank you.
Hello, Pavel.
Quoted 33 lines · Pavel Emelyanov
Then I introduce the kernel.ns_last_pid sysctl that is allows for
MAY_OPEN|MAP_WRITEfor the namespace's init only and allows forMAY_WRITEfor anyone else. Thus, if we want to write to this file from non-init task it must have the respective fd inherited from the init on fork. It works OK for checkpoint/restore.The patch is:
kernel/pid_namespace.c +10
-0@@ -15,6 +15,7 @@#include <linux/acct.h> #include <linux/slab.h> #include <linux/proc_fs.h> +#include <linux/sysctl.h> #define BITS_PER_PAGE (PAGE_SIZE*8)@@ -191,9 +192,54 @@ void zap_pid_ns_processes(struct pid_namespace *pid_ns)return ; } +static int pid_ns_ctl_permissions (struct nsproxy *namespaces, +struct ctl_table *table,int op) +{ +int mode =0644 ; + +if ((op &MAY_OPEN ) && + current != namespaces->pid_ns->child_reaper) +/* +* Writing to this sysctl is allowed only for init
kernel/pid_namespace.c void zap_pid_ns_processes(struct pid_namespace *pid_ns) · line 213
Quoted 10 lines
kernel/pid_namespace.c +10
-0+* and to whoever it grands the open file +*/ + mode &= ~0222 ; + +return sysctl_test_perm (mode, op); +} + +static struct ctl_table_root pid_ns_root = { + .permissions = pid_ns_ctl_permissions, +};
Hmmm... I hope this could be prettier. I'm having trouble following where the MAY_OPEN comes from. Can you please explain? Can't we for now allow this for root and then later allow CAP_CHECKPOINT that Cyrill suggested? Or do we want to allow setting pids even w/o CR for NS creator?
kernel/pid_namespace.c void zap_pid_ns_processes(struct pid_namespace *pid_ns) · line 221
Quoted 7 lines
kernel/pid_namespace.c +7
-0+static int pid_ns_ctl_handler (struct ctl_table *table,int write, +void __user *buffer,size_t *lenp,loff_t *ppos) +{ +struct ctl_table tmp = *table; + tmp.data = ¤t->nsproxy->pid_ns->last_pid; +return proc_dointvec (&tmp, write, buffer, lenp, ppos); +}
Probably better to call set_last_pid() on write path instead?
Quoted 4 lines
Well, after a bit more thinking I found one more pros for this sysctl - when restoring a container we'll have the possibility to set the
last_pidto what we want to prevent the pids reuse after the restore.
Hmmm... I personally like this one better. Restoring multilevel pids would be more tedious but should still be possible and I really like that it's staying out of clone path and all modifications are to ns and pid code. Oleg, what do you think?
Thank you.
Signature · 1 line
tejun
Quoted 23 lines
Quoted 9 lines
kernel/pid_namespace.c +9
-0+static int pid_ns_ctl_permissions (struct nsproxy *namespaces, +struct ctl_table *table,int op) +{ +int mode =0644 ; + +if ((op &MAY_OPEN ) && + current != namespaces->pid_ns->child_reaper) +/* +* Writing to this sysctl is allowed only for init kernel/pid_namespace.c void zap_pid_ns_processes(struct pid_namespace *pid_ns) · line 213
Quoted 10 lines
kernel/pid_namespace.c +10
-0+* and to whoever it grands the open file +*/ + mode &= ~0222 ; + +return sysctl_test_perm (mode, op); +} + +static struct ctl_table_root pid_ns_root = { + .permissions = pid_ns_ctl_permissions, +};Hmmm... I hope this could be prettier. I'm having trouble following where the
MAY_OPENcomes from. Can you please explain?
Quoted 1 line
From this calltrace:
pid_ns_ctl_permissions
sysctl_perm
proc_sys_permission
inode_permission
do_last <<<<< MAY_OPEN appears here
path_openat
do_filp_open
do_sys_open
sys_open
Quoted 3 lines
Can't we for now allow this for root and then later allow
CAP_CHECKPOINTthat Cyrill suggested? Or do we want to allow setting pids even w/o CR for NS creator?
I think that systemd guys can play with it. E.g. respawning daemons with predefined pids sounds like an interesting thing to play with.
Quoted 9 lines
kernel/pid_namespace.c void zap_pid_ns_processes(struct pid_namespace *pid_ns) · line 221
Quoted 7 lines
kernel/pid_namespace.c +7
-0+static int pid_ns_ctl_handler (struct ctl_table *table,int write, +void __user *buffer,size_t *lenp,loff_t *ppos) +{ +struct ctl_table tmp = *table; + tmp.data = ¤t->nsproxy->pid_ns->last_pid; +return proc_dointvec (&tmp, write, buffer, lenp, ppos); +}Probably better to call
set_last_pid()on write path instead?
Why? The usage of this sysctl is going to be synchronized by external locks, so why should we care?
Quoted 12 lines
Quoted 4 lines
Well, after a bit more thinking I found one more pros for this sysctl - when restoring a container we'll have the possibility to set the
last_pidto what we want to prevent the pids reuse after the restore.Hmmm... I personally like this one better. Restoring multilevel pids would be more tedious but should still be possible and I really like that it's staying out of clone path and all modifications are to ns and pid code. Oleg, what do you think?
Thank you.
Quoted 6 lines · Pavel Emelyanov
Quoted 3 lines
Can't we for now allow this for root and then later allow
CAP_CHECKPOINTthat Cyrill suggested? Or do we want to allow setting pids even w/o CR for NS creator?I think that systemd guys can play with it. E.g. respawning daemons with predefined pids sounds like an interesting thing to play with.
This whole userspace C/R stuff and being able to set the child's pid has potential of being very useful for GDB too, allowing a much better reimplementation of its old checkpointing feature [*], and allowing for a faster reverse debugging implementation, by being able to do faster rewinding -- restore snapshot and replay instructions up to N (by single stepping or running to breakpoint), rather than manually undoing the effects of each instruction, one by one.
IOW, root only would be a shame from GDB's perspective.
[*] GDB has an old checkpointing feature ("checkpoint; info checkpoints" commands) based on forcing the tracee to fork, and holding on the fork child behind the scenes as a checkpoint. To restore the debugging state to a previous checkpoint, gdb swaps the current debuggee for the fork child as transparently for the user as was possible. Obviously this has a bunch of limitations and downsides like only working on non-threaded programs, and the inferior's pid changing...
Signature · 1 line
Pedro Alves
Hello,
Quoted 8 lines · Pedro Alves
This whole userspace C/R stuff and being able to set the child's pid has potential of being very useful for GDB too, allowing a much better reimplementation of its old checkpointing feature [*], and allowing for a faster reverse debugging implementation, by being able to do faster rewinding -- restore snapshot and replay instructions up to N (by single stepping or running to breakpoint), rather than manually undoing the effects of each instruction, one by one.
IOW, root only would be a shame from GDB's perspective.
Would CAP_CHECKPOINT be a shame too? I'm reluctant about priviledge through fd inheritance mostly because of its unusualness. I don't think priv management is a good problem space for small creative solutions. We're much better off with mundane mechanisms which people are already familiar with and is easy to account for.
Thank you.
Signature · 1 line
tejun
Hello Tejun,
Quoted 10 lines · Tejun Heo
Hello,
Quoted 7 lines · Pedro Alves
This whole userspace C/R stuff and being able to set the child's pid has potential of being very useful for GDB too, allowing a much better reimplementation of its old checkpointing feature [*], and allowing for a faster reverse debugging implementation, by being able to do faster rewinding -- restore snapshot and replay instructions up to N (by single stepping or running to breakpoint), rather than manually undoing the effects of each instruction, one by one.
Quoted 3 lines
Quoted 1 line
IOW, root only would be a shame from GDB's perspective.
Would
CAP_CHECKPOINTbe a shame too?
I think CAP_CHECKPOINT (or something through some LSM) would be definitely better.
Quoted 5 lines
I'm reluctant about priviledge
through fd inheritance mostly because of its unusualness. I don't think priv management is a good problem space for small creative solutions. We're much better off with mundane mechanisms which people are already familiar with and is easy to account for.
fd inheritance wouldn't work for gdb; a user spawned gdb wouldn't inherit an open fd to kernel.ns_last_pid from anywhere.
Signature · 1 line
Pedro Alves
Hello,
Quoted 5 lines · Pedro Alves
Quoted 1 line
Would
CAP_CHECKPOINTbe a shame too?I think
CAP_CHECKPOINT(or something through some LSM) would be definitely better.
Quoted 8 lines
Quoted 5 lines
I'm reluctant about priviledge
through fd inheritance mostly because of its unusualness. I don't think priv management is a good problem space for small creative solutions. We're much better off with mundane mechanisms which people are already familiar with and is easy to account for.fd inheritance wouldn't work for gdb; a user spawned gdb wouldn't inherit an open fd to kernel.ns_last_pid from anywhere.
I see. So, let's do it for root for now and later add finer grained CAP as necessary/viable. Pavel, what do you think?
Thanks.
Signature · 1 line
tejun
Quoted 3 lines · Tejun Heo
I see. So, let's do it for root for now and later add finer grained CAP as necessary/viable.
Agreed.
Stupid off-topic question, I am just curious... Why /proc/sys dislikes chmod ? I do not understand this code, but it seems that it would be simple to change proc_sys_setattr() to respect ATTR_MODE and update PROC_I(inode)->sysctl_entry->mode, no?
Quoted 1 line
Pavel, what do you think?
Yes...
Oleg.
Hey, Oleg.
Quoted 11 lines · Oleg Nesterov
Quoted 3 lines · Tejun Heo
I see. So, let's do it for root for now and later add finer grained CAP as necessary/viable.
Agreed.
Stupid off-topic question, I am just curious... Why
/proc/sysdislikes chmod ? I do not understand this code, but it seems that it would be simple to changeproc_sys_setattr()to respectATTR_MODEand update PROC_I(inode)->sysctl_entry->mode, no?
I have no idea either but suspect it's something historical. It came from sysctl and sysctl didn't have any way to diddle with permissions, so when later adding proc interface, it probably just stayed that way.
Thanks.
Signature · 1 line
tejun
Quoted 14 lines · Tejun Heo
Hello,
Quoted 11 lines · Pedro Alves
Quoted 1 line
Would
CAP_CHECKPOINTbe a shame too?I think
CAP_CHECKPOINT(or something through some LSM) would be definitely better.Quoted 5 lines
I'm reluctant about priviledge
through fd inheritance mostly because of its unusualness. I don't think priv management is a good problem space for small creative solutions. We're much better off with mundane mechanisms which people are already familiar with and is easy to account for.
Quoted 5 lines
Quoted 2 lines
fd inheritance wouldn't work for gdb; a user spawned gdb wouldn't inherit an open fd to kernel.ns_last_pid from anywhere.
I see. So, let's do it for root for now and later add finer grained CAP as necessary/viable. Pavel, what do you think?
OK, I'll send the respective patches soon.
Quoted 2 lines
Thanks.
Quoted 15 lines · Pavel Emelyanov
Quoted 14 lines · Tejun Heo
Hello,
Quoted 11 lines · Pedro Alves
Quoted 1 line
Would
CAP_CHECKPOINTbe a shame too?I think
CAP_CHECKPOINT(or something through some LSM) would be definitely better.Quoted 5 lines
I'm reluctant about priviledge
through fd inheritance mostly because of its unusualness. I don't think priv management is a good problem space for small creative solutions. We're much better off with mundane mechanisms which people are already familiar with and is easy to account for.
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
fd inheritance wouldn't work for gdb; a user spawned gdb wouldn't inherit an open fd to kernel.ns_last_pid from anywhere.
I see. So, let's do it for root for now and later add finer grained CAP as necessary/viable. Pavel, what do you think?
OK, I'll send the respective patches soon.
Hm... Started testing this stuff and thought about Pedro's wish to use this in gdb one more time :(
The thing is, that we (in checkpoint/restore) are going to use this sysctl when creating a pid namespace from scratch, thus having full control over all the forks happening in this namespace.
But when it comes to the ability for gdb to create a task with a given pid in a _living_ namespace this solution simply won't work! It doesn't guarantee, that after setting the last_pid via sysctl this last_pid stays the same at the time we do call fork()/clone(). Because there are other tasks that can call fork themselves ignoring any lask_pid locking we can play with.
That said I see only two real-life scenarios of how to use _this_ API:
1. creating tasks in a new pid namespace, making sure all the fork-ers care about the proper locking;
2. forking tasks in a loop checking that getpid() returns desired value and hoping that other tasks do not fork() at speed high enough for spoiling every single last_pid value set via sysctl.
Is any of these scenarios suitable for Pedro? Other thoughts on this?
Quoted 3 lines
Quoted 2 lines
Thanks.
Quoted 8 lines · Pavel Emelyanov
Hm... Started testing this stuff and thought about Pedro's wish to use this in gdb one more time :(
The thing is, that we (in checkpoint/restore) are going to use this sysctl when creating a pid namespace from scratch, thus having full control over all the forks happening in this namespace.
Quoted 5 lines
But when it comes to the ability for gdb to create a task with a given pid in a _living_ namespace this solution simply won't work! It doesn't guarantee, that after setting the
last_pidvia sysctl thislast_pidstays the same at the time we do call fork()/clone(). Because there are other tasks that can call fork themselves ignoring anylask_pidlocking we can play with.
Sure. ->last_pid is a hint, nothing more.
But nothing can work reliably on the running system. Even if we can't race with another fork(), the required pid_nr can be already in use.
Oleg.
Quoted 15 lines · Oleg Nesterov
Quoted 8 lines · Pavel Emelyanov
Hm... Started testing this stuff and thought about Pedro's wish to use this in gdb one more time :(
The thing is, that we (in checkpoint/restore) are going to use this sysctl when creating a pid namespace from scratch, thus having full control over all the forks happening in this namespace.
Quoted 5 lines
But when it comes to the ability for gdb to create a task with a given pid in a _living_ namespace this solution simply won't work! It doesn't guarantee, that after setting the
last_pidvia sysctl thislast_pidstays the same at the time we do call fork()/clone(). Because there are other tasks that can call fork themselves ignoring anylask_pidlocking we can play with.
Quoted 6 lines
Sure. ->last_pid is a hint, nothing more.
But nothing can work reliably on the running system. Even if we can't race with another
fork(), the requiredpid_nrcan be already in use.Oleg.
OK, here's another proposal that seem to suit all of us:
1. me wants to clone tasks with pids set
2. Pedro wants to fork task with not changing pids and w/o root perms
3. Oleg and Tejun want to have little intrusion into fork() path
The proposal is to implement the PR_RESERVE_PID prctl which allocates and puts a pid on the current. The subsequent fork() uses this pid, this pid survives and keeps its bit in the pidmap after detach. The 2nd fork() after the 1st task death thus can reuse the same pid again. This basic thing doesn't require root perms at all and safe against pid reuse problems. When requesting for pid reservation task may specify a pid number it wants to have, but this requires root perms (CAP_SYS_ADMIN).
Pedro, I suppose this will work for your checkpoint feature in gdb, am I right?
Few comments about intrusion:
* the common path - if (pid != &init_struct_pid) - on fork is just modified
* we have -1 argument to copy_process
* one more field on struct pid is OK, since it size doesn't change (32 bit level is anyway not required, it's OK to reduce on down to 16 bits)
* no clone flags extension
* no new locking - the reserved pid manipulations happen under tasklist_lock and existing common paths do not require more of it
* yes, we have +1 member on task_struct :(
Current API problems:
* Only one fork() with pid at a time. Next call to PR_RESERVE_PID will kill the previous reservation (don't know how to fix)
* No way to fork() an init of a pid sub-namespace with desired pid in current (can be fixed for a flag for PR_RESERVE_PID saying that we need a pid for a namespace of a next level)
* No way to grab existing pid for reserve (can be fixed, if someone wants this)
Oleg, Tejun, do you agree with such an approach?
---
include/linux/pid.h +10 -2
@@ -57,13 +57,16 @@ struct upid {struct pid {atomic_t count; -unsigned int level; +unsigned short flags; +unsigned short level;/* lists of tasks that use this pid */ struct hlist_head tasks[PIDTYPE_MAX ];struct rcu_head rcu;struct upid numbers[1 ]; }; +#define PID_RESERVED 0x1 +extern struct pid init_struct_pid;struct pid_link
@@ -72,6 +75,11 @@ struct pid_linkstruct pid *pid; }; +int reserve_pid (int nr); +void __unreserve_pid (struct task_struct *p); +void unreserve_pid (void ); +int pid_attached (struct pid *pid); +static inline struct pid *get_pid (struct pid *pid) {if (pid)
@@ -119,7 +127,7 @@ extern struct pid *find_get_pid(int nr);extern struct pid *find_ge_pid (int nr,struct pid_namespace *);int next_pidmap (struct pid_namespace *pid_ns,unsigned int last); -extern struct pid *alloc_pid (struct pid_namespace *ns); +extern struct pid *alloc_pid (struct pid_namespace *ns,int want_pid);extern void free_pid (struct pid *pid);/*
include/linux/prctl.h +3 -0
@@ -102,4 +102,7 @@#define PR_MCE_KILL_GET 34 +#define PR_RESERVE_PID 35 +#define PR_UNRESERVE_PID 36 +#endif /* _LINUX_PRCTL_H */
include/linux/sched.h +1 -0
@@ -1336,6 +1336,7 @@ struct task_struct {struct pid_link pids[PIDTYPE_MAX ];struct list_head thread_group; +struct pid *rsv_pid;struct completion *vfork_done;/* for vfork() */ int __user *set_child_tid;/* CLONE_CHILD_SETTID */ int __user *clear_child_tid;/* CLONE_CHILD_CLEARTID */
kernel/exit.c +2 -0
@@ -177,6 +177,8 @@ repeat:proc_flush_task (p);write_lock_irq (&tasklist_lock); +if (p->rsv_pid) +__unreserve_pid (p);ptrace_release_task (p);__exit_signal (p);
kernel/fork.c +8 -7
@@ -297,6 +297,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig) tsk->btrace_seq =0 ;#endif tsk->splice_pipe =NULL ; + tsk->rsv_pid =NULL ;account_kernel_stack (ti,1 );
@@ -1049,11 +1050,11 @@ static struct task_struct *copy_process(unsigned long clone_flags,struct pt_regs *regs, unsigned long stack_size, int __user *child_tidptr, - struct pid *pid, int trace) { int retval;struct task_struct *p; +struct pid *pid = current->rsv_pid;int cgroup_callbacks_done =0 ;if ((clone_flags & (CLONE_NEWNS |CLONE_FS )) == (CLONE_NEWNS |CLONE_FS ))
@@ -1249,9 +1250,9 @@ static struct task_struct *copy_process(unsigned long clone_flags,if (retval) goto bad_fork_cleanup_io; -if (pid != &init_struct_pid) { +if (pid ==NULL ||pid_attached (pid)) { retval = -ENOMEM ; - pid =alloc_pid (p->nsproxy->pid_ns); + pid =alloc_pid (p->nsproxy->pid_ns,0 );if (!pid) goto bad_fork_cleanup_io; }
@@ -1383,7 +1384,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,return p; bad_fork_free_pid: -if (pid != &init_struct_pid) +if (pid != current->rsv_pid)free_pid (pid); bad_fork_cleanup_io:if (p->io_context)
@@ -1447,8 +1448,8 @@ struct task_struct * __cpuinit fork_idle(int cpu)struct task_struct *task;struct pt_regs regs; - task =copy_process (CLONE_VM ,0 ,idle_regs (®s),0 ,NULL , - &init_struct_pid,0 ); + current->rsv_pid = &init_struct_pid; + task =copy_process (CLONE_VM ,0 ,idle_regs (®s),0 ,NULL ,0 );if (!IS_ERR (task)) {init_idle_pids (task->pids);init_idle (task, cpu);
@@ -1508,7 +1509,7 @@ long do_fork(unsigned long clone_flags, } p =copy_process (clone_flags, stack_start, regs, stack_size, - child_tidptr,NULL , trace); + child_tidptr, trace);/* * Do this prior waking up the new thread - the thread pointer * might get invalid after that point, if the thread exits quickly.
kernel/pid.c +104 -23
187 lines not on this page · raw message
kernel/sys.c +6 -0
@@ -1841,6 +1841,12 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,else error =PR_MCE_KILL_DEFAULT ;break ; +case PR_RESERVE_PID : + error =reserve_pid (arg2); +break ; +case PR_UNRESERVE_PID : +unreserve_pid (); +break ;default : error = -EINVAL ;break ;
Quoted 3 lines · Pavel Emelyanov
The proposal is to implement the
PR_RESERVE_PIDprctl which allocates and puts a pid on the current. The subsequentfork()uses this pid,
Oh. This is subjective, yes, but this doesn't clean to me.
Amd why?? On the running system PR_RESERVE_PID can obviously fail anyway. It only helps to avoid the race with another fork.
Quoted 2 lines
* one more field on struct pid is OK, since it size doesn't change (32 bit level is anyway not required, it's OK to reduce on down to 16 bits)
Even if sizeof is the same, the new member and the code which plays with ->flags doesn't make the things better ;)
Quoted 1 line
* yes, we have +1 member on
task_struct:(
Yes, and this task_struct->rsv_pid acts as implicit parameter for the next clone(). Doesn't look very nice to me. Plus the code complications.
Quoted 1 line
Oleg, Tejun, do you agree with such an approach?
If set_last_pid doesn't work, I'd prefer CLONE_CHILD_USEPIDS.
Oleg.
Quoted 9 lines · Oleg Nesterov
Quoted 3 lines · Pavel Emelyanov
The proposal is to implement the
PR_RESERVE_PIDprctl which allocates and puts a pid on the current. The subsequentfork()uses this pid,Oh. This is subjective, yes, but this doesn't clean to me.
Amd why?? On the running system
PR_RESERVE_PIDcan obviously fail anyway. It only helps to avoid the race with another fork.
No. It can fail if you try to allocate a pid with given number. The API allows for pid generation. AFAIU this can help with Pedro's requirements to resurrect task with the same pid value it used to have before.
Quoted 10 lines
Quoted 2 lines
* one more field on struct pid is OK, since it size doesn't change (32 bit level is anyway not required, it's OK to reduce on down to 16 bits)
Even if sizeof is the same, the new member and the code which plays with ->flags doesn't make the things better ;)
Quoted 1 line
* yes, we have +1 member on
task_struct:(Yes, and this
task_struct->rsv_pidacts as implicit parameter for the nextclone(). Doesn't look very nice to me. Plus the code complications.
Well, the last_pid is also an implicit parameter for the next clone() with sysctl approach :) But the code complication is the problem, yes :(
Quoted 3 lines
Quoted 1 line
Oleg, Tejun, do you agree with such an approach?
If
set_last_piddoesn't work, I'd preferCLONE_CHILD_USEPIDS.
OK, thanks.
Quoted 4 lines
Oleg.
.
Quoted 7 lines · Pavel Emelyanov
Quoted 5 lines · Oleg Nesterov
Quoted 3 lines · Pavel Emelyanov
The proposal is to implement the
PR_RESERVE_PIDprctl which allocates and puts a pid on the current. The subsequentfork()uses this pid,
Quoted 8 lines
Quoted 4 lines
Oh. This is subjective, yes, but this doesn't clean to me.
Amd why?? On the running system
PR_RESERVE_PIDcan obviously fail anyway. It only helps to avoid the race with another fork.No. It can fail if you try to allocate a pid with given number. The API allows for pid generation. AFAIU this can help with Pedro's requirements to resurrect task with the same pid value it used to have before.
Yes gdb can do fork() in a row (until it unreserves the pid) and the pid will be the same.
OK, I misunderstood. I thought you insist that PR_RESERVE_PID itself is reliable.
But this can only work in the simplest case. How you can restore the multithread tracee? You need to unreserve/reserve the previous pid, and we have the same problems again, no?
Quoted 5 lines
Quoted 2 lines
Yes, and this
task_struct->rsv_pidacts as implicit parameter for the nextclone(). Doesn't look very nice to me. Plus the code complications.Well, the
last_pidis also an implicit parameter for the nextclone()with sysctl approach :)
Yes. but it is already here ;)
Oleg.
Quoted 17 lines · Oleg Nesterov
Quoted 7 lines · Pavel Emelyanov
Quoted 5 lines · Oleg Nesterov
Quoted 3 lines · Pavel Emelyanov
The proposal is to implement the
PR_RESERVE_PIDprctl which allocates and puts a pid on the current. The subsequentfork()uses this pid,Quoted 8 lines
Quoted 4 lines
Oh. This is subjective, yes, but this doesn't clean to me.
Amd why?? On the running system
PR_RESERVE_PIDcan obviously fail anyway. It only helps to avoid the race with another fork.No. It can fail if you try to allocate a pid with given number. The API allows for pid generation. AFAIU this can help with Pedro's requirements to resurrect task with the same pid value it used to have before.
Quoted 7 lines
Yes gdb can do
fork()in a row (until it unreserves the pid) and the pid will be the same.OK, I misunderstood. I thought you insist that
PR_RESERVE_PIDitself is reliable.But this can only work in the simplest case.
Yup!
Quoted 1 line
How you can restore the multithread tracee?
Don't know :) But if this approach sounds promising (I see, that now it's not, but...) I can think more on it.
Quoted 1 line
You need to unreserve/reserve the previous pid, and we have the same problems again, no?
With the existing patch - yes, but as I said above - we need to decide which direction to go and then I'll think further.
By now your opinion is to better stay where we are ;) but if moving is unavoidable, then it's better to take the CLONE_CHILD_USEPIDS route. That's my position as well.
Quoted 9 lines
Quoted 5 lines
Quoted 2 lines
Yes, and this
task_struct->rsv_pidacts as implicit parameter for the nextclone(). Doesn't look very nice to me. Plus the code complications.Well, the
last_pidis also an implicit parameter for the nextclone()with sysctl approach :)Yes. but it is already here ;)
Oleg.
Quoted 10 lines · Pavel Emelyanov
Quoted 1 line · Oleg Nesterov
How you can restore the multithread tracee?
Don't know :) But if this approach sounds promising (I see, that now it's not, but...) I can think more on it.
Quoted 1 line
You need to unreserve/reserve the previous pid, and we have the same problems again, no?
With the existing patch - yes, but as I said above - we need to decide which direction to go and then I'll think further.
Thanks for thinking about all this. Being able to reserve pids would be nice, but I won't pretend to know the kernel's internals enough to be able to suggest a sane and acceptable way to do it. We'd have to be able to restore multi-threaded tracees (which would also mean that there are pids which leaders and others which are clones), and, we'd have to support a single-threaded tracer debugging (and spawning) more than one process, while not all tracees are involved in C/R. Maybe this (reservation) issue should be be considered an orthogonal mechanism for now.
Quoted 4 lines
By now your opinion is to better stay where we are ;) but if moving is unavoidable, then it's better to take the
CLONE_CHILD_USEPIDSroute. That's my position as well.From the perspective of a client that is
going to use this on a live system, CLONE_CHILD_USEPIDS seems a little better, in that the pid race is only against another task reusing the same pid,
while with setting last_pid, you have a try/whoops-not-the-pid-I-want/kill/retry/rinse/repeat/ loop racing against all fork/clone's in the system, along with possibly
needing to first to do a kill(PID, 0) to check whether the PID is
available (unless setting last_pid already detects that).
BTW, it's not only GDB that would want this for live systems. Check out Berkeley Lab's C/R (https://ftg.lbl.gov/projects/CheckpointRestart/),
where these guys use mixed kernel/userspace C/R in clusters for high-end scientific computing to e.g., migrate tasks between nodes, and pause/resume parallel MPI jobs (on live systems). (Apologies if everyone already knows about this :-) .)
Quoted 1 line
From what I read from their papers, in their approach, from userspace, they
spawn new children as usual, with whatever pids the kernel wants, and then afterwards (from userspace, but through a kernel module), magically change the process and threads's pids to the pids they really want. They also fixup the parent pids, and session ids after the fact, along the way.
Signature · 1 line
Pedro Alves
Re: [RFC][PATCH 0/3] fork: Add the ability to create tasks with?given pids
Quoted 4 lines · Pedro Alves
From the perspective of a client that is
going to use this on a live system,CLONE_CHILD_USEPIDSseems a little better, in that the pid race is only against another task reusing the same pid,
Yes. Except you can't use on a live system at all. It simply doesn't work after the first "normal" clone() without CLONE_CHILD_USEPIDS.
Although we can probably allow CLONE_CHILD_USEPIDS for CAP_SYS_ADMIN.
Quoted 2 lines
while with setting
last_pid, you have a try/whoops-not-the-pid-I-want/kill/retry/rinse/repeat/ loop racing against all fork/clone's in the system,
Yes, setting last_pid can race with another fork(). I agree this sucks. But simple ;)
Oleg.
Quoted 28 lines · Pavel Emelyanov
OK, here's another proposal that seem to suit all of us:
1. me wants to clone tasks with pids set
2. Pedro wants to fork task with not changing pids and w/o root perms
3. Oleg and Tejun want to have little intrusion intofork()pathThe proposal is to implement the
PR_RESERVE_PIDprctl which allocates and puts a pid on the current. The subsequentfork()uses this pid, this pid survives and keeps its bit in the pidmap after detach. The 2ndfork()after the 1st task death thus can reuse the same pid again. This basic thing doesn't require root perms at all and safe against pid reuse problems. When requesting for pid reservation task may specify a pid number it wants to have, but this requires root perms (CAP_SYS_ADMIN).Pedro, I suppose this will work for your checkpoint feature in gdb, am I right?
Few comments about intrusion:
* the common path - if (pid !=&init_struct_pid) - on fork is just modified
* we have -1 argument tocopy_process
* one more field on struct pid is OK, since it size doesn't change (32 bit level is anyway not required, it's OK to reduce on down to 16 bits)
* no clone flags extension
* no new locking - the reserved pid manipulations happen undertasklist_lockand existing common paths do not require more of it
* yes, we have +1 member ontask_struct:(Current API problems:
Quoted 7 lines
* Only one
fork()with pid at a time. Next call toPR_RESERVE_PIDwill kill the previous reservation (don't know how to fix)
* No way tofork()an init of a pid sub-namespace with desired pid in current (can be fixed for a flag forPR_RESERVE_PIDsaying that we need a pid for a namespace of a next level)* No way to grab existing pid for reserve (can be fixed, if someone wants this)
We can add flag to sys_wait4(), and stash pid in wait_task_zombie(), right before release_task() code will looks something like this:
- if (p != NULL)
+ if (p != NULL) {
+ if ((wo->wo_flags & WCATCHPID) && !current->pid_stash) {
+ struct pid *pid = task_pid(p);
+
+ pid->flags |= PID_STASHED;
+ current->pid_stash = get_pid(pid);
+ }
release_task(p);
+ }
And next fork() creates child with the same pid.
So, struct pid will work like boomerang =)
Quoted 15 lines · Konstantin Khlebnikov
We can add flag to
sys_wait4(), and stash pid inwait_task_zombie(), right beforerelease_task()code will looks something like this:(quoted diff) +8
-1- if (p != NULL) + if (p != NULL) { + if ((wo->wo_flags & WCATCHPID) && !current->pid_stash) { + struct pid *pid = task_pid(p); + + pid->flags |= PID_STASHED; + current->pid_stash = get_pid(pid); + } release_task(p); + }
Quoted 2 lines
And next
fork()creates child with the same pid.
So, struct pid will work like boomerang =)
Like PR_RESERVE_PID, this can only help if the tracee (or whatever) is single-threaded and it is the natural child.
Personally I do not think such a limited interface makes sense.
Oleg.
Hello, Pavel.
Quoted 28 lines · Pavel Emelyanov
OK, here's another proposal that seem to suit all of us:
1. me wants to clone tasks with pids set
2. Pedro wants to fork task with not changing pids and w/o root perms
3. Oleg and Tejun want to have little intrusion intofork()pathThe proposal is to implement the
PR_RESERVE_PIDprctl which allocates and puts a pid on the current. The subsequentfork()uses this pid, this pid survives and keeps its bit in the pidmap after detach. The 2ndfork()after the 1st task death thus can reuse the same pid again. This basic thing doesn't require root perms at all and safe against pid reuse problems. When requesting for pid reservation task may specify a pid number it wants to have, but this requires root perms (CAP_SYS_ADMIN).Pedro, I suppose this will work for your checkpoint feature in gdb, am I right?
Few comments about intrusion:
* the common path - if (pid !=
&init_struct_pid) - on fork is just modified
* we have -1 argument tocopy_process
* one more field on struct pid is OK, since it size doesn't change (32 bit level is anyway not required, it's OK to reduce on down to 16 bits)
* no clone flags extension
* no new locking - the reserved pid manipulations happen undertasklist_lockand existing common paths do not require more of it
* yes, we have +1 member ontask_struct:(Current API problems:
Quoted 8 lines
* Only one
fork()with pid at a time. Next call toPR_RESERVE_PIDwill kill the previous reservation (don't know how to fix)
* No way tofork()an init of a pid sub-namespace with desired pid in current (can be fixed for a flag forPR_RESERVE_PIDsaying that we need a pid for a namespace of a next level)
* No way to grab existing pid for reserve (can be fixed, if someone wants this)Oleg, Tejun, do you agree with such an approach?
Hmmm... Any attempt to reserve PIDs without full control over the namespace is futile. It can never be complete / reliable. Let's just forget about it. If anyone, including gdb, wants to have fun with CR, let them manage namespace too; otherwise, it's never gonna be reliable.
If you take the above out, setting last_pid is as simple as it gets and good enough. It's essentially few tens of lines of code to add userland interface for setting one pid_t value. Let's restrict manipulation to root for now and see whether finer grained CAP_* makes sense as we go along.
Thanks.
Signature · 1 line
tejun
Quoted 38 lines · Tejun Heo
Hello, Pavel.
Quoted 35 lines · Pavel Emelyanov
OK, here's another proposal that seem to suit all of us:
1. me wants to clone tasks with pids set
2. Pedro wants to fork task with not changing pids and w/o root perms
3. Oleg and Tejun want to have little intrusion intofork()pathThe proposal is to implement the
PR_RESERVE_PIDprctl which allocates and puts a pid on the current. The subsequentfork()uses this pid, this pid survives and keeps its bit in the pidmap after detach. The 2ndfork()after the 1st task death thus can reuse the same pid again. This basic thing doesn't require root perms at all and safe against pid reuse problems. When requesting for pid reservation task may specify a pid number it wants to have, but this requires root perms (CAP_SYS_ADMIN).Pedro, I suppose this will work for your checkpoint feature in gdb, am I right?
Few comments about intrusion:
* the common path - if (pid !=
&init_struct_pid) - on fork is just modified
* we have -1 argument tocopy_process
* one more field on struct pid is OK, since it size doesn't change (32 bit level is anyway not required, it's OK to reduce on down to 16 bits)
* no clone flags extension
* no new locking - the reserved pid manipulations happen undertasklist_lockand existing common paths do not require more of it
* yes, we have +1 member ontask_struct:(Current API problems:
* Only one
fork()with pid at a time. Next call toPR_RESERVE_PIDwill kill the previous reservation (don't know how to fix)
* No way tofork()an init of a pid sub-namespace with desired pid in current (can be fixed for a flag forPR_RESERVE_PIDsaying that we need a pid for a namespace of a next level)
* No way to grab existing pid for reserve (can be fixed, if someone wants this)
Quoted 4 lines
Quoted 1 line
Oleg, Tejun, do you agree with such an approach?
Hmmm... Any attempt to reserve PIDs without full control over the namespace is futile. It can never be complete / reliable.
Why? What's the _real_ problem with the
pid = prctl(PR_RESERVE_PID, 0); /* let the kernel _generate_ a pid for us */
while (1) {
real_pid = fork();
BUG_ON(pid != real_pid);
if (real_pid == 0)
return do_child();
wait();
}model? Let's temporarily forget about the single reserved pid implementation limitation and concentrate on the approach itself.
Quoted 10 lines
Let's just
forget about it. If anyone, including gdb, wants to have fun with CR, let them manage namespace too; otherwise, it's never gonna be reliable.If you take the above out, setting
last_pidis as simple as it gets and good enough. It's essentially few tens of lines of code to add userland interface for setting onepid_tvalue. Let's restrict manipulation to root for now and see whether finer grained CAP_* makes sense as we go along.
That's OK for me, I'll send the patches soon, but I'd like to hear for some sane explanation of the above.
Quoted 2 lines
Thanks.
Hello, Pavel.
Quoted 15 lines · Pavel Emelyanov
Quoted 2 lines
Hmmm... Any attempt to reserve PIDs without full control over the namespace is futile. It can never be complete / reliable.
Why? What's the _real_ problem with the
pid =
prctl(PR_RESERVE_PID, 0); /* let the kernel _generate_ a pid for us */while (1) { real_pid = fork(); BUG_ON(pid != real_pid); if (real_pid == 0) return do_child(); wait(); }
Quoted 2 lines
model? Let's temporarily forget about the single reserved pid implementation limitation and concentrate on the approach itself.
PID is ns-shared resource. If you don't have full control over it and there are other tasks allocating from it, there is no way to reserve specific pid reliably no matter what you do. The only things you can do are - either reserve the pids you want before anyone else takes it or somehow revoke pids held by other tasks.
Full ns control + set_last_pid essentially gives the ns owner full reservation + a way to control allocation.
I suppose you're suggesting that with reserve approach, we can also support recycling pids of existing tasks which is suggested to be useful for systemd and gdb.
Using this kind of black magic for general system management seems like a really bad idea to me. It is extremely obscure and unexpected and we actually should be looking to dissuade such usage even if the natural implementation of the mechanism allows for it.
For gdb, it *might* be useful but the usage isn't out there yet and the suggested mechanism isn't enough to support the suggested usage (ie. multithread). We have neither concrete problem or solution.
So, let's do the simple 30 line non-invasive thing now and worry about the complex problem when it's actually necessary. It's not like the ability to set last_pid is gonna interfere with future changes or anything.
Thanks.
Signature · 1 line
tejun
Hello,
Quoted 5 lines · Pavel Emelyanov
Quoted 2 lines
Hmmm... I hope this could be prettier. I'm having trouble following where the
MAY_OPENcomes from. Can you please explain?From this calltrace:
Quoted 9 lines
pid_ns_ctl_permissions
sysctl_perm
proc_sys_permission
inode_permission
do_last<<<<<MAY_OPENappears here
path_openat
do_filp_open
do_sys_open
sys_open
Thanks a lot. :)
Quoted 6 lines
Quoted 3 lines
Can't we for now allow this for root and then later allow
CAP_CHECKPOINTthat Cyrill suggested? Or do we want to allow setting pids even w/o CR for NS creator?I think that systemd guys can play with it. E.g. respawning daemons with predefined pids sounds like an interesting thing to play with.
But wouldn't CAP_CHECKPOINT be enough for systemd?
Quoted 10 lines
Quoted 9 lines
kernel/pid_namespace.c void zap_pid_ns_processes(struct pid_namespace *pid_ns) · line 221
Quoted 7 lines
kernel/pid_namespace.c +7
-0+static int pid_ns_ctl_handler (struct ctl_table *table,int write, +void __user *buffer,size_t *lenp,loff_t *ppos) +{ +struct ctl_table tmp = *table; + tmp.data = ¤t->nsproxy->pid_ns->last_pid; +return proc_dointvec (&tmp, write, buffer, lenp, ppos); +}Probably better to call
set_last_pid()on write path instead?
Quoted 2 lines
Why? The usage of this sysctl is going to be synchronized by external locks, so why should we care?
I think the question should usually be the other way around. Why deviate when the deviation doesn't earn any tangible benefit? If you think setting it explicitly is justified, explain why in the comment of the setter and places where those explicit settings are.
Thanks.
Signature · 1 line
tejun
Quoted 4 lines · Tejun Heo
I think the question should usually be the other way around. Why deviate when the deviation doesn't earn any tangible benefit? If you think setting it explicitly is justified, explain why in the comment of the setter and places where those explicit settings are.
Hmmm... I think I trimmed a bit too much. Let me add back a bit.
So, either just use set_last_pid() or explain in the comment of set_last_pid() that there are other places which set last_pid but it won't race because they're synchronized through outer lock and similar comment where the explicit setting of last_pid is too. As it currently stands, it really is difficult to find out who else would be changing last_pid - it's buried in an argument to a proc parse function. IMHO just using set_last_pid() would be better here.
Thanks.
Signature · 1 line
tejun
Quoted 20 lines · Tejun Heo
Hello,
Quoted 5 lines · Pavel Emelyanov
Quoted 2 lines
Hmmm... I hope this could be prettier. I'm having trouble following where the
MAY_OPENcomes from. Can you please explain?From this calltrace:
Quoted 9 lines
pid_ns_ctl_permissions
sysctl_perm
proc_sys_permission
inode_permission
do_last<<<<<MAY_OPENappears here
path_openat
do_filp_open
do_sys_open
sys_openThanks a lot. :)
Quoted 8 lines
Quoted 6 lines
Quoted 3 lines
Can't we for now allow this for root and then later allow
CAP_CHECKPOINTthat Cyrill suggested? Or do we want to allow setting pids even w/o CR for NS creator?I think that systemd guys can play with it. E.g. respawning daemons with predefined pids sounds like an interesting thing to play with.
But wouldn't
CAP_CHECKPOINTbe enough for systemd?
It would, but what's the point in granting to a systemd (which can be a container's init by the way) the ability to use the _whole_ checkpoint/restore engine?
Even more - protecting with the capability implies, that any task might want to play with it. But what's the point for an arbitrary task, that just _lives_ in a pid namespace to set the last_pid of its namespace?
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
kernel/pid_namespace.c void zap_pid_ns_processes(struct pid_namespace *pid_ns) · line 221
Quoted 7 lines
kernel/pid_namespace.c +7
-0+static int pid_ns_ctl_handler (struct ctl_table *table,int write, +void __user *buffer,size_t *lenp,loff_t *ppos) +{ +struct ctl_table tmp = *table; + tmp.data = ¤t->nsproxy->pid_ns->last_pid; +return proc_dointvec (&tmp, write, buffer, lenp, ppos); +}Probably better to call
set_last_pid()on write path instead?
Quoted 7 lines
Quoted 2 lines
Why? The usage of this sysctl is going to be synchronized by external locks, so why should we care?
I think the question should usually be the other way around. Why deviate when the deviation doesn't earn any tangible benefit? If you think setting it explicitly is justified, explain why in the comment of the setter and places where those explicit settings are.
The set_last_pid() is the way to update the last_pid by two concurrent updaters. Since setting the last_pid via sysctl is racy by its nature, using that race protection is just pointless.
And yes, I agree, that writing this comment is a good idea :)
Quoted 2 lines
Thanks.
Quoted 8 lines · Pavel Emelyanov
Quoted 5 lines
Quoted 3 lines
I think that systemd guys can play with it. E.g. respawning daemons with predefined pids sounds like an interesting thing to play with.
But wouldn't
CAP_CHECKPOINTbe enough for systemd?It would, but what's the point in granting to a systemd (which can be a container's init by the way) the ability to use the _whole_ checkpoint/restore engine?
Christ, stop making it sound like we would *want* systemd to do even more odd things.
Quite frankly, any feature that is sold with ".. and systemd can use this fox Xyz", is a *misfeature* in my opinion. Core infrastructure like systemd should use a *minimal* interface, not some random extended features.
I'm starting to really dislike this whole feature discussion.
Linus
Quoted 10 lines · Linus Torvalds
Quoted 8 lines · Pavel Emelyanov
Quoted 5 lines
Quoted 3 lines
I think that systemd guys can play with it. E.g. respawning daemons with predefined pids sounds like an interesting thing to play with.
But wouldn't
CAP_CHECKPOINTbe enough for systemd?It would, but what's the point in granting to a systemd (which can be a container's init by the way) the ability to use the _whole_ checkpoint/restore engine?
Quoted 7 lines
Christ, stop making it sound like we would *want* systemd to do even more odd things.
Quite frankly, any feature that is sold with ".. and systemd can use this fox Xyz", is a *misfeature* in my opinion. Core infrastructure like systemd should use a *minimal* interface, not some random extended features.
Damn, surely it should use a minimal! But our opinion doesn't prevent this daemon from doing very weird stuff, and (believe it or not) I'm not trying to sell this feature for systemd. Just trying to minimize the impact from systemd's use of it :(
Quoted 5 lines
I'm starting to really dislike this whole feature discussion.
Linus
.
Thanks,
Pavel
Quoted 10 lines · Linus Torvalds
Quoted 8 lines · Pavel Emelyanov
Quoted 5 lines
Quoted 3 lines
I think that systemd guys can play with it. E.g. respawning daemons with predefined pids sounds like an interesting thing to play with.
But wouldn't
CAP_CHECKPOINTbe enough for systemd?It would, but what's the point in granting to a systemd (which can be a container's init by the way) the ability to use the _whole_ checkpoint/restore engine?
Quoted 7 lines
Christ, stop making it sound like we would *want* systemd to do even more odd things.
Quite frankly, any feature that is sold with ".. and systemd can use this fox Xyz", is a *misfeature* in my opinion. Core infrastructure like systemd should use a *minimal* interface, not some random extended features.
No worries, there is no plan from the systemd maintainers side to use or need predictable PIDs. We still start, stop and restart service the old-school way, and will leave checkpoint/restart to somebody else. :)
Kay
Quoted 10 lines · Tejun Heo
kernel/pid_namespace.c void zap_pid_ns_processes(struct pid_namespace *pid_ns) · line 221
Quoted 8 lines
kernel/pid_namespace.c +7
-0+static int pid_ns_ctl_handler (struct ctl_table *table,int write, +void __user *buffer,size_t *lenp,loff_t *ppos) +{ +struct ctl_table tmp = *table; + tmp.data = ¤t->nsproxy->pid_ns->last_pid; +return proc_dointvec (&tmp, write, buffer, lenp, ppos); +}Probably better to call
set_last_pid()on write path instead?
I am not sure... set_last_pid() is "special", it tries to avoid the races with itself to prevent the pid-reuse. It doesn't hurt, but perhaps
set_last_pid(pid_ns, pid_ns->last_pid, new_pid);
looks a bit confusing.
Hmm. On the second thought, perhaps this makes sense anyway. Just to keep the "only set_last_pid() can change ->last_pid" property.
But this is almost cosmetic.
Quoted 9 lines
Quoted 4 lines
Well, after a bit more thinking I found one more pros for this sysctl - when restoring a container we'll have the possibility to set the
last_pidto what we want to prevent the pids reuse after the restore.Hmmm... I personally like this one better. Restoring multilevel pid would be more tedious but should still be possible and I really like that it's staying out of clone path and all modifications are to ns and pid code. Oleg, what do you think?
Obviously, I'd prefer this one too ;)
But. Personally I do not like the fact that only init can open this file for writing... (I guess Pavel already hates me ;)
If we add this sysctl, then I think there should be some way to use outside of "checkpoint-restore" world. For example, see the comment from Pedro. This use-case looks unexpected (to me), but reasonable. Or. Say, set_last_pid can be useful to test the pid-reuse races.
In any case. To me, it is not really good to have /proc/*/set_last_pid without the ability to use it somehow on the running system.
Oleg.