diff options
author | Junxiao Bi <junxiao.bi@oracle.com> | 2013-11-21 17:31:56 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-11-21 19:42:27 -0500 |
commit | 76ae281f6307331aa063288edb6422ae99f435f0 (patch) | |
tree | 4e26896b837b1c22009039f4f8a78dbc2b8500e8 /fs/configfs | |
parent | 527d1511310a89650000081869260394e20c7013 (diff) |
configfs: fix race between dentry put and lookup
A race window in configfs, it starts from one dentry is UNHASHED and end
before configfs_d_iput is called. In this window, if a lookup happen,
since the original dentry was UNHASHED, so a new dentry will be
allocated, and then in configfs_attach_attr(), sd->s_dentry will be
updated to the new dentry. Then in configfs_d_iput(),
BUG_ON(sd->s_dentry != dentry) will be triggered and system panic.
sys_open: sys_close:
... fput
dput
dentry_kill
__d_drop <--- dentry unhashed here,
but sd->dentry still point
to this dentry.
lookup_real
configfs_lookup
configfs_attach_attr---> update sd->s_dentry
to new allocated dentry here.
d_kill
configfs_d_iput <--- BUG_ON(sd->s_dentry != dentry)
triggered here.
To fix it, change configfs_d_iput to not update sd->s_dentry if
sd->s_count > 2, that means there are another dentry is using the sd
beside the one that is going to be put. Use configfs_dirent_lock in
configfs_attach_attr to sync with configfs_d_iput.
With the following steps, you can reproduce the bug.
1. enable ocfs2, this will mount configfs at /sys/kernel/config and
fill configure in it.
2. run the following script.
while [ 1 ]; do cat /sys/kernel/config/cluster/$your_cluster_name/idle_timeout_ms > /dev/null; done &
while [ 1 ]; do cat /sys/kernel/config/cluster/$your_cluster_name/idle_timeout_ms > /dev/null; done &
Signed-off-by: Junxiao Bi <junxiao.bi@oracle.com>
Cc: Joel Becker <jlbec@evilplan.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/configfs')
-rw-r--r-- | fs/configfs/dir.c | 16 |
1 files changed, 14 insertions, 2 deletions
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c index 4522e0755773..e081acbac2e7 100644 --- a/fs/configfs/dir.c +++ b/fs/configfs/dir.c | |||
@@ -56,10 +56,19 @@ static void configfs_d_iput(struct dentry * dentry, | |||
56 | struct configfs_dirent *sd = dentry->d_fsdata; | 56 | struct configfs_dirent *sd = dentry->d_fsdata; |
57 | 57 | ||
58 | if (sd) { | 58 | if (sd) { |
59 | BUG_ON(sd->s_dentry != dentry); | ||
60 | /* Coordinate with configfs_readdir */ | 59 | /* Coordinate with configfs_readdir */ |
61 | spin_lock(&configfs_dirent_lock); | 60 | spin_lock(&configfs_dirent_lock); |
62 | sd->s_dentry = NULL; | 61 | /* Coordinate with configfs_attach_attr where will increase |
62 | * sd->s_count and update sd->s_dentry to new allocated one. | ||
63 | * Only set sd->dentry to null when this dentry is the only | ||
64 | * sd owner. | ||
65 | * If not do so, configfs_d_iput may run just after | ||
66 | * configfs_attach_attr and set sd->s_dentry to null | ||
67 | * even it's still in use. | ||
68 | */ | ||
69 | if (atomic_read(&sd->s_count) <= 2) | ||
70 | sd->s_dentry = NULL; | ||
71 | |||
63 | spin_unlock(&configfs_dirent_lock); | 72 | spin_unlock(&configfs_dirent_lock); |
64 | configfs_put(sd); | 73 | configfs_put(sd); |
65 | } | 74 | } |
@@ -416,8 +425,11 @@ static int configfs_attach_attr(struct configfs_dirent * sd, struct dentry * den | |||
416 | struct configfs_attribute * attr = sd->s_element; | 425 | struct configfs_attribute * attr = sd->s_element; |
417 | int error; | 426 | int error; |
418 | 427 | ||
428 | spin_lock(&configfs_dirent_lock); | ||
419 | dentry->d_fsdata = configfs_get(sd); | 429 | dentry->d_fsdata = configfs_get(sd); |
420 | sd->s_dentry = dentry; | 430 | sd->s_dentry = dentry; |
431 | spin_unlock(&configfs_dirent_lock); | ||
432 | |||
421 | error = configfs_create(dentry, (attr->ca_mode & S_IALLUGO) | S_IFREG, | 433 | error = configfs_create(dentry, (attr->ca_mode & S_IALLUGO) | S_IFREG, |
422 | configfs_init_file); | 434 | configfs_init_file); |
423 | if (error) { | 435 | if (error) { |