diff options
author | Ming Lei <ming.lei@canonical.com> | 2013-06-15 04:36:38 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-06-18 13:41:55 -0400 |
commit | 875979368eb4cfecff9f0e97625b90cc6009269d (patch) | |
tree | e10845e63381a741f60465d808d9b39f18217c6f /drivers/base/firmware_class.c | |
parent | 7d132055814ef17a6c7b69f342244c410a5e000f (diff) |
firmware loader: fix use-after-free by double abort
fw_priv->buf is accessed in both request_firmware_load() and
writing to sysfs file of 'loading' context, but not protected
by 'fw_lock' entirely. The patch makes sure that access on
'fw_priv->buf' is protected by the lock.
So fixes the double abort problem reported by nirinA raseliarison:
http://lkml.org/lkml/2013/6/14/188
Reported-and-tested-by: nirinA raseliarison <nirina.raseliarison@gmail.com>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: stable <stable@vger.kernel.org> # 3.9
Signed-off-by: Ming Lei <ming.lei@canonical.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/base/firmware_class.c')
-rw-r--r-- | drivers/base/firmware_class.c | 27 |
1 files changed, 18 insertions, 9 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 4b1f9265887f..01e21037d8fe 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c | |||
@@ -450,8 +450,18 @@ static void fw_load_abort(struct firmware_priv *fw_priv) | |||
450 | { | 450 | { |
451 | struct firmware_buf *buf = fw_priv->buf; | 451 | struct firmware_buf *buf = fw_priv->buf; |
452 | 452 | ||
453 | /* | ||
454 | * There is a small window in which user can write to 'loading' | ||
455 | * between loading done and disappearance of 'loading' | ||
456 | */ | ||
457 | if (test_bit(FW_STATUS_DONE, &buf->status)) | ||
458 | return; | ||
459 | |||
453 | set_bit(FW_STATUS_ABORT, &buf->status); | 460 | set_bit(FW_STATUS_ABORT, &buf->status); |
454 | complete_all(&buf->completion); | 461 | complete_all(&buf->completion); |
462 | |||
463 | /* avoid user action after loading abort */ | ||
464 | fw_priv->buf = NULL; | ||
455 | } | 465 | } |
456 | 466 | ||
457 | #define is_fw_load_aborted(buf) \ | 467 | #define is_fw_load_aborted(buf) \ |
@@ -528,7 +538,12 @@ static ssize_t firmware_loading_show(struct device *dev, | |||
528 | struct device_attribute *attr, char *buf) | 538 | struct device_attribute *attr, char *buf) |
529 | { | 539 | { |
530 | struct firmware_priv *fw_priv = to_firmware_priv(dev); | 540 | struct firmware_priv *fw_priv = to_firmware_priv(dev); |
531 | int loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status); | 541 | int loading = 0; |
542 | |||
543 | mutex_lock(&fw_lock); | ||
544 | if (fw_priv->buf) | ||
545 | loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status); | ||
546 | mutex_unlock(&fw_lock); | ||
532 | 547 | ||
533 | return sprintf(buf, "%d\n", loading); | 548 | return sprintf(buf, "%d\n", loading); |
534 | } | 549 | } |
@@ -570,12 +585,12 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
570 | const char *buf, size_t count) | 585 | const char *buf, size_t count) |
571 | { | 586 | { |
572 | struct firmware_priv *fw_priv = to_firmware_priv(dev); | 587 | struct firmware_priv *fw_priv = to_firmware_priv(dev); |
573 | struct firmware_buf *fw_buf = fw_priv->buf; | 588 | struct firmware_buf *fw_buf; |
574 | int loading = simple_strtol(buf, NULL, 10); | 589 | int loading = simple_strtol(buf, NULL, 10); |
575 | int i; | 590 | int i; |
576 | 591 | ||
577 | mutex_lock(&fw_lock); | 592 | mutex_lock(&fw_lock); |
578 | 593 | fw_buf = fw_priv->buf; | |
579 | if (!fw_buf) | 594 | if (!fw_buf) |
580 | goto out; | 595 | goto out; |
581 | 596 | ||
@@ -777,10 +792,6 @@ static void firmware_class_timeout_work(struct work_struct *work) | |||
777 | struct firmware_priv, timeout_work.work); | 792 | struct firmware_priv, timeout_work.work); |
778 | 793 | ||
779 | mutex_lock(&fw_lock); | 794 | mutex_lock(&fw_lock); |
780 | if (test_bit(FW_STATUS_DONE, &(fw_priv->buf->status))) { | ||
781 | mutex_unlock(&fw_lock); | ||
782 | return; | ||
783 | } | ||
784 | fw_load_abort(fw_priv); | 795 | fw_load_abort(fw_priv); |
785 | mutex_unlock(&fw_lock); | 796 | mutex_unlock(&fw_lock); |
786 | } | 797 | } |
@@ -861,8 +872,6 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, | |||
861 | 872 | ||
862 | cancel_delayed_work_sync(&fw_priv->timeout_work); | 873 | cancel_delayed_work_sync(&fw_priv->timeout_work); |
863 | 874 | ||
864 | fw_priv->buf = NULL; | ||
865 | |||
866 | device_remove_file(f_dev, &dev_attr_loading); | 875 | device_remove_file(f_dev, &dev_attr_loading); |
867 | err_del_bin_attr: | 876 | err_del_bin_attr: |
868 | device_remove_bin_file(f_dev, &firmware_attr_data); | 877 | device_remove_bin_file(f_dev, &firmware_attr_data); |