linux-gbmc: pmbus: protect regulator ops with mutex Backport upstream patches for CVE-2026-31486 and CVE-2026-72395 to linux-gbmc 6.12 (linux-gbmc_lts.bb). In the PMBus core driver, regulator routines get_voltage, set_voltage, and list_voltage previously omitted data->update_lock acquisition, allowing concurrent threads (e.g. hwmon polling) to interleave PMBUS_PAGE register switching and corrupt telemetry or direct voltage writes to the wrong rail. This backport: 1. Protects regulator operations with update_lock. 2. Defers regulator_notifier_call_chain() dispatch to an asynchronous workqueue outside update_lock to prevent recursive mutex deadlock with pmbus_fault_handler(). 3. Iterates over atomic fault event masks bit-by-bit to ensure regulator_handle_critical() processes all simultaneous events. Upstream-Status: Backport [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=754bd2b4a084b90b5e7b630e1f423061a9b9b761] Upstream-Status: Backport [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b0ff6b6ae9c5183ef701ece7016698bde5a5bfba] CVE: CVE-2026-31486 CVE: CVE-2026-72395 Tested: - Successfully compiled full firmware image via BitBake: bitbake obmc-phosphor-image (10,484 tasks passed, 0 patch fuzz). - Signed and flashed image on real hardware (wkcw14-nfd11). - Executed 5,000-cycle high-frequency concurrent page-switching stress test (in1_input vs in2_input) without read failures or bus errors: Total Iterations: 5000 | Read Failures: 0 | New Kernel Errors: 0 Google-Bug-Id: 540140798 Change-Id: Id5a541cf765384d6047a5fe2741e6ad149843007 Signed-off-by: Joseph Chan <josephctchan@google.com>
diff --git a/recipes-kernel/linux/files/0001-hwmon-pmbus-core-Protect-regulator-operations-with-m.patch b/recipes-kernel/linux/files/0001-hwmon-pmbus-core-Protect-regulator-operations-with-m.patch new file mode 100644 index 0000000..60fbda7 --- /dev/null +++ b/recipes-kernel/linux/files/0001-hwmon-pmbus-core-Protect-regulator-operations-with-m.patch
@@ -0,0 +1,196 @@ +From 754bd2b4a084b90b5e7b630e1f423061a9b9b761 Mon Sep 17 00:00:00 2001 +From: Guenter Roeck <linux@roeck-us.net> +Date: Wed, 18 Feb 2026 10:14:22 -0800 +Subject: [PATCH] hwmon: (pmbus/core) Protect regulator operations with mutex + +Upstream-Status: Backport [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=754bd2b4a084b90b5e7b630e1f423061a9b9b761] +CVE: CVE-2026-31486 + +PMBus regulator operations get_voltage, set_voltage, and list_voltage +access PMBus registers without holding data->update_lock. This can +lead to race conditions where the PMBus page is switched by another +thread (such as hwmon sensor reads) while a regulator operation is +in progress, leading to corrupted data or writes deployed to the wrong rail. + +Simply acquiring update_lock in these functions can result in deadlocks +because pmbus_regulator_notify() is called with update_lock held (e.g. +from pmbus_fault_handler), and notifier callbacks may invoke regulator +functions that attempt to acquire the same lock. + +Rework pmbus_regulator_notify() to defer notification dispatch to a +worker thread outside of update_lock using atomic event bitmasks, and +protect get_voltage, set_voltage, and list_voltage with update_lock. + +Signed-off-by: Guenter Roeck <linux@roeck-us.net> +--- + drivers/hwmon/pmbus/pmbus_core.c | 66 ++++++++++++++++++++++++++++---- + 1 file changed, 58 insertions(+), 8 deletions(-) + +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +index ba7e00b5da2a..c208df3498f3 100644 +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -118,6 +118,9 @@ struct pmbus_data { + int vout_low[PMBUS_PAGES]; /* voltage low margin */ + int vout_high[PMBUS_PAGES]; /* voltage high margin */ + ktime_t next_access_backoff; /* Wait until at least this time */ ++ ++ struct work_struct notify_work; ++ atomic_t pmbus_notif[PMBUS_PAGES]; + }; + + struct pmbus_debugfs_entry { +@@ -3136,13 +3139,19 @@ static int pmbus_regulator_get_voltage(struct regulator_dev *rdev) + .page = rdev_get_id(rdev), + .class = PSC_VOLTAGE_OUT, + .convert = true, + }; ++ int ret; + ++ mutex_lock(&data->update_lock); + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT); +- if (s.data < 0) +- return s.data; ++ if (s.data < 0) { ++ ret = s.data; ++ goto unlock; ++ } + +- return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */ ++ ret = (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */ ++unlock: ++ mutex_unlock(&data->update_lock); ++ return ret; + } + + static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, +@@ -3157,17 +3166,23 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, + .convert = true, + .data = -1, + }; + int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */ +- int low, high; ++ int low, high, ret; + + *selector = 0; + ++ mutex_lock(&data->update_lock); + low = pmbus_regulator_get_low_margin(client, s.page); +- if (low < 0) +- return low; ++ if (low < 0) { ++ ret = low; ++ goto unlock; ++ } + + high = pmbus_regulator_get_high_margin(client, s.page); +- if (high < 0) +- return high; ++ if (high < 0) { ++ ret = high; ++ goto unlock; ++ } + + /* Make sure we are within margins */ + if (low > val) +@@ -3177,7 +3192,10 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, + + val = pmbus_data2reg(data, &s, val); + +- return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); ++ ret = _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); ++unlock: ++ mutex_unlock(&data->update_lock); ++ return ret; + } + + static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, +@@ -3185,26 +3203,58 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, + { + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); +- int val, low, high; ++ struct pmbus_data *data = i2c_get_clientdata(client); ++ int val, low, high, ret; + + if (selector >= rdev->desc->n_voltages || + selector < rdev->desc->linear_min_sel) + return -EINVAL; + + selector -= rdev->desc->linear_min_sel; + val = DIV_ROUND_CLOSEST(rdev->desc->min_uV + + (rdev->desc->uV_step * selector), 1000); /* convert to mV */ + ++ mutex_lock(&data->update_lock); + low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev)); +- if (low < 0) +- return low; ++ if (low < 0) { ++ ret = low; ++ goto unlock; ++ } + + high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev)); +- if (high < 0) +- return high; ++ if (high < 0) { ++ ret = high; ++ goto unlock; ++ } + + if (val >= low && val <= high) +- return val * 1000; /* unit is uV */ ++ ret = val * 1000; /* unit is uV */ ++ else ++ ret = 0; + +- return 0; ++unlock: ++ mutex_unlock(&data->update_lock); ++ return ret; + } ++ ++static void pmbus_regulator_notify_work(struct work_struct *work) ++{ ++ struct pmbus_data *data = container_of(work, struct pmbus_data, notify_work); ++ int i, j; ++ ++ for (i = 0; i < data->info->pages; i++) { ++ int event = atomic_xchg(&data->pmbus_notif[i], 0); ++ ++ if (!event) ++ continue; ++ ++ for (j = 0; j < data->info->num_regulators; j++) { ++ if (i == rdev_get_id(data->rdevs[j])) { ++ regulator_notifier_call_chain(data->rdevs[j], event, NULL); ++ break; ++ } ++ } ++ } ++} ++ ++static int pmbus_regulator_notify(struct pmbus_data *data, int page, int event) ++{ ++ atomic_or(event, &data->pmbus_notif[page]); ++ schedule_work(&data->notify_work); ++ return 0; ++} ++ ++static void pmbus_regulator_cancel_work(void *data) ++{ ++ struct pmbus_data *pdata = data; ++ ++ cancel_work_sync(&pdata->notify_work); ++} +@@ -3233,6 +3283,10 @@ static int pmbus_regulator_register(struct pmbus_data *data) + if (!data->rdevs) + return -ENOMEM; + ++ INIT_WORK(&data->notify_work, pmbus_regulator_notify_work); ++ ret = devm_add_action_or_reset(dev, pmbus_regulator_cancel_work, data); ++ if (ret) ++ return ret; ++ + for (i = 0; i < info->num_regulators; i++) { +--
diff --git a/recipes-kernel/linux/files/0002-hwmon-pmbus-core-Fix-critical-regulator-event-notifi.patch b/recipes-kernel/linux/files/0002-hwmon-pmbus-core-Fix-critical-regulator-event-notifi.patch new file mode 100644 index 0000000..b20eaa8 --- /dev/null +++ b/recipes-kernel/linux/files/0002-hwmon-pmbus-core-Fix-critical-regulator-event-notifi.patch
@@ -0,0 +1,41 @@ +From b0ff6b6ae9c5183ef701ece7016698bde5a5bfba Mon Sep 17 00:00:00 2001 +From: Guenter Roeck <linux@roeck-us.net> +Date: Fri, 20 Feb 2026 14:32:01 -0800 +Subject: [PATCH] hwmon: (pmbus/core) Fix critical regulator event notification + +Upstream-Status: Backport [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b0ff6b6ae9c5183ef701ece7016698bde5a5bfba] +CVE: CVE-2026-72395 + +Commit 754bd2b4a084 ("hwmon: (pmbus/core) Protect regulator operations +with mutex") passed a combined bitmask of events to +regulator_notifier_call_chain(). Because regulator_handle_critical() +evaluates event types with a strict switch statement, multi-bit flags +fail to match, causing critical hardware protection triggers to be bypassed. + +Iterate through the event bitmask and pass each event individually. + +Signed-off-by: Guenter Roeck <linux@roeck-us.net> +--- + drivers/hwmon/pmbus/pmbus_core.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +index c208df3498f3..dbce73e8912b 100644 +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -3242,8 +3242,11 @@ static void pmbus_regulator_notify_work(struct work_struct *work) + + for (j = 0; j < data->info->num_regulators; j++) { + if (i == rdev_get_id(data->rdevs[j])) { +- regulator_notifier_call_chain(data->rdevs[j], event, NULL); +- break; ++ unsigned long ev = event; ++ int bit; ++ ++ for_each_set_bit(bit, &ev, BITS_PER_LONG) ++ regulator_notifier_call_chain(data->rdevs[j], BIT(bit), NULL); ++ break; + } + } + } +--
diff --git a/recipes-kernel/linux/linux-gbmc_lts.bb b/recipes-kernel/linux/linux-gbmc_lts.bb index 766d832..4680c1e 100644 --- a/recipes-kernel/linux/linux-gbmc_lts.bb +++ b/recipes-kernel/linux/linux-gbmc_lts.bb
@@ -26,6 +26,8 @@ file://0006-i2c-npcm-Enable-slave-in-eob-interrupt.patch \ file://0001-i2c-npcm-Add-clock-toggle-recovery.patch \ file://DOWNSTREAM_0001-i2c-npcm-speed-set.patch \ + file://0001-hwmon-pmbus-core-Protect-regulator-operations-with-m.patch \ + file://0002-hwmon-pmbus-core-Fix-critical-regulator-event-notifi.patch \ " # Newer kernels don't have HGPIO in pinctrl names