
On 06/05/2025 18:12, Tavian Barnes wrote:
hda_generic_machine_select() appends -idisp to the tplg filename by allocating a new string with devm_kasprintf(), then stores the string right back into the global variable snd_soc_acpi_intel_hda_machines. When the module is unloaded, this memory is freed, resulting in a global variable pointing to freed memory. Reloading the modules then triggers a use-after-free:
BUG: KFENCE: use-after-free read in string+0x48/0xe0
Use-after-free read at 0x00000000967e0109 (in kfence-#99): string+0x48/0xe0 vsnprintf+0x329/0x6e0 devm_kvasprintf+0x54/0xb0 devm_kasprintf+0x58/0x80 hda_machine_select.cold+0x198/0x17a2 [snd_sof_intel_hda_generic] sof_probe_work+0x7f/0x600 [snd_sof] process_one_work+0x17b/0x330 worker_thread+0x2ce/0x3f0 kthread+0xcf/0x100 ret_from_fork+0x31/0x50 ret_from_fork_asm+0x1a/0x30
kfence-#99: 0x00000000198a940f-0x00000000ace47d9d, size=64, cache=kmalloc-64
allocated by task 333 on cpu 8 at 17.798069s (130.453553s ago): devm_kmalloc+0x52/0x120 devm_kvasprintf+0x66/0xb0 devm_kasprintf+0x58/0x80 hda_machine_select.cold+0x198/0x17a2 [snd_sof_intel_hda_generic] sof_probe_work+0x7f/0x600 [snd_sof] process_one_work+0x17b/0x330 worker_thread+0x2ce/0x3f0 kthread+0xcf/0x100 ret_from_fork+0x31/0x50 ret_from_fork_asm+0x1a/0x30
freed by task 1543 on cpu 4 at 141.586686s (6.665010s ago): release_nodes+0x43/0xb0 devres_release_all+0x90/0xf0 device_unbind_cleanup+0xe/0x70 device_release_driver_internal+0x1c1/0x200 driver_detach+0x48/0x90 bus_remove_driver+0x6d/0xf0 pci_unregister_driver+0x42/0xb0 __do_sys_delete_module+0x1d1/0x310 do_syscall_64+0x82/0x190 entry_SYSCALL_64_after_hwframe+0x76/0x7e
Fix it by saving the filename in pdata->tplg_filename instead, just like every other code path that appends to the tplg filename.
Fixes: 5458411d7594 ("ASoC: SOF: Intel: hda: refactoring topology name fixup for HDA mach") Signed-off-by: Tavian Barnes tavianator@tavianator.com
v2: Fix typo
sound/soc/sof/intel/hda.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index b34e5fdf10f1..1767977e7cff 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -1069,7 +1069,7 @@ static void hda_generic_machine_select(struct snd_sof_dev *sdev, if (!tplg_filename) return;
hda_mach->sof_tplg_filename = tplg_filename;
pdata->tplg_filename = tplg_filename; } if (codec_num == 2 ||
I think the correct way would be to make a local copy of the snd_soc_acpi_intel_hda_machines array as we might be modifying it:
/* * make a local copy of the match array since we might * be modifying it */ hda_mach = devm_kmemdup_array(sdev->dev, snd_soc_acpi_intel_hda_machines, 2 /* we have one entry + sentinel in the array */, sizeof(snd_soc_acpi_intel_hda_machines[0]), GFP_KERNEL); if (!hda_mach) { dev_err(bus->dev, "%s: Failed to duplicate the HDA match table\n", __func__); return; }
or if the "2" is not satisfactory (we _know_ that the array size is 2 for this and will not change): include/sound/soc-acpi-intel-match.h +extern size_t snd_soc_acpi_num_intel_hda_machines;
sound/soc/intel/common/soc-acpi-intel-hda-match.c +size_t snd_soc_acpi_num_intel_hda_machines = ARRAY_SIZE(snd_soc_acpi_intel_hda_machines); +EXPORT_SYMBOL_GPL(snd_soc_acpi_num_intel_hda_machines);
and: /* * make a local copy of the match array since we might * be modifying it */ hda_mach = devm_kmemdup_array(sdev->dev, snd_soc_acpi_intel_hda_machines, snd_soc_acpi_num_intel_hda_machines, sizeof(snd_soc_acpi_intel_hda_machines[0]), GFP_KERNEL); if (!hda_mach) { dev_err(bus->dev, "%s: Failed to duplicate the HDA match table\n", __func__); return; }