From: Greg Kroah-Hartman Date: Tue, 16 Jun 2026 05:16:28 +0000 (+0530) Subject: 6.1-stable patches X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a73a4789e82c4c078930742526ec97c9cad00d95;p=thirdparty%2Fkernel%2Fstable-queue.git 6.1-stable patches added patches: alsa-aoa-i2sbus-clear-stale-prepared-state.patch alsa-aoa-skip-devices-with-no-codecs-in-i2sbus_resume.patch alsa-aoa-use-guard-for-mutex-locks.patch arm64-mm-enable-batched-tlb-flush-in-unmap_hotplug_range.patch f2fs-fix-to-do-sanity-check-on-dcc-discard_cmd_cnt-conditionally.patch f2fs-fix-uaf-caused-by-decrementing-sbi-nr_pages-in-f2fs_write_end_io.patch f2fs-use-kfree-instead-of-kvfree-to-free-some-memory.patch fbdev-defio-disconnect-deferred-i-o-from-the-lifetime-of-struct-fb_info.patch ksmbd-require-minimum-ace-size-in-smb_check_perm_dacl.patch lib-test_hmm-evict-device-pages-on-file-close-to-avoid-use-after-free.patch loongarch-add-spectre-boundry-for-syscall-dispatch-table.patch media-rc-igorplugusb-heed-coherency-rules.patch media-rc-ttusbir-respect-dma-coherency-rules.patch net-bridge-use-a-stable-fdb-dst-snapshot-in-rcu-readers.patch net-mctp-fix-don-t-require-received-header-reserved-bits-to-be-zero.patch net-qrtr-ns-change-servers-radix-tree-to-xarray.patch net-qrtr-ns-free-the-node-during-ctrl_cmd_bye.patch net-qrtr-ns-limit-the-maximum-number-of-lookups.patch net-qrtr-ns-limit-the-total-number-of-nodes.patch sched-use-u64-for-bandwidth-ratio-calculations.patch smb-client-validate-the-whole-dacl-before-rewriting-it-in-cifsacl.patch spi-fix-resource-leaks-on-device-setup-failure.patch spi-imx-convert-to-platform-remove-callback-returning-void.patch spi-imx-fix-use-after-free-on-unbind.patch thermal-core-fix-thermal-zone-governor-cleanup-issues.patch wifi-mwifiex-fix-use-after-free-in-mwifiex_adapter_cleanup.patch --- diff --git a/queue-6.1/alsa-aoa-i2sbus-clear-stale-prepared-state.patch b/queue-6.1/alsa-aoa-i2sbus-clear-stale-prepared-state.patch new file mode 100644 index 0000000000..7cf26bf500 --- /dev/null +++ b/queue-6.1/alsa-aoa-i2sbus-clear-stale-prepared-state.patch @@ -0,0 +1,165 @@ +From stable+bounces-242469-greg=kroah.com@vger.kernel.org Fri May 1 22:29:52 2026 +From: Sasha Levin +Date: Fri, 1 May 2026 12:59:01 -0400 +Subject: ALSA: aoa: i2sbus: clear stale prepared state +To: stable@vger.kernel.org +Cc: "Cássio Gabriel" , "kernel test robot" , "Takashi Iwai" , "Sasha Levin" +Message-ID: <20260501165901.3628210-2-sashal@kernel.org> + +From: Cássio Gabriel + +[ Upstream commit 5ed060d5491597490fb53ec69da3edc4b1e8c165 ] + +The i2sbus PCM code uses pi->active to constrain the sibling stream to +an already prepared duplex format and rate in i2sbus_pcm_open(). + +That state is set from i2sbus_pcm_prepare(), but the current code only +clears it on close. As a result, the sibling stream can inherit stale +constraints after the prepared state has been torn down. + +Clear pi->active when hw_params() or hw_free() tears down the prepared +state, and set it again only after prepare succeeds. + +Replace the stale FIXME in the duplex constraint comment with a description +of the current driver behavior: i2sbus still programs a single shared +transport configuration for both directions, so mixed formats are not +supported in duplex mode. + +Reported-by: kernel test robot +Closes: https://lore.kernel.org/oe-kbuild-all/202604010125.AvkWBYKI-lkp@intel.com/ +Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") +Cc: stable@vger.kernel.org +Signed-off-by: Cássio Gabriel +Link: https://patch.msgid.link/20260331-aoa-i2sbus-clear-stale-active-v2-1-3764ae2889a1@gmail.com +Signed-off-by: Takashi Iwai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/aoa/soundbus/i2sbus/pcm.c | 55 ++++++++++++++++++++++++++++++++-------- + 1 file changed, 44 insertions(+), 11 deletions(-) + +--- a/sound/aoa/soundbus/i2sbus/pcm.c ++++ b/sound/aoa/soundbus/i2sbus/pcm.c +@@ -165,17 +165,16 @@ static int i2sbus_pcm_open(struct i2sbus + * currently in use (if any). */ + hw->rate_min = 5512; + hw->rate_max = 192000; +- /* if the other stream is active, then we can only +- * support what it is currently using. +- * FIXME: I lied. This comment is wrong. We can support +- * anything that works with the same serial format, ie. +- * when recording 24 bit sound we can well play 16 bit +- * sound at the same time iff using the same transfer mode. ++ /* If the other stream is already prepared, keep this stream ++ * on the same duplex format and rate. ++ * ++ * i2sbus_pcm_prepare() still programs one shared transport ++ * configuration for both directions, so mixed duplex formats ++ * are not supported here. + */ + if (other->active) { +- /* FIXME: is this guaranteed by the alsa api? */ + hw->formats &= pcm_format_to_bits(i2sdev->format); +- /* see above, restrict rates to the one we already have */ ++ /* Restrict rates to the one already in use. */ + hw->rate_min = i2sdev->rate; + hw->rate_max = i2sdev->rate; + } +@@ -283,6 +282,23 @@ void i2sbus_wait_for_stop_both(struct i2 + } + #endif + ++static void i2sbus_pcm_clear_active(struct i2sbus_dev *i2sdev, int in) ++{ ++ struct pcm_info *pi; ++ ++ guard(mutex)(&i2sdev->lock); ++ ++ get_pcm_info(i2sdev, in, &pi, NULL); ++ pi->active = 0; ++} ++ ++static inline int i2sbus_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, int in) ++{ ++ i2sbus_pcm_clear_active(snd_pcm_substream_chip(substream), in); ++ return 0; ++} ++ + static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in) + { + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); +@@ -291,14 +307,27 @@ static inline int i2sbus_hw_free(struct + get_pcm_info(i2sdev, in, &pi, NULL); + if (pi->dbdma_ring.stopping) + i2sbus_wait_for_stop(i2sdev, pi); ++ i2sbus_pcm_clear_active(i2sdev, in); + return 0; + } + ++static int i2sbus_playback_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ return i2sbus_hw_params(substream, params, 0); ++} ++ + static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream) + { + return i2sbus_hw_free(substream, 0); + } + ++static int i2sbus_record_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ return i2sbus_hw_params(substream, params, 1); ++} ++ + static int i2sbus_record_hw_free(struct snd_pcm_substream *substream) + { + return i2sbus_hw_free(substream, 1); +@@ -335,7 +364,6 @@ static int i2sbus_pcm_prepare(struct i2s + return -EINVAL; + + runtime = pi->substream->runtime; +- pi->active = 1; + if (other->active && + ((i2sdev->format != runtime->format) + || (i2sdev->rate != runtime->rate))) +@@ -450,9 +478,11 @@ static int i2sbus_pcm_prepare(struct i2s + + /* early exit if already programmed correctly */ + /* not locking these is fine since we touch them only in this function */ +- if (in_le32(&i2sdev->intfregs->serial_format) == sfr +- && in_le32(&i2sdev->intfregs->data_word_sizes) == dws) ++ if (in_le32(&i2sdev->intfregs->serial_format) == sfr && ++ in_le32(&i2sdev->intfregs->data_word_sizes) == dws) { ++ pi->active = 1; + return 0; ++ } + + /* let's notify the codecs about clocks going away. + * For now we only do mastering on the i2s cell... */ +@@ -490,6 +520,7 @@ static int i2sbus_pcm_prepare(struct i2s + if (cii->codec->switch_clock) + cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE); + ++ pi->active = 1; + return 0; + } + +@@ -746,6 +777,7 @@ static snd_pcm_uframes_t i2sbus_playback + static const struct snd_pcm_ops i2sbus_playback_ops = { + .open = i2sbus_playback_open, + .close = i2sbus_playback_close, ++ .hw_params = i2sbus_playback_hw_params, + .hw_free = i2sbus_playback_hw_free, + .prepare = i2sbus_playback_prepare, + .trigger = i2sbus_playback_trigger, +@@ -814,6 +846,7 @@ static snd_pcm_uframes_t i2sbus_record_p + static const struct snd_pcm_ops i2sbus_record_ops = { + .open = i2sbus_record_open, + .close = i2sbus_record_close, ++ .hw_params = i2sbus_record_hw_params, + .hw_free = i2sbus_record_hw_free, + .prepare = i2sbus_record_prepare, + .trigger = i2sbus_record_trigger, diff --git a/queue-6.1/alsa-aoa-skip-devices-with-no-codecs-in-i2sbus_resume.patch b/queue-6.1/alsa-aoa-skip-devices-with-no-codecs-in-i2sbus_resume.patch new file mode 100644 index 0000000000..92b3236b2d --- /dev/null +++ b/queue-6.1/alsa-aoa-skip-devices-with-no-codecs-in-i2sbus_resume.patch @@ -0,0 +1,83 @@ +From stable+bounces-242484-greg=kroah.com@vger.kernel.org Fri May 1 23:11:08 2026 +From: Sasha Levin +Date: Fri, 1 May 2026 13:40:55 -0400 +Subject: ALSA: aoa: Skip devices with no codecs in i2sbus_resume() +To: stable@vger.kernel.org +Cc: Thorsten Blum , Takashi Iwai , Sasha Levin +Message-ID: <20260501174056.3862921-2-sashal@kernel.org> + +From: Thorsten Blum + +[ Upstream commit fd7df93013c5118812e63a52635dc6c3a805a1de ] + +In i2sbus_resume(), skip devices with an empty codec list, which avoids +using an uninitialized 'sysclock_factor' in the 32-bit format path in +i2sbus_pcm_prepare(). + +In i2sbus_pcm_prepare(), replace two list_for_each_entry() loops with a +single list_first_entry() now that the codec list is guaranteed to be +non-empty by all callers. + +Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") +Cc: stable@vger.kernel.org +Signed-off-by: Thorsten Blum +Link: https://patch.msgid.link/20260310102921.210109-3-thorsten.blum@linux.dev +Signed-off-by: Takashi Iwai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/aoa/soundbus/i2sbus/core.c | 3 +++ + sound/aoa/soundbus/i2sbus/pcm.c | 16 +++++----------- + 2 files changed, 8 insertions(+), 11 deletions(-) + +--- a/sound/aoa/soundbus/i2sbus/core.c ++++ b/sound/aoa/soundbus/i2sbus/core.c +@@ -411,6 +411,9 @@ static int i2sbus_resume(struct macio_de + int err, ret = 0; + + list_for_each_entry(i2sdev, &control->list, item) { ++ if (list_empty(&i2sdev->sound.codec_list)) ++ continue; ++ + /* reset i2s bus format etc. */ + i2sbus_pcm_prepare_both(i2sdev); + +--- a/sound/aoa/soundbus/i2sbus/pcm.c ++++ b/sound/aoa/soundbus/i2sbus/pcm.c +@@ -411,6 +411,9 @@ static int i2sbus_pcm_prepare(struct i2s + /* set stop command */ + command->command = cpu_to_le16(DBDMA_STOP); + ++ cii = list_first_entry(&i2sdev->sound.codec_list, ++ struct codec_info_item, list); ++ + /* ok, let's set the serial format and stuff */ + switch (runtime->format) { + /* 16 bit formats */ +@@ -418,13 +421,7 @@ static int i2sbus_pcm_prepare(struct i2s + case SNDRV_PCM_FORMAT_U16_BE: + /* FIXME: if we add different bus factors we need to + * do more here!! */ +- bi.bus_factor = 0; +- list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { +- bi.bus_factor = cii->codec->bus_factor; +- break; +- } +- if (!bi.bus_factor) +- return -ENODEV; ++ bi.bus_factor = cii->codec->bus_factor; + input_16bit = 1; + break; + case SNDRV_PCM_FORMAT_S32_BE: +@@ -438,10 +435,7 @@ static int i2sbus_pcm_prepare(struct i2s + return -EINVAL; + } + /* we assume all sysclocks are the same! */ +- list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { +- bi.sysclock_factor = cii->codec->sysclock_factor; +- break; +- } ++ bi.sysclock_factor = cii->codec->sysclock_factor; + + if (clock_and_divisors(bi.sysclock_factor, + bi.bus_factor, diff --git a/queue-6.1/alsa-aoa-use-guard-for-mutex-locks.patch b/queue-6.1/alsa-aoa-use-guard-for-mutex-locks.patch new file mode 100644 index 0000000000..b1a812003d --- /dev/null +++ b/queue-6.1/alsa-aoa-use-guard-for-mutex-locks.patch @@ -0,0 +1,1041 @@ +From stable+bounces-242468-greg=kroah.com@vger.kernel.org Fri May 1 22:29:48 2026 +From: Sasha Levin +Date: Fri, 1 May 2026 12:59:00 -0400 +Subject: ALSA: aoa: Use guard() for mutex locks +To: stable@vger.kernel.org +Cc: Takashi Iwai , Sasha Levin +Message-ID: <20260501165901.3628210-1-sashal@kernel.org> + +From: Takashi Iwai + +[ Upstream commit 1cb6ecbb372002ef9e531c5377e5f60122411e40 ] + +Replace the manual mutex lock/unlock pairs with guard() for code +simplification. + +Only code refactoring, and no behavior change. + +Signed-off-by: Takashi Iwai +Link: https://patch.msgid.link/20250829151335.7342-14-tiwai@suse.de +Stable-dep-of: 5ed060d54915 ("ALSA: aoa: i2sbus: clear stale prepared state") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/aoa/codecs/onyx.c | 104 +++++++++++------------------------- + sound/aoa/codecs/tas.c | 113 +++++++++++++--------------------------- + sound/aoa/core/gpio-feature.c | 20 ++----- + sound/aoa/core/gpio-pmf.c | 26 +++------ + sound/aoa/soundbus/i2sbus/pcm.c | 76 ++++++++------------------ + 5 files changed, 112 insertions(+), 227 deletions(-) + +--- a/sound/aoa/codecs/onyx.c ++++ b/sound/aoa/codecs/onyx.c +@@ -121,10 +121,9 @@ static int onyx_snd_vol_get(struct snd_k + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + s8 l, r; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l); + onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r); +- mutex_unlock(&onyx->mutex); + + ucontrol->value.integer.value[0] = l + VOLUME_RANGE_SHIFT; + ucontrol->value.integer.value[1] = r + VOLUME_RANGE_SHIFT; +@@ -145,15 +144,13 @@ static int onyx_snd_vol_put(struct snd_k + ucontrol->value.integer.value[1] > -1 + VOLUME_RANGE_SHIFT) + return -EINVAL; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l); + onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r); + + if (l + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[0] && +- r + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[1]) { +- mutex_unlock(&onyx->mutex); ++ r + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[1]) + return 0; +- } + + onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, + ucontrol->value.integer.value[0] +@@ -161,7 +158,6 @@ static int onyx_snd_vol_put(struct snd_k + onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, + ucontrol->value.integer.value[1] + - VOLUME_RANGE_SHIFT); +- mutex_unlock(&onyx->mutex); + + return 1; + } +@@ -197,9 +193,8 @@ static int onyx_snd_inputgain_get(struct + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 ig; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &ig); +- mutex_unlock(&onyx->mutex); + + ucontrol->value.integer.value[0] = + (ig & ONYX_ADC_PGA_GAIN_MASK) + INPUTGAIN_RANGE_SHIFT; +@@ -216,14 +211,13 @@ static int onyx_snd_inputgain_put(struct + if (ucontrol->value.integer.value[0] < 3 + INPUTGAIN_RANGE_SHIFT || + ucontrol->value.integer.value[0] > 28 + INPUTGAIN_RANGE_SHIFT) + return -EINVAL; +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v); + n = v; + n &= ~ONYX_ADC_PGA_GAIN_MASK; + n |= (ucontrol->value.integer.value[0] - INPUTGAIN_RANGE_SHIFT) + & ONYX_ADC_PGA_GAIN_MASK; + onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, n); +- mutex_unlock(&onyx->mutex); + + return n != v; + } +@@ -251,9 +245,8 @@ static int onyx_snd_capture_source_get(s + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + s8 v; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v); +- mutex_unlock(&onyx->mutex); + + ucontrol->value.enumerated.item[0] = !!(v&ONYX_ADC_INPUT_MIC); + +@@ -264,13 +257,12 @@ static void onyx_set_capture_source(stru + { + s8 v; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v); + v &= ~ONYX_ADC_INPUT_MIC; + if (mic) + v |= ONYX_ADC_INPUT_MIC; + onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, v); +- mutex_unlock(&onyx->mutex); + } + + static int onyx_snd_capture_source_put(struct snd_kcontrol *kcontrol, +@@ -311,9 +303,8 @@ static int onyx_snd_mute_get(struct snd_ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 c; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &c); +- mutex_unlock(&onyx->mutex); + + ucontrol->value.integer.value[0] = !(c & ONYX_MUTE_LEFT); + ucontrol->value.integer.value[1] = !(c & ONYX_MUTE_RIGHT); +@@ -328,9 +319,9 @@ static int onyx_snd_mute_put(struct snd_ + u8 v = 0, c = 0; + int err = -EBUSY; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + if (onyx->analog_locked) +- goto out_unlock; ++ return -EBUSY; + + onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); + c = v; +@@ -341,9 +332,6 @@ static int onyx_snd_mute_put(struct snd_ + c |= ONYX_MUTE_RIGHT; + err = onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, c); + +- out_unlock: +- mutex_unlock(&onyx->mutex); +- + return !err ? (v != c) : err; + } + +@@ -372,9 +360,8 @@ static int onyx_snd_single_bit_get(struc + u8 address = (pv >> 8) & 0xff; + u8 mask = pv & 0xff; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, address, &c); +- mutex_unlock(&onyx->mutex); + + ucontrol->value.integer.value[0] = !!(c & mask) ^ polarity; + +@@ -393,11 +380,10 @@ static int onyx_snd_single_bit_put(struc + u8 address = (pv >> 8) & 0xff; + u8 mask = pv & 0xff; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + if (spdiflock && onyx->spdif_locked) { + /* even if alsamixer doesn't care.. */ +- err = -EBUSY; +- goto out_unlock; ++ return -EBUSY; + } + onyx_read_register(onyx, address, &v); + c = v; +@@ -406,9 +392,6 @@ static int onyx_snd_single_bit_put(struc + c |= mask; + err = onyx_write_register(onyx, address, c); + +- out_unlock: +- mutex_unlock(&onyx->mutex); +- + return !err ? (v != c) : err; + } + +@@ -489,7 +472,7 @@ static int onyx_spdif_get(struct snd_kco + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 v; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v); + ucontrol->value.iec958.status[0] = v & 0x3e; + +@@ -501,7 +484,6 @@ static int onyx_spdif_get(struct snd_kco + + onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); + ucontrol->value.iec958.status[4] = v & 0x0f; +- mutex_unlock(&onyx->mutex); + + return 0; + } +@@ -512,7 +494,7 @@ static int onyx_spdif_put(struct snd_kco + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 v; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v); + v = (v & ~0x3e) | (ucontrol->value.iec958.status[0] & 0x3e); + onyx_write_register(onyx, ONYX_REG_DIG_INFO1, v); +@@ -527,7 +509,6 @@ static int onyx_spdif_put(struct snd_kco + onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); + v = (v & ~0x0f) | (ucontrol->value.iec958.status[4] & 0x0f); + onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v); +- mutex_unlock(&onyx->mutex); + + return 1; + } +@@ -672,14 +653,13 @@ static int onyx_usable(struct codec_info + struct onyx *onyx = cii->codec_data; + int spdif_enabled, analog_enabled; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); + spdif_enabled = !!(v & ONYX_SPDIF_ENABLE); + onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); + analog_enabled = + (v & (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT)) + != (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT); +- mutex_unlock(&onyx->mutex); + + switch (ti->tag) { + case 0: return 1; +@@ -695,9 +675,8 @@ static int onyx_prepare(struct codec_inf + { + u8 v; + struct onyx *onyx = cii->codec_data; +- int err = -EBUSY; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + + #ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE + if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) { +@@ -706,10 +685,9 @@ static int onyx_prepare(struct codec_inf + if (onyx_write_register(onyx, + ONYX_REG_DAC_CONTROL, + v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT)) +- goto out_unlock; ++ return -EBUSY; + onyx->analog_locked = 1; +- err = 0; +- goto out_unlock; ++ return 0; + } + #endif + switch (substream->runtime->rate) { +@@ -719,8 +697,7 @@ static int onyx_prepare(struct codec_inf + /* these rates are ok for all outputs */ + /* FIXME: program spdif channel control bits here so that + * userspace doesn't have to if it only plays pcm! */ +- err = 0; +- goto out_unlock; ++ return 0; + default: + /* got some rate that the digital output can't do, + * so disable and lock it */ +@@ -728,16 +705,12 @@ static int onyx_prepare(struct codec_inf + if (onyx_write_register(onyx, + ONYX_REG_DIG_INFO4, + v & ~ONYX_SPDIF_ENABLE)) +- goto out_unlock; ++ return -EBUSY; + onyx->spdif_locked = 1; +- err = 0; +- goto out_unlock; ++ return 0; + } + +- out_unlock: +- mutex_unlock(&onyx->mutex); +- +- return err; ++ return -EBUSY; + } + + static int onyx_open(struct codec_info_item *cii, +@@ -745,9 +718,8 @@ static int onyx_open(struct codec_info_i + { + struct onyx *onyx = cii->codec_data; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx->open_count++; +- mutex_unlock(&onyx->mutex); + + return 0; + } +@@ -757,11 +729,10 @@ static int onyx_close(struct codec_info_ + { + struct onyx *onyx = cii->codec_data; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + onyx->open_count--; + if (!onyx->open_count) + onyx->spdif_locked = onyx->analog_locked = 0; +- mutex_unlock(&onyx->mutex); + + return 0; + } +@@ -771,7 +742,7 @@ static int onyx_switch_clock(struct code + { + struct onyx *onyx = cii->codec_data; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + /* this *MUST* be more elaborate later... */ + switch (what) { + case CLOCK_SWITCH_PREPARE_SLAVE: +@@ -783,7 +754,6 @@ static int onyx_switch_clock(struct code + default: /* silence warning */ + break; + } +- mutex_unlock(&onyx->mutex); + + return 0; + } +@@ -794,27 +764,21 @@ static int onyx_suspend(struct codec_inf + { + struct onyx *onyx = cii->codec_data; + u8 v; +- int err = -ENXIO; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v)) +- goto out_unlock; ++ return -ENXIO; + onyx_write_register(onyx, ONYX_REG_CONTROL, v | ONYX_ADPSV | ONYX_DAPSV); + /* Apple does a sleep here but the datasheet says to do it on resume */ +- err = 0; +- out_unlock: +- mutex_unlock(&onyx->mutex); +- +- return err; ++ return 0; + } + + static int onyx_resume(struct codec_info_item *cii) + { + struct onyx *onyx = cii->codec_data; + u8 v; +- int err = -ENXIO; + +- mutex_lock(&onyx->mutex); ++ guard(mutex)(&onyx->mutex); + + /* reset codec */ + onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); +@@ -826,17 +790,13 @@ static int onyx_resume(struct codec_info + + /* take codec out of suspend (if it still is after reset) */ + if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v)) +- goto out_unlock; ++ return -ENXIO; + onyx_write_register(onyx, ONYX_REG_CONTROL, v & ~(ONYX_ADPSV | ONYX_DAPSV)); + /* FIXME: should divide by sample rate, but 8k is the lowest we go */ + msleep(2205000/8000); + /* reset all values */ + onyx_register_init(onyx); +- err = 0; +- out_unlock: +- mutex_unlock(&onyx->mutex); +- +- return err; ++ return 0; + } + + #endif /* CONFIG_PM */ +--- a/sound/aoa/codecs/tas.c ++++ b/sound/aoa/codecs/tas.c +@@ -235,10 +235,9 @@ static int tas_snd_vol_get(struct snd_kc + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + ucontrol->value.integer.value[0] = tas->cached_volume_l; + ucontrol->value.integer.value[1] = tas->cached_volume_r; +- mutex_unlock(&tas->mtx); + return 0; + } + +@@ -254,18 +253,15 @@ static int tas_snd_vol_put(struct snd_kc + ucontrol->value.integer.value[1] > 177) + return -EINVAL; + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + if (tas->cached_volume_l == ucontrol->value.integer.value[0] +- && tas->cached_volume_r == ucontrol->value.integer.value[1]) { +- mutex_unlock(&tas->mtx); ++ && tas->cached_volume_r == ucontrol->value.integer.value[1]) + return 0; +- } + + tas->cached_volume_l = ucontrol->value.integer.value[0]; + tas->cached_volume_r = ucontrol->value.integer.value[1]; + if (tas->hw_enabled) + tas_set_volume(tas); +- mutex_unlock(&tas->mtx); + return 1; + } + +@@ -285,10 +281,9 @@ static int tas_snd_mute_get(struct snd_k + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + ucontrol->value.integer.value[0] = !tas->mute_l; + ucontrol->value.integer.value[1] = !tas->mute_r; +- mutex_unlock(&tas->mtx); + return 0; + } + +@@ -297,18 +292,15 @@ static int tas_snd_mute_put(struct snd_k + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + if (tas->mute_l == !ucontrol->value.integer.value[0] +- && tas->mute_r == !ucontrol->value.integer.value[1]) { +- mutex_unlock(&tas->mtx); ++ && tas->mute_r == !ucontrol->value.integer.value[1]) + return 0; +- } + + tas->mute_l = !ucontrol->value.integer.value[0]; + tas->mute_r = !ucontrol->value.integer.value[1]; + if (tas->hw_enabled) + tas_set_volume(tas); +- mutex_unlock(&tas->mtx); + return 1; + } + +@@ -337,10 +329,9 @@ static int tas_snd_mixer_get(struct snd_ + struct tas *tas = snd_kcontrol_chip(kcontrol); + int idx = kcontrol->private_value; + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + ucontrol->value.integer.value[0] = tas->mixer_l[idx]; + ucontrol->value.integer.value[1] = tas->mixer_r[idx]; +- mutex_unlock(&tas->mtx); + + return 0; + } +@@ -351,19 +342,16 @@ static int tas_snd_mixer_put(struct snd_ + struct tas *tas = snd_kcontrol_chip(kcontrol); + int idx = kcontrol->private_value; + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + if (tas->mixer_l[idx] == ucontrol->value.integer.value[0] +- && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) { +- mutex_unlock(&tas->mtx); ++ && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) + return 0; +- } + + tas->mixer_l[idx] = ucontrol->value.integer.value[0]; + tas->mixer_r[idx] = ucontrol->value.integer.value[1]; + + if (tas->hw_enabled) + tas_set_mixer(tas); +- mutex_unlock(&tas->mtx); + return 1; + } + +@@ -396,9 +384,8 @@ static int tas_snd_drc_range_get(struct + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + ucontrol->value.integer.value[0] = tas->drc_range; +- mutex_unlock(&tas->mtx); + return 0; + } + +@@ -411,16 +398,13 @@ static int tas_snd_drc_range_put(struct + ucontrol->value.integer.value[0] > TAS3004_DRC_MAX) + return -EINVAL; + +- mutex_lock(&tas->mtx); +- if (tas->drc_range == ucontrol->value.integer.value[0]) { +- mutex_unlock(&tas->mtx); ++ guard(mutex)(&tas->mtx); ++ if (tas->drc_range == ucontrol->value.integer.value[0]) + return 0; +- } + + tas->drc_range = ucontrol->value.integer.value[0]; + if (tas->hw_enabled) + tas3004_set_drc(tas); +- mutex_unlock(&tas->mtx); + return 1; + } + +@@ -440,9 +424,8 @@ static int tas_snd_drc_switch_get(struct + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + ucontrol->value.integer.value[0] = tas->drc_enabled; +- mutex_unlock(&tas->mtx); + return 0; + } + +@@ -451,16 +434,13 @@ static int tas_snd_drc_switch_put(struct + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); +- if (tas->drc_enabled == ucontrol->value.integer.value[0]) { +- mutex_unlock(&tas->mtx); ++ guard(mutex)(&tas->mtx); ++ if (tas->drc_enabled == ucontrol->value.integer.value[0]) + return 0; +- } + + tas->drc_enabled = !!ucontrol->value.integer.value[0]; + if (tas->hw_enabled) + tas3004_set_drc(tas); +- mutex_unlock(&tas->mtx); + return 1; + } + +@@ -486,9 +466,8 @@ static int tas_snd_capture_source_get(st + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B); +- mutex_unlock(&tas->mtx); + return 0; + } + +@@ -500,7 +479,7 @@ static int tas_snd_capture_source_put(st + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + oldacr = tas->acr; + + /* +@@ -512,13 +491,10 @@ static int tas_snd_capture_source_put(st + if (ucontrol->value.enumerated.item[0]) + tas->acr |= TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL | + TAS_ACR_B_MON_SEL_RIGHT; +- if (oldacr == tas->acr) { +- mutex_unlock(&tas->mtx); ++ if (oldacr == tas->acr) + return 0; +- } + if (tas->hw_enabled) + tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr); +- mutex_unlock(&tas->mtx); + return 1; + } + +@@ -557,9 +533,8 @@ static int tas_snd_treble_get(struct snd + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + ucontrol->value.integer.value[0] = tas->treble; +- mutex_unlock(&tas->mtx); + return 0; + } + +@@ -571,16 +546,13 @@ static int tas_snd_treble_put(struct snd + if (ucontrol->value.integer.value[0] < TAS3004_TREBLE_MIN || + ucontrol->value.integer.value[0] > TAS3004_TREBLE_MAX) + return -EINVAL; +- mutex_lock(&tas->mtx); +- if (tas->treble == ucontrol->value.integer.value[0]) { +- mutex_unlock(&tas->mtx); ++ guard(mutex)(&tas->mtx); ++ if (tas->treble == ucontrol->value.integer.value[0]) + return 0; +- } + + tas->treble = ucontrol->value.integer.value[0]; + if (tas->hw_enabled) + tas_set_treble(tas); +- mutex_unlock(&tas->mtx); + return 1; + } + +@@ -608,9 +580,8 @@ static int tas_snd_bass_get(struct snd_k + { + struct tas *tas = snd_kcontrol_chip(kcontrol); + +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + ucontrol->value.integer.value[0] = tas->bass; +- mutex_unlock(&tas->mtx); + return 0; + } + +@@ -622,16 +593,13 @@ static int tas_snd_bass_put(struct snd_k + if (ucontrol->value.integer.value[0] < TAS3004_BASS_MIN || + ucontrol->value.integer.value[0] > TAS3004_BASS_MAX) + return -EINVAL; +- mutex_lock(&tas->mtx); +- if (tas->bass == ucontrol->value.integer.value[0]) { +- mutex_unlock(&tas->mtx); ++ guard(mutex)(&tas->mtx); ++ if (tas->bass == ucontrol->value.integer.value[0]) + return 0; +- } + + tas->bass = ucontrol->value.integer.value[0]; + if (tas->hw_enabled) + tas_set_bass(tas); +- mutex_unlock(&tas->mtx); + return 1; + } + +@@ -722,13 +690,13 @@ static int tas_switch_clock(struct codec + break; + case CLOCK_SWITCH_SLAVE: + /* Clocks are back, re-init the codec */ +- mutex_lock(&tas->mtx); +- tas_reset_init(tas); +- tas_set_volume(tas); +- tas_set_mixer(tas); +- tas->hw_enabled = 1; +- tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio); +- mutex_unlock(&tas->mtx); ++ scoped_guard(mutex, &tas->mtx) { ++ tas_reset_init(tas); ++ tas_set_volume(tas); ++ tas_set_mixer(tas); ++ tas->hw_enabled = 1; ++ tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio); ++ } + break; + default: + /* doesn't happen as of now */ +@@ -743,23 +711,21 @@ static int tas_switch_clock(struct codec + * our i2c device is suspended, and then take note of that! */ + static int tas_suspend(struct tas *tas) + { +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + tas->hw_enabled = 0; + tas->acr |= TAS_ACR_ANALOG_PDOWN; + tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr); +- mutex_unlock(&tas->mtx); + return 0; + } + + static int tas_resume(struct tas *tas) + { + /* reset codec */ +- mutex_lock(&tas->mtx); ++ guard(mutex)(&tas->mtx); + tas_reset_init(tas); + tas_set_volume(tas); + tas_set_mixer(tas); + tas->hw_enabled = 1; +- mutex_unlock(&tas->mtx); + return 0; + } + +@@ -802,14 +768,13 @@ static int tas_init_codec(struct aoa_cod + return -EINVAL; + } + +- mutex_lock(&tas->mtx); +- if (tas_reset_init(tas)) { +- printk(KERN_ERR PFX "tas failed to initialise\n"); +- mutex_unlock(&tas->mtx); +- return -ENXIO; ++ scoped_guard(mutex, &tas->mtx) { ++ if (tas_reset_init(tas)) { ++ printk(KERN_ERR PFX "tas failed to initialise\n"); ++ return -ENXIO; ++ } ++ tas->hw_enabled = 1; + } +- tas->hw_enabled = 1; +- mutex_unlock(&tas->mtx); + + if (tas->codec.soundbus_dev->attach_codec(tas->codec.soundbus_dev, + aoa_get_card(), +--- a/sound/aoa/core/gpio-feature.c ++++ b/sound/aoa/core/gpio-feature.c +@@ -212,10 +212,9 @@ static void ftr_handle_notify(struct wor + struct gpio_notification *notif = + container_of(work, struct gpio_notification, work.work); + +- mutex_lock(¬if->mutex); ++ guard(mutex)(¬if->mutex); + if (notif->notify) + notif->notify(notif->data); +- mutex_unlock(¬if->mutex); + } + + static void gpio_enable_dual_edge(int gpio) +@@ -341,19 +340,17 @@ static int ftr_set_notify(struct gpio_ru + if (!irq) + return -ENODEV; + +- mutex_lock(¬if->mutex); ++ guard(mutex)(¬if->mutex); + + old = notif->notify; + +- if (!old && !notify) { +- err = 0; +- goto out_unlock; +- } ++ if (!old && !notify) ++ return 0; + + if (old && notify) { + if (old == notify && notif->data == data) + err = 0; +- goto out_unlock; ++ return err; + } + + if (old && !notify) +@@ -362,16 +359,13 @@ static int ftr_set_notify(struct gpio_ru + if (!old && notify) { + err = request_irq(irq, ftr_handle_notify_irq, 0, name, notif); + if (err) +- goto out_unlock; ++ return err; + } + + notif->notify = notify; + notif->data = data; + +- err = 0; +- out_unlock: +- mutex_unlock(¬if->mutex); +- return err; ++ return 0; + } + + static int ftr_get_detect(struct gpio_runtime *rt, +--- a/sound/aoa/core/gpio-pmf.c ++++ b/sound/aoa/core/gpio-pmf.c +@@ -74,10 +74,9 @@ static void pmf_handle_notify(struct wor + struct gpio_notification *notif = + container_of(work, struct gpio_notification, work.work); + +- mutex_lock(¬if->mutex); ++ guard(mutex)(¬if->mutex); + if (notif->notify) + notif->notify(notif->data); +- mutex_unlock(¬if->mutex); + } + + static void pmf_gpio_init(struct gpio_runtime *rt) +@@ -154,19 +153,17 @@ static int pmf_set_notify(struct gpio_ru + return -EINVAL; + } + +- mutex_lock(¬if->mutex); ++ guard(mutex)(¬if->mutex); + + old = notif->notify; + +- if (!old && !notify) { +- err = 0; +- goto out_unlock; +- } ++ if (!old && !notify) ++ return 0; + + if (old && notify) { + if (old == notify && notif->data == data) + err = 0; +- goto out_unlock; ++ return err; + } + + if (old && !notify) { +@@ -178,10 +175,8 @@ static int pmf_set_notify(struct gpio_ru + if (!old && notify) { + irq_client = kzalloc(sizeof(struct pmf_irq_client), + GFP_KERNEL); +- if (!irq_client) { +- err = -ENOMEM; +- goto out_unlock; +- } ++ if (!irq_client) ++ return -ENOMEM; + irq_client->data = notif; + irq_client->handler = pmf_handle_notify_irq; + irq_client->owner = THIS_MODULE; +@@ -192,17 +187,14 @@ static int pmf_set_notify(struct gpio_ru + printk(KERN_ERR "snd-aoa: gpio layer failed to" + " register %s irq (%d)\n", name, err); + kfree(irq_client); +- goto out_unlock; ++ return err; + } + notif->gpio_private = irq_client; + } + notif->notify = notify; + notif->data = data; + +- err = 0; +- out_unlock: +- mutex_unlock(¬if->mutex); +- return err; ++ return 0; + } + + static int pmf_get_detect(struct gpio_runtime *rt, +--- a/sound/aoa/soundbus/i2sbus/pcm.c ++++ b/sound/aoa/soundbus/i2sbus/pcm.c +@@ -79,11 +79,10 @@ static int i2sbus_pcm_open(struct i2sbus + u64 formats = 0; + unsigned int rates = 0; + struct transfer_info v; +- int result = 0; + int bus_factor = 0, sysclock_factor = 0; + int found_this; + +- mutex_lock(&i2sdev->lock); ++ guard(mutex)(&i2sdev->lock); + + get_pcm_info(i2sdev, in, &pi, &other); + +@@ -92,8 +91,7 @@ static int i2sbus_pcm_open(struct i2sbus + + if (pi->active) { + /* alsa messed up */ +- result = -EBUSY; +- goto out_unlock; ++ return -EBUSY; + } + + /* we now need to assign the hw */ +@@ -117,10 +115,8 @@ static int i2sbus_pcm_open(struct i2sbus + ti++; + } + } +- if (!masks_inited || !bus_factor || !sysclock_factor) { +- result = -ENODEV; +- goto out_unlock; +- } ++ if (!masks_inited || !bus_factor || !sysclock_factor) ++ return -ENODEV; + /* bus dependent stuff */ + hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | +@@ -194,15 +190,12 @@ static int i2sbus_pcm_open(struct i2sbus + hw->periods_max = MAX_DBDMA_COMMANDS; + err = snd_pcm_hw_constraint_integer(pi->substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +- if (err < 0) { +- result = err; +- goto out_unlock; +- } ++ if (err < 0) ++ return err; + list_for_each_entry(cii, &sdev->codec_list, list) { + if (cii->codec->open) { + err = cii->codec->open(cii, pi->substream); + if (err) { +- result = err; + /* unwind */ + found_this = 0; + list_for_each_entry_reverse(rev, +@@ -214,14 +207,12 @@ static int i2sbus_pcm_open(struct i2sbus + if (rev == cii) + found_this = 1; + } +- goto out_unlock; ++ return err; + } + } + } + +- out_unlock: +- mutex_unlock(&i2sdev->lock); +- return result; ++ return 0; + } + + #undef CHECK_RATE +@@ -232,7 +223,7 @@ static int i2sbus_pcm_close(struct i2sbu + struct pcm_info *pi; + int err = 0, tmp; + +- mutex_lock(&i2sdev->lock); ++ guard(mutex)(&i2sdev->lock); + + get_pcm_info(i2sdev, in, &pi, NULL); + +@@ -246,7 +237,6 @@ static int i2sbus_pcm_close(struct i2sbu + + pi->substream = NULL; + pi->active = 0; +- mutex_unlock(&i2sdev->lock); + return err; + } + +@@ -330,33 +320,26 @@ static int i2sbus_pcm_prepare(struct i2s + int input_16bit; + struct pcm_info *pi, *other; + int cnt; +- int result = 0; + unsigned int cmd, stopaddr; + +- mutex_lock(&i2sdev->lock); ++ guard(mutex)(&i2sdev->lock); + + get_pcm_info(i2sdev, in, &pi, &other); + +- if (pi->dbdma_ring.running) { +- result = -EBUSY; +- goto out_unlock; +- } ++ if (pi->dbdma_ring.running) ++ return -EBUSY; + if (pi->dbdma_ring.stopping) + i2sbus_wait_for_stop(i2sdev, pi); + +- if (!pi->substream || !pi->substream->runtime) { +- result = -EINVAL; +- goto out_unlock; +- } ++ if (!pi->substream || !pi->substream->runtime) ++ return -EINVAL; + + runtime = pi->substream->runtime; + pi->active = 1; + if (other->active && + ((i2sdev->format != runtime->format) +- || (i2sdev->rate != runtime->rate))) { +- result = -EINVAL; +- goto out_unlock; +- } ++ || (i2sdev->rate != runtime->rate))) ++ return -EINVAL; + + i2sdev->format = runtime->format; + i2sdev->rate = runtime->rate; +@@ -412,10 +395,8 @@ static int i2sbus_pcm_prepare(struct i2s + bi.bus_factor = cii->codec->bus_factor; + break; + } +- if (!bi.bus_factor) { +- result = -ENODEV; +- goto out_unlock; +- } ++ if (!bi.bus_factor) ++ return -ENODEV; + input_16bit = 1; + break; + case SNDRV_PCM_FORMAT_S32_BE: +@@ -426,8 +407,7 @@ static int i2sbus_pcm_prepare(struct i2s + input_16bit = 0; + break; + default: +- result = -EINVAL; +- goto out_unlock; ++ return -EINVAL; + } + /* we assume all sysclocks are the same! */ + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { +@@ -438,10 +418,8 @@ static int i2sbus_pcm_prepare(struct i2s + if (clock_and_divisors(bi.sysclock_factor, + bi.bus_factor, + runtime->rate, +- &sfr) < 0) { +- result = -EINVAL; +- goto out_unlock; +- } ++ &sfr) < 0) ++ return -EINVAL; + switch (bi.bus_factor) { + case 32: + sfr |= I2S_SF_SERIAL_FORMAT_I2S_32X; +@@ -457,10 +435,8 @@ static int i2sbus_pcm_prepare(struct i2s + int err = 0; + if (cii->codec->prepare) + err = cii->codec->prepare(cii, &bi, pi->substream); +- if (err) { +- result = err; +- goto out_unlock; +- } ++ if (err) ++ return err; + } + /* codecs are fine with it, so set our clocks */ + if (input_16bit) +@@ -476,7 +452,7 @@ static int i2sbus_pcm_prepare(struct i2s + /* not locking these is fine since we touch them only in this function */ + if (in_le32(&i2sdev->intfregs->serial_format) == sfr + && in_le32(&i2sdev->intfregs->data_word_sizes) == dws) +- goto out_unlock; ++ return 0; + + /* let's notify the codecs about clocks going away. + * For now we only do mastering on the i2s cell... */ +@@ -514,9 +490,7 @@ static int i2sbus_pcm_prepare(struct i2s + if (cii->codec->switch_clock) + cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE); + +- out_unlock: +- mutex_unlock(&i2sdev->lock); +- return result; ++ return 0; + } + + #ifdef CONFIG_PM diff --git a/queue-6.1/arm64-mm-enable-batched-tlb-flush-in-unmap_hotplug_range.patch b/queue-6.1/arm64-mm-enable-batched-tlb-flush-in-unmap_hotplug_range.patch new file mode 100644 index 0000000000..ec0e018905 --- /dev/null +++ b/queue-6.1/arm64-mm-enable-batched-tlb-flush-in-unmap_hotplug_range.patch @@ -0,0 +1,137 @@ +From stable+bounces-241756-greg=kroah.com@vger.kernel.org Tue Apr 28 23:49:57 2026 +From: Sasha Levin +Date: Tue, 28 Apr 2026 14:19:49 -0400 +Subject: arm64/mm: Enable batched TLB flush in unmap_hotplug_range() +To: stable@vger.kernel.org +Cc: Anshuman Khandual , Will Deacon , linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, "David Hildenbrand (Arm)" , Ryan Roberts , Catalin Marinas , Sasha Levin +Message-ID: <20260428181949.3127002-1-sashal@kernel.org> + +From: Anshuman Khandual + +[ Upstream commit 48478b9f791376b4b89018d7afdfd06865498f65 ] + +During a memory hot remove operation, both linear and vmemmap mappings for +the memory range being removed, get unmapped via unmap_hotplug_range() but +mapped pages get freed only for vmemmap mapping. This is just a sequential +operation where each table entry gets cleared, followed by a leaf specific +TLB flush, and then followed by memory free operation when applicable. + +This approach was simple and uniform both for vmemmap and linear mappings. +But linear mapping might contain CONT marked block memory where it becomes +necessary to first clear out all entire in the range before a TLB flush. +This is as per the architecture requirement. Hence batch all TLB flushes +during the table tear down walk and finally do it in unmap_hotplug_range(). + +Prior to this fix, it was hypothetically possible for a speculative access +to a higher address in the contiguous block to fill the TLB with shattered +entries for the entire contiguous range after a lower address had already +been cleared and invalidated. Due to the table entries being shattered, the +subsequent TLB invalidation for the higher address would not then clear the +TLB entries for the lower address, meaning stale TLB entries could persist. + +Besides it also helps in improving the performance via TLBI range operation +along with reduced synchronization instructions. The time spent executing +unmap_hotplug_range() improved 97% measured over a 2GB memory hot removal +in KVM guest. + +This scheme is not applicable during vmemmap mapping tear down where memory +needs to be freed and hence a TLB flush is required after clearing out page +table entry. + +Cc: Will Deacon +Cc: linux-arm-kernel@lists.infradead.org +Cc: linux-kernel@vger.kernel.org +Closes: https://lore.kernel.org/all/aWZYXhrT6D2M-7-N@willie-the-truck/ +Fixes: bbd6ec605c0f ("arm64/mm: Enable memory hot remove") +Cc: stable@vger.kernel.org +Reviewed-by: David Hildenbrand (Arm) +Reviewed-by: Ryan Roberts +Signed-off-by: Ryan Roberts +Signed-off-by: Anshuman Khandual +Signed-off-by: Catalin Marinas +[ replaced `__pte_clear()` with `pte_clear()` ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + arch/arm64/mm/mmu.c | 36 ++++++++++++++++++++---------------- + 1 file changed, 20 insertions(+), 16 deletions(-) + +--- a/arch/arm64/mm/mmu.c ++++ b/arch/arm64/mm/mmu.c +@@ -925,10 +925,14 @@ static void unmap_hotplug_pte_range(pmd_ + + WARN_ON(!pte_present(pte)); + pte_clear(&init_mm, addr, ptep); +- flush_tlb_kernel_range(addr, addr + PAGE_SIZE); +- if (free_mapped) ++ if (free_mapped) { ++ /* CONT blocks are not supported in the vmemmap */ ++ WARN_ON(pte_cont(pte)); ++ flush_tlb_kernel_range(addr, addr + PAGE_SIZE); + free_hotplug_page_range(pte_page(pte), + PAGE_SIZE, altmap); ++ } ++ /* unmap_hotplug_range() flushes TLB for !free_mapped */ + } while (addr += PAGE_SIZE, addr < end); + } + +@@ -949,15 +953,14 @@ static void unmap_hotplug_pmd_range(pud_ + WARN_ON(!pmd_present(pmd)); + if (pmd_sect(pmd)) { + pmd_clear(pmdp); +- +- /* +- * One TLBI should be sufficient here as the PMD_SIZE +- * range is mapped with a single block entry. +- */ +- flush_tlb_kernel_range(addr, addr + PAGE_SIZE); +- if (free_mapped) ++ if (free_mapped) { ++ /* CONT blocks are not supported in the vmemmap */ ++ WARN_ON(pmd_cont(pmd)); ++ flush_tlb_kernel_range(addr, addr + PMD_SIZE); + free_hotplug_page_range(pmd_page(pmd), + PMD_SIZE, altmap); ++ } ++ /* unmap_hotplug_range() flushes TLB for !free_mapped */ + continue; + } + WARN_ON(!pmd_table(pmd)); +@@ -982,15 +985,12 @@ static void unmap_hotplug_pud_range(p4d_ + WARN_ON(!pud_present(pud)); + if (pud_sect(pud)) { + pud_clear(pudp); +- +- /* +- * One TLBI should be sufficient here as the PUD_SIZE +- * range is mapped with a single block entry. +- */ +- flush_tlb_kernel_range(addr, addr + PAGE_SIZE); +- if (free_mapped) ++ if (free_mapped) { ++ flush_tlb_kernel_range(addr, addr + PUD_SIZE); + free_hotplug_page_range(pud_page(pud), + PUD_SIZE, altmap); ++ } ++ /* unmap_hotplug_range() flushes TLB for !free_mapped */ + continue; + } + WARN_ON(!pud_table(pud)); +@@ -1020,6 +1020,7 @@ static void unmap_hotplug_p4d_range(pgd_ + static void unmap_hotplug_range(unsigned long addr, unsigned long end, + bool free_mapped, struct vmem_altmap *altmap) + { ++ unsigned long start = addr; + unsigned long next; + pgd_t *pgdp, pgd; + +@@ -1041,6 +1042,9 @@ static void unmap_hotplug_range(unsigned + WARN_ON(!pgd_present(pgd)); + unmap_hotplug_p4d_range(pgdp, addr, next, free_mapped, altmap); + } while (addr = next, addr < end); ++ ++ if (!free_mapped) ++ flush_tlb_kernel_range(start, end); + } + + static void free_empty_pte_table(pmd_t *pmdp, unsigned long addr, diff --git a/queue-6.1/f2fs-fix-to-do-sanity-check-on-dcc-discard_cmd_cnt-conditionally.patch b/queue-6.1/f2fs-fix-to-do-sanity-check-on-dcc-discard_cmd_cnt-conditionally.patch new file mode 100644 index 0000000000..566efabdc0 --- /dev/null +++ b/queue-6.1/f2fs-fix-to-do-sanity-check-on-dcc-discard_cmd_cnt-conditionally.patch @@ -0,0 +1,151 @@ +From stable+bounces-240654-greg=kroah.com@vger.kernel.org Fri Apr 24 18:33:43 2026 +From: Sasha Levin +Date: Fri, 24 Apr 2026 09:03:33 -0400 +Subject: f2fs: fix to do sanity check on dcc->discard_cmd_cnt conditionally +To: stable@vger.kernel.org +Cc: Chao Yu , stable@kernel.org, syzbot+62538b67389ee582837a@syzkaller.appspotmail.com, Jaegeuk Kim , Sasha Levin +Message-ID: <20260424130333.1916989-2-sashal@kernel.org> + +From: Chao Yu + +[ Upstream commit 6af249c996f7d73a3435f9e577956fa259347d18 ] + +Syzbot reported a f2fs bug as below: + +------------[ cut here ]------------ +kernel BUG at fs/f2fs/segment.c:1900! +Oops: invalid opcode: 0000 [#1] SMP KASAN PTI +CPU: 1 UID: 0 PID: 6527 Comm: syz.5.110 Not tainted syzkaller #0 PREEMPT_{RT,(full)} +Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026 +RIP: 0010:f2fs_issue_discard_timeout+0x59b/0x5a0 fs/f2fs/segment.c:1900 +Code: d9 80 e1 07 80 c1 03 38 c1 0f 8c d6 fe ff ff 48 89 df e8 a8 5e fa fd e9 c9 fe ff ff e8 4e 46 94 fd 90 0f 0b e8 46 46 94 fd 90 <0f> 0b 0f 1f 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 +RSP: 0018:ffffc9000494f940 EFLAGS: 00010283 +RAX: ffffffff843009ca RBX: 0000000000000001 RCX: 0000000000080000 +RDX: ffffc9001ca78000 RSI: 00000000000029f3 RDI: 00000000000029f4 +RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 +R10: dffffc0000000000 R11: ffffed100893a431 R12: 1ffff1100893a430 +R13: 1ffff1100c2b702c R14: dffffc0000000000 R15: ffff8880449d2160 +FS: 00007ffa35fed6c0(0000) GS:ffff88812643d000(0000) knlGS:0000000000000000 +CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 +CR2: 00007f2b68634000 CR3: 0000000039f62000 CR4: 00000000003526f0 +Call Trace: + + __f2fs_remount fs/f2fs/super.c:2960 [inline] + f2fs_reconfigure+0x108a/0x1710 fs/f2fs/super.c:5443 + reconfigure_super+0x227/0x8a0 fs/super.c:1080 + do_remount fs/namespace.c:3391 [inline] + path_mount+0xdc5/0x10e0 fs/namespace.c:4151 + do_mount fs/namespace.c:4172 [inline] + __do_sys_mount fs/namespace.c:4361 [inline] + __se_sys_mount+0x31d/0x420 fs/namespace.c:4338 + do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] + do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94 + entry_SYSCALL_64_after_hwframe+0x77/0x7f +RIP: 0033:0x7ffa37dbda0a + +The root cause is there will be race condition in between f2fs_ioc_fitrim() +and f2fs_remount(): + +- f2fs_remount - f2fs_ioc_fitrim + - f2fs_issue_discard_timeout + - __issue_discard_cmd + - __drop_discard_cmd + - __wait_all_discard_cmd + - f2fs_trim_fs + - f2fs_write_checkpoint + - f2fs_clear_prefree_segments + - f2fs_issue_discard + - __issue_discard_async + - __queue_discard_cmd + - __update_discard_tree_range + - __insert_discard_cmd + - __create_discard_cmd + : atomic_inc(&dcc->discard_cmd_cnt); + - sanity check on dcc->discard_cmd_cnt (expect discard_cmd_cnt to be zero) + +This will only happen when fitrim races w/ remount rw, if we remount to +readonly filesystem, remount will wait until mnt_pcp.mnt_writers to zero, +that means fitrim is not in process at that time. + +Cc: stable@kernel.org +Fixes: 2482c4325dfe ("f2fs: detect bug_on in f2fs_wait_discard_bios") +Reported-by: syzbot+62538b67389ee582837a@syzkaller.appspotmail.com +Closes: https://lore.kernel.org/linux-f2fs-devel/69b07d7c.050a0220.8df7.09a1.GAE@google.com +Signed-off-by: Chao Yu +Signed-off-by: Jaegeuk Kim +[ adapted `f2fs_remount` call to pass `*flags & SB_RDONLY` for the old mount API ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/f2fs/f2fs.h | 2 +- + fs/f2fs/segment.c | 6 +++--- + fs/f2fs/super.c | 10 ++++++++-- + 3 files changed, 12 insertions(+), 6 deletions(-) + +--- a/fs/f2fs/f2fs.h ++++ b/fs/f2fs/f2fs.h +@@ -3638,7 +3638,7 @@ bool f2fs_is_checkpointed_data(struct f2 + int f2fs_start_discard_thread(struct f2fs_sb_info *sbi); + void f2fs_drop_discard_cmd(struct f2fs_sb_info *sbi); + void f2fs_stop_discard_thread(struct f2fs_sb_info *sbi); +-bool f2fs_issue_discard_timeout(struct f2fs_sb_info *sbi); ++bool f2fs_issue_discard_timeout(struct f2fs_sb_info *sbi, bool need_check); + void f2fs_clear_prefree_segments(struct f2fs_sb_info *sbi, + struct cp_control *cpc); + void f2fs_dirty_to_prefree(struct f2fs_sb_info *sbi); +--- a/fs/f2fs/segment.c ++++ b/fs/f2fs/segment.c +@@ -1659,7 +1659,7 @@ void f2fs_stop_discard_thread(struct f2f + } + + /* This comes from f2fs_put_super */ +-bool f2fs_issue_discard_timeout(struct f2fs_sb_info *sbi) ++bool f2fs_issue_discard_timeout(struct f2fs_sb_info *sbi, bool need_check) + { + struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info; + struct discard_policy dpolicy; +@@ -1673,7 +1673,7 @@ bool f2fs_issue_discard_timeout(struct f + /* just to make sure there is no pending discard commands */ + __wait_all_discard_cmd(sbi, NULL); + +- f2fs_bug_on(sbi, atomic_read(&dcc->discard_cmd_cnt)); ++ f2fs_bug_on(sbi, need_check && atomic_read(&dcc->discard_cmd_cnt)); + return dropped; + } + +@@ -2119,7 +2119,7 @@ static void destroy_discard_cmd_control( + * fill_super(), it needs to give a chance to handle them. + */ + if (unlikely(atomic_read(&dcc->discard_cmd_cnt))) +- f2fs_issue_discard_timeout(sbi); ++ f2fs_issue_discard_timeout(sbi, true); + + kfree(dcc); + SM_I(sbi)->dcc_info = NULL; +--- a/fs/f2fs/super.c ++++ b/fs/f2fs/super.c +@@ -1619,7 +1619,7 @@ static void f2fs_put_super(struct super_ + } + + /* be sure to wait for any on-going discard commands */ +- dropped = f2fs_issue_discard_timeout(sbi); ++ dropped = f2fs_issue_discard_timeout(sbi, true); + + if ((f2fs_hw_support_discard(sbi) || f2fs_hw_should_discard(sbi)) && + !sbi->discard_blks && !dropped) { +@@ -2452,8 +2452,14 @@ static int f2fs_remount(struct super_blo + } else { + dcc = SM_I(sbi)->dcc_info; + f2fs_stop_discard_thread(sbi); ++ /* ++ * f2fs_ioc_fitrim() won't race w/ "remount ro" ++ * so it's safe to check discard_cmd_cnt in ++ * f2fs_issue_discard_timeout(). ++ */ + if (atomic_read(&dcc->discard_cmd_cnt)) +- f2fs_issue_discard_timeout(sbi); ++ f2fs_issue_discard_timeout(sbi, ++ *flags & SB_RDONLY); + need_restart_discard = true; + } + } diff --git a/queue-6.1/f2fs-fix-uaf-caused-by-decrementing-sbi-nr_pages-in-f2fs_write_end_io.patch b/queue-6.1/f2fs-fix-uaf-caused-by-decrementing-sbi-nr_pages-in-f2fs_write_end_io.patch new file mode 100644 index 0000000000..a4345d5368 --- /dev/null +++ b/queue-6.1/f2fs-fix-uaf-caused-by-decrementing-sbi-nr_pages-in-f2fs_write_end_io.patch @@ -0,0 +1,76 @@ +From stable+bounces-240704-greg=kroah.com@vger.kernel.org Fri Apr 24 19:09:25 2026 +From: Sasha Levin +Date: Fri, 24 Apr 2026 09:33:38 -0400 +Subject: f2fs: fix UAF caused by decrementing sbi->nr_pages[] in f2fs_write_end_io() +To: stable@vger.kernel.org +Cc: Yongpeng Yang , stable@kernel.org, syzbot+6e4cb1cac5efc96ea0ca@syzkaller.appspotmail.com, Chao Yu , Jaegeuk Kim , Sasha Levin +Message-ID: <20260424133338.1965493-1-sashal@kernel.org> + +From: Yongpeng Yang + +[ Upstream commit 2d9c4a4ed4eef1f82c5b16b037aee8bad819fd53 ] + +The xfstests case "generic/107" and syzbot have both reported a NULL +pointer dereference. + +The concurrent scenario that triggers the panic is as follows: + +F2FS_WB_CP_DATA write callback umount + - f2fs_write_checkpoint + - f2fs_wait_on_all_pages(sbi, F2FS_WB_CP_DATA) +- blk_mq_end_request + - bio_endio + - f2fs_write_end_io + : dec_page_count(sbi, F2FS_WB_CP_DATA) + : wake_up(&sbi->cp_wait) + - kill_f2fs_super + - kill_block_super + - f2fs_put_super + : iput(sbi->node_inode) + : sbi->node_inode = NULL + : f2fs_in_warm_node_list + - is_node_folio // sbi->node_inode is NULL and panic + +The root cause is that f2fs_put_super() calls iput(sbi->node_inode) and +sets sbi->node_inode to NULL after sbi->nr_pages[F2FS_WB_CP_DATA] is +decremented to zero. As a result, f2fs_in_warm_node_list() may +dereference a NULL node_inode when checking whether a folio belongs to +the node inode, leading to a panic. + +This patch fixes the issue by calling f2fs_in_warm_node_list() before +decrementing sbi->nr_pages[F2FS_WB_CP_DATA], thus preventing the +use-after-free condition. + +Cc: stable@kernel.org +Fixes: 50fa53eccf9f ("f2fs: fix to avoid broken of dnode block list") +Reported-by: syzbot+6e4cb1cac5efc96ea0ca@syzkaller.appspotmail.com +Signed-off-by: Yongpeng Yang +Reviewed-by: Chao Yu +Signed-off-by: Jaegeuk Kim +[ folio => page ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/f2fs/data.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/fs/f2fs/data.c ++++ b/fs/f2fs/data.c +@@ -356,6 +356,8 @@ static void f2fs_write_end_io(struct bio + + f2fs_bug_on(sbi, page->mapping == NODE_MAPPING(sbi) && + page->index != nid_of_node(page)); ++ if (f2fs_in_warm_node_list(sbi, page)) ++ f2fs_del_fsync_node_entry(sbi, page); + + dec_page_count(sbi, type); + +@@ -367,8 +369,6 @@ static void f2fs_write_end_io(struct bio + wq_has_sleeper(&sbi->cp_wait)) + wake_up(&sbi->cp_wait); + +- if (f2fs_in_warm_node_list(sbi, page)) +- f2fs_del_fsync_node_entry(sbi, page); + clear_page_private_gcing(page); + end_page_writeback(page); + } diff --git a/queue-6.1/f2fs-use-kfree-instead-of-kvfree-to-free-some-memory.patch b/queue-6.1/f2fs-use-kfree-instead-of-kvfree-to-free-some-memory.patch new file mode 100644 index 0000000000..c2bb19825b --- /dev/null +++ b/queue-6.1/f2fs-use-kfree-instead-of-kvfree-to-free-some-memory.patch @@ -0,0 +1,108 @@ +From stable+bounces-240653-greg=kroah.com@vger.kernel.org Fri Apr 24 18:33:42 2026 +From: Sasha Levin +Date: Fri, 24 Apr 2026 09:03:32 -0400 +Subject: f2fs: use kfree() instead of kvfree() to free some memory +To: stable@vger.kernel.org +Cc: Jiazi Li , "peixuan.qiu" , Chao Yu , Jaegeuk Kim , Sasha Levin +Message-ID: <20260424130333.1916989-1-sashal@kernel.org> + +From: Jiazi Li + +[ Upstream commit e9705c61b1dbe7bac9dc189de434994d8a76b191 ] + +options in f2fs_fill_super is alloc by kstrdup: + options = kstrdup((const char *)data, GFP_KERNEL) +sit_bitmap[_mir], nat_bitmap[_mir] are alloc by kmemdup: + sit_i->sit_bitmap = kmemdup(src_bitmap, sit_bitmap_size, GFP_KERNEL); + sit_i->sit_bitmap_mir = kmemdup(src_bitmap, + sit_bitmap_size, GFP_KERNEL); + nm_i->nat_bitmap = kmemdup(version_bitmap, nm_i->bitmap_size, + GFP_KERNEL); + nm_i->nat_bitmap_mir = kmemdup(version_bitmap, nm_i->bitmap_size, + GFP_KERNEL); +write_io is alloc by f2fs_kmalloc: + sbi->write_io[i] = f2fs_kmalloc(sbi, + array_size(n, sizeof(struct f2fs_bio_info)) + +Use kfree is more efficient. + +Signed-off-by: Jiazi Li +Signed-off-by: peixuan.qiu +Reviewed-by: Chao Yu +Signed-off-by: Jaegeuk Kim +Stable-dep-of: 6af249c996f7 ("f2fs: fix to do sanity check on dcc->discard_cmd_cnt conditionally") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/f2fs/node.c | 4 ++-- + fs/f2fs/segment.c | 4 ++-- + fs/f2fs/super.c | 8 ++++---- + 3 files changed, 8 insertions(+), 8 deletions(-) + +--- a/fs/f2fs/node.c ++++ b/fs/f2fs/node.c +@@ -3433,10 +3433,10 @@ void f2fs_destroy_node_manager(struct f2 + } + kvfree(nm_i->free_nid_count); + +- kvfree(nm_i->nat_bitmap); ++ kfree(nm_i->nat_bitmap); + kvfree(nm_i->nat_bits); + #ifdef CONFIG_F2FS_CHECK_FS +- kvfree(nm_i->nat_bitmap_mir); ++ kfree(nm_i->nat_bitmap_mir); + #endif + sbi->nm_info = NULL; + kfree(nm_i); +--- a/fs/f2fs/segment.c ++++ b/fs/f2fs/segment.c +@@ -5180,9 +5180,9 @@ static void destroy_sit_info(struct f2fs + kvfree(sit_i->dirty_sentries_bitmap); + + SM_I(sbi)->sit_info = NULL; +- kvfree(sit_i->sit_bitmap); ++ kfree(sit_i->sit_bitmap); + #ifdef CONFIG_F2FS_CHECK_FS +- kvfree(sit_i->sit_bitmap_mir); ++ kfree(sit_i->sit_bitmap_mir); + kvfree(sit_i->invalid_segmap); + #endif + kfree(sit_i); +--- a/fs/f2fs/super.c ++++ b/fs/f2fs/super.c +@@ -1683,7 +1683,7 @@ static void f2fs_put_super(struct super_ + destroy_percpu_info(sbi); + f2fs_destroy_iostat(sbi); + for (i = 0; i < NR_PAGE_TYPE; i++) +- kvfree(sbi->write_io[i]); ++ kfree(sbi->write_io[i]); + #if IS_ENABLED(CONFIG_UNICODE) + utf8_unload(sb->s_encoding); + #endif +@@ -4548,7 +4548,7 @@ reset_checkpoint: + if (err) + goto sync_free_meta; + } +- kvfree(options); ++ kfree(options); + + /* recover broken superblock */ + if (recovery) { +@@ -4627,7 +4627,7 @@ free_iostat: + f2fs_destroy_iostat(sbi); + free_bio_info: + for (i = 0; i < NR_PAGE_TYPE; i++) +- kvfree(sbi->write_io[i]); ++ kfree(sbi->write_io[i]); + + #if IS_ENABLED(CONFIG_UNICODE) + utf8_unload(sb->s_encoding); +@@ -4639,7 +4639,7 @@ free_options: + kfree(F2FS_OPTION(sbi).s_qf_names[i]); + #endif + fscrypt_free_dummy_policy(&F2FS_OPTION(sbi).dummy_enc_policy); +- kvfree(options); ++ kfree(options); + free_sb_buf: + kfree(raw_super); + free_sbi: diff --git a/queue-6.1/fbdev-defio-disconnect-deferred-i-o-from-the-lifetime-of-struct-fb_info.patch b/queue-6.1/fbdev-defio-disconnect-deferred-i-o-from-the-lifetime-of-struct-fb_info.patch new file mode 100644 index 0000000000..16560d95c9 --- /dev/null +++ b/queue-6.1/fbdev-defio-disconnect-deferred-i-o-from-the-lifetime-of-struct-fb_info.patch @@ -0,0 +1,361 @@ +From stable+bounces-244001-greg=kroah.com@vger.kernel.org Tue May 5 13:03:20 2026 +From: Sasha Levin +Date: Tue, 5 May 2026 03:31:29 -0400 +Subject: fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info +To: stable@vger.kernel.org +Cc: Thomas Zimmermann , Helge Deller , linux-fbdev@vger.kernel.org, dri-devel@lists.freedesktop.org, Sasha Levin +Message-ID: <20260505073129.414475-1-sashal@kernel.org> + +From: Thomas Zimmermann + +[ Upstream commit 9ded47ad003f09a94b6a710b5c47f4aa5ceb7429 ] + +Hold state of deferred I/O in struct fb_deferred_io_state. Allocate an +instance as part of initializing deferred I/O and remove it only after +the final mapping has been closed. If the fb_info and the contained +deferred I/O meanwhile goes away, clear struct fb_deferred_io_state.info +to invalidate the mapping. Any access will then result in a SIGBUS +signal. + +Fixes a long-standing problem, where a device hot-unplug happens while +user space still has an active mapping of the graphics memory. The hot- +unplug frees the instance of struct fb_info. Accessing the memory will +operate on undefined state. + +Signed-off-by: Thomas Zimmermann +Fixes: 60b59beafba8 ("fbdev: mm: Deferred IO support") +Cc: Helge Deller +Cc: linux-fbdev@vger.kernel.org +Cc: dri-devel@lists.freedesktop.org +Cc: stable@vger.kernel.org # v2.6.22+ +Signed-off-by: Helge Deller +[ context + _obj() conversion ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/video/fbdev/core/fb_defio.c | 164 +++++++++++++++++++++++++++++++----- + include/linux/fb.h | 4 + 2 files changed, 145 insertions(+), 23 deletions(-) + +--- a/drivers/video/fbdev/core/fb_defio.c ++++ b/drivers/video/fbdev/core/fb_defio.c +@@ -23,6 +23,75 @@ + #include + #include + ++/* ++ * struct fb_deferred_io_state ++ */ ++ ++struct fb_deferred_io_state { ++ struct kref ref; ++ ++ struct mutex lock; /* mutex that protects the pageref list */ ++ /* fields protected by lock */ ++ struct fb_info *info; ++}; ++ ++static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void) ++{ ++ struct fb_deferred_io_state *fbdefio_state; ++ ++ fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL); ++ if (!fbdefio_state) ++ return NULL; ++ ++ kref_init(&fbdefio_state->ref); ++ mutex_init(&fbdefio_state->lock); ++ ++ return fbdefio_state; ++} ++ ++static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state) ++{ ++ mutex_destroy(&fbdefio_state->lock); ++ ++ kfree(fbdefio_state); ++} ++ ++static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state) ++{ ++ kref_get(&fbdefio_state->ref); ++} ++ ++static void __fb_deferred_io_state_release(struct kref *ref) ++{ ++ struct fb_deferred_io_state *fbdefio_state = ++ container_of(ref, struct fb_deferred_io_state, ref); ++ ++ fb_deferred_io_state_release(fbdefio_state); ++} ++ ++static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state) ++{ ++ kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release); ++} ++ ++/* ++ * struct vm_operations_struct ++ */ ++ ++static void fb_deferred_io_vm_open(struct vm_area_struct *vma) ++{ ++ struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data; ++ ++ fb_deferred_io_state_get(fbdefio_state); ++} ++ ++static void fb_deferred_io_vm_close(struct vm_area_struct *vma) ++{ ++ struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data; ++ ++ fb_deferred_io_state_put(fbdefio_state); ++} ++ + static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs) + { + void *screen_base = (void __force *) info->screen_base; +@@ -93,17 +162,31 @@ static void fb_deferred_io_pageref_put(s + /* this is to find and return the vmalloc-ed fb pages */ + static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf) + { ++ struct fb_info *info; + unsigned long offset; + struct page *page; +- struct fb_info *info = vmf->vma->vm_private_data; ++ vm_fault_t ret; ++ struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data; ++ ++ mutex_lock(&fbdefio_state->lock); ++ ++ info = fbdefio_state->info; ++ if (!info) { ++ ret = VM_FAULT_SIGBUS; /* our device is gone */ ++ goto err_mutex_unlock; ++ } + + offset = vmf->pgoff << PAGE_SHIFT; +- if (offset >= info->fix.smem_len) +- return VM_FAULT_SIGBUS; ++ if (offset >= info->fix.smem_len) { ++ ret = VM_FAULT_SIGBUS; ++ goto err_mutex_unlock; ++ } + + page = fb_deferred_io_page(info, offset); +- if (!page) +- return VM_FAULT_SIGBUS; ++ if (!page) { ++ ret = VM_FAULT_SIGBUS; ++ goto err_mutex_unlock; ++ } + + get_page(page); + +@@ -115,8 +198,15 @@ static vm_fault_t fb_deferred_io_fault(s + BUG_ON(!page->mapping); + page->index = vmf->pgoff; /* for page_mkclean() */ + ++ mutex_unlock(&fbdefio_state->lock); ++ + vmf->page = page; ++ + return 0; ++ ++err_mutex_unlock: ++ mutex_unlock(&fbdefio_state->lock); ++ return ret; + } + + int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync) +@@ -143,15 +233,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync); + * Adds a page to the dirty list. Call this from struct + * vm_operations_struct.page_mkwrite. + */ +-static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset, +- struct page *page) ++static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state, ++ unsigned long offset, struct page *page) + { +- struct fb_deferred_io *fbdefio = info->fbdefio; ++ struct fb_info *info; ++ struct fb_deferred_io *fbdefio; + struct fb_deferred_io_pageref *pageref; + vm_fault_t ret; + + /* protect against the workqueue changing the page list */ +- mutex_lock(&fbdefio->lock); ++ mutex_lock(&fbdefio_state->lock); ++ ++ info = fbdefio_state->info; ++ if (!info) { ++ ret = VM_FAULT_SIGBUS; /* our device is gone */ ++ goto err_mutex_unlock; ++ } ++ ++ fbdefio = info->fbdefio; + + /* first write in this cycle, notify the driver */ + if (fbdefio->first_io && list_empty(&fbdefio->pagereflist)) +@@ -173,14 +272,14 @@ static vm_fault_t fb_deferred_io_track_p + */ + lock_page(pageref->page); + +- mutex_unlock(&fbdefio->lock); ++ mutex_unlock(&fbdefio_state->lock); + + /* come back after delay to process the deferred IO */ + schedule_delayed_work(&info->deferred_work, fbdefio->delay); + return VM_FAULT_LOCKED; + + err_mutex_unlock: +- mutex_unlock(&fbdefio->lock); ++ mutex_unlock(&fbdefio_state->lock); + return ret; + } + +@@ -198,25 +297,28 @@ err_mutex_unlock: + * Returns: + * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise. + */ +-static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf) ++static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state, ++ struct vm_fault *vmf) + { + unsigned long offset = vmf->pgoff << PAGE_SHIFT; + struct page *page = vmf->page; + + file_update_time(vmf->vma->vm_file); + +- return fb_deferred_io_track_page(info, offset, page); ++ return fb_deferred_io_track_page(fbdefio_state, offset, page); + } + + /* vm_ops->page_mkwrite handler */ + static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) + { +- struct fb_info *info = vmf->vma->vm_private_data; ++ struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data; + +- return fb_deferred_io_page_mkwrite(info, vmf); ++ return fb_deferred_io_page_mkwrite(fbdefio_state, vmf); + } + + static const struct vm_operations_struct fb_deferred_io_vm_ops = { ++ .open = fb_deferred_io_vm_open, ++ .close = fb_deferred_io_vm_close, + .fault = fb_deferred_io_fault, + .page_mkwrite = fb_deferred_io_mkwrite, + }; +@@ -231,7 +333,10 @@ int fb_deferred_io_mmap(struct fb_info * + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + if (!(info->flags & FBINFO_VIRTFB)) + vma->vm_flags |= VM_IO; +- vma->vm_private_data = info; ++ vma->vm_private_data = info->fbdefio_state; ++ ++ fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */ ++ + return 0; + } + EXPORT_SYMBOL_GPL(fb_deferred_io_mmap); +@@ -242,9 +347,10 @@ static void fb_deferred_io_work(struct w + struct fb_info *info = container_of(work, struct fb_info, deferred_work.work); + struct fb_deferred_io_pageref *pageref, *next; + struct fb_deferred_io *fbdefio = info->fbdefio; ++ struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state; + + /* here we mkclean the pages, then do all deferred IO */ +- mutex_lock(&fbdefio->lock); ++ mutex_lock(&fbdefio_state->lock); + list_for_each_entry(pageref, &fbdefio->pagereflist, list) { + struct page *cur = pageref->page; + lock_page(cur); +@@ -259,12 +365,13 @@ static void fb_deferred_io_work(struct w + list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list) + fb_deferred_io_pageref_put(pageref, info); + +- mutex_unlock(&fbdefio->lock); ++ mutex_unlock(&fbdefio_state->lock); + } + + int fb_deferred_io_init(struct fb_info *info) + { + struct fb_deferred_io *fbdefio = info->fbdefio; ++ struct fb_deferred_io_state *fbdefio_state; + struct fb_deferred_io_pageref *pagerefs; + unsigned long npagerefs, i; + int ret; +@@ -274,7 +381,11 @@ int fb_deferred_io_init(struct fb_info * + if (WARN_ON(!info->fix.smem_len)) + return -EINVAL; + +- mutex_init(&fbdefio->lock); ++ fbdefio_state = fb_deferred_io_state_alloc(); ++ if (!fbdefio_state) ++ return -ENOMEM; ++ fbdefio_state->info = info; ++ + INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); + INIT_LIST_HEAD(&fbdefio->pagereflist); + if (fbdefio->delay == 0) /* set a default of 1 s */ +@@ -293,10 +404,12 @@ int fb_deferred_io_init(struct fb_info * + info->npagerefs = npagerefs; + info->pagerefs = pagerefs; + ++ info->fbdefio_state = fbdefio_state; ++ + return 0; + + err: +- mutex_destroy(&fbdefio->lock); ++ fb_deferred_io_state_release(fbdefio_state); + return ret; + } + EXPORT_SYMBOL_GPL(fb_deferred_io_init); +@@ -337,11 +450,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release + + void fb_deferred_io_cleanup(struct fb_info *info) + { +- struct fb_deferred_io *fbdefio = info->fbdefio; ++ struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state; + + fb_deferred_io_lastclose(info); + ++ info->fbdefio_state = NULL; ++ ++ mutex_lock(&fbdefio_state->lock); ++ fbdefio_state->info = NULL; ++ mutex_unlock(&fbdefio_state->lock); ++ ++ fb_deferred_io_state_put(fbdefio_state); ++ + kvfree(info->pagerefs); +- mutex_destroy(&fbdefio->lock); + } + EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup); +--- a/include/linux/fb.h ++++ b/include/linux/fb.h +@@ -213,12 +213,13 @@ struct fb_deferred_io { + unsigned long delay; + bool sort_pagereflist; /* sort pagelist by offset */ + int open_count; /* number of opened files; protected by fb_info lock */ +- struct mutex lock; /* mutex that protects the pageref list */ + struct list_head pagereflist; /* list of pagerefs for touched pages */ + /* callback */ + void (*first_io)(struct fb_info *info); + void (*deferred_io)(struct fb_info *info, struct list_head *pagelist); + }; ++ ++struct fb_deferred_io_state; + #endif + + /* +@@ -479,6 +480,7 @@ struct fb_info { + unsigned long npagerefs; + struct fb_deferred_io_pageref *pagerefs; + struct fb_deferred_io *fbdefio; ++ struct fb_deferred_io_state *fbdefio_state; + #endif + + const struct fb_ops *fbops; diff --git a/queue-6.1/ksmbd-require-minimum-ace-size-in-smb_check_perm_dacl.patch b/queue-6.1/ksmbd-require-minimum-ace-size-in-smb_check_perm_dacl.patch new file mode 100644 index 0000000000..973f8ccc1d --- /dev/null +++ b/queue-6.1/ksmbd-require-minimum-ace-size-in-smb_check_perm_dacl.patch @@ -0,0 +1,107 @@ +From stable+bounces-241013-greg=kroah.com@vger.kernel.org Fri Apr 24 22:52:26 2026 +From: Sasha Levin +Date: Fri, 24 Apr 2026 13:22:12 -0400 +Subject: ksmbd: require minimum ACE size in smb_check_perm_dacl() +To: stable@vger.kernel.org +Cc: Michael Bommarito , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260424172212.2366923-1-sashal@kernel.org> + +From: Michael Bommarito + +[ Upstream commit d07b26f39246a82399661936dd0c853983cfade7 ] + +Both ACE-walk loops in smb_check_perm_dacl() only guard against an +under-sized remaining buffer, not against an ACE whose declared +`ace->size` is smaller than the struct it claims to describe: + + if (offsetof(struct smb_ace, access_req) > aces_size) + break; + ace_size = le16_to_cpu(ace->size); + if (ace_size > aces_size) + break; + +The first check only requires the 4-byte ACE header to be in bounds; +it does not require access_req (4 bytes at offset 4) to be readable. +An attacker who has set a crafted DACL on a file they own can declare +ace->size == 4 with aces_size == 4, pass both checks, and then + + granted |= le32_to_cpu(ace->access_req); /* upper loop */ + compare_sids(&sid, &ace->sid); /* lower loop */ + +reads access_req at offset 4 (OOB by up to 4 bytes) and ace->sid at +offset 8 (OOB by up to CIFS_SID_BASE_SIZE + SID_MAX_SUB_AUTHORITIES +* 4 bytes). + +Tighten both loops to require + + ace_size >= offsetof(struct smb_ace, sid) + CIFS_SID_BASE_SIZE + +which is the smallest valid on-wire ACE layout (4-byte header + +4-byte access_req + 8-byte sid base with zero sub-auths). Also +reject ACEs whose sid.num_subauth exceeds SID_MAX_SUB_AUTHORITIES +before letting compare_sids() dereference sub_auth[] entries. + +parse_sec_desc() already enforces an equivalent check (lines 441-448); +smb_check_perm_dacl() simply grew weaker validation over time. + +Reachability: authenticated SMB client with permission to set an ACL +on a file. On a subsequent CREATE against that file, the kernel +walks the stored DACL via smb_check_perm_dacl() and triggers the +OOB read. Not pre-auth, and the OOB read is not reflected to the +attacker, but KASAN reports and kernel state corruption are +possible. + +Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-6 +Assisted-by: Codex:gpt-5-4 +Signed-off-by: Michael Bommarito +Acked-by: Namjae Jeon +Signed-off-by: Steve French +[ changed le16_to_cpu to le32_to_cpu for num_aces field which is __le32 ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/smbacl.c | 17 +++++++++++++---- + 1 file changed, 13 insertions(+), 4 deletions(-) + +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -1297,10 +1297,13 @@ int smb_check_perm_dacl(struct ksmbd_con + ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl)); + aces_size = acl_size - sizeof(struct smb_acl); + for (i = 0; i < le32_to_cpu(pdacl->num_aces); i++) { +- if (offsetof(struct smb_ace, access_req) > aces_size) ++ if (offsetof(struct smb_ace, sid) + ++ aces_size < CIFS_SID_BASE_SIZE) + break; + ace_size = le16_to_cpu(ace->size); +- if (ace_size > aces_size) ++ if (ace_size > aces_size || ++ ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + aces_size -= ace_size; + granted |= le32_to_cpu(ace->access_req); +@@ -1318,13 +1321,19 @@ int smb_check_perm_dacl(struct ksmbd_con + ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl)); + aces_size = acl_size - sizeof(struct smb_acl); + for (i = 0; i < le32_to_cpu(pdacl->num_aces); i++) { +- if (offsetof(struct smb_ace, access_req) > aces_size) ++ if (offsetof(struct smb_ace, sid) + ++ aces_size < CIFS_SID_BASE_SIZE) + break; + ace_size = le16_to_cpu(ace->size); +- if (ace_size > aces_size) ++ if (ace_size > aces_size || ++ ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + aces_size -= ace_size; + ++ if (ace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ break; ++ + if (!compare_sids(&sid, &ace->sid) || + !compare_sids(&sid_unix_NFS_mode, &ace->sid)) { + found = 1; diff --git a/queue-6.1/lib-test_hmm-evict-device-pages-on-file-close-to-avoid-use-after-free.patch b/queue-6.1/lib-test_hmm-evict-device-pages-on-file-close-to-avoid-use-after-free.patch new file mode 100644 index 0000000000..e6da8c2e61 --- /dev/null +++ b/queue-6.1/lib-test_hmm-evict-device-pages-on-file-close-to-avoid-use-after-free.patch @@ -0,0 +1,170 @@ +From stable+bounces-241759-greg=kroah.com@vger.kernel.org Wed Apr 29 00:23:13 2026 +From: Sasha Levin +Date: Tue, 28 Apr 2026 14:53:06 -0400 +Subject: lib: test_hmm: evict device pages on file close to avoid use-after-free +To: stable@vger.kernel.org +Cc: Alistair Popple , Zenghui Yu , Balbir Singh , David Hildenbrand , Jason Gunthorpe , Leon Romanovsky , Liam Howlett , "Lorenzo Stoakes (Oracle)" , Michal Hocko , Mike Rapoport , Suren Baghdasaryan , Matthew Brost , Andrew Morton , Sasha Levin +Message-ID: <20260428185306.3151433-1-sashal@kernel.org> + +From: Alistair Popple + +[ Upstream commit 744dd97752ef1076a8d8672bb0d8aa2c7abc1144 ] + +Patch series "Minor hmm_test fixes and cleanups". + +Two bugfixes a cleanup for the HMM kernel selftests. These were mostly +reported by Zenghui Yu with special thanks to Lorenzo for analysing and +pointing out the problems. + +This patch (of 3): + +When dmirror_fops_release() is called it frees the dmirror struct but +doesn't migrate device private pages back to system memory first. This +leaves those pages with a dangling zone_device_data pointer to the freed +dmirror. + +If a subsequent fault occurs on those pages (eg. during coredump) the +dmirror_devmem_fault() callback dereferences the stale pointer causing a +kernel panic. This was reported [1] when running mm/ksft_hmm.sh on arm64, +where a test failure triggered SIGABRT and the resulting coredump walked +the VMAs faulting in the stale device private pages. + +Fix this by calling dmirror_device_evict_chunk() for each devmem chunk in +dmirror_fops_release() to migrate all device private pages back to system +memory before freeing the dmirror struct. The function is moved earlier +in the file to avoid a forward declaration. + +Link: https://lore.kernel.org/20260331063445.3551404-1-apopple@nvidia.com +Link: https://lore.kernel.org/20260331063445.3551404-2-apopple@nvidia.com +Fixes: b2ef9f5a5cb3 ("mm/hmm/test: add selftest driver for HMM") +Signed-off-by: Alistair Popple +Reported-by: Zenghui Yu +Closes: https://lore.kernel.org/linux-mm/8bd0396a-8997-4d2e-a13f-5aac033083d7@linux.dev/ +Reviewed-by: Balbir Singh +Tested-by: Zenghui Yu +Cc: David Hildenbrand +Cc: Jason Gunthorpe +Cc: Leon Romanovsky +Cc: Liam Howlett +Cc: Lorenzo Stoakes (Oracle) +Cc: Michal Hocko +Cc: Mike Rapoport +Cc: Suren Baghdasaryan +Cc: Zenghui Yu +Cc: Matthew Brost +Cc: +Signed-off-by: Andrew Morton +[ kept the existing simpler `dmirror_device_evict_chunk()` body instead of the upstream compound-folio version ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + lib/test_hmm.c | 86 ++++++++++++++++++++++++++++++++------------------------- + 1 file changed, 49 insertions(+), 37 deletions(-) + +--- a/lib/test_hmm.c ++++ b/lib/test_hmm.c +@@ -183,11 +183,60 @@ static int dmirror_fops_open(struct inod + return 0; + } + ++static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk) ++{ ++ unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT; ++ unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT; ++ unsigned long npages = end_pfn - start_pfn + 1; ++ unsigned long i; ++ unsigned long *src_pfns; ++ unsigned long *dst_pfns; ++ ++ src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL); ++ dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL); ++ ++ migrate_device_range(src_pfns, start_pfn, npages); ++ for (i = 0; i < npages; i++) { ++ struct page *dpage, *spage; ++ ++ spage = migrate_pfn_to_page(src_pfns[i]); ++ if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE)) ++ continue; ++ ++ if (WARN_ON(!is_device_private_page(spage) && ++ !is_device_coherent_page(spage))) ++ continue; ++ spage = BACKING_PAGE(spage); ++ dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL); ++ lock_page(dpage); ++ copy_highpage(dpage, spage); ++ dst_pfns[i] = migrate_pfn(page_to_pfn(dpage)); ++ if (src_pfns[i] & MIGRATE_PFN_WRITE) ++ dst_pfns[i] |= MIGRATE_PFN_WRITE; ++ } ++ migrate_device_pages(src_pfns, dst_pfns, npages); ++ migrate_device_finalize(src_pfns, dst_pfns, npages); ++ kvfree(src_pfns); ++ kvfree(dst_pfns); ++} ++ + static int dmirror_fops_release(struct inode *inode, struct file *filp) + { + struct dmirror *dmirror = filp->private_data; ++ struct dmirror_device *mdevice = dmirror->mdevice; ++ int i; + + mmu_interval_notifier_remove(&dmirror->notifier); ++ ++ if (mdevice->devmem_chunks) { ++ for (i = 0; i < mdevice->devmem_count; i++) { ++ struct dmirror_chunk *devmem = ++ mdevice->devmem_chunks[i]; ++ ++ dmirror_device_evict_chunk(devmem); ++ } ++ } ++ + xa_destroy(&dmirror->pt); + kfree(dmirror); + return 0; +@@ -1223,43 +1272,6 @@ static int dmirror_snapshot(struct dmirr + return ret; + } + +-static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk) +-{ +- unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT; +- unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT; +- unsigned long npages = end_pfn - start_pfn + 1; +- unsigned long i; +- unsigned long *src_pfns; +- unsigned long *dst_pfns; +- +- src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL); +- dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL); +- +- migrate_device_range(src_pfns, start_pfn, npages); +- for (i = 0; i < npages; i++) { +- struct page *dpage, *spage; +- +- spage = migrate_pfn_to_page(src_pfns[i]); +- if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE)) +- continue; +- +- if (WARN_ON(!is_device_private_page(spage) && +- !is_device_coherent_page(spage))) +- continue; +- spage = BACKING_PAGE(spage); +- dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL); +- lock_page(dpage); +- copy_highpage(dpage, spage); +- dst_pfns[i] = migrate_pfn(page_to_pfn(dpage)); +- if (src_pfns[i] & MIGRATE_PFN_WRITE) +- dst_pfns[i] |= MIGRATE_PFN_WRITE; +- } +- migrate_device_pages(src_pfns, dst_pfns, npages); +- migrate_device_finalize(src_pfns, dst_pfns, npages); +- kvfree(src_pfns); +- kvfree(dst_pfns); +-} +- + /* Removes free pages from the free list so they can't be re-allocated */ + static void dmirror_remove_free_pages(struct dmirror_chunk *devmem) + { diff --git a/queue-6.1/loongarch-add-spectre-boundry-for-syscall-dispatch-table.patch b/queue-6.1/loongarch-add-spectre-boundry-for-syscall-dispatch-table.patch new file mode 100644 index 0000000000..4386d9b126 --- /dev/null +++ b/queue-6.1/loongarch-add-spectre-boundry-for-syscall-dispatch-table.patch @@ -0,0 +1,45 @@ +From sashal@kernel.org Tue Apr 28 14:49:45 2026 +From: Sasha Levin +Date: Tue, 28 Apr 2026 05:19:39 -0400 +Subject: LoongArch: Add spectre boundry for syscall dispatch table +To: stable@vger.kernel.org +Cc: Greg Kroah-Hartman , Huacai Chen , Sasha Levin +Message-ID: <20260428091939.2488103-1-sashal@kernel.org> + +From: Greg Kroah-Hartman + +[ Upstream commit 0c965d2784fbbd7f8e3b96d875c9cfdf7c00da3d ] + +The LoongArch syscall number is directly controlled by userspace, but +does not have a array_index_nospec() boundry to prevent access past the +syscall function pointer tables. + +Cc: stable@vger.kernel.org +Assisted-by: gkh_clanker_2000 +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Huacai Chen +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + arch/loongarch/kernel/syscall.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/arch/loongarch/kernel/syscall.c ++++ b/arch/loongarch/kernel/syscall.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -54,7 +55,7 @@ void noinstr do_syscall(struct pt_regs * + nr = syscall_enter_from_user_mode(regs, nr); + + if (nr < NR_syscalls) { +- syscall_fn = sys_call_table[nr]; ++ syscall_fn = sys_call_table[array_index_nospec(nr, NR_syscalls)]; + regs->regs[4] = syscall_fn(regs->orig_a0, regs->regs[5], regs->regs[6], + regs->regs[7], regs->regs[8], regs->regs[9]); + } diff --git a/queue-6.1/media-rc-igorplugusb-heed-coherency-rules.patch b/queue-6.1/media-rc-igorplugusb-heed-coherency-rules.patch new file mode 100644 index 0000000000..fb625eb13f --- /dev/null +++ b/queue-6.1/media-rc-igorplugusb-heed-coherency-rules.patch @@ -0,0 +1,85 @@ +From stable+bounces-242552-greg=kroah.com@vger.kernel.org Sat May 2 04:57:36 2026 +From: Sasha Levin +Date: Fri, 1 May 2026 19:27:30 -0400 +Subject: media: rc: igorplugusb: heed coherency rules +To: stable@vger.kernel.org +Cc: Oliver Neukum , Sean Young , Hans Verkuil , Sasha Levin +Message-ID: <20260501232730.4096516-1-sashal@kernel.org> + +From: Oliver Neukum + +[ Upstream commit eac69475b01fe1e861dfe3960b57fa95671c132e ] + +In a control request, the USB request structure +can be subject to DMA on some HCs. Hence it must obey +the rules for DMA coherency. Allocate it separately. + +Fixes: b1c97193c6437 ("[media] rc: port IgorPlug-USB to rc-core") +Cc: stable@vger.kernel.org +Signed-off-by: Oliver Neukum +Signed-off-by: Sean Young +Signed-off-by: Hans Verkuil +[ replaced kzalloc_obj(*ir->request, GFP_KERNEL) with kzalloc(sizeof(*ir->request), GFP_KERNEL) ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/rc/igorplugusb.c | 16 +++++++++++----- + 1 file changed, 11 insertions(+), 5 deletions(-) + +--- a/drivers/media/rc/igorplugusb.c ++++ b/drivers/media/rc/igorplugusb.c +@@ -34,7 +34,7 @@ struct igorplugusb { + struct device *dev; + + struct urb *urb; +- struct usb_ctrlrequest request; ++ struct usb_ctrlrequest *request; + + struct timer_list timer; + +@@ -122,7 +122,7 @@ static void igorplugusb_cmd(struct igorp + { + int ret; + +- ir->request.bRequest = cmd; ++ ir->request->bRequest = cmd; + ir->urb->transfer_flags = 0; + ret = usb_submit_urb(ir->urb, GFP_ATOMIC); + if (ret && ret != -EPERM) +@@ -164,13 +164,17 @@ static int igorplugusb_probe(struct usb_ + if (!ir) + return -ENOMEM; + ++ ir->request = kzalloc(sizeof(*ir->request), GFP_KERNEL); ++ if (!ir->request) ++ goto fail; ++ + ir->dev = &intf->dev; + + timer_setup(&ir->timer, igorplugusb_timer, 0); + +- ir->request.bRequest = GET_INFRACODE; +- ir->request.bRequestType = USB_TYPE_VENDOR | USB_DIR_IN; +- ir->request.wLength = cpu_to_le16(MAX_PACKET); ++ ir->request->bRequest = GET_INFRACODE; ++ ir->request->bRequestType = USB_TYPE_VENDOR | USB_DIR_IN; ++ ir->request->wLength = cpu_to_le16(MAX_PACKET); + + ir->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ir->urb) +@@ -228,6 +232,7 @@ fail: + usb_free_urb(ir->urb); + rc_free_device(ir->rc); + kfree(ir->buf_in); ++ kfree(ir->request); + + return ret; + } +@@ -243,6 +248,7 @@ static void igorplugusb_disconnect(struc + usb_unpoison_urb(ir->urb); + usb_free_urb(ir->urb); + kfree(ir->buf_in); ++ kfree(ir->request); + } + + static const struct usb_device_id igorplugusb_table[] = { diff --git a/queue-6.1/media-rc-ttusbir-respect-dma-coherency-rules.patch b/queue-6.1/media-rc-ttusbir-respect-dma-coherency-rules.patch new file mode 100644 index 0000000000..59b238edfe --- /dev/null +++ b/queue-6.1/media-rc-ttusbir-respect-dma-coherency-rules.patch @@ -0,0 +1,83 @@ +From stable+bounces-242474-greg=kroah.com@vger.kernel.org Fri May 1 22:43:30 2026 +From: Sasha Levin +Date: Fri, 1 May 2026 13:13:21 -0400 +Subject: media: rc: ttusbir: respect DMA coherency rules +To: stable@vger.kernel.org +Cc: Oliver Neukum , Sean Young , Hans Verkuil , Sasha Levin +Message-ID: <20260501171321.3681337-1-sashal@kernel.org> + +From: Oliver Neukum + +[ Upstream commit 50acaad3d202c064779db8dc3d010007347f59c7 ] + +Buffers must not share a cache line with other data structures. +Allocate separately. + +Fixes: 0938069fa0897 ("[media] rc: Add support for the TechnoTrend USB IR Receiver") +Cc: stable@vger.kernel.org +Signed-off-by: Oliver Neukum +Signed-off-by: Sean Young +Signed-off-by: Hans Verkuil +[ kept kzalloc(sizeof(*tt), GFP_KERNEL) instead of kzalloc_obj() ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/rc/ttusbir.c | 13 +++++++++---- + 1 file changed, 9 insertions(+), 4 deletions(-) + +--- a/drivers/media/rc/ttusbir.c ++++ b/drivers/media/rc/ttusbir.c +@@ -32,7 +32,7 @@ struct ttusbir { + + struct led_classdev led; + struct urb *bulk_urb; +- uint8_t bulk_buffer[5]; ++ u8 *bulk_buffer; + int bulk_out_endp, iso_in_endp; + bool led_on, is_led_on; + atomic_t led_complete; +@@ -186,13 +186,16 @@ static int ttusbir_probe(struct usb_inte + struct rc_dev *rc; + int i, j, ret; + int altsetting = -1; ++ u8 *buffer; + + tt = kzalloc(sizeof(*tt), GFP_KERNEL); ++ buffer = kzalloc(5, GFP_KERNEL); + rc = rc_allocate_device(RC_DRIVER_IR_RAW); +- if (!tt || !rc) { ++ if (!tt || !rc || buffer) { + ret = -ENOMEM; + goto out; + } ++ tt->bulk_buffer = buffer; + + /* find the correct alt setting */ + for (i = 0; i < intf->num_altsetting && altsetting == -1; i++) { +@@ -281,8 +284,8 @@ static int ttusbir_probe(struct usb_inte + tt->bulk_buffer[3] = 0x01; + + usb_fill_bulk_urb(tt->bulk_urb, tt->udev, usb_sndbulkpipe(tt->udev, +- tt->bulk_out_endp), tt->bulk_buffer, sizeof(tt->bulk_buffer), +- ttusbir_bulk_complete, tt); ++ tt->bulk_out_endp), tt->bulk_buffer, 5, ++ ttusbir_bulk_complete, tt); + + tt->led.name = "ttusbir:green:power"; + tt->led.default_trigger = "rc-feedback"; +@@ -351,6 +354,7 @@ out: + kfree(tt); + } + rc_free_device(rc); ++ kfree(buffer); + + return ret; + } +@@ -373,6 +377,7 @@ static void ttusbir_disconnect(struct us + } + usb_kill_urb(tt->bulk_urb); + usb_free_urb(tt->bulk_urb); ++ kfree(tt->bulk_buffer); + usb_set_intfdata(intf, NULL); + kfree(tt); + } diff --git a/queue-6.1/net-bridge-use-a-stable-fdb-dst-snapshot-in-rcu-readers.patch b/queue-6.1/net-bridge-use-a-stable-fdb-dst-snapshot-in-rcu-readers.patch new file mode 100644 index 0000000000..7fecd6b3ae --- /dev/null +++ b/queue-6.1/net-bridge-use-a-stable-fdb-dst-snapshot-in-rcu-readers.patch @@ -0,0 +1,176 @@ +From stable+bounces-242993-greg=kroah.com@vger.kernel.org Mon May 4 16:16:32 2026 +From: Sasha Levin +Date: Mon, 4 May 2026 06:45:39 -0400 +Subject: net: bridge: use a stable FDB dst snapshot in RCU readers +To: stable@vger.kernel.org +Cc: Zhengchuan Liang , stable@kernel.org, Yifan Wu , Juefei Pu , Yuan Tan , Xin Liu , Ren Wei , Ren Wei , Ido Schimmel , Nikolay Aleksandrov , Paolo Abeni , Sasha Levin +Message-ID: <20260504104539.2075824-1-sashal@kernel.org> + +From: Zhengchuan Liang + +[ Upstream commit df4601653201de21b487c3e7fffd464790cab808 ] + +Local FDB entries can be rewritten in place by `fdb_delete_local()`, which +updates `f->dst` to another port or to `NULL` while keeping the entry +alive. Several bridge RCU readers inspect `f->dst`, including +`br_fdb_fillbuf()` through the `brforward_read()` sysfs path. + +These readers currently load `f->dst` multiple times and can therefore +observe inconsistent values across the check and later dereference. +In `br_fdb_fillbuf()`, this means a concurrent local-FDB update can change +`f->dst` after the NULL check and before the `port_no` dereference, +leading to a NULL-ptr-deref. + +Fix this by taking a single `READ_ONCE()` snapshot of `f->dst` in each +affected RCU reader and using that snapshot for the rest of the access +sequence. Also publish the in-place `f->dst` updates in `fdb_delete_local()` +with `WRITE_ONCE()` so the readers and writer use matching access patterns. + +Fixes: 960b589f86c7 ("bridge: Properly check if local fdb entry can be deleted in br_fdb_change_mac_address") +Cc: stable@kernel.org +Reported-by: Yifan Wu +Reported-by: Juefei Pu +Co-developed-by: Yuan Tan +Signed-off-by: Yuan Tan +Suggested-by: Xin Liu +Tested-by: Ren Wei +Signed-off-by: Zhengchuan Liang +Signed-off-by: Ren Wei +Reviewed-by: Ido Schimmel +Acked-by: Nikolay Aleksandrov +Link: https://patch.msgid.link/6570fabb85ecadb8baaf019efe856f407711c7b9.1776043229.git.zcliangcn@gmail.com +Signed-off-by: Paolo Abeni +[ kept combined `BR_PROXYARP_WIFI | BR_NEIGH_SUPPRESS` check and `cb->args[2]` instead of `br_is_neigh_suppress_enabled()` helper and `ctx->fdb_idx` ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bridge/br_arp_nd_proxy.c | 8 +++++--- + net/bridge/br_fdb.c | 28 ++++++++++++++++++---------- + 2 files changed, 23 insertions(+), 13 deletions(-) + +--- a/net/bridge/br_arp_nd_proxy.c ++++ b/net/bridge/br_arp_nd_proxy.c +@@ -199,11 +199,12 @@ void br_do_proxy_suppress_arp(struct sk_ + + f = br_fdb_find_rcu(br, n->ha, vid); + if (f) { ++ const struct net_bridge_port *dst = READ_ONCE(f->dst); + bool replied = false; + + if ((p && (p->flags & BR_PROXYARP)) || +- (f->dst && (f->dst->flags & (BR_PROXYARP_WIFI | +- BR_NEIGH_SUPPRESS)))) { ++ (dst && (dst->flags & (BR_PROXYARP_WIFI | ++ BR_NEIGH_SUPPRESS)))) { + if (!vid) + br_arp_send(br, p, skb->dev, sip, tip, + sha, n->ha, sha, 0, 0); +@@ -463,9 +464,10 @@ void br_do_suppress_nd(struct sk_buff *s + + f = br_fdb_find_rcu(br, n->ha, vid); + if (f) { ++ const struct net_bridge_port *dst = READ_ONCE(f->dst); + bool replied = false; + +- if (f->dst && (f->dst->flags & BR_NEIGH_SUPPRESS)) { ++ if (dst && (dst->flags & BR_NEIGH_SUPPRESS)) { + if (vid != 0) + br_nd_send(br, p, skb, n, + skb->vlan_proto, +--- a/net/bridge/br_fdb.c ++++ b/net/bridge/br_fdb.c +@@ -239,6 +239,7 @@ struct net_device *br_fdb_find_port(cons + const unsigned char *addr, + __u16 vid) + { ++ const struct net_bridge_port *dst; + struct net_bridge_fdb_entry *f; + struct net_device *dev = NULL; + struct net_bridge *br; +@@ -251,8 +252,11 @@ struct net_device *br_fdb_find_port(cons + br = netdev_priv(br_dev); + rcu_read_lock(); + f = br_fdb_find_rcu(br, addr, vid); +- if (f && f->dst) +- dev = f->dst->dev; ++ if (f) { ++ dst = READ_ONCE(f->dst); ++ if (dst) ++ dev = dst->dev; ++ } + rcu_read_unlock(); + + return dev; +@@ -342,7 +346,7 @@ static void fdb_delete_local(struct net_ + vg = nbp_vlan_group(op); + if (op != p && ether_addr_equal(op->dev->dev_addr, addr) && + (!vid || br_vlan_find(vg, vid))) { +- f->dst = op; ++ WRITE_ONCE(f->dst, op); + clear_bit(BR_FDB_ADDED_BY_USER, &f->flags); + return; + } +@@ -353,7 +357,7 @@ static void fdb_delete_local(struct net_ + /* Maybe bridge device has same hw addr? */ + if (p && ether_addr_equal(br->dev->dev_addr, addr) && + (!vid || (v && br_vlan_should_use(v)))) { +- f->dst = NULL; ++ WRITE_ONCE(f->dst, NULL); + clear_bit(BR_FDB_ADDED_BY_USER, &f->flags); + return; + } +@@ -783,6 +787,7 @@ int br_fdb_test_addr(struct net_device * + int br_fdb_fillbuf(struct net_bridge *br, void *buf, + unsigned long maxnum, unsigned long skip) + { ++ const struct net_bridge_port *dst; + struct net_bridge_fdb_entry *f; + struct __fdb_entry *fe = buf; + int num = 0; +@@ -798,7 +803,8 @@ int br_fdb_fillbuf(struct net_bridge *br + continue; + + /* ignore pseudo entry for local MAC address */ +- if (!f->dst) ++ dst = READ_ONCE(f->dst); ++ if (!dst) + continue; + + if (skip) { +@@ -810,8 +816,8 @@ int br_fdb_fillbuf(struct net_bridge *br + memcpy(fe->mac_addr, f->key.addr.addr, ETH_ALEN); + + /* due to ABI compat need to split into hi/lo */ +- fe->port_no = f->dst->port_no; +- fe->port_hi = f->dst->port_no >> 8; ++ fe->port_no = dst->port_no; ++ fe->port_hi = dst->port_no >> 8; + + fe->is_local = test_bit(BR_FDB_LOCAL, &f->flags); + if (!test_bit(BR_FDB_STATIC, &f->flags)) +@@ -924,9 +930,11 @@ int br_fdb_dump(struct sk_buff *skb, + + rcu_read_lock(); + hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { ++ const struct net_bridge_port *dst = READ_ONCE(f->dst); ++ + if (*idx < cb->args[2]) + goto skip; +- if (filter_dev && (!f->dst || f->dst->dev != filter_dev)) { ++ if (filter_dev && (!dst || dst->dev != filter_dev)) { + if (filter_dev != dev) + goto skip; + /* !f->dst is a special case for bridge +@@ -934,10 +942,10 @@ int br_fdb_dump(struct sk_buff *skb, + * Therefore need a little more filtering + * we only want to dump the !f->dst case + */ +- if (f->dst) ++ if (dst) + goto skip; + } +- if (!filter_dev && f->dst) ++ if (!filter_dev && dst) + goto skip; + + err = fdb_fill_info(skb, br, f, diff --git a/queue-6.1/net-mctp-fix-don-t-require-received-header-reserved-bits-to-be-zero.patch b/queue-6.1/net-mctp-fix-don-t-require-received-header-reserved-bits-to-be-zero.patch new file mode 100644 index 0000000000..be873d05ef --- /dev/null +++ b/queue-6.1/net-mctp-fix-don-t-require-received-header-reserved-bits-to-be-zero.patch @@ -0,0 +1,90 @@ +From stable+bounces-242877-greg=kroah.com@vger.kernel.org Mon May 4 14:07:34 2026 +From: Sasha Levin +Date: Mon, 4 May 2026 04:34:02 -0400 +Subject: net: mctp: fix don't require received header reserved bits to be zero +To: stable@vger.kernel.org +Cc: Yuan Zhaoming , Jeremy Kerr , Jakub Kicinski , Sasha Levin +Message-ID: <20260504083402.1884264-1-sashal@kernel.org> + +From: Yuan Zhaoming + +[ Upstream commit a663bac71a2f0b3ac6c373168ca57b2a6e6381aa ] + +>From the MCTP Base specification (DSP0236 v1.2.1), the first byte of +the MCTP header contains a 4 bit reserved field, and 4 bit version. + +On our current receive path, we require those 4 reserved bits to be +zero, but the 9500-8i card is non-conformant, and may set these +reserved bits. + +DSP0236 states that the reserved bits must be written as zero, and +ignored when read. While the device might not conform to the former, +we should accept these message to conform to the latter. + +Relax our check on the MCTP version byte to allow non-zero bits in the +reserved field. + +Fixes: 889b7da23abf ("mctp: Add initial routing framework") +Signed-off-by: Yuan Zhaoming +Cc: stable@vger.kernel.org +Acked-by: Jeremy Kerr +Link: https://patch.msgid.link/20260417141340.5306-1-yuanzhaoming901030@126.com +Signed-off-by: Jakub Kicinski +[ Context ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/mctp.h | 3 +++ + net/mctp/route.c | 8 ++++++-- + 2 files changed, 9 insertions(+), 2 deletions(-) + +--- a/include/net/mctp.h ++++ b/include/net/mctp.h +@@ -26,6 +26,9 @@ struct mctp_hdr { + #define MCTP_VER_MIN 1 + #define MCTP_VER_MAX 1 + ++/* Definitions for ver field */ ++#define MCTP_HDR_VER_MASK GENMASK(3, 0) ++ + /* Definitions for flags_seq_tag field */ + #define MCTP_HDR_FLAG_SOM BIT(7) + #define MCTP_HDR_FLAG_EOM BIT(6) +--- a/net/mctp/route.c ++++ b/net/mctp/route.c +@@ -335,6 +335,7 @@ static int mctp_route_input(struct mctp_ + unsigned long f; + u8 tag, flags; + int rc; ++ u8 ver; + + msk = NULL; + rc = -EINVAL; +@@ -357,7 +358,8 @@ static int mctp_route_input(struct mctp_ + mh = mctp_hdr(skb); + skb_pull(skb, sizeof(struct mctp_hdr)); + +- if (mh->ver != 1) ++ ver = mh->ver & MCTP_HDR_VER_MASK; ++ if (ver < MCTP_VER_MIN || ver > MCTP_VER_MAX) + goto out; + + flags = mh->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); +@@ -1124,6 +1126,7 @@ static int mctp_pkttype_receive(struct s + struct mctp_skb_cb *cb; + struct mctp_route *rt; + struct mctp_hdr *mh; ++ u8 ver; + + rcu_read_lock(); + mdev = __mctp_dev_get(dev); +@@ -1141,7 +1144,8 @@ static int mctp_pkttype_receive(struct s + + /* We have enough for a header; decode and route */ + mh = mctp_hdr(skb); +- if (mh->ver < MCTP_VER_MIN || mh->ver > MCTP_VER_MAX) ++ ver = mh->ver & MCTP_HDR_VER_MASK; ++ if (ver < MCTP_VER_MIN || ver > MCTP_VER_MAX) + goto err_drop; + + /* source must be valid unicast or null; drop reserved ranges and diff --git a/queue-6.1/net-qrtr-ns-change-servers-radix-tree-to-xarray.patch b/queue-6.1/net-qrtr-ns-change-servers-radix-tree-to-xarray.patch new file mode 100644 index 0000000000..4ff4ab588c --- /dev/null +++ b/queue-6.1/net-qrtr-ns-change-servers-radix-tree-to-xarray.patch @@ -0,0 +1,310 @@ +From stable+bounces-242838-greg=kroah.com@vger.kernel.org Mon May 4 10:49:28 2026 +From: Sasha Levin +Date: Mon, 4 May 2026 01:19:18 -0400 +Subject: net: qrtr: ns: Change servers radix tree to xarray +To: stable@vger.kernel.org +Cc: Vignesh Viswanathan , Chris Lew , Simon Horman , "David S. Miller" , Sasha Levin +Message-ID: <20260504051919.1726114-1-sashal@kernel.org> + +From: Vignesh Viswanathan + +[ Upstream commit 608a147a88728f84bbd2efdde3d4984339f1d872 ] + +There is a use after free scenario while iterating through the servers +radix tree despite the ns being a single threaded process. This can +happen when the radix tree APIs are not synchronized with the +rcu_read_lock() APIs. + +Convert the radix tree for servers to xarray to take advantage of the +built in rcu lock usage provided by xarray. + +Signed-off-by: Chris Lew +Signed-off-by: Vignesh Viswanathan +Reviewed-by: Simon Horman +Signed-off-by: David S. Miller +Stable-dep-of: 68efba36446a ("net: qrtr: ns: Free the node during ctrl_cmd_bye()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/qrtr/ns.c | 133 ++++++++++------------------------------------------------ + 1 file changed, 24 insertions(+), 109 deletions(-) + +--- a/net/qrtr/ns.c ++++ b/net/qrtr/ns.c +@@ -67,7 +67,7 @@ struct qrtr_server { + + struct qrtr_node { + unsigned int id; +- struct radix_tree_root servers; ++ struct xarray servers; + }; + + /* Max lookup limit is chosen based on the current platform requirements. If the +@@ -89,6 +89,7 @@ static struct qrtr_node *node_get(unsign + return NULL; + + node->id = node_id; ++ xa_init(&node->servers); + + if (radix_tree_insert(&nodes, node_id, node)) { + kfree(node); +@@ -199,40 +200,23 @@ static void lookup_notify(struct sockadd + + static int announce_servers(struct sockaddr_qrtr *sq) + { +- struct radix_tree_iter iter; + struct qrtr_server *srv; + struct qrtr_node *node; +- void __rcu **slot; ++ unsigned long index; + int ret; + + node = node_get(qrtr_ns.local_node); + if (!node) + return 0; + +- rcu_read_lock(); + /* Announce the list of servers registered in this node */ +- radix_tree_for_each_slot(slot, &node->servers, &iter, 0) { +- srv = radix_tree_deref_slot(slot); +- if (!srv) +- continue; +- if (radix_tree_deref_retry(srv)) { +- slot = radix_tree_iter_retry(&iter); +- continue; +- } +- slot = radix_tree_iter_resume(slot, &iter); +- rcu_read_unlock(); +- ++ xa_for_each(&node->servers, index, srv) { + ret = service_announce_new(sq, srv); + if (ret < 0) { + pr_err("failed to announce new service\n"); + return ret; + } +- +- rcu_read_lock(); + } +- +- rcu_read_unlock(); +- + return 0; + } + +@@ -262,14 +246,17 @@ static struct qrtr_server *server_add(un + goto err; + + /* Delete the old server on the same port */ +- old = radix_tree_lookup(&node->servers, port); ++ old = xa_store(&node->servers, port, srv, GFP_KERNEL); + if (old) { +- radix_tree_delete(&node->servers, port); +- kfree(old); ++ if (xa_is_err(old)) { ++ pr_err("failed to add server [0x%x:0x%x] ret:%d\n", ++ srv->service, srv->instance, xa_err(old)); ++ goto err; ++ } else { ++ kfree(old); ++ } + } + +- radix_tree_insert(&node->servers, port, srv); +- + trace_qrtr_ns_server_add(srv->service, srv->instance, + srv->node, srv->port); + +@@ -286,11 +273,11 @@ static int server_del(struct qrtr_node * + struct qrtr_server *srv; + struct list_head *li; + +- srv = radix_tree_lookup(&node->servers, port); ++ srv = xa_load(&node->servers, port); + if (!srv) + return -ENOENT; + +- radix_tree_delete(&node->servers, port); ++ xa_erase(&node->servers, port); + + /* Broadcast the removal of local servers */ + if (srv->node == qrtr_ns.local_node && bcast) +@@ -350,13 +337,12 @@ static int ctrl_cmd_hello(struct sockadd + static int ctrl_cmd_bye(struct sockaddr_qrtr *from) + { + struct qrtr_node *local_node; +- struct radix_tree_iter iter; + struct qrtr_ctrl_pkt pkt; + struct qrtr_server *srv; + struct sockaddr_qrtr sq; + struct msghdr msg = { }; + struct qrtr_node *node; +- void __rcu **slot; ++ unsigned long index; + struct kvec iv; + int ret; + +@@ -367,22 +353,9 @@ static int ctrl_cmd_bye(struct sockaddr_ + if (!node) + return 0; + +- rcu_read_lock(); + /* Advertise removal of this client to all servers of remote node */ +- radix_tree_for_each_slot(slot, &node->servers, &iter, 0) { +- srv = radix_tree_deref_slot(slot); +- if (!srv) +- continue; +- if (radix_tree_deref_retry(srv)) { +- slot = radix_tree_iter_retry(&iter); +- continue; +- } +- slot = radix_tree_iter_resume(slot, &iter); +- rcu_read_unlock(); ++ xa_for_each(&node->servers, index, srv) + server_del(node, srv->port, true); +- rcu_read_lock(); +- } +- rcu_read_unlock(); + + /* Advertise the removal of this client to all local servers */ + local_node = node_get(qrtr_ns.local_node); +@@ -393,18 +366,7 @@ static int ctrl_cmd_bye(struct sockaddr_ + pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); + pkt.client.node = cpu_to_le32(from->sq_node); + +- rcu_read_lock(); +- radix_tree_for_each_slot(slot, &local_node->servers, &iter, 0) { +- srv = radix_tree_deref_slot(slot); +- if (!srv) +- continue; +- if (radix_tree_deref_retry(srv)) { +- slot = radix_tree_iter_retry(&iter); +- continue; +- } +- slot = radix_tree_iter_resume(slot, &iter); +- rcu_read_unlock(); +- ++ xa_for_each(&local_node->servers, index, srv) { + sq.sq_family = AF_QIPCRTR; + sq.sq_node = srv->node; + sq.sq_port = srv->port; +@@ -417,11 +379,7 @@ static int ctrl_cmd_bye(struct sockaddr_ + pr_err("failed to send bye cmd\n"); + return ret; + } +- rcu_read_lock(); + } +- +- rcu_read_unlock(); +- + return 0; + } + +@@ -429,7 +387,6 @@ static int ctrl_cmd_del_client(struct so + unsigned int node_id, unsigned int port) + { + struct qrtr_node *local_node; +- struct radix_tree_iter iter; + struct qrtr_lookup *lookup; + struct qrtr_ctrl_pkt pkt; + struct msghdr msg = { }; +@@ -438,7 +395,7 @@ static int ctrl_cmd_del_client(struct so + struct qrtr_node *node; + struct list_head *tmp; + struct list_head *li; +- void __rcu **slot; ++ unsigned long index; + struct kvec iv; + int ret; + +@@ -484,18 +441,7 @@ static int ctrl_cmd_del_client(struct so + pkt.client.node = cpu_to_le32(node_id); + pkt.client.port = cpu_to_le32(port); + +- rcu_read_lock(); +- radix_tree_for_each_slot(slot, &local_node->servers, &iter, 0) { +- srv = radix_tree_deref_slot(slot); +- if (!srv) +- continue; +- if (radix_tree_deref_retry(srv)) { +- slot = radix_tree_iter_retry(&iter); +- continue; +- } +- slot = radix_tree_iter_resume(slot, &iter); +- rcu_read_unlock(); +- ++ xa_for_each(&local_node->servers, index, srv) { + sq.sq_family = AF_QIPCRTR; + sq.sq_node = srv->node; + sq.sq_port = srv->port; +@@ -508,11 +454,7 @@ static int ctrl_cmd_del_client(struct so + pr_err("failed to send del client cmd\n"); + return ret; + } +- rcu_read_lock(); + } +- +- rcu_read_unlock(); +- + return 0; + } + +@@ -585,13 +527,12 @@ static int ctrl_cmd_del_server(struct so + static int ctrl_cmd_new_lookup(struct sockaddr_qrtr *from, + unsigned int service, unsigned int instance) + { +- struct radix_tree_iter node_iter; + struct qrtr_server_filter filter; +- struct radix_tree_iter srv_iter; + struct qrtr_lookup *lookup; ++ struct qrtr_server *srv; + struct qrtr_node *node; +- void __rcu **node_slot; +- void __rcu **srv_slot; ++ unsigned long node_idx; ++ unsigned long srv_idx; + + /* Accept only local observers */ + if (from->sq_node != qrtr_ns.local_node) +@@ -616,40 +557,14 @@ static int ctrl_cmd_new_lookup(struct so + filter.service = service; + filter.instance = instance; + +- rcu_read_lock(); +- radix_tree_for_each_slot(node_slot, &nodes, &node_iter, 0) { +- node = radix_tree_deref_slot(node_slot); +- if (!node) +- continue; +- if (radix_tree_deref_retry(node)) { +- node_slot = radix_tree_iter_retry(&node_iter); +- continue; +- } +- node_slot = radix_tree_iter_resume(node_slot, &node_iter); +- +- radix_tree_for_each_slot(srv_slot, &node->servers, +- &srv_iter, 0) { +- struct qrtr_server *srv; +- +- srv = radix_tree_deref_slot(srv_slot); +- if (!srv) +- continue; +- if (radix_tree_deref_retry(srv)) { +- srv_slot = radix_tree_iter_retry(&srv_iter); +- continue; +- } +- ++ xa_for_each(&nodes, node_idx, node) { ++ xa_for_each(&node->servers, srv_idx, srv) { + if (!server_match(srv, &filter)) + continue; + +- srv_slot = radix_tree_iter_resume(srv_slot, &srv_iter); +- +- rcu_read_unlock(); + lookup_notify(from, srv, true); +- rcu_read_lock(); + } + } +- rcu_read_unlock(); + + /* Empty notification, to indicate end of listing */ + lookup_notify(from, NULL, true); diff --git a/queue-6.1/net-qrtr-ns-free-the-node-during-ctrl_cmd_bye.patch b/queue-6.1/net-qrtr-ns-free-the-node-during-ctrl_cmd_bye.patch new file mode 100644 index 0000000000..de10d36c1e --- /dev/null +++ b/queue-6.1/net-qrtr-ns-free-the-node-during-ctrl_cmd_bye.patch @@ -0,0 +1,77 @@ +From stable+bounces-242839-greg=kroah.com@vger.kernel.org Mon May 4 10:49:31 2026 +From: Sasha Levin +Date: Mon, 4 May 2026 01:19:19 -0400 +Subject: net: qrtr: ns: Free the node during ctrl_cmd_bye() +To: stable@vger.kernel.org +Cc: Manivannan Sadhasivam , Jakub Kicinski , Sasha Levin +Message-ID: <20260504051919.1726114-2-sashal@kernel.org> + +From: Manivannan Sadhasivam + +[ Upstream commit 68efba36446a7774ea5b971257ade049272a07ac ] + +A node sends the BYE packet when it is about to go down. So the nameserver +should advertise the removal of the node to all remote and local observers +and free the node finally. But currently, the nameserver doesn't free the +node memory even after processing the BYE packet. This causes the node +memory to leak. + +Hence, remove the node from Xarray list and free the node memory during +both success and failure case of ctrl_cmd_bye(). + +Cc: stable@vger.kernel.org +Fixes: 0c2204a4ad71 ("net: qrtr: Migrate nameservice to kernel from userspace") +Signed-off-by: Manivannan Sadhasivam +Link: https://patch.msgid.link/20260409-qrtr-fix-v3-3-00a8a5ff2b51@oss.qualcomm.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/qrtr/ns.c | 20 +++++++++++++++----- + 1 file changed, 15 insertions(+), 5 deletions(-) + +--- a/net/qrtr/ns.c ++++ b/net/qrtr/ns.c +@@ -344,7 +344,7 @@ static int ctrl_cmd_bye(struct sockaddr_ + struct qrtr_node *node; + unsigned long index; + struct kvec iv; +- int ret; ++ int ret = 0; + + iv.iov_base = &pkt; + iv.iov_len = sizeof(pkt); +@@ -359,8 +359,10 @@ static int ctrl_cmd_bye(struct sockaddr_ + + /* Advertise the removal of this client to all local servers */ + local_node = node_get(qrtr_ns.local_node); +- if (!local_node) +- return 0; ++ if (!local_node) { ++ ret = 0; ++ goto delete_node; ++ } + + memset(&pkt, 0, sizeof(pkt)); + pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); +@@ -377,10 +379,18 @@ static int ctrl_cmd_bye(struct sockaddr_ + ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); + if (ret < 0) { + pr_err("failed to send bye cmd\n"); +- return ret; ++ goto delete_node; + } + } +- return 0; ++ ++ /* Ignore -ENODEV */ ++ ret = 0; ++ ++delete_node: ++ xa_erase(&nodes, from->sq_node); ++ kfree(node); ++ ++ return ret; + } + + static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, diff --git a/queue-6.1/net-qrtr-ns-limit-the-maximum-number-of-lookups.patch b/queue-6.1/net-qrtr-ns-limit-the-maximum-number-of-lookups.patch new file mode 100644 index 0000000000..e2c1edef6c --- /dev/null +++ b/queue-6.1/net-qrtr-ns-limit-the-maximum-number-of-lookups.patch @@ -0,0 +1,95 @@ +From stable+bounces-242834-greg=kroah.com@vger.kernel.org Mon May 4 10:06:43 2026 +From: Sasha Levin +Date: Mon, 4 May 2026 00:36:35 -0400 +Subject: net: qrtr: ns: Limit the maximum number of lookups +To: stable@vger.kernel.org +Cc: Manivannan Sadhasivam , Jakub Kicinski , Sasha Levin +Message-ID: <20260504043635.1540294-1-sashal@kernel.org> + +From: Manivannan Sadhasivam + +[ Upstream commit 5640227d9a21c6a8be249a10677b832e7f40dc55 ] + +Current code does no bound checking on the number of lookups a client can +perform. Though the code restricts the lookups to local clients, there is +still a possibility of a malicious local client sending a flood of +NEW_LOOKUP messages over the same socket. + +Fix this issue by limiting the maximum number of lookups to 64 globally. +Since the nameserver allows only atmost one local observer, this global +lookup count will ensure that the lookups stay within the limit. + +Note that, limit of 64 is chosen based on the current platform +requirements. If requirement changes in the future, this limit can be +increased. + +Cc: stable@vger.kernel.org +Fixes: 0c2204a4ad71 ("net: qrtr: Migrate nameservice to kernel from userspace") +Signed-off-by: Manivannan Sadhasivam +Link: https://patch.msgid.link/20260409-qrtr-fix-v3-2-00a8a5ff2b51@oss.qualcomm.com +Signed-off-by: Jakub Kicinski +[ adapted comment block to only mention QRTR_NS_MAX_LOOKUPS and kept kzalloc() instead of kzalloc_obj() due to missing prerequisite commits ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/qrtr/ns.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +--- a/net/qrtr/ns.c ++++ b/net/qrtr/ns.c +@@ -21,6 +21,7 @@ static struct { + struct socket *sock; + struct sockaddr_qrtr bcast_sq; + struct list_head lookups; ++ u32 lookup_count; + struct workqueue_struct *workqueue; + struct work_struct work; + void (*saved_data_ready)(struct sock *sk); +@@ -69,6 +70,11 @@ struct qrtr_node { + struct radix_tree_root servers; + }; + ++/* Max lookup limit is chosen based on the current platform requirements. If the ++ * requirement changes in the future, this value can be increased. ++ */ ++#define QRTR_NS_MAX_LOOKUPS 64 ++ + static struct qrtr_node *node_get(unsigned int node_id) + { + struct qrtr_node *node; +@@ -457,6 +463,7 @@ static int ctrl_cmd_del_client(struct so + + list_del(&lookup->li); + kfree(lookup); ++ qrtr_ns.lookup_count--; + } + + /* Remove the server belonging to this port but don't broadcast +@@ -590,6 +597,11 @@ static int ctrl_cmd_new_lookup(struct so + if (from->sq_node != qrtr_ns.local_node) + return -EINVAL; + ++ if (qrtr_ns.lookup_count >= QRTR_NS_MAX_LOOKUPS) { ++ pr_err_ratelimited("QRTR client node exceeds max lookup limit!\n"); ++ return -ENOSPC; ++ } ++ + lookup = kzalloc(sizeof(*lookup), GFP_KERNEL); + if (!lookup) + return -ENOMEM; +@@ -598,6 +610,7 @@ static int ctrl_cmd_new_lookup(struct so + lookup->service = service; + lookup->instance = instance; + list_add_tail(&lookup->li, &qrtr_ns.lookups); ++ qrtr_ns.lookup_count++; + + memset(&filter, 0, sizeof(filter)); + filter.service = service; +@@ -664,6 +677,7 @@ static void ctrl_cmd_del_lookup(struct s + + list_del(&lookup->li); + kfree(lookup); ++ qrtr_ns.lookup_count--; + } + } + diff --git a/queue-6.1/net-qrtr-ns-limit-the-total-number-of-nodes.patch b/queue-6.1/net-qrtr-ns-limit-the-total-number-of-nodes.patch new file mode 100644 index 0000000000..1ba84b54c4 --- /dev/null +++ b/queue-6.1/net-qrtr-ns-limit-the-total-number-of-nodes.patch @@ -0,0 +1,72 @@ +From stable+bounces-242972-greg=kroah.com@vger.kernel.org Mon May 4 15:29:18 2026 +From: Sasha Levin +Date: Mon, 4 May 2026 05:59:01 -0400 +Subject: net: qrtr: ns: Limit the total number of nodes +To: stable@vger.kernel.org +Cc: Manivannan Sadhasivam , Jakub Kicinski , Sasha Levin +Message-ID: <20260504095901.2009146-1-sashal@kernel.org> + +From: Manivannan Sadhasivam + +[ Upstream commit 27d5e84e810b0849d08b9aec68e48570461ce313 ] + +Currently, the nameserver doesn't limit the number of nodes it handles. +This can be an attack vector if a malicious client starts registering +random nodes, leading to memory exhaustion. + +Hence, limit the maximum number of nodes to 64. Note that, limit of 64 is +chosen based on the current platform requirements. If requirement changes +in the future, this limit can be increased. + +Cc: stable@vger.kernel.org +Fixes: 0c2204a4ad71 ("net: qrtr: Migrate nameservice to kernel from userspace") +Signed-off-by: Manivannan Sadhasivam +Link: https://patch.msgid.link/20260409-qrtr-fix-v3-4-00a8a5ff2b51@oss.qualcomm.com +Signed-off-by: Jakub Kicinski +[ dropped node_count-- hunk since ctrl_cmd_bye() has no delete_node ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/qrtr/ns.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +--- a/net/qrtr/ns.c ++++ b/net/qrtr/ns.c +@@ -75,6 +75,16 @@ struct qrtr_node { + */ + #define QRTR_NS_MAX_LOOKUPS 64 + ++/* Max nodes, server, lookup limits are chosen based on the current platform ++ * requirements. If the requirement changes in the future, these values can be ++ * increased. ++ */ ++#define QRTR_NS_MAX_NODES 64 ++#define QRTR_NS_MAX_SERVERS 256 ++#define QRTR_NS_MAX_LOOKUPS 64 ++ ++static u8 node_count; ++ + static struct qrtr_node *node_get(unsigned int node_id) + { + struct qrtr_node *node; +@@ -83,6 +93,11 @@ static struct qrtr_node *node_get(unsign + if (node) + return node; + ++ if (node_count >= QRTR_NS_MAX_NODES) { ++ pr_err_ratelimited("QRTR clients exceed max node limit!\n"); ++ return NULL; ++ } ++ + /* If node didn't exist, allocate and insert it to the tree */ + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) +@@ -96,6 +111,8 @@ static struct qrtr_node *node_get(unsign + return NULL; + } + ++ node_count++; ++ + return node; + } + diff --git a/queue-6.1/sched-use-u64-for-bandwidth-ratio-calculations.patch b/queue-6.1/sched-use-u64-for-bandwidth-ratio-calculations.patch new file mode 100644 index 0000000000..dd0c0567c1 --- /dev/null +++ b/queue-6.1/sched-use-u64-for-bandwidth-ratio-calculations.patch @@ -0,0 +1,74 @@ +From stable+bounces-242627-greg=kroah.com@vger.kernel.org Sun May 3 05:28:19 2026 +From: Sasha Levin +Date: Sat, 2 May 2026 19:58:03 -0400 +Subject: sched: Use u64 for bandwidth ratio calculations +To: stable@vger.kernel.org +Cc: Joseph Salisbury , "Peter Zijlstra (Intel)" , Sasha Levin +Message-ID: <20260502235803.921618-1-sashal@kernel.org> + +From: Joseph Salisbury + +[ Upstream commit c6e80201e057dfb7253385e60bf541121bf5dc33 ] + +to_ratio() computes BW_SHIFT-scaled bandwidth ratios from u64 period and +runtime values, but it returns unsigned long. tg_rt_schedulable() also +stores the current group limit and the accumulated child sum in unsigned +long. + +On 32-bit builds, large bandwidth ratios can be truncated and the RT +group sum can wrap when enough siblings are present. That can let an +overcommitted RT hierarchy pass the schedulability check, and it also +narrows the helper result for other callers. + +Return u64 from to_ratio() and use u64 for the RT group totals so +bandwidth ratios are preserved and compared at full width on both 32-bit +and 64-bit builds. + +Fixes: b40b2e8eb521 ("sched: rt: multi level group constraints") +Assisted-by: Codex:GPT-5 +Signed-off-by: Joseph Salisbury +Signed-off-by: Peter Zijlstra (Intel) +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260403210014.2713404-1-joseph.salisbury@oracle.com +[ dropped `extern` keyword from `to_ratio()` declaration ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/sched/core.c | 2 +- + kernel/sched/rt.c | 2 +- + kernel/sched/sched.h | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -4692,7 +4692,7 @@ void sched_post_fork(struct task_struct + uclamp_post_fork(p); + } + +-unsigned long to_ratio(u64 period, u64 runtime) ++u64 to_ratio(u64 period, u64 runtime) + { + if (runtime == RUNTIME_INF) + return BW_UNIT; +--- a/kernel/sched/rt.c ++++ b/kernel/sched/rt.c +@@ -2768,7 +2768,7 @@ static int tg_rt_schedulable(struct task + { + struct rt_schedulable_data *d = data; + struct task_group *child; +- unsigned long total, sum = 0; ++ u64 total, sum = 0; + u64 period, runtime; + + period = ktime_to_ns(tg->rt_bandwidth.rt_period); +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -2371,7 +2371,7 @@ extern void init_dl_inactive_task_timer( + #define RATIO_SHIFT 8 + #define MAX_BW_BITS (64 - BW_SHIFT) + #define MAX_BW ((1ULL << MAX_BW_BITS) - 1) +-unsigned long to_ratio(u64 period, u64 runtime); ++u64 to_ratio(u64 period, u64 runtime); + + extern void init_entity_runnable_average(struct sched_entity *se); + extern void post_init_entity_util_avg(struct task_struct *p); diff --git a/queue-6.1/series b/queue-6.1/series index 4744f6bc27..07c1839b59 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -328,3 +328,29 @@ fs-fcntl-fix-softirq-unsafe-lock-order-in-fasync-signaling.patch mm-hugetlb-avoid-false-positive-lockdep-assertion.patch mm-damon-ops-common-call-folio_test_lru-after-folio_.patch mm-huge_memory-update-file-pmd-counter-before-folio_.patch +f2fs-use-kfree-instead-of-kvfree-to-free-some-memory.patch +f2fs-fix-to-do-sanity-check-on-dcc-discard_cmd_cnt-conditionally.patch +f2fs-fix-uaf-caused-by-decrementing-sbi-nr_pages-in-f2fs_write_end_io.patch +ksmbd-require-minimum-ace-size-in-smb_check_perm_dacl.patch +smb-client-validate-the-whole-dacl-before-rewriting-it-in-cifsacl.patch +loongarch-add-spectre-boundry-for-syscall-dispatch-table.patch +arm64-mm-enable-batched-tlb-flush-in-unmap_hotplug_range.patch +lib-test_hmm-evict-device-pages-on-file-close-to-avoid-use-after-free.patch +wifi-mwifiex-fix-use-after-free-in-mwifiex_adapter_cleanup.patch +spi-imx-convert-to-platform-remove-callback-returning-void.patch +spi-imx-fix-use-after-free-on-unbind.patch +thermal-core-fix-thermal-zone-governor-cleanup-issues.patch +alsa-aoa-use-guard-for-mutex-locks.patch +alsa-aoa-i2sbus-clear-stale-prepared-state.patch +media-rc-ttusbir-respect-dma-coherency-rules.patch +alsa-aoa-skip-devices-with-no-codecs-in-i2sbus_resume.patch +media-rc-igorplugusb-heed-coherency-rules.patch +sched-use-u64-for-bandwidth-ratio-calculations.patch +net-qrtr-ns-limit-the-maximum-number-of-lookups.patch +net-qrtr-ns-change-servers-radix-tree-to-xarray.patch +net-qrtr-ns-free-the-node-during-ctrl_cmd_bye.patch +net-mctp-fix-don-t-require-received-header-reserved-bits-to-be-zero.patch +net-qrtr-ns-limit-the-total-number-of-nodes.patch +net-bridge-use-a-stable-fdb-dst-snapshot-in-rcu-readers.patch +spi-fix-resource-leaks-on-device-setup-failure.patch +fbdev-defio-disconnect-deferred-i-o-from-the-lifetime-of-struct-fb_info.patch diff --git a/queue-6.1/smb-client-validate-the-whole-dacl-before-rewriting-it-in-cifsacl.patch b/queue-6.1/smb-client-validate-the-whole-dacl-before-rewriting-it-in-cifsacl.patch new file mode 100644 index 0000000000..eeb693dbda --- /dev/null +++ b/queue-6.1/smb-client-validate-the-whole-dacl-before-rewriting-it-in-cifsacl.patch @@ -0,0 +1,192 @@ +From stable+bounces-241075-greg=kroah.com@vger.kernel.org Sat Apr 25 05:18:25 2026 +From: Sasha Levin +Date: Fri, 24 Apr 2026 19:46:38 -0400 +Subject: smb: client: validate the whole DACL before rewriting it in cifsacl +To: stable@vger.kernel.org +Cc: Michael Bommarito , Steve French , Sasha Levin +Message-ID: <20260424234638.2560269-1-sashal@kernel.org> + +From: Michael Bommarito + +[ Upstream commit 0a8cf165566ba55a39fd0f4de172119dd646d39a ] + +build_sec_desc() and id_mode_to_cifs_acl() derive a DACL pointer from a +server-supplied dacloffset and then use the incoming ACL to rebuild the +chmod/chown security descriptor. + +The original fix only checked that the struct smb_acl header fits before +reading dacl_ptr->size or dacl_ptr->num_aces. That avoids the immediate +header-field OOB read, but the rewrite helpers still walk ACEs based on +pdacl->num_aces with no structural validation of the incoming DACL body. + +A malicious server can return a truncated DACL that still contains a +header, claims one or more ACEs, and then drive +replace_sids_and_copy_aces() or set_chmod_dacl() past the validated +extent while they compare or copy attacker-controlled ACEs. + +Factor the DACL structural checks into validate_dacl(), extend them to +validate each ACE against the DACL bounds, and use the shared validator +before the chmod/chown rebuild paths. parse_dacl() reuses the same +validator so the read-side parser and write-side rewrite paths agree on +what constitutes a well-formed incoming DACL. + +Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-6 +Assisted-by: Codex:gpt-5-4 +Signed-off-by: Michael Bommarito +Signed-off-by: Steve French +[ renamed smb_acl/smb_ace/smb_sid/smb_ntsd to cifs_* and widened num_aces from u16 to u32 for 6.1's __le32 field ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifsacl.c | 95 ++++++++++++++++++++++++++++++++++++++++++------ + 1 file changed, 85 insertions(+), 10 deletions(-) + +--- a/fs/smb/client/cifsacl.c ++++ b/fs/smb/client/cifsacl.c +@@ -753,6 +753,78 @@ static void dump_ace(struct cifs_ace *pa + } + #endif + ++static int validate_dacl(struct cifs_acl *pdacl, char *end_of_acl) ++{ ++ int i, ace_hdr_size, ace_size, min_ace_size; ++ u16 dacl_size; ++ u32 num_aces; ++ char *acl_base, *end_of_dacl; ++ struct cifs_ace *pace; ++ ++ if (!pdacl) ++ return 0; ++ ++ if (end_of_acl < (char *)pdacl + sizeof(struct cifs_acl)) { ++ cifs_dbg(VFS, "ACL too small to parse DACL\n"); ++ return -EINVAL; ++ } ++ ++ dacl_size = le16_to_cpu(pdacl->size); ++ if (dacl_size < sizeof(struct cifs_acl) || ++ end_of_acl < (char *)pdacl + dacl_size) { ++ cifs_dbg(VFS, "ACL too small to parse DACL\n"); ++ return -EINVAL; ++ } ++ ++ num_aces = le32_to_cpu(pdacl->num_aces); ++ if (!num_aces) ++ return 0; ++ ++ ace_hdr_size = offsetof(struct cifs_ace, sid) + ++ offsetof(struct cifs_sid, sub_auth); ++ min_ace_size = ace_hdr_size + sizeof(__le32); ++ if (num_aces > (dacl_size - sizeof(struct cifs_acl)) / min_ace_size) { ++ cifs_dbg(VFS, "ACL too small to parse DACL\n"); ++ return -EINVAL; ++ } ++ ++ end_of_dacl = (char *)pdacl + dacl_size; ++ acl_base = (char *)pdacl; ++ ace_size = sizeof(struct cifs_acl); ++ ++ for (i = 0; i < num_aces; ++i) { ++ if (end_of_dacl - acl_base < ace_size) { ++ cifs_dbg(VFS, "ACL too small to parse ACE\n"); ++ return -EINVAL; ++ } ++ ++ pace = (struct cifs_ace *)(acl_base + ace_size); ++ acl_base = (char *)pace; ++ ++ if (end_of_dacl - acl_base < ace_hdr_size || ++ pace->sid.num_subauth == 0 || ++ pace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) { ++ cifs_dbg(VFS, "ACL too small to parse ACE\n"); ++ return -EINVAL; ++ } ++ ++ ace_size = ace_hdr_size + sizeof(__le32) * pace->sid.num_subauth; ++ if (end_of_dacl - acl_base < ace_size || ++ le16_to_cpu(pace->size) < ace_size) { ++ cifs_dbg(VFS, "ACL too small to parse ACE\n"); ++ return -EINVAL; ++ } ++ ++ ace_size = le16_to_cpu(pace->size); ++ if (end_of_dacl - acl_base < ace_size) { ++ cifs_dbg(VFS, "ACL too small to parse ACE\n"); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ + static void parse_dacl(struct cifs_acl *pdacl, char *end_of_acl, + struct cifs_sid *pownersid, struct cifs_sid *pgrpsid, + struct cifs_fattr *fattr, bool mode_from_special_sid) +@@ -760,7 +832,7 @@ static void parse_dacl(struct cifs_acl * + int i; + int num_aces = 0; + int acl_size; +- char *acl_base; ++ char *acl_base, *end_of_dacl; + struct cifs_ace **ppace; + + /* BB need to add parm so we can store the SID BB */ +@@ -772,11 +844,8 @@ static void parse_dacl(struct cifs_acl * + return; + } + +- /* validate that we do not go past end of acl */ +- if (end_of_acl < (char *)pdacl + le16_to_cpu(pdacl->size)) { +- cifs_dbg(VFS, "ACL too small to parse DACL\n"); ++ if (validate_dacl(pdacl, end_of_acl)) + return; +- } + + cifs_dbg(NOISY, "DACL revision %d size %d num aces %d\n", + le16_to_cpu(pdacl->revision), le16_to_cpu(pdacl->size), +@@ -787,6 +856,7 @@ static void parse_dacl(struct cifs_acl * + user/group/other have no permissions */ + fattr->cf_mode &= ~(0777); + ++ end_of_dacl = (char *)pdacl + le16_to_cpu(pdacl->size); + acl_base = (char *)pdacl; + acl_size = sizeof(struct cifs_acl); + +@@ -804,7 +874,7 @@ static void parse_dacl(struct cifs_acl * + for (i = 0; i < num_aces; ++i) { + ppace[i] = (struct cifs_ace *) (acl_base + acl_size); + #ifdef CONFIG_CIFS_DEBUG2 +- dump_ace(ppace[i], end_of_acl); ++ dump_ace(ppace[i], end_of_dacl); + #endif + if (mode_from_special_sid && + ppace[i]->sid.num_subauth >= 3 && +@@ -1263,10 +1333,9 @@ static int build_sec_desc(struct cifs_nt + dacloffset = le32_to_cpu(pntsd->dacloffset); + if (dacloffset) { + dacl_ptr = (struct cifs_acl *)((char *)pntsd + dacloffset); +- if (end_of_acl < (char *)dacl_ptr + le16_to_cpu(dacl_ptr->size)) { +- cifs_dbg(VFS, "Server returned illegal ACL size\n"); +- return -EINVAL; +- } ++ rc = validate_dacl(dacl_ptr, end_of_acl); ++ if (rc) ++ return rc; + } + + owner_sid_ptr = (struct cifs_sid *)((char *)pntsd + +@@ -1630,6 +1699,12 @@ id_mode_to_cifs_acl(struct inode *inode, + dacloffset = le32_to_cpu(pntsd->dacloffset); + if (dacloffset) { + dacl_ptr = (struct cifs_acl *)((char *)pntsd + dacloffset); ++ rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen); ++ if (rc) { ++ kfree(pntsd); ++ cifs_put_tlink(tlink); ++ return rc; ++ } + if (mode_from_sid) + nsecdesclen += + le32_to_cpu(dacl_ptr->num_aces) * sizeof(struct cifs_ace); diff --git a/queue-6.1/spi-fix-resource-leaks-on-device-setup-failure.patch b/queue-6.1/spi-fix-resource-leaks-on-device-setup-failure.patch new file mode 100644 index 0000000000..5a2ff557b7 --- /dev/null +++ b/queue-6.1/spi-fix-resource-leaks-on-device-setup-failure.patch @@ -0,0 +1,134 @@ +From stable+bounces-243851-greg=kroah.com@vger.kernel.org Mon May 4 20:52:31 2026 +From: Sasha Levin +Date: Mon, 4 May 2026 11:02:09 -0400 +Subject: spi: fix resource leaks on device setup failure +To: stable@vger.kernel.org +Cc: Johan Hovold , Saravana Kannan , Mark Brown , Sasha Levin +Message-ID: <20260504150209.2395361-1-sashal@kernel.org> + +From: Johan Hovold + +[ Upstream commit db357034f7e0cf23f233f414a8508312dfe8fbbe ] + +Make sure to call controller cleanup() if spi_setup() fails while +registering a device to avoid leaking any resources allocated by +setup(). + +Fixes: c7299fea6769 ("spi: Fix spi device unregister flow") +Cc: stable@vger.kernel.org # 5.13 +Cc: Saravana Kannan +Signed-off-by: Johan Hovold +Link: https://patch.msgid.link/20260410154907.129248-2-johan@kernel.org +Signed-off-by: Mark Brown +[ adjusted context ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/spi/spi.c | 61 ++++++++++++++++++++++++++++++++---------------------- + 1 file changed, 37 insertions(+), 24 deletions(-) + +--- a/drivers/spi/spi.c ++++ b/drivers/spi/spi.c +@@ -42,6 +42,8 @@ EXPORT_TRACEPOINT_SYMBOL(spi_transfer_st + + #include "internals.h" + ++static int __spi_setup(struct spi_device *spi, bool initial_setup); ++ + static DEFINE_IDR(spi_master_idr); + + static void spidev_release(struct device *dev) +@@ -666,7 +668,7 @@ static int __spi_add_device(struct spi_d + * normally rely on the device being setup. Devices + * using SPI_CS_HIGH can't coexist well otherwise... + */ +- status = spi_setup(spi); ++ status = __spi_setup(spi, true); + if (status < 0) { + dev_err(dev, "can't setup %s, status %d\n", + dev_name(&spi->dev), status); +@@ -3688,27 +3690,7 @@ static int spi_set_cs_timing(struct spi_ + return status; + } + +-/** +- * spi_setup - setup SPI mode and clock rate +- * @spi: the device whose settings are being modified +- * Context: can sleep, and no requests are queued to the device +- * +- * SPI protocol drivers may need to update the transfer mode if the +- * device doesn't work with its default. They may likewise need +- * to update clock rates or word sizes from initial values. This function +- * changes those settings, and must be called from a context that can sleep. +- * Except for SPI_CS_HIGH, which takes effect immediately, the changes take +- * effect the next time the device is selected and data is transferred to +- * or from it. When this function returns, the spi device is deselected. +- * +- * Note that this call will fail if the protocol driver specifies an option +- * that the underlying controller or its driver does not support. For +- * example, not all hardware supports wire transfers using nine bit words, +- * LSB-first wire encoding, or active-high chipselects. +- * +- * Return: zero on success, else a negative error code. +- */ +-int spi_setup(struct spi_device *spi) ++static int __spi_setup(struct spi_device *spi, bool initial_setup) + { + unsigned bad_bits, ugly_bits; + int status = 0; +@@ -3787,7 +3769,7 @@ int spi_setup(struct spi_device *spi) + status = spi_set_cs_timing(spi); + if (status) { + mutex_unlock(&spi->controller->io_mutex); +- return status; ++ goto err_cleanup; + } + + if (spi->controller->auto_runtime_pm && spi->controller->set_cs) { +@@ -3796,7 +3778,7 @@ int spi_setup(struct spi_device *spi) + mutex_unlock(&spi->controller->io_mutex); + dev_err(&spi->controller->dev, "Failed to power device: %d\n", + status); +- return status; ++ goto err_cleanup; + } + + /* +@@ -3833,6 +3815,37 @@ int spi_setup(struct spi_device *spi) + status); + + return status; ++ ++err_cleanup: ++ if (initial_setup) ++ spi_cleanup(spi); ++ ++ return status; ++} ++ ++/** ++ * spi_setup - setup SPI mode and clock rate ++ * @spi: the device whose settings are being modified ++ * Context: can sleep, and no requests are queued to the device ++ * ++ * SPI protocol drivers may need to update the transfer mode if the ++ * device doesn't work with its default. They may likewise need ++ * to update clock rates or word sizes from initial values. This function ++ * changes those settings, and must be called from a context that can sleep. ++ * Except for SPI_CS_HIGH, which takes effect immediately, the changes take ++ * effect the next time the device is selected and data is transferred to ++ * or from it. When this function returns, the SPI device is deselected. ++ * ++ * Note that this call will fail if the protocol driver specifies an option ++ * that the underlying controller or its driver does not support. For ++ * example, not all hardware supports wire transfers using nine bit words, ++ * LSB-first wire encoding, or active-high chipselects. ++ * ++ * Return: zero on success, else a negative error code. ++ */ ++int spi_setup(struct spi_device *spi) ++{ ++ return __spi_setup(spi, false); + } + EXPORT_SYMBOL_GPL(spi_setup); + diff --git a/queue-6.1/spi-imx-convert-to-platform-remove-callback-returning-void.patch b/queue-6.1/spi-imx-convert-to-platform-remove-callback-returning-void.patch new file mode 100644 index 0000000000..ee8851bf45 --- /dev/null +++ b/queue-6.1/spi-imx-convert-to-platform-remove-callback-returning-void.patch @@ -0,0 +1,62 @@ +From stable+bounces-242188-greg=kroah.com@vger.kernel.org Thu Apr 30 23:43:54 2026 +From: Sasha Levin +Date: Thu, 30 Apr 2026 14:13:43 -0400 +Subject: spi: imx: Convert to platform remove callback returning void +To: stable@vger.kernel.org +Cc: "Uwe Kleine-König" , "Mark Brown" , "Sasha Levin" +Message-ID: <20260430181344.1923600-1-sashal@kernel.org> + +From: Uwe Kleine-König + +[ Upstream commit 423e548127223d597bb65a149ebcb3c50ea08846 ] + +The .remove() callback for a platform driver returns an int which makes +many driver authors wrongly assume it's possible to do error handling by +returning an error code. However the value returned is (mostly) ignored +and this typically results in resource leaks. To improve here there is a +quest to make the remove callback return void. In the first step of this +quest all drivers are converted to .remove_new() which already returns +void. + +Trivially convert this driver from always returning zero in the remove +callback to the void returning variant. + +Signed-off-by: Uwe Kleine-König +Link: https://lore.kernel.org/r/20230306065733.2170662-3-u.kleine-koenig@pengutronix.de +Signed-off-by: Mark Brown +Stable-dep-of: 1c78c2002380 ("spi: imx: fix use-after-free on unbind") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/spi/spi-imx.c | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +--- a/drivers/spi/spi-imx.c ++++ b/drivers/spi/spi-imx.c +@@ -1879,7 +1879,7 @@ out_controller_put: + return ret; + } + +-static int spi_imx_remove(struct platform_device *pdev) ++static void spi_imx_remove(struct platform_device *pdev) + { + struct spi_controller *controller = platform_get_drvdata(pdev); + struct spi_imx_data *spi_imx = spi_controller_get_devdata(controller); +@@ -1898,8 +1898,6 @@ static int spi_imx_remove(struct platfor + pm_runtime_disable(spi_imx->dev); + + spi_imx_sdma_exit(spi_imx); +- +- return 0; + } + + static int __maybe_unused spi_imx_runtime_resume(struct device *dev) +@@ -1961,7 +1959,7 @@ static struct platform_driver spi_imx_dr + .pm = &imx_spi_pm, + }, + .probe = spi_imx_probe, +- .remove = spi_imx_remove, ++ .remove_new = spi_imx_remove, + }; + module_platform_driver(spi_imx_driver); + diff --git a/queue-6.1/spi-imx-fix-use-after-free-on-unbind.patch b/queue-6.1/spi-imx-fix-use-after-free-on-unbind.patch new file mode 100644 index 0000000000..d19831e853 --- /dev/null +++ b/queue-6.1/spi-imx-fix-use-after-free-on-unbind.patch @@ -0,0 +1,51 @@ +From stable+bounces-242189-greg=kroah.com@vger.kernel.org Thu Apr 30 23:43:54 2026 +From: Sasha Levin +Date: Thu, 30 Apr 2026 14:13:44 -0400 +Subject: spi: imx: fix use-after-free on unbind +To: stable@vger.kernel.org +Cc: Johan Hovold , Marc Kleine-Budde , Mark Brown , Sasha Levin +Message-ID: <20260430181344.1923600-2-sashal@kernel.org> + +From: Johan Hovold + +[ Upstream commit 1c78c2002380a1fe31bfb01a3d5f29809e55a096 ] + +The SPI subsystem frees the controller and any subsystem allocated +driver data as part of deregistration (unless the allocation is device +managed). + +Take another reference before deregistering the controller so that the +driver data is not freed until the driver is done with it. + +Fixes: 307c897db762 ("spi: spi-imx: replace struct spi_imx_data::bitbang by pointer to struct spi_controller") +Cc: stable@vger.kernel.org # 5.19 +Acked-by: Marc Kleine-Budde +Signed-off-by: Johan Hovold +Link: https://patch.msgid.link/20260324082326.901043-2-johan@kernel.org +Signed-off-by: Mark Brown +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/spi/spi-imx.c | 4 ++++ + 1 file changed, 4 insertions(+) + +--- a/drivers/spi/spi-imx.c ++++ b/drivers/spi/spi-imx.c +@@ -1885,6 +1885,8 @@ static void spi_imx_remove(struct platfo + struct spi_imx_data *spi_imx = spi_controller_get_devdata(controller); + int ret; + ++ spi_controller_get(controller); ++ + spi_unregister_controller(controller); + + ret = pm_runtime_get_sync(spi_imx->dev); +@@ -1898,6 +1900,8 @@ static void spi_imx_remove(struct platfo + pm_runtime_disable(spi_imx->dev); + + spi_imx_sdma_exit(spi_imx); ++ ++ spi_controller_put(controller); + } + + static int __maybe_unused spi_imx_runtime_resume(struct device *dev) diff --git a/queue-6.1/thermal-core-fix-thermal-zone-governor-cleanup-issues.patch b/queue-6.1/thermal-core-fix-thermal-zone-governor-cleanup-issues.patch new file mode 100644 index 0000000000..4bd1adde84 --- /dev/null +++ b/queue-6.1/thermal-core-fix-thermal-zone-governor-cleanup-issues.patch @@ -0,0 +1,69 @@ +From stable+bounces-242192-greg=kroah.com@vger.kernel.org Thu Apr 30 23:58:04 2026 +From: Sasha Levin +Date: Thu, 30 Apr 2026 14:27:52 -0400 +Subject: thermal: core: Fix thermal zone governor cleanup issues +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Sasha Levin +Message-ID: <20260430182752.1934906-1-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit 41ff66baf81c6541f4f985dd7eac4494d03d9440 ] + +If thermal_zone_device_register_with_trips() fails after adding +a thermal governor to the thermal zone being registered, the +governor is not removed from it as appropriate which may lead to +a memory leak. + +In turn, thermal_zone_device_unregister() calls thermal_set_governor() +without acquiring the thermal zone lock beforehand which may race with +a governor update via sysfs and may lead to a use-after-free in that +case. + +Address these issues by adding two thermal_set_governor() calls, one to +thermal_release() to remove the governor from the given thermal zone, +and one to the thermal zone registration error path to cover failures +preceding the thermal zone device registration. + +Fixes: e33df1d2f3a0 ("thermal: let governors have private data for each thermal zone") +Cc: All applicable +Signed-off-by: Rafael J. Wysocki +Link: https://patch.msgid.link/5092923.31r3eYUQgx@rafael.j.wysocki +[ adapted context for missing mutex_destroy/complete ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/thermal/thermal_core.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +--- a/drivers/thermal/thermal_core.c ++++ b/drivers/thermal/thermal_core.c +@@ -756,6 +756,7 @@ static void thermal_release(struct devic + sizeof("thermal_zone") - 1)) { + tz = to_thermal_zone(dev); + thermal_zone_destroy_device_groups(tz); ++ thermal_set_governor(tz, NULL); + kfree(tz); + } else if (!strncmp(dev_name(dev), "cooling_device", + sizeof("cooling_device") - 1)) { +@@ -1260,8 +1261,10 @@ thermal_zone_device_register_with_trips( + /* sys I/F */ + /* Add nodes that are always present via .groups */ + result = thermal_zone_create_device_groups(tz, mask); +- if (result) ++ if (result) { ++ thermal_set_governor(tz, NULL); + goto remove_id; ++ } + + /* A new thermal zone needs to be updated anyway. */ + atomic_set(&tz->need_update, 1); +@@ -1396,8 +1399,6 @@ void thermal_zone_device_unregister(stru + + cancel_delayed_work_sync(&tz->poll_queue); + +- thermal_set_governor(tz, NULL); +- + thermal_remove_hwmon_sysfs(tz); + ida_free(&thermal_tz_ida, tz->id); + ida_destroy(&tz->ida); diff --git a/queue-6.1/wifi-mwifiex-fix-use-after-free-in-mwifiex_adapter_cleanup.patch b/queue-6.1/wifi-mwifiex-fix-use-after-free-in-mwifiex_adapter_cleanup.patch new file mode 100644 index 0000000000..7dfb8ffab4 --- /dev/null +++ b/queue-6.1/wifi-mwifiex-fix-use-after-free-in-mwifiex_adapter_cleanup.patch @@ -0,0 +1,49 @@ +From stable+bounces-242184-greg=kroah.com@vger.kernel.org Thu Apr 30 23:27:07 2026 +From: Sasha Levin +Date: Thu, 30 Apr 2026 13:56:43 -0400 +Subject: wifi: mwifiex: fix use-after-free in mwifiex_adapter_cleanup() +To: stable@vger.kernel.org +Cc: Daniel Hodges , Johannes Berg , Sasha Levin +Message-ID: <20260430175643.1905462-1-sashal@kernel.org> + +From: Daniel Hodges + +[ Upstream commit ae5e95d4157481693be2317e3ffcd84e36010cbb ] + +The mwifiex_adapter_cleanup() function uses timer_delete() +(non-synchronous) for the wakeup_timer before the adapter structure is +freed. This is incorrect because timer_delete() does not wait for any +running timer callback to complete. + +If the wakeup_timer callback (wakeup_timer_fn) is executing when +mwifiex_adapter_cleanup() is called, the callback will continue to +access adapter fields (adapter->hw_status, adapter->if_ops.card_reset, +etc.) which may be freed by mwifiex_free_adapter() called later in the +mwifiex_remove_card() path. + +Use timer_delete_sync() instead to ensure any running timer callback has +completed before returning. + +Fixes: 4636187da60b ("mwifiex: add wakeup timer based recovery mechanism") +Cc: stable@vger.kernel.org +Signed-off-by: Daniel Hodges +Link: https://patch.msgid.link/20260206194401.2346-1-git@danielhodges.dev +Signed-off-by: Johannes Berg +[ changed `timer_delete_sync()` to `del_timer_sync()` ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/wireless/marvell/mwifiex/init.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/net/wireless/marvell/mwifiex/init.c ++++ b/drivers/net/wireless/marvell/mwifiex/init.c +@@ -388,7 +388,7 @@ static void mwifiex_invalidate_lists(str + static void + mwifiex_adapter_cleanup(struct mwifiex_adapter *adapter) + { +- del_timer(&adapter->wakeup_timer); ++ del_timer_sync(&adapter->wakeup_timer); + cancel_delayed_work_sync(&adapter->devdump_work); + mwifiex_cancel_all_pending_cmd(adapter); + wake_up_interruptible(&adapter->cmd_wait_q.wait);