The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write data to kernel memory it had no business touching, for leds number 3 and above. If one is lucky, that illegal write would cause an OOPS, but chances are it would silently corrupt a byte. The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add sysfs led class support to thinkpad leds (v3.2)". Fix the bug by refactoring the entire code to be far more obvious on what it wants to do. Also do some defensive "constification". Issue reported by Karol Lewandowski <lmctlx@xxxxxxxxx> (he's an lucky guy and got an OOPS instead of silent corruption :-) ). Root cause of the OOPS identified by Adrian Bunk <bunk@xxxxxxxxxx>. Thanks, Adrian! Signed-off-by: Henrique de Moraes Holschuh <hmh@xxxxxxxxxx> Tested-by: Karol Lewandowski <lmctlx@xxxxxxxxx> Cc: Adrian Bunk <bunk@xxxxxxxxxx> --- drivers/misc/thinkpad_acpi.c | 55 +++++++++++++++++++++-------------------- 1 files changed, 28 insertions(+), 27 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 58973da..b596929 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -3853,7 +3853,7 @@ static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = { "tpacpi::standby", }; -static int led_get_status(unsigned int led) +static int led_get_status(const unsigned int led) { int status; enum led_status_t led_s; @@ -3877,41 +3877,42 @@ static int led_get_status(unsigned int led) /* not reached */ } -static int led_set_status(unsigned int led, enum led_status_t ledstatus) +static int led_set_status(const unsigned int led, + const enum led_status_t ledstatus) { /* off, on, blink. Index is led_status_t */ - static const int led_sled_arg1[] = { 0, 1, 3 }; - static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */ - static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */ - static const int led_led_arg1[] = { 0, 0x80, 0xc0 }; + static const unsigned int led_sled_arg1[] = { 0, 1, 3 }; + static const unsigned int led_led_arg1[] = { 0, 0x80, 0xc0 }; int rc = 0; switch (led_supported) { case TPACPI_LED_570: - /* 570 */ - led = 1 << led; - if (!acpi_evalf(led_handle, NULL, NULL, "vdd", - led, led_sled_arg1[ledstatus])) - rc = -EIO; - break; + /* 570 */ + if (led > 7) + return -EINVAL; + if (!acpi_evalf(led_handle, NULL, NULL, "vdd", + (1 << led), led_sled_arg1[ledstatus])) + rc = -EIO; + break; case TPACPI_LED_OLD: - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ - led = 1 << led; - rc = ec_write(TPACPI_LED_EC_HLMS, led); - if (rc >= 0) - rc = ec_write(TPACPI_LED_EC_HLBL, - led * led_exp_hlbl[ledstatus]); - if (rc >= 0) - rc = ec_write(TPACPI_LED_EC_HLCL, - led * led_exp_hlcl[ledstatus]); - break; + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ + if (led > 7) + return -EINVAL; + rc = ec_write(TPACPI_LED_EC_HLMS, (1 << led)); + if (rc >= 0) + rc = ec_write(TPACPI_LED_EC_HLBL, + (ledstatus == TPACPI_LED_BLINK) << led); + if (rc >= 0) + rc = ec_write(TPACPI_LED_EC_HLCL, + (ledstatus != TPACPI_LED_OFF) << led); + break; case TPACPI_LED_NEW: - /* all others */ - if (!acpi_evalf(led_handle, NULL, NULL, "vdd", - led, led_led_arg1[ledstatus])) - rc = -EIO; - break; + /* all others */ + if (!acpi_evalf(led_handle, NULL, NULL, "vdd", + led, led_led_arg1[ledstatus])) + rc = -EIO; + break; default: rc = -ENXIO; } -- 1.5.5.3 -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html