[PATCH 0/5] ASoC: topology: fix error handling flow
While experimenting and introducing errors in Baytrail topology files until I got them right, I encountered multiple kernel oopses and memory leaks. This is a first batch to harden the code, but we should probably think of a tool to fuzz the topology...
Pierre-Louis Bossart (5): ASoC: topology: fix kernel oops on route addition error ASoC: topology: fix tlvs in error handling for widget_dmixer ASoC: topology: use break on errors, not continue ASoC: topology: factor kfree(se) in error handling ASoC: topology: add more logs when topology load fails.
sound/soc/soc-topology.c | 97 ++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 39 deletions(-)
base-commit: a5911ac5790acaf98c929b826b3f7b4a438f9759
When errors happens while loading graph components, the kernel oopses while trying to remove all topology components. This can be root-caused to a list pointing to memory that was already freed on error.
remove_route() is already called on errors and will perform the required cleanups so there's no need to free the route memory in soc_tplg_dapm_graph_elems_load() if the route was added to the list. We do however want to free the routes allocated but not added to the list.
Fixes: 7df04ea7a31ea ('ASoC: topology: modify dapm route loading routine and add dapm route unloading') Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com --- sound/soc/soc-topology.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 43e5745b06aa..f336a9cfc16f 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1261,17 +1261,29 @@ static int soc_tplg_dapm_graph_elems_load(struct soc_tplg *tplg, list_add(&routes[i]->dobj.list, &tplg->comp->dobj_list);
ret = soc_tplg_add_route(tplg, routes[i]); - if (ret < 0) + if (ret < 0) { + /* + * this route was added to the list, it will + * be freed in remove_route() so increment the + * counter to skip it in the error handling + * below. + */ + i++; break; + }
/* add route, but keep going if some fail */ snd_soc_dapm_add_routes(dapm, routes[i], 1); }
- /* free memory allocated for all dapm routes in case of error */ - if (ret < 0) - for (i = 0; i < count ; i++) - kfree(routes[i]); + /* + * free memory allocated for all dapm routes not added to the + * list in case of error + */ + if (ret < 0) { + while (i < count) + kfree(routes[i++]); + }
/* * free pointer to array of dapm routes as this is no longer needed.
we need to free all allocated tlvs, not just the one allocated in the loop before releasing kcontrols - other the tlvs references will leak.
Fixes: 9f90af3a995298 ('ASoC: topology: Consolidate and fix asoc_tplg_dapm_widget_*_create flow') Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com --- sound/soc/soc-topology.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index f336a9cfc16f..6eaa00c21011 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1371,7 +1371,6 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_dmixer_create( if (err < 0) { dev_err(tplg->dev, "ASoC: failed to init %s\n", mc->hdr.name); - soc_tplg_free_tlv(tplg, &kc[i]); goto err_sm; } } @@ -1379,6 +1378,7 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_dmixer_create(
err_sm: for (; i >= 0; i--) { + soc_tplg_free_tlv(tplg, &kc[i]); sm = (struct soc_mixer_control *)kc[i].private_value; kfree(sm); kfree(kc[i].name);
Since the beginning of the topology, the code continues to the next object even when an error is detected.
The topology should be handled with an all-or-nothing design, loading a partially valid topology is a sure way to get bug reports that are difficult to deal with.
Changing the behavior may break previous solutions and expose problems in topology files delivered in the past, so it's probably not wise to add this patch to stable branches without revalidation.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com --- sound/soc/soc-topology.c | 53 +++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 25 deletions(-)
diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 6eaa00c21011..d42f73f7038f 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -741,7 +741,8 @@ static int soc_tplg_dbytes_create(struct soc_tplg *tplg, unsigned int count, struct snd_soc_tplg_bytes_control *be; struct soc_bytes_ext *sbe; struct snd_kcontrol_new kc; - int i, err; + int i; + int err = 0;
if (soc_tplg_check_elem_count(tplg, sizeof(struct snd_soc_tplg_bytes_control), count, @@ -786,7 +787,7 @@ static int soc_tplg_dbytes_create(struct soc_tplg *tplg, unsigned int count, if (err) { soc_control_err(tplg, &be->hdr, be->hdr.name); kfree(sbe); - continue; + break; }
/* pass control to driver for optional further init */ @@ -796,7 +797,7 @@ static int soc_tplg_dbytes_create(struct soc_tplg *tplg, unsigned int count, dev_err(tplg->dev, "ASoC: failed to init %s\n", be->hdr.name); kfree(sbe); - continue; + break; }
/* register control here */ @@ -806,12 +807,12 @@ static int soc_tplg_dbytes_create(struct soc_tplg *tplg, unsigned int count, dev_err(tplg->dev, "ASoC: failed to add %s\n", be->hdr.name); kfree(sbe); - continue; + break; }
list_add(&sbe->dobj.list, &tplg->comp->dobj_list); } - return 0; + return err;
}
@@ -821,7 +822,8 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, struct snd_soc_tplg_mixer_control *mc; struct soc_mixer_control *sm; struct snd_kcontrol_new kc; - int i, err; + int i; + int err = 0;
if (soc_tplg_check_elem_count(tplg, sizeof(struct snd_soc_tplg_mixer_control), @@ -880,7 +882,7 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, if (err) { soc_control_err(tplg, &mc->hdr, mc->hdr.name); kfree(sm); - continue; + break; }
/* create any TLV data */ @@ -889,7 +891,7 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, dev_err(tplg->dev, "ASoC: failed to create TLV %s\n", mc->hdr.name); kfree(sm); - continue; + break; }
/* pass control to driver for optional further init */ @@ -900,7 +902,7 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, mc->hdr.name); soc_tplg_free_tlv(tplg, &kc); kfree(sm); - continue; + break; }
/* register control here */ @@ -911,13 +913,13 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, mc->hdr.name); soc_tplg_free_tlv(tplg, &kc); kfree(sm); - continue; + break; }
list_add(&sm->dobj.list, &tplg->comp->dobj_list); }
- return 0; + return err; }
static int soc_tplg_denum_create_texts(struct soc_enum *se, @@ -997,7 +999,8 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, struct snd_soc_tplg_enum_control *ec; struct soc_enum *se; struct snd_kcontrol_new kc; - int i, ret, err; + int i; + int err = 0;
if (soc_tplg_check_elem_count(tplg, sizeof(struct snd_soc_tplg_enum_control), @@ -1053,7 +1056,7 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, "ASoC: could not create values for %s\n", ec->hdr.name); kfree(se); - continue; + goto err_denum; } /* fall through */ case SND_SOC_TPLG_CTL_ENUM: @@ -1065,15 +1068,16 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, "ASoC: could not create texts for %s\n", ec->hdr.name); kfree(se); - continue; + goto err_denum; } break; default: + err = -EINVAL; dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n", ec->hdr.ops.info, ec->hdr.name); kfree(se); - continue; + goto err_denum; }
/* map io handlers */ @@ -1081,7 +1085,7 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, if (err) { soc_control_err(tplg, &ec->hdr, ec->hdr.name); kfree(se); - continue; + goto err_denum; }
/* pass control to driver for optional further init */ @@ -1091,23 +1095,23 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, dev_err(tplg->dev, "ASoC: failed to init %s\n", ec->hdr.name); kfree(se); - continue; + goto err_denum; }
/* register control here */ - ret = soc_tplg_add_kcontrol(tplg, - &kc, &se->dobj.control.kcontrol); - if (ret < 0) { + err = soc_tplg_add_kcontrol(tplg, + &kc, &se->dobj.control.kcontrol); + if (err < 0) { dev_err(tplg->dev, "ASoC: could not add kcontrol %s\n", ec->hdr.name); kfree(se); - continue; + goto err_denum; }
list_add(&se->dobj.list, &tplg->comp->dobj_list); } - - return 0; +err_denum: + return err; }
static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, @@ -1361,8 +1365,7 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_dmixer_create( if (err < 0) { dev_err(tplg->dev, "ASoC: failed to create TLV %s\n", mc->hdr.name); - kfree(sm); - continue; + goto err_sm; }
/* pass control to driver for optional further init */
No need to repeat the same thing multiple times when it can be done in one location.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com --- sound/soc/soc-topology.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index d42f73f7038f..4004fc882912 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1055,7 +1055,6 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, dev_err(tplg->dev, "ASoC: could not create values for %s\n", ec->hdr.name); - kfree(se); goto err_denum; } /* fall through */ @@ -1067,7 +1066,6 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, dev_err(tplg->dev, "ASoC: could not create texts for %s\n", ec->hdr.name); - kfree(se); goto err_denum; } break; @@ -1076,7 +1074,6 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n", ec->hdr.ops.info, ec->hdr.name); - kfree(se); goto err_denum; }
@@ -1084,7 +1081,6 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, err = soc_tplg_kcontrol_bind_io(&ec->hdr, &kc, tplg); if (err) { soc_control_err(tplg, &ec->hdr, ec->hdr.name); - kfree(se); goto err_denum; }
@@ -1094,7 +1090,6 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, if (err < 0) { dev_err(tplg->dev, "ASoC: failed to init %s\n", ec->hdr.name); - kfree(se); goto err_denum; }
@@ -1104,13 +1099,15 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, if (err < 0) { dev_err(tplg->dev, "ASoC: could not add kcontrol %s\n", ec->hdr.name); - kfree(se); goto err_denum; }
list_add(&se->dobj.list, &tplg->comp->dobj_list); } + return 0; + err_denum: + kfree(se); return err; }
Add more dev_err() logs to help trace topology load failures, since we have multiple error causes (e.g. invalid header or header that could not be loaded).
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Reviewed-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Reviewed-by: Rander Wang rander.wang@linux.intel.com --- sound/soc/soc-topology.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 4004fc882912..cee998671318 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1263,6 +1263,7 @@ static int soc_tplg_dapm_graph_elems_load(struct soc_tplg *tplg,
ret = soc_tplg_add_route(tplg, routes[i]); if (ret < 0) { + dev_err(tplg->dev, "ASoC: topology: add_route failed: %d\n", ret); /* * this route was added to the list, it will * be freed in remove_route() so increment the @@ -2743,15 +2744,21 @@ static int soc_tplg_process_headers(struct soc_tplg *tplg)
/* make sure header is valid before loading */ ret = soc_valid_header(tplg, hdr); - if (ret < 0) + if (ret < 0) { + dev_err(tplg->dev, + "ASoC: topology: invalid header: %d\n", ret); return ret; - else if (ret == 0) + } else if (ret == 0) { break; + }
/* load the header object */ ret = soc_tplg_load_header(tplg, hdr); - if (ret < 0) + if (ret < 0) { + dev_err(tplg->dev, + "ASoC: topology: could not load header: %d\n", ret); return ret; + }
/* goto next header */ tplg->hdr_pos += le32_to_cpu(hdr->payload_size) +
On Tue, 7 Jul 2020 15:37:44 -0500, Pierre-Louis Bossart wrote:
While experimenting and introducing errors in Baytrail topology files until I got them right, I encountered multiple kernel oopses and memory leaks. This is a first batch to harden the code, but we should probably think of a tool to fuzz the topology...
Pierre-Louis Bossart (5): ASoC: topology: fix kernel oops on route addition error ASoC: topology: fix tlvs in error handling for widget_dmixer ASoC: topology: use break on errors, not continue ASoC: topology: factor kfree(se) in error handling ASoC: topology: add more logs when topology load fails.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[1/5] ASoC: topology: fix kernel oops on route addition error commit: 6f0307df83f2aa6bdf656c2219c89ce96502d20e [2/5] ASoC: topology: fix tlvs in error handling for widget_dmixer commit: 8edac489e7c3fce44208373bb3e7b5835a672c66 [3/5] ASoC: topology: use break on errors, not continue commit: 129fc2ba01c8322575173cc97afa324e54a5d4ce [4/5] ASoC: topology: factor kfree(se) in error handling commit: 952bd9377ef61c843236fd3e7cf65076025e26a4 [5/5] ASoC: topology: add more logs when topology load fails. commit: 8bf9475fe49fa96eea57724bb04e0931d101bc5a
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (2)
-
Mark Brown
-
Pierre-Louis Bossart