diff options
author | Alex Chiang <achiang@hp.com> | 2009-03-13 14:07:36 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-03-24 19:38:26 -0400 |
commit | 669420644c79c207f83fdf9105ae782867e2991f (patch) | |
tree | 668491b3700bcc65e45d5ff9471f6fde5d5743af | |
parent | ffa6a7054d172a2f57248dff2de600ca795c5656 (diff) |
sysfs: only allow one scheduled removal callback per kobj
The only way for a sysfs attribute to remove itself (without
deadlock) is to use the sysfs_schedule_callback() interface.
Vegard Nossum discovered that a poorly written sysfs ->store
callback can repeatedly schedule remove callbacks on the same
device over and over, e.g.
$ while true ; do echo 1 > /sys/devices/.../remove ; done
If the 'remove' attribute uses the sysfs_schedule_callback API
and also does not protect itself from concurrent accesses, its
callback handler will be called multiple times, and will
eventually attempt to perform operations on a freed kobject,
leading to many problems.
Instead of requiring all callers of sysfs_schedule_callback to
implement their own synchronization, provide the protection in
the infrastructure.
Now, sysfs_schedule_callback will only allow one scheduled
callback per kobject. On subsequent calls with the same kobject,
return -EAGAIN.
This is a short term fix. The long term fix is to allow sysfs
attributes to remove themselves directly, without any of this
callback hokey pokey.
[cornelia.huck@de.ibm.com: s390 ccwgroup bits]
Reported-by: vegard.nossum@gmail.com
Signed-off-by: Alex Chiang <achiang@hp.com>
Acked-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | drivers/s390/cio/ccwgroup.c | 5 | ||||
-rw-r--r-- | fs/sysfs/file.c | 26 |
2 files changed, 26 insertions, 5 deletions
diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c index 918e6fce2573..b91c1719b075 100644 --- a/drivers/s390/cio/ccwgroup.c +++ b/drivers/s390/cio/ccwgroup.c | |||
@@ -104,8 +104,9 @@ ccwgroup_ungroup_store(struct device *dev, struct device_attribute *attr, const | |||
104 | rc = device_schedule_callback(dev, ccwgroup_ungroup_callback); | 104 | rc = device_schedule_callback(dev, ccwgroup_ungroup_callback); |
105 | out: | 105 | out: |
106 | if (rc) { | 106 | if (rc) { |
107 | /* Release onoff "lock" when ungrouping failed. */ | 107 | if (rc != -EAGAIN) |
108 | atomic_set(&gdev->onoff, 0); | 108 | /* Release onoff "lock" when ungrouping failed. */ |
109 | atomic_set(&gdev->onoff, 0); | ||
109 | return rc; | 110 | return rc; |
110 | } | 111 | } |
111 | return count; | 112 | return count; |
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 1f4a3f877262..289c43a47263 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c | |||
@@ -659,13 +659,16 @@ void sysfs_remove_file_from_group(struct kobject *kobj, | |||
659 | EXPORT_SYMBOL_GPL(sysfs_remove_file_from_group); | 659 | EXPORT_SYMBOL_GPL(sysfs_remove_file_from_group); |
660 | 660 | ||
661 | struct sysfs_schedule_callback_struct { | 661 | struct sysfs_schedule_callback_struct { |
662 | struct kobject *kobj; | 662 | struct list_head workq_list; |
663 | struct kobject *kobj; | ||
663 | void (*func)(void *); | 664 | void (*func)(void *); |
664 | void *data; | 665 | void *data; |
665 | struct module *owner; | 666 | struct module *owner; |
666 | struct work_struct work; | 667 | struct work_struct work; |
667 | }; | 668 | }; |
668 | 669 | ||
670 | static DEFINE_MUTEX(sysfs_workq_mutex); | ||
671 | static LIST_HEAD(sysfs_workq); | ||
669 | static void sysfs_schedule_callback_work(struct work_struct *work) | 672 | static void sysfs_schedule_callback_work(struct work_struct *work) |
670 | { | 673 | { |
671 | struct sysfs_schedule_callback_struct *ss = container_of(work, | 674 | struct sysfs_schedule_callback_struct *ss = container_of(work, |
@@ -674,6 +677,9 @@ static void sysfs_schedule_callback_work(struct work_struct *work) | |||
674 | (ss->func)(ss->data); | 677 | (ss->func)(ss->data); |
675 | kobject_put(ss->kobj); | 678 | kobject_put(ss->kobj); |
676 | module_put(ss->owner); | 679 | module_put(ss->owner); |
680 | mutex_lock(&sysfs_workq_mutex); | ||
681 | list_del(&ss->workq_list); | ||
682 | mutex_unlock(&sysfs_workq_mutex); | ||
677 | kfree(ss); | 683 | kfree(ss); |
678 | } | 684 | } |
679 | 685 | ||
@@ -695,15 +701,25 @@ static void sysfs_schedule_callback_work(struct work_struct *work) | |||
695 | * until @func returns. | 701 | * until @func returns. |
696 | * | 702 | * |
697 | * Returns 0 if the request was submitted, -ENOMEM if storage could not | 703 | * Returns 0 if the request was submitted, -ENOMEM if storage could not |
698 | * be allocated, -ENODEV if a reference to @owner isn't available. | 704 | * be allocated, -ENODEV if a reference to @owner isn't available, |
705 | * -EAGAIN if a callback has already been scheduled for @kobj. | ||
699 | */ | 706 | */ |
700 | int sysfs_schedule_callback(struct kobject *kobj, void (*func)(void *), | 707 | int sysfs_schedule_callback(struct kobject *kobj, void (*func)(void *), |
701 | void *data, struct module *owner) | 708 | void *data, struct module *owner) |
702 | { | 709 | { |
703 | struct sysfs_schedule_callback_struct *ss; | 710 | struct sysfs_schedule_callback_struct *ss, *tmp; |
704 | 711 | ||
705 | if (!try_module_get(owner)) | 712 | if (!try_module_get(owner)) |
706 | return -ENODEV; | 713 | return -ENODEV; |
714 | |||
715 | mutex_lock(&sysfs_workq_mutex); | ||
716 | list_for_each_entry_safe(ss, tmp, &sysfs_workq, workq_list) | ||
717 | if (ss->kobj == kobj) { | ||
718 | mutex_unlock(&sysfs_workq_mutex); | ||
719 | return -EAGAIN; | ||
720 | } | ||
721 | mutex_unlock(&sysfs_workq_mutex); | ||
722 | |||
707 | ss = kmalloc(sizeof(*ss), GFP_KERNEL); | 723 | ss = kmalloc(sizeof(*ss), GFP_KERNEL); |
708 | if (!ss) { | 724 | if (!ss) { |
709 | module_put(owner); | 725 | module_put(owner); |
@@ -715,6 +731,10 @@ int sysfs_schedule_callback(struct kobject *kobj, void (*func)(void *), | |||
715 | ss->data = data; | 731 | ss->data = data; |
716 | ss->owner = owner; | 732 | ss->owner = owner; |
717 | INIT_WORK(&ss->work, sysfs_schedule_callback_work); | 733 | INIT_WORK(&ss->work, sysfs_schedule_callback_work); |
734 | INIT_LIST_HEAD(&ss->workq_list); | ||
735 | mutex_lock(&sysfs_workq_mutex); | ||
736 | list_add_tail(&ss->workq_list, &sysfs_workq); | ||
737 | mutex_unlock(&sysfs_workq_mutex); | ||
718 | schedule_work(&ss->work); | 738 | schedule_work(&ss->work); |
719 | return 0; | 739 | return 0; |
720 | } | 740 | } |