UAF: Global Static Jack GPIO data Pointer in tegra_asoc_machine.c
Summary
A use-after-free vulnerability exists in the Tegra ASoC machine driver due to storing
a per-device machine pointer in a module-level static snd_soc_jack_gpio structure. The
pointer can be dereferenced via a GPIO interrupt handler after the device-managed memory has
been freed.
Vulnerability Details
File: sound/soc/tegra/tegra_asoc_machine.c
Root Cause
Three jack GPIO structures are declared as module-level (file-scope) statics:
// lines 31–35
static struct snd_soc_jack_gpio tegra_machine_hp_jack_gpio = { … };
// lines 46–50
static struct snd_soc_jack_gpio tegra_machine_headset_jack_gpio = { … };
// lines 72–76
static struct snd_soc_jack_gpio tegra_machine_mic_jack_gpio = { … };
During card initialization (tegra_asoc_machine_init, line 139), the per-device machine
pointer — allocated with devm_kzalloc — is written into the global structure:
// line 202
tegra_machine_mic_jack_gpio.data = machine;
// line 203
tegra_machine_mic_jack_gpio.desc = machine->gpiod_mic_det;
The .data field is then consumed by the GPIO interrupt callback:
// lines 53–63
static int coupled_mic_hp_check(void *data)
{
struct tegra_machine *machine = (struct tegra_machine *)data;
if (gpiod_get_value_cansleep(machine->gpiod_hp_det) && // UAF dereference
gpiod_get_value_cansleep(machine->gpiod_mic_det))
return SND_JACK_MICROPHONE;
return 0;
}
Use-After-Free Path
- Device probes:
devm_kzallocallocatesmachine;tegra_machine_mic_jack_gpio.data = machine. snd_soc_jack_add_gpios(line 211) registers a GPIO IRQ that callscoupled_mic_hp_check.- Device is unbound (e.g., driver module removed, or hotplug event).
devmcleanup runs in LIFO order:devm_snd_soc_register_cardfires first, beginning card teardown and jack GPIO IRQ removal. However, the IRQ is not guaranteed to be fully quiesced before thedevm_kzalloc-managedmachineblock is freed.- A concurrent or late-arriving GPIO interrupt invokes
coupled_mic_hp_check(data)wheredatapoints to the now-freedmachinestruct → use-after-free.
Second UAF Path: Multiple Device Instances
Because all global statics are shared across all driver instances:
- Device A probes:
tegra_machine_mic_jack_gpio.data = machine_A. - Device B probes:
tegra_machine_mic_jack_gpio.data = machine_B— silently overwrites. - Device B is unbound:
machine_Bfreed by devm. - Device A’s GPIO interrupt fires →
coupled_mic_hp_checkuses freedmachine_B→ UAF.
Neither instance is aware of the other; the shared global creates an implicit cross-device lifetime dependency with no synchronization.
Impact
- Type: Use-After-Free (CWE-416) in kernel interrupt context
- Severity: High — kernel memory corruption from interrupt context; exploitable for privilege escalation (ring 3 → ring 0) by a local unprivileged user who can trigger GPIO events (e.g., via a malicious peripheral or by scripting headphone insertion/removal while racing device unbind)
- Affected subsystem: ALSA/ASoC Tegra machine driver; impacts NVIDIA Tegra-based boards (Chromebooks, Jetson, Shield) running affected kernels
Relevant Lines
| Line | Code |
|---|---|
| 24–76 | Module-level static jack and jack-GPIO structures |
| 53–63 | coupled_mic_hp_check — dereferences .data as freed machine * |
| 202–203 | tegra_machine_mic_jack_gpio.data = machine — stores per-device ptr in global |
| 163, 183 | Similar pattern for hp_jack_gpio and headset_jack_gpio |
| 436 | machine = devm_kzalloc(...) — lifetime tied to device, not module |
Fix
Replace the module-level static snd_soc_jack_gpio instances with per-device allocations
(e.g., embed them in struct tegra_machine), so their lifetime matches the machine struct
they reference. Ensure IRQ teardown is fully completed (e.g., via synchronize_irq) before
the machine allocation is freed.