sys_getppid oopses on debug kernel (v2)
Quoted 14 lines · Andrew Morton
Although I'm not sure it's needed for this problem. A
getppid()which doesasmlinkage long
sys_getppid(void){ int pid; read_lock(&tasklist_lock); pid = current->group_leader->real_parent->tgid; read_unlock(&tasklist_lock); return pid; }
Quoted 1 line
seems like a fine implementation to me ;)
Why do we need to use ->group_leader? All threads should have the same ->real_parent.
Why do we need tasklist_lock? I think rcu_read_lock() is enough.
In other words, do you see any problems with this code
smlinkage long sys_getppid(void)
{
int pid;
rcu_read_lock();
pid = rcu_dereference(current->real_parent)->tgid;
rcu_read_unlock();
return pid;
}? Yes, we may read a stale value for ->real_parent, but the memory can't be freed while we are under rcu_read_lock(). And in this case the returned value is ok because the task could be reparented just after return anyway.
Oleg.
Quoted 10 lines
Quoted 10 lines
Although I'm not sure it's needed for this problem. A
getppid()which doesasmlinkage long
sys_getppid(void){ int pid; read_lock(&tasklist_lock); pid = current->group_leader->real_parent->tgid; read_unlock(&tasklist_lock);
Quoted 8 lines
Quoted 4 lines
return pid; }seems like a fine implementation to me ;)
Why do we need to use ->group_leader? All threads should have the same ->real_parent.
I'm not sure this is true for old LinuxThreads...
Quoted 15 lines
Why do we need
tasklist_lock? I thinkrcu_read_lock()is enough.In other words, do you see any problems with this code
smlinkage long sys_getppid(void) { int pid; rcu_read_lock(); pid = rcu_dereference(current->real_parent)->tgid; rcu_read_unlock(); return pid; }
Quoted 4 lines
? Yes, we may read a stale value for ->real_parent, but the memory can't be freed while we are under
rcu_read_lock(). And in this case the returned value is ok because the task could be reparented just after return anyway.
Your patch doesn't cure the problem.rcu_read_lock just disables preemtion and rcu_dereference introduces memory barrier. _None_ of this _prevents_
another CPU from freeing old real_parent in parallel with your dereference.
You can minimize the probability very much by making local_irq_disable()/enable() around the code in question, but still it won't be a real fix (at least due to NMIs).
Kirill
Quoted 4 lines · Kirill Korotaev
Quoted 3 lines
Why do we need to use ->group_leader? All threads should have the same ->real_parent.
I'm not sure this is true for old LinuxThreads...
Yes, from the user-space pov it may be not true, but ->group_leader->real_parent should be equal ->real_parent anyway.
Quoted 15 lines
Quoted 15 lines
Why do we need
tasklist_lock? I thinkrcu_read_lock()is enough.In other words, do you see any problems with this code
smlinkage long sys_getppid(void) { int pid; rcu_read_lock(); pid = rcu_dereference(current->real_parent)->tgid; rcu_read_unlock(); return pid; }
Quoted 8 lines
Quoted 4 lines
? Yes, we may read a stale value for ->real_parent, but the memory can't be freed while we are under
rcu_read_lock(). And in this case the returned value is ok because the task could be reparented just after return anyway.Your patch doesn't cure the problem.
rcu_read_lockjust disables preemtion andrcu_dereferenceintroduces memory barrier. _None_ of this _prevents_
another CPU from freeing oldreal_parentin parallel with your dereference.
How so? Note that release_task() doesn't call put_task_struct(), it does call_rcu(&p->rcu, delayed_put_task_struct) instead. When delayed_put_task_struct() is called, all CPUs must see the new value of ->real_parent (otherwise RCU is just broken). If CPU sees the old value of ->real_parent, rcu_read_lock() protects us from delayed_put_task_struct() on another CPU.
Ok, I think this is the same "classic" pattern as:
old = global_ptr; global_ptr = new; call_rcu(..free_old...); vs rcu_read_lock(); use(global_ptr); rcu_read_unlock();
Do you agree?
Oleg.
Quoted 22 lines
Quoted 4 lines
Your patch doesn't cure the problem.
rcu_read_lockjust disables preemtion andrcu_dereferenceintroduces memory barrier. _None_ of this _prevents_
another CPU from freeing oldreal_parentin parallel with your dereference.How so? Note that
release_task()doesn't callput_task_struct(), it doescall_rcu(&p->rcu, delayed_put_task_struct)instead. Whendelayed_put_task_struct()is called, all CPUs must see the new value of ->real_parent (otherwise RCU is just broken). If CPU sees the old value of ->real_parent,rcu_read_lock()protects us fromdelayed_put_task_struct()on another CPU.Ok, I think this is the same "classic" pattern as:
old = global_ptr; global_ptr = new; call_rcu(..free_old...); vs rcu_read_lock(); use(global_ptr); rcu_read_unlock();
Quoted 1 line
Do you agree?
Agree :)
Sorry, I didn't notice that task structs are freed now with RCU :))) In this case your patch really helps the problem. Care to prepare it?
Thanks,
Kirill
PATCH 1/1 sys_getppid-oopses-on-debug-kernel-v2-simplify
On top of Kirill's sys_getppid-oopses-on-debug-kernel-v2.patch
- We don't need ->group_leader->real_parent, all threads should have the same ->real_parent.
- We don't need tasklist_lock, task_struct is freed by RCU, so rcu_read_lock() should be enough.
(Compile tested)
kernel/timer.c renamed from kernel/timer.c~ +7 -17
@@ -1324,28 +1324,18 @@ asmlinkage long sys_getpid(void) }/* -* Accessing ->group_leader->real_parent is not SMP-safe, it could -* change from under us. However, rather than getting any lock -* we can use an optimistic algorithm: get the parent -* pid, and go back and check that the parent is still -* the same. If it has changed (which is extremely unlikely -* indeed), we just try again.. -* -* NOTE! This depends on the fact that even if we _do_ -* get an old value of "parent", we can happily dereference -* the pointer (it was and remains a dereferencable kernel pointer -* no matter what): we just can't necessarily trust the result -* until we know that the parent pointer is valid. -* -* NOTE2: ->group_leader never changes from under us. +* Accessing ->real_parent is not SMP-safe, it could +* change from under us. However, we can use a stale +* value of ->real_parent under rcu_read_lock(), see +* release_task()->call_rcu(delayed_put_task_struct). */ asmlinkage long sys_getppid (void ) {int pid; -read_lock (&tasklist_lock); - pid = current->group_leader->real_parent->tgid; -read_unlock (&tasklist_lock); +rcu_read_lock (); + pid =rcu_dereference (current->real_parent)->tgid; +rcu_read_unlock ();return pid; }