]> git.hungrycats.org Git - linux/commitdiff
[PATCH] Fix proc_pid_lookup vs exit race
authorAndrew Morton <akpm@osdl.org>
Mon, 29 Dec 2003 13:44:02 +0000 (05:44 -0800)
committerLinus Torvalds <torvalds@home.osdl.org>
Mon, 29 Dec 2003 13:44:02 +0000 (05:44 -0800)
From: Manfred Spraul <manfred@colorfullife.com>

Fixes a race between proc_pid_lookup and sys_exit.

- The inodes and dentries for /proc/<pid>/whatever are cached in the dentry
  cache.  d_revalidate is used to protect against stale data: d_revalidate
  returns invalid if the task exited.

  Additionally, sys_exit flushes the dentries for the task that died -
  otherwise the dentries would stay around until they arrive at the end of
  the LRU, which could take some time.  But there is one race:

  - proc_pid_lookup finds a task and prepares new dentries for it. It must
    drop all locks for that operation.
  - the process exits, and the /proc/ dentries are flushed. Nothing
    happens, because they are not yet in the hash tables.
  - proc_pid_lookup adds the task to the dentry cache.

  Result: dentry of a dead task in the hash tables.

  The patch fixes that problem by flushing again if proc_pid_lookup notices
  that the thread exited while it created the dentry.  The patch should go
  in, but it's not critical.

- task->proc_dentry must be the dentry of /proc/<pid>.  That way sys_exit
  can flush the whole subtree at exit time.  proc_task_lookup is a direct
  copy of proc_pid_lookup and handles /proc/<>/task/<pid>.  It contains the
  lines that set task->proc_dentry.  This is bogus, and must be removed.

  This hunk is much more critical, because creates a de-facto dentry leak
  (they are recovered after flushing real dentries from the cache).

fs/proc/base.c

index b04942e0746b072585337b769ec28dbe188e1ea5..f16a555f7933980f8d8ab4a21bf7e0f0e73f73cc 100644 (file)
@@ -1555,6 +1555,7 @@ struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, struct
        struct inode *inode;
        struct proc_inode *ei;
        unsigned tgid;
+       int died;
 
        if (dentry->d_name.len == 4 && !memcmp(dentry->d_name.name,"self",4)) {
                inode = new_inode(dir->i_sb);
@@ -1598,12 +1599,21 @@ struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, struct
 
        dentry->d_op = &pid_base_dentry_operations;
 
+       died = 0;
+       d_add(dentry, inode);
        spin_lock(&task->proc_lock);
        task->proc_dentry = dentry;
-       d_add(dentry, inode);
+       if (!pid_alive(task)) {
+               dentry = proc_pid_unhash(task);
+               died = 1;
+       }
        spin_unlock(&task->proc_lock);
 
        put_task_struct(task);
+       if (died) {
+               proc_pid_flush(dentry);
+               goto out;
+       }
        return NULL;
 out:
        return ERR_PTR(-ENOENT);
@@ -1643,10 +1653,7 @@ static struct dentry *proc_task_lookup(struct inode *dir, struct dentry * dentry
 
        dentry->d_op = &pid_base_dentry_operations;
 
-       spin_lock(&task->proc_lock);
-       task->proc_dentry = dentry;
        d_add(dentry, inode);
-       spin_unlock(&task->proc_lock);
 
        put_task_struct(task);
        return NULL;