volume: validate host-supplied config and fix init error path#10919
volume: validate host-supplied config and fix init error path#10919lgirdwood wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Hardens the volume component against malformed host-supplied IPC payloads and fixes a use-after-free in an IPC3 init error path.
Changes:
- Add IPC4 init payload size validation before iterating per-channel config.
- Tighten attenuation control payload validation to require exactly 32 bits.
- Fix IPC3 init error cleanup order to avoid dereferencing freed memory.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/audio/volume/volume_ipc4.c | Adds IPC4 init/config size validation and tightens attenuation payload size checks. |
| src/audio/volume/volume_ipc3.c | Adjusts error-path frees to avoid a use-after-free during init failure. |
| if (cfg->size < sizeof(*vol) + channels_count * sizeof(vol->config[0])) { | ||
| comp_err(dev, "Invalid init payload size %zu for %u channels", | ||
| cfg->size, channels_count); | ||
| return -EINVAL; | ||
| } |
There was a problem hiding this comment.
No overflow here: channels_count is validated to be in [1, SOF_IPC_MAX_CHANNELS] (== 8) a few lines above, before this size check, so channels_count * sizeof(vol->config[0]) is bounded by 8 * a small struct and can't overflow size_t.
| if (data_size < (int)sizeof(uint32_t) || data_size > sizeof(uint32_t)) { | ||
| comp_err(dev, "attenuation data size %d is incorrect", data_size); | ||
| return -EINVAL; | ||
| } |
There was a problem hiding this comment.
Addressed — the check is now a single data_size != (int)sizeof(uint32_t), with the cast keeping both sides signed (no signed/unsigned comparison).
| mod_free(mod, cd); | ||
| /* free cd->vol before cd: freeing cd first would leave the | ||
| * following cd->vol read dereferencing freed memory | ||
| */ |
There was a problem hiding this comment.
please remove the obvious comment
| /* only support attenuation in format of 32bit; the payload is | ||
| * dereferenced as a uint32_t below so it must be exactly that size | ||
| */ | ||
| if (data_size < (int)sizeof(uint32_t) || data_size > sizeof(uint32_t)) { |
There was a problem hiding this comment.
ehm, data_size != sizeof(uint32_t)?
There was a problem hiding this comment.
Done — simplified to a single data_size != (int)sizeof(uint32_t) check. (Cast to int to keep it a clean signed comparison, since data_size is int — also addresses the signed/unsigned note.)
On an invalid ramp type the init error path freed the component data then read a field from it. Free the dependent allocation before the component data and stop dereferencing it afterwards. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
|
Some valgrind errors: |
Init read the per-channel config[] array using the stream channel count without checking the payload was large enough, reading past the mailbox. The payload comes in two forms: an all-channels entry (channel_id set to ALL_CHANNELS_MASK) that carries a single config applied to every channel, or one config entry per channel. The per-channel loop always indexed config[channel], so the common all-channels payload (one entry) was read out of bounds for every channel beyond the first; the result was then discarded, masking the over-read. Require at least one entry before dereferencing config[0] to detect the form, require one entry per channel only for the per-channel form, and index config[] by the selected entry so no read goes past the payload. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
The attenuation setter only rejected oversized payloads, then dereferenced the data as a 32-bit value; a shorter payload read past the mailbox. Require exactly a 32-bit payload. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
|
Pushed a fix: the testbench gain CI (sof-hda-benchmark-gain16) was failing init with The previous size check required one config entry per channel, but the common all-channels payload (channel_id == ALL_CHANNELS_MASK) carries a single entry for all channels — a 64-byte payload = base_cfg(40) + 1 entry(24). The check wrongly rejected it. The init loop also indexed
This both fixes the CI regression and closes the original out-of-bounds read. Commit message updated to match. |
Hardening of host-supplied data in the volume component, plus an error-path
fix:
freed and then dereferenced)
config array before iterating it
No functional change for valid configurations.