audio: up_down_mixer: fix use-after-free on init allocation failure by tmleman · Pull Request #10976 · thesofproject/sof · GitHub
Skip to content

audio: up_down_mixer: fix use-after-free on init allocation failure#10976

Open
tmleman wants to merge 1 commit into
thesofproject:mainfrom
tmleman:topic/upstream/pr/audio/up_down_mixer/fix/use_after_free
Open

audio: up_down_mixer: fix use-after-free on init allocation failure#10976
tmleman wants to merge 1 commit into
thesofproject:mainfrom
tmleman:topic/upstream/pr/audio/up_down_mixer/fix/use_after_free

Conversation

@tmleman

@tmleman tmleman commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

up_down_mixer_init() called comp_free(dev) when mod_zalloc() failed and then returned -ENOMEM. That is a lifecycle violation: a module's init() callback must never free its own comp_dev. On init failure the module adapter framework already unwinds the component:

module_init() -> mod_free_all(mod)
module_adapter_new_ext() -> module_adapter_mem_free(mod) (err path)

The stray comp_free(dev) ran a second, full teardown (module_adapter_free -> module .free callback + mod_free_all() + module_adapter_mem_free() + freeing dev) that collided with the framework cleanup in two ways:

  1. NULL dereference: comp_free() invoked the module .free callback (up_down_mixer_free) before mod->priv.private had been assigned, so module_get_private_data() returned NULL and the callback dereferenced it. This is the SEGV observed first.

  2. Use-after-free / double free: comp_free() had already freed (and, under CONFIG_SYS_HEAP_SANITIZER_ASAN, poisoned) the module resources and the comp_dev. Control then returned to module_init(), which called mod_free_all() on that freed memory, after which the module_adapter_new_ext() error path freed it once more via module_adapter_mem_free().

Fix by removing the comp_free(dev) call and simply returning -ENOMEM, which is exactly what every other module does on this path (aria, asrc, copier, crossover, drc, ...): propagate the error and let the adapter framework own the cleanup. As defensive hardening, also guard up_down_mixer_free() against a NULL private pointer.

The bug was latent: with the static Zephyr memory pools these small allocations never failed, so the error path was never taken. It became reachable only after the native_sim host-heap change routed module allocations onto the C library allocator, where sustained memory pressure over many iterations eventually makes mod_zalloc() fail. Found by the IPC4 libFuzzer target on native_sim under AddressSanitizer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a lifecycle/cleanup bug in the up_down_mixer module’s IPC4 adapter path by ensuring init() does not attempt to tear down its own comp_dev on early allocation failure, and by hardening the module .free callback against being invoked before private data is set.

Changes:

  • Remove comp_free(dev) from up_down_mixer_init() when mod_zalloc() fails; return -ENOMEM and let the module adapter framework unwind.
  • Add a NULL-guard in up_down_mixer_free() to safely handle being called before mod->priv.private is assigned.

up_down_mixer_init() called comp_free(dev) when mod_zalloc() failed and
then returned -ENOMEM. That is a lifecycle violation: a module's init()
callback must never free its own comp_dev. On init failure the module
adapter framework already unwinds the component:

  module_init()            -> mod_free_all(mod)
  module_adapter_new_ext() -> module_adapter_mem_free(mod)   (err path)

The stray comp_free(dev) ran a second, full teardown (module_adapter_free
-> module .free callback + mod_free_all() + module_adapter_mem_free() +
freeing dev) that collided with the framework cleanup in two ways:

  1. NULL dereference: comp_free() invoked the module .free callback
     (up_down_mixer_free) before mod->priv.private had been assigned, so
     module_get_private_data() returned NULL and the callback
     dereferenced it. This is the SEGV observed first.

  2. Use-after-free / double free: comp_free() had already freed (and,
     under CONFIG_SYS_HEAP_SANITIZER_ASAN, poisoned) the module resources
     and the comp_dev. Control then returned to module_init(), which
     called mod_free_all() on that freed memory, after which the
     module_adapter_new_ext() error path freed it once more via
     module_adapter_mem_free().

Fix by removing the comp_free(dev) call and simply returning -ENOMEM,
which is exactly what every other module does on this path (aria, asrc,
copier, crossover, drc, ...): propagate the error and let the adapter
framework own the cleanup. As defensive hardening, also guard
up_down_mixer_free() against a NULL private pointer.

The bug was latent: with the static Zephyr memory pools these small
allocations never failed, so the error path was never taken. It became
reachable only after the native_sim host-heap change routed module
allocations onto the C library allocator, where sustained memory
pressure over many iterations eventually makes mod_zalloc() fail. Found
by the IPC4 libFuzzer target on native_sim under AddressSanitizer.

Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants