aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRussell Currey <ruscur@russell.cc>2017-04-19 03:39:26 -0400
committerMichael Ellerman <mpe@ellerman.id.au>2017-05-02 08:41:43 -0400
commitdaeba2956f32f91f3493788ff6ee02fb1b2f02fa (patch)
tree8c87a0fdeb9e2078a24846ea41c0a242daf0c26a
parenta715626a8e904e7226915d1bc4885317ea9da141 (diff)
powerpc/eeh: Avoid use after free in eeh_handle_special_event()
eeh_handle_special_event() is called when an EEH event is detected but can't be narrowed down to a specific PE. This function looks through every PE to find one in an erroneous state, then calls the regular event handler eeh_handle_normal_event() once it knows which PE has an error. However, if eeh_handle_normal_event() found that the PE cannot possibly be recovered, it will free it, rendering the passed PE stale. This leads to a use after free in eeh_handle_special_event() as it attempts to clear the "recovering" state on the PE after eeh_handle_normal_event() returns. Thus, make sure the PE is valid when attempting to clear state in eeh_handle_special_event(). Fixes: 8a6b1bc70dbb ("powerpc/eeh: EEH core to handle special event") Cc: stable@vger.kernel.org # v3.11+ Reported-by: Alexey Kardashevskiy <aik@ozlabs.ru> Signed-off-by: Russell Currey <ruscur@russell.cc> Reviewed-by: Gavin Shan <gwshan@linux.vnet.ibm.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
-rw-r--r--arch/powerpc/kernel/eeh_driver.c19
1 files changed, 15 insertions, 4 deletions
diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c
index b94887165a10..e50d1470714f 100644
--- a/arch/powerpc/kernel/eeh_driver.c
+++ b/arch/powerpc/kernel/eeh_driver.c
@@ -724,7 +724,7 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus,
724 */ 724 */
725#define MAX_WAIT_FOR_RECOVERY 300 725#define MAX_WAIT_FOR_RECOVERY 300
726 726
727static void eeh_handle_normal_event(struct eeh_pe *pe) 727static bool eeh_handle_normal_event(struct eeh_pe *pe)
728{ 728{
729 struct pci_bus *frozen_bus; 729 struct pci_bus *frozen_bus;
730 struct eeh_dev *edev, *tmp; 730 struct eeh_dev *edev, *tmp;
@@ -736,7 +736,7 @@ static void eeh_handle_normal_event(struct eeh_pe *pe)
736 if (!frozen_bus) { 736 if (!frozen_bus) {
737 pr_err("%s: Cannot find PCI bus for PHB#%x-PE#%x\n", 737 pr_err("%s: Cannot find PCI bus for PHB#%x-PE#%x\n",
738 __func__, pe->phb->global_number, pe->addr); 738 __func__, pe->phb->global_number, pe->addr);
739 return; 739 return false;
740 } 740 }
741 741
742 eeh_pe_update_time_stamp(pe); 742 eeh_pe_update_time_stamp(pe);
@@ -870,7 +870,7 @@ static void eeh_handle_normal_event(struct eeh_pe *pe)
870 pr_info("EEH: Notify device driver to resume\n"); 870 pr_info("EEH: Notify device driver to resume\n");
871 eeh_pe_dev_traverse(pe, eeh_report_resume, NULL); 871 eeh_pe_dev_traverse(pe, eeh_report_resume, NULL);
872 872
873 return; 873 return false;
874 874
875excess_failures: 875excess_failures:
876 /* 876 /*
@@ -915,8 +915,12 @@ perm_error:
915 pci_lock_rescan_remove(); 915 pci_lock_rescan_remove();
916 pci_hp_remove_devices(frozen_bus); 916 pci_hp_remove_devices(frozen_bus);
917 pci_unlock_rescan_remove(); 917 pci_unlock_rescan_remove();
918
919 /* The passed PE should no longer be used */
920 return true;
918 } 921 }
919 } 922 }
923 return false;
920} 924}
921 925
922static void eeh_handle_special_event(void) 926static void eeh_handle_special_event(void)
@@ -982,7 +986,14 @@ static void eeh_handle_special_event(void)
982 */ 986 */
983 if (rc == EEH_NEXT_ERR_FROZEN_PE || 987 if (rc == EEH_NEXT_ERR_FROZEN_PE ||
984 rc == EEH_NEXT_ERR_FENCED_PHB) { 988 rc == EEH_NEXT_ERR_FENCED_PHB) {
985 eeh_handle_normal_event(pe); 989 /*
990 * eeh_handle_normal_event() can make the PE stale if it
991 * determines that the PE cannot possibly be recovered.
992 * Don't modify the PE state if that's the case.
993 */
994 if (eeh_handle_normal_event(pe))
995 continue;
996
986 eeh_pe_state_clear(pe, EEH_PE_RECOVERING); 997 eeh_pe_state_clear(pe, EEH_PE_RECOVERING);
987 } else { 998 } else {
988 pci_lock_rescan_remove(); 999 pci_lock_rescan_remove();