Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[S390] cio: fix ccwgroup unregistration race condition

A race condition exists in the ccwgroup device unregistration code
which can cause a kernel panic due to a use-after-free bug. This
race condition might be triggered when all ccw devices associated with
a ccwgroup device are removed at the same time (e.g. because the
corresponding channel path becomes no longer available).

Fix this race condition by clearing the references from the associated
ccw devices to the ccw group device during unregistration of the
ccw group device.

Signed-off-by: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>

authored by

Peter Oberparleiter and committed by
Martin Schwidefsky
c0301754 f602be63

+40 -38
+40 -38
drivers/s390/cio/ccwgroup.c
··· 67 67 } 68 68 69 69 /* 70 + * Remove references from ccw devices to ccw group device and from 71 + * ccw group device to ccw devices. 72 + */ 73 + static void __ccwgroup_remove_cdev_refs(struct ccwgroup_device *gdev) 74 + { 75 + struct ccw_device *cdev; 76 + int i; 77 + 78 + for (i = 0; i < gdev->count; i++) { 79 + cdev = gdev->cdev[i]; 80 + if (!cdev) 81 + continue; 82 + spin_lock_irq(cdev->ccwlock); 83 + dev_set_drvdata(&cdev->dev, NULL); 84 + spin_unlock_irq(cdev->ccwlock); 85 + gdev->cdev[i] = NULL; 86 + put_device(&cdev->dev); 87 + } 88 + } 89 + 90 + /* 70 91 * Provide an 'ungroup' attribute so the user can remove group devices no 71 92 * longer needed or accidentially created. Saves memory :) 72 93 */ ··· 99 78 if (device_is_registered(&gdev->dev)) { 100 79 __ccwgroup_remove_symlinks(gdev); 101 80 device_unregister(dev); 81 + __ccwgroup_remove_cdev_refs(gdev); 102 82 } 103 83 mutex_unlock(&gdev->reg_mutex); 104 84 } ··· 138 116 static void 139 117 ccwgroup_release (struct device *dev) 140 118 { 141 - struct ccwgroup_device *gdev; 142 - int i; 143 - 144 - gdev = to_ccwgroupdev(dev); 145 - 146 - for (i = 0; i < gdev->count; i++) { 147 - if (gdev->cdev[i]) { 148 - spin_lock_irq(gdev->cdev[i]->ccwlock); 149 - if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev) 150 - dev_set_drvdata(&gdev->cdev[i]->dev, NULL); 151 - spin_unlock_irq(gdev->cdev[i]->ccwlock); 152 - put_device(&gdev->cdev[i]->dev); 153 - } 154 - } 155 - kfree(gdev); 119 + kfree(to_ccwgroupdev(dev)); 156 120 } 157 121 158 122 static int ··· 647 639 mutex_lock(&gdev->reg_mutex); 648 640 __ccwgroup_remove_symlinks(gdev); 649 641 device_unregister(dev); 642 + __ccwgroup_remove_cdev_refs(gdev); 650 643 mutex_unlock(&gdev->reg_mutex); 651 644 put_device(dev); 652 645 } ··· 669 660 return 0; 670 661 } 671 662 672 - static struct ccwgroup_device * 673 - __ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev) 674 - { 675 - struct ccwgroup_device *gdev; 676 - 677 - gdev = dev_get_drvdata(&cdev->dev); 678 - if (gdev) { 679 - if (get_device(&gdev->dev)) { 680 - mutex_lock(&gdev->reg_mutex); 681 - if (device_is_registered(&gdev->dev)) 682 - return gdev; 683 - mutex_unlock(&gdev->reg_mutex); 684 - put_device(&gdev->dev); 685 - } 686 - return NULL; 687 - } 688 - return NULL; 689 - } 690 - 691 663 /** 692 664 * ccwgroup_remove_ccwdev() - remove function for slave devices 693 665 * @cdev: ccw device to be removed ··· 684 694 /* Ignore offlining errors, device is gone anyway. */ 685 695 ccw_device_set_offline(cdev); 686 696 /* If one of its devices is gone, the whole group is done for. */ 687 - gdev = __ccwgroup_get_gdev_by_cdev(cdev); 688 - if (gdev) { 697 + spin_lock_irq(cdev->ccwlock); 698 + gdev = dev_get_drvdata(&cdev->dev); 699 + if (!gdev) { 700 + spin_unlock_irq(cdev->ccwlock); 701 + return; 702 + } 703 + /* Get ccwgroup device reference for local processing. */ 704 + get_device(&gdev->dev); 705 + spin_unlock_irq(cdev->ccwlock); 706 + /* Unregister group device. */ 707 + mutex_lock(&gdev->reg_mutex); 708 + if (device_is_registered(&gdev->dev)) { 689 709 __ccwgroup_remove_symlinks(gdev); 690 710 device_unregister(&gdev->dev); 691 - mutex_unlock(&gdev->reg_mutex); 692 - put_device(&gdev->dev); 711 + __ccwgroup_remove_cdev_refs(gdev); 693 712 } 713 + mutex_unlock(&gdev->reg_mutex); 714 + /* Release ccwgroup device reference for local processing. */ 715 + put_device(&gdev->dev); 694 716 } 695 717 696 718 MODULE_LICENSE("GPL");