ACPI: PM: Fix NULL argument handling in acpi_device_get/set_power()
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 29 Aug 2022 15:21:43 +0000 (17:21 +0200)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Tue, 30 Aug 2022 12:22:51 +0000 (14:22 +0200)
In principle, it should be valid to pass NULL as the ACPI device
pointer to acpi_device_get_power() and acpi_device_set_power() and they
both are expected to return -EINVAL in that case, but that has been
broken recently by commit 62fcb99bdf10 ("ACPI: Drop parent field from
struct acpi_device") which has caused the ACPI device pointer to be
dereferenced in these functions before the NULL check.

Fix that and while at it make acpi_device_set_power() only use the
parent field if the target ACPI device object's ignore_parent flag
in not set.

Fixes: 62fcb99bdf10 ("ACPI: Drop parent field from struct acpi_device")
Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/acpi/device_pm.c

index 028d8d1..3aca67c 100644 (file)
@@ -74,13 +74,15 @@ static int acpi_dev_pm_explicit_get(struct acpi_device *device, int *state)
  */
 int acpi_device_get_power(struct acpi_device *device, int *state)
 {
-       struct acpi_device *parent = acpi_dev_parent(device);
        int result = ACPI_STATE_UNKNOWN;
+       struct acpi_device *parent;
        int error;
 
        if (!device || !state)
                return -EINVAL;
 
+       parent = acpi_dev_parent(device);
+
        if (!device->flags.power_manageable) {
                /* TBD: Non-recursive algorithm for walking up hierarchy. */
                *state = parent ? parent->power.state : ACPI_STATE_D0;
@@ -159,7 +161,6 @@ static int acpi_dev_pm_explicit_set(struct acpi_device *adev, int state)
  */
 int acpi_device_set_power(struct acpi_device *device, int state)
 {
-       struct acpi_device *parent = acpi_dev_parent(device);
        int target_state = state;
        int result = 0;
 
@@ -192,13 +193,17 @@ int acpi_device_set_power(struct acpi_device *device, int state)
                return -ENODEV;
        }
 
-       if (!device->power.flags.ignore_parent && parent &&
-           state < parent->power.state) {
-               acpi_handle_debug(device->handle,
-                                 "Cannot transition to %s for parent in %s\n",
-                                 acpi_power_state_string(state),
-                                 acpi_power_state_string(parent->power.state));
-               return -ENODEV;
+       if (!device->power.flags.ignore_parent) {
+               struct acpi_device *parent;
+
+               parent = acpi_dev_parent(device);
+               if (parent && state < parent->power.state) {
+                       acpi_handle_debug(device->handle,
+                                         "Cannot transition to %s for parent in %s\n",
+                                         acpi_power_state_string(state),
+                                         acpi_power_state_string(parent->power.state));
+                       return -ENODEV;
+               }
        }
 
        /*