From b457a3bd08381dadf1f6d340421e87bb56db7019 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 11 Jun 2026 17:59:33 +0200 Subject: [PATCH 1/3] audio: kpb: validate channel count in kpb_set_micselect kpb_set_micselect() computed mic_cnt = channels - KPB_REFERENCE_SUPPORT_CHANNELS without checking the lower bound. With a host-configured channel count below 2 the unsigned subtraction wraps, producing a huge loop bound and out-of-bounds writes to the fixed offsets[] array. Reject payloads smaller than the config struct and channel counts outside the supported range before computing mic_cnt, and bound the offsets[] index inside the loop. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 71fdca29e73e..94cd13b945a6 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -2459,12 +2459,27 @@ static int kpb_set_micselect(struct comp_dev *dev, const void *data, { const struct kpb_micselector_config *mic_sel = data; struct comp_data *kpb = comp_get_drvdata(dev); - const size_t mic_cnt = kpb->config.channels - KPB_REFERENCE_SUPPORT_CHANNELS; - const uint8_t valid_mask = KPB_COUNT_TO_BITMASK(mic_cnt); + size_t mic_cnt; + uint8_t valid_mask; size_t i; + if (max_data_size < (int)sizeof(*mic_sel)) { + comp_err(dev, "micselector payload too small: got %d, need %d", + max_data_size, (int)sizeof(*mic_sel)); + return -EINVAL; + } + + if (kpb->config.channels < KPB_REFERENCE_SUPPORT_CHANNELS || + kpb->config.channels > KPB_MAX_SUPPORTED_CHANNELS) { + comp_err(dev, "unsupported channel count %u", kpb->config.channels); + return -EINVAL; + } + + mic_cnt = kpb->config.channels - KPB_REFERENCE_SUPPORT_CHANNELS; + valid_mask = KPB_COUNT_TO_BITMASK(mic_cnt); + if ((valid_mask & mic_sel->mask) == 0) { - comp_err(dev, "error: invalid micselector bit mask"); + comp_err(dev, "invalid micselector bit mask"); return -EINVAL; } /* selected mics counter */ @@ -2472,6 +2487,10 @@ static int kpb_set_micselect(struct comp_dev *dev, const void *data, for (i = 0; i < mic_cnt; i++) { if (KPB_IS_BIT_SET(mic_sel->mask, i)) { + if (num_of_sel_mic >= ARRAY_SIZE(kpb->offsets)) { + comp_err(dev, "too many selected mics"); + return -EINVAL; + } kpb->offsets[num_of_sel_mic] = i; num_of_sel_mic++; } From 91e8d78c15d4e5b75873fbe49a5a560b63f67af3 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 11 Jun 2026 18:00:41 +0200 Subject: [PATCH 2/3] audio: kpb: validate FMT module list against payload size The KP_BUF_CFG_FM_MODULE large-config path cast the host payload to struct kpb_task_params and iterated dev_ids[] for number_of_modules entries without checking it against the declared payload length, so a number_of_modules larger than the payload caused out-of-bounds reads. Verify the payload covers the header and all declared dev_ids[] entries before processing the list. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 94cd13b945a6..67313982ed61 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -2688,6 +2688,15 @@ static int kpb_set_large_config(struct comp_dev *dev, uint32_t param_id, const struct kpb_task_params *cfg = (struct kpb_task_params *)data; uint32_t outpin_id = extended_param_id.part.parameter_instance; + /* payload must cover the header and all declared dev_ids[] entries */ + if (!cfg || data_offset < offsetof(struct kpb_task_params, dev_ids)) + return -EINVAL; + + if (cfg->number_of_modules > + (data_offset - offsetof(struct kpb_task_params, dev_ids)) / + sizeof(cfg->dev_ids[0])) + return -EINVAL; + return configure_fast_mode_task(dev, cfg, outpin_id); } #endif From d24e307d35fea637969f64d89b0c88f56d90c310 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Fri, 12 Jun 2026 17:19:06 +0200 Subject: [PATCH 3/3] audio: kpb: clean up FMT module list on prepare failure prepare_fmt_modules_list() populates kpb_list_item[], device_list[] and modules_list_item[] entries as it walks the module list. On any mid-loop failure it returned without undoing those entries, while the caller had already cleared the previous list, leaving a half-configured Fast Mode Task list with stale component references. Roll back the touched entries via clear_fmt_modules_list() on the error path, and add a defensive bound check on outpin_idx. Signed-off-by: Tomasz Leman --- src/audio/kpb.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/audio/kpb.c b/src/audio/kpb.c index 67313982ed61..7dc874bf58d2 100644 --- a/src/audio/kpb.c +++ b/src/audio/kpb.c @@ -2561,32 +2561,42 @@ static int prepare_fmt_modules_list(struct comp_dev *kpb_dev, struct kpb_fmt_dev_list *fmt_device_list = &((struct comp_data *)comp_get_drvdata(kpb_dev))->fmt_device_list; + if (outpin_idx >= KPB_MAX_SINK_CNT) + return -EINVAL; + fmt_device_list->kpb_list_item[outpin_idx] = kpb_dev; ret = devicelist_push(&fmt_device_list->device_list[outpin_idx], &fmt_device_list->kpb_list_item[outpin_idx]); if (ret < 0) - return ret; + goto err; for (size_t mod_idx = 0; mod_idx < modules_to_prepare->number_of_modules; ++mod_idx) { uint32_t comp_id = IPC4_COMP_ID(modules_to_prepare->dev_ids[mod_idx].module_id, modules_to_prepare->dev_ids[mod_idx].instance_id); dev = ipc4_get_comp_dev(comp_id); - if (!dev) - return -EINVAL; + if (!dev) { + ret = -EINVAL; + goto err; + } struct comp_dev **new_list_item_ptr; ret = alloc_fmt_module_list_item(fmt_device_list, dev, &new_list_item_ptr); if (ret < 0) - return ret; + goto err; *new_list_item_ptr = dev; ret = devicelist_push(&fmt_device_list->device_list[outpin_idx], new_list_item_ptr); if (ret < 0) - return ret; + goto err; } return 0; + +err: + /* drop any entries pushed so far to avoid leaving a half-configured list */ + clear_fmt_modules_list(fmt_device_list, outpin_idx); + return ret; } static int clear_fmt_modules_list(struct kpb_fmt_dev_list *fmt_device_list,