[PATCH] md / procfs: avoid Oops if md-mod removed while /proc/mdstat is being polled.

From: NeilBrown
Date: Thu Feb 27 2014 - 01:25:13 EST




If poll or select is waiting on /proc/mdstat when md-mod is unloaded
an oops will ensure when the poll/select completes.

This is because the wait_queue_head which is registered with poll_wait()
is local to the module and no longer exists when the poll completes and
detaches that wait_queue_head (in poll_free_wait -> remove_wait_queue).

To fix this we need the wait_queue_head to have (at least) the same life
time as the proc_dir_entry. So this patch places it in that structure.

We:
- add pde_poll_wait to struct proc_dir_entry
- call poll_wait() passing this when poll() is called on the proc file
- export a function proc_wake_up which will call wake_up() on pde_poll_wait

and make use of all that in md.c

Reported-by: "majianpeng" <majianpeng@xxxxxxxxx>
Signed-off-by: NeilBrown <neilb@xxxxxxx>

--

Do we have a maintainer for fs/proc ??
If I could get a couple of Acks, or constructive comments, on this,
I would appreciate it.
Thanks,
NeilBrown


diff --git a/drivers/md/md.c b/drivers/md/md.c
index 4ad5cc4e63e8..1bf70d9c55d3 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -193,12 +193,12 @@ EXPORT_SYMBOL_GPL(bio_clone_mddev);
* start array, stop array, error, add device, remove device,
* start build, activate spare
*/
-static DECLARE_WAIT_QUEUE_HEAD(md_event_waiters);
+static struct proc_dir_entry *mdstat_pde;
static atomic_t md_event_count;
void md_new_event(struct mddev *mddev)
{
atomic_inc(&md_event_count);
- wake_up(&md_event_waiters);
+ proc_wake_up(mdstat_pde);
}
EXPORT_SYMBOL_GPL(md_new_event);

@@ -208,7 +208,7 @@ EXPORT_SYMBOL_GPL(md_new_event);
static void md_new_event_inintr(struct mddev *mddev)
{
atomic_inc(&md_event_count);
- wake_up(&md_event_waiters);
+ proc_wake_up(mdstat_pde);
}

/*
@@ -7187,8 +7187,6 @@ static unsigned int mdstat_poll(struct file *filp, poll_table *wait)
struct seq_file *seq = filp->private_data;
int mask;

- poll_wait(filp, &md_event_waiters, wait);
-
/* always allow read */
mask = POLLIN | POLLRDNORM;

@@ -8557,7 +8555,7 @@ static void md_geninit(void)
{
pr_debug("md: sizeof(mdp_super_t) = %d\n", (int)sizeof(mdp_super_t));

- proc_create("mdstat", S_IRUGO, NULL, &md_seq_fops);
+ mdstat_pde = proc_create("mdstat", S_IRUGO, NULL, &md_seq_fops);
}

static int __init md_init(void)
diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index b7f268eb5f45..c579da4cd765 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -357,6 +357,7 @@ static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent,
atomic_set(&ent->count, 1);
spin_lock_init(&ent->pde_unload_lock);
INIT_LIST_HEAD(&ent->pde_openers);
+ init_waitqueue_head(&ent->pde_poll_wait);
out:
return ent;
}
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 124fc43c7090..353fc199e8b5 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -234,13 +234,21 @@ static unsigned int proc_reg_poll(struct file *file, struct poll_table_struct *p
unsigned int (*poll)(struct file *, struct poll_table_struct *);
if (use_pde(pde)) {
poll = pde->proc_fops->poll;
- if (poll)
+ if (poll) {
+ poll_wait(file, &pde->pde_poll_wait, pts);
rv = poll(file, pts);
+ }
unuse_pde(pde);
}
return rv;
}

+void proc_wake_up(struct proc_dir_entry *pde)
+{
+ wake_up(&pde->pde_poll_wait);
+}
+EXPORT_SYMBOL_GPL(proc_wake_up);
+
static long proc_reg_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct proc_dir_entry *pde = PDE(file_inode(file));
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 651d09a11dde..6f9f84eecded 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -46,6 +46,7 @@ struct proc_dir_entry {
struct completion *pde_unload_completion;
struct list_head pde_openers; /* who did ->open, but not ->release */
spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
+ wait_queue_head_t pde_poll_wait; /* For proc_reg_poll */
u8 namelen;
char name[];
};
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 608e60a74c3c..a4a3d5f001ef 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -34,6 +34,7 @@ static inline struct proc_dir_entry *proc_create(
return proc_create_data(name, mode, parent, proc_fops, NULL);
}

+extern void proc_wake_up(struct proc_dir_entry *pde);
extern void proc_set_size(struct proc_dir_entry *, loff_t);
extern void proc_set_user(struct proc_dir_entry *, kuid_t, kgid_t);
extern void *PDE_DATA(const struct inode *);

Attachment: signature.asc
Description: PGP signature