ASoC: dpcm: prevent snd_soc_dpcm use after free

pick from mr-r-qsm8250:
The dpcm get from fe_clients/be_clients may be free before use
Add a spin lock at snd_soc_card level,to protect the dpcm instance.
The lock may be used in atomic context, so use spin lock.

possible race condition between
void dpcm_be_disconnect(
	...
	list_del(&dpcm->list_be);
	list_del(&dpcm->list_fe);
	kfree(dpcm);
	...

and
	for_each_dpcm_fe()
	for_each_dpcm_be*()

race condition example
Thread 1:
    snd_soc_dapm_mixer_update_power()
        -> soc_dpcm_runtime_update()
            -> dpcm_be_disconnect()
                -> kfree(dpcm);
Thread 2:
    dpcm_fe_dai_trigger()
        -> dpcm_be_dai_trigger()
            -> snd_soc_dpcm_can_be_free_stop()
                -> if (dpcm->fe == fe)

Excpetion Scenario:
	two FE link to same BE
	FE1 -> BE
	FE2 ->

	Thread 1: switch of mixer between FE2 -> BE
	Thread 2: pcm_stop FE1

Exception:

Unable to handle kernel paGing request at virtual address dead0000000000e0

pc=<> [<ffffff9fdb095a98>] dpcm_be_dai_trigger+0x1a0/0x360
	sound/soc/soc-pcm.c:3226
		if (dpcm->fe == fe)
lr=<> [<ffffff9fdb098a54>] dpcm_fe_dai_do_trigger+0x184/0x258

Backtrace:
dpcm_be_dai_trigger+0x1a0/0x360
dpcm_fe_dai_do_trigger+0x184/0x258
dpcm_fe_dai_trigger+0x40/0x48
snd_pcm_do_stop+0x48/0x58
snd_pcm_action+0xb0/0x140
snd_pcm_release_substream+0xac/0x198
snd_pcm_release+0x3c/0x98
__fput+0xbc/0x1b8
____fput+0xc/0x18
task_work_run+0x8c/0xb0
do_notify_resume+0x410/0x2068
work_pending+0x8/0x10

Signed-off-by: KaiChieh Chuang <kaichieh.chuang@mediatek.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
Git-commit: bbfaa7d36c1eb465f120f2a3dfe25c1fe022195d
Git-repo: https://android.googlesource.com/kernel/common/
Signed-off-by: Soumya Managoli <smanag@codeaurora.org>

Change-Id: I8974d25435d6faf54117ca209c9a00e3515fc314
Signed-off-by: zhangh12 <zhangh12@motorola.com>
Reviewed-on: https://gerrit.mot.com/2062908
SLTApproved: Slta Waiver
SME-Granted: SME Approvals Granted
Tested-by: Jira Key
Reviewed-by: Xiangpo Zhao <zhaoxp3@motorola.com>
Submit-Approved: Jira Key
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
This commit is contained in:
zhangh12
2021-09-09 11:36:31 +08:00
committed by Pranav Vashi
parent 20cd543c9d
commit d7a0e16754
3 changed files with 29 additions and 7 deletions

View File

@@ -1171,6 +1171,8 @@ struct snd_soc_card {
struct mutex dapm_mutex;
struct mutex dapm_power_mutex;
spinlock_t dpcm_lock;
bool instantiated;
int (*probe)(struct snd_soc_card *card);

View File

@@ -3023,6 +3023,7 @@ int snd_soc_register_card(struct snd_soc_card *card)
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
mutex_init(&card->dapm_power_mutex);
spin_lock_init(&card->dpcm_lock);
ret = snd_soc_instantiate_card(card);
if (ret != 0)

View File

@@ -1254,8 +1254,10 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe,
dpcm->fe = fe;
be->dpcm[stream].runtime = fe->dpcm[stream].runtime;
dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW;
spin_lock(&fe->card->dpcm_lock);
list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients);
list_add(&dpcm->list_fe, &be->dpcm[stream].fe_clients);
spin_unlock(&fe->card->dpcm_lock);
dev_dbg(fe->dev, "connected new DPCM %s path %s %s %s\n",
stream ? "capture" : "playback", fe->dai_link->name,
@@ -1322,8 +1324,10 @@ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream)
#ifdef CONFIG_DEBUG_FS
debugfs_remove(dpcm->debugfs_state);
#endif
spin_lock(&fe->card->dpcm_lock);
list_del(&dpcm->list_be);
list_del(&dpcm->list_fe);
spin_unlock(&fe->card->dpcm_lock);
kfree(dpcm);
}
}
@@ -1575,9 +1579,11 @@ void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream)
{
struct snd_soc_dpcm *dpcm;
spin_lock(&fe->card->dpcm_lock);
list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be)
dpcm->be->dpcm[stream].runtime_update =
SND_SOC_DPCM_UPDATE_NO;
spin_unlock(&fe->card->dpcm_lock);
}
static void dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe,
@@ -2818,11 +2824,13 @@ close:
dpcm_be_dai_shutdown(fe, stream);
disconnect:
/* disconnect any non started BEs */
spin_lock(&fe->card->dpcm_lock);
list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
struct snd_soc_pcm_runtime *be = dpcm->be;
if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START)
dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE;
}
spin_unlock(&fe->card->dpcm_lock);
return ret;
}
@@ -3252,7 +3260,9 @@ int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe,
{
struct snd_soc_dpcm *dpcm;
int state;
int ret = 1;
spin_lock(&fe->card->dpcm_lock);
list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) {
if (dpcm->fe == fe)
@@ -3261,12 +3271,15 @@ int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe,
state = dpcm->fe->dpcm[stream].state;
if (state == SND_SOC_DPCM_STATE_START ||
state == SND_SOC_DPCM_STATE_PAUSED ||
state == SND_SOC_DPCM_STATE_SUSPEND)
return 0;
state == SND_SOC_DPCM_STATE_SUSPEND) {
ret = 0;
break;
}
}
spin_unlock(&fe->card->dpcm_lock);
/* it's safe to free/stop this BE DAI */
return 1;
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_free_stop);
@@ -3279,7 +3292,9 @@ int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe,
{
struct snd_soc_dpcm *dpcm;
int state;
int ret = 1;
spin_lock(&fe->card->dpcm_lock);
list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) {
if (dpcm->fe == fe)
@@ -3289,12 +3304,15 @@ int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe,
if (state == SND_SOC_DPCM_STATE_START ||
state == SND_SOC_DPCM_STATE_PAUSED ||
state == SND_SOC_DPCM_STATE_SUSPEND ||
state == SND_SOC_DPCM_STATE_PREPARE)
return 0;
state == SND_SOC_DPCM_STATE_PREPARE) {
ret = 0;
break;
}
}
spin_unlock(&fe->card->dpcm_lock);
/* it's safe to change hw_params */
return 1;
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_params);
@@ -3360,6 +3378,7 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe,
goto out;
}
spin_lock(&fe->card->dpcm_lock);
list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
struct snd_soc_pcm_runtime *be = dpcm->be;
params = &dpcm->hw_params;
@@ -3380,7 +3399,7 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe,
params_channels(params),
params_rate(params));
}
spin_unlock(&fe->card->dpcm_lock);
out:
return offset;
}