Fix races around the access to ->s_options
Put generic_show_options read access to s_options under rcu_read_lock,
split save_mount_options() into "we are setting it the first time"
(uses in foo_fill_super()) and "we are relacing and freeing the old one",
synchronize_rcu() before kfree() in the latter.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/drivers/isdn/capi/capifs.c b/drivers/isdn/capi/capifs.c
index b129409..8f9f3b5 100644
--- a/drivers/isdn/capi/capifs.c
+++ b/drivers/isdn/capi/capifs.c
@@ -75,8 +75,7 @@
}
}
- kfree(s->s_options);
- s->s_options = new_opt;
+ replace_mount_options(s, new_opt);
config.setuid = setuid;
config.setgid = setgid;
diff --git a/fs/affs/super.c b/fs/affs/super.c
index 5ce695e..63f5183 100644
--- a/fs/affs/super.c
+++ b/fs/affs/super.c
@@ -507,8 +507,7 @@
kfree(new_opts);
return -EINVAL;
}
- kfree(sb->s_options);
- sb->s_options = new_opts;
+ replace_mount_options(sb, new_opts);
sbi->s_flags = mount_flags;
sbi->s_mode = mode;
diff --git a/fs/afs/super.c b/fs/afs/super.c
index 2753f16..76828e5 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -408,17 +408,17 @@
deactivate_locked_super(sb);
goto error;
}
- sb->s_options = new_opts;
+ save_mount_options(sb, new_opts);
sb->s_flags |= MS_ACTIVE;
} else {
_debug("reuse");
- kfree(new_opts);
ASSERTCMP(sb->s_flags, &, MS_ACTIVE);
}
simple_set_mnt(mnt, sb);
afs_put_volume(params.volume);
afs_put_cell(params.cell);
+ kfree(new_opts);
_leave(" = 0 [%p]", sb);
return 0;
diff --git a/fs/hpfs/super.c b/fs/hpfs/super.c
index fecf402..fc77965 100644
--- a/fs/hpfs/super.c
+++ b/fs/hpfs/super.c
@@ -423,8 +423,7 @@
if (!(*flags & MS_RDONLY)) mark_dirty(s);
- kfree(s->s_options);
- s->s_options = new_opts;
+ replace_mount_options(s, new_opts);
return 0;
diff --git a/fs/namespace.c b/fs/namespace.c
index 0d2003f..134d494 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -695,12 +695,16 @@
*/
int generic_show_options(struct seq_file *m, struct vfsmount *mnt)
{
- const char *options = mnt->mnt_sb->s_options;
+ const char *options;
+
+ rcu_read_lock();
+ options = rcu_dereference(mnt->mnt_sb->s_options);
if (options != NULL && options[0]) {
seq_putc(m, ',');
mangle(m, options);
}
+ rcu_read_unlock();
return 0;
}
@@ -721,11 +725,22 @@
*/
void save_mount_options(struct super_block *sb, char *options)
{
- kfree(sb->s_options);
- sb->s_options = kstrdup(options, GFP_KERNEL);
+ BUG_ON(sb->s_options);
+ rcu_assign_pointer(sb->s_options, kstrdup(options, GFP_KERNEL));
}
EXPORT_SYMBOL(save_mount_options);
+void replace_mount_options(struct super_block *sb, char *options)
+{
+ char *old = sb->s_options;
+ rcu_assign_pointer(sb->s_options, options);
+ if (old) {
+ synchronize_rcu();
+ kfree(old);
+ }
+}
+EXPORT_SYMBOL(replace_mount_options);
+
#ifdef CONFIG_PROC_FS
/* iterator */
static void *m_start(struct seq_file *m, loff_t *pos)
diff --git a/fs/reiserfs/super.c b/fs/reiserfs/super.c
index d444fe0..1215a4f 100644
--- a/fs/reiserfs/super.c
+++ b/fs/reiserfs/super.c
@@ -1316,8 +1316,7 @@
}
out_ok:
- kfree(s->s_options);
- s->s_options = new_opts;
+ replace_mount_options(s, new_opts);
return 0;
out_err:
diff --git a/include/linux/fs.h b/include/linux/fs.h
index ed78842..3b534e5 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2368,6 +2368,7 @@
extern int generic_show_options(struct seq_file *m, struct vfsmount *mnt);
extern void save_mount_options(struct super_block *sb, char *options);
+extern void replace_mount_options(struct super_block *sb, char *options);
static inline ino_t parent_ino(struct dentry *dentry)
{