ASoC: dpcm: prevent snd_soc_dpcm use after free

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=<> [<ffffff8960e2cd10>] dpcm_be_dai_trigger+0x29c/0x47c
	sound/soc/soc-pcm.c:3226
		if (dpcm->fe == fe)
lr=<> [<ffffff8960e2f694>] dpcm_fe_dai_do_trigger+0x94/0x26c

Backtrace:
[<ffffff89602dba80>] notify_die+0x68/0xb8
[<ffffff896028c7dc>] die+0x118/0x2a8
[<ffffff89602a2f84>] __do_kernel_fault+0x13c/0x14c
[<ffffff89602a27f4>] do_translation_fault+0x64/0xa0
[<ffffff8960280cf8>] do_mem_abort+0x4c/0xd0
[<ffffff8960282ad0>] el1_da+0x24/0x40
[<ffffff8960e2cd10>] dpcm_be_dai_trigger+0x29c/0x47c
[<ffffff8960e2f694>] dpcm_fe_dai_do_trigger+0x94/0x26c
[<ffffff8960e2edec>] dpcm_fe_dai_trigger+0x3c/0x44
[<ffffff8960de5588>] snd_pcm_do_stop+0x50/0x5c
[<ffffff8960dded24>] snd_pcm_action+0xb4/0x13c
[<ffffff8960ddfdb4>] snd_pcm_drop+0xa0/0x128
[<ffffff8960de69bc>] snd_pcm_common_ioctl+0x9d8/0x30f0
[<ffffff8960de1cac>] snd_pcm_ioctl_compat+0x29c/0x2f14
[<ffffff89604c9d60>] compat_SyS_ioctl+0x128/0x244
[<ffffff8960283740>] el0_svc_naked+0x34/0x38
[<ffffffffffffffff>] 0xffffffffffffffff

Signed-off-by: KaiChieh Chuang <kaichieh.chuang@mediatek.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
KaiChieh Chuang 2019-03-07 07:51:09 +08:00 коммит произвёл Mark Brown
Родитель 2944d29d7c
Коммит bbfaa7d36c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 24D68B725D5487D0
3 изменённых файлов: 29 добавлений и 7 удалений

Просмотреть файл

@ -1083,6 +1083,8 @@ struct snd_soc_card {
struct mutex mutex; struct mutex mutex;
struct mutex dapm_mutex; struct mutex dapm_mutex;
spinlock_t dpcm_lock;
bool instantiated; bool instantiated;
bool topology_shortname_created; bool topology_shortname_created;

Просмотреть файл

@ -2819,6 +2819,7 @@ int snd_soc_register_card(struct snd_soc_card *card)
card->instantiated = 0; card->instantiated = 0;
mutex_init(&card->mutex); mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex); mutex_init(&card->dapm_mutex);
spin_lock_init(&card->dpcm_lock);
return snd_soc_bind_card(card); return snd_soc_bind_card(card);
} }

Просмотреть файл

@ -1228,8 +1228,10 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe,
dpcm->fe = fe; dpcm->fe = fe;
be->dpcm[stream].runtime = fe->dpcm[stream].runtime; be->dpcm[stream].runtime = fe->dpcm[stream].runtime;
dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW; 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_be, &fe->dpcm[stream].be_clients);
list_add(&dpcm->list_fe, &be->dpcm[stream].fe_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", dev_dbg(fe->dev, "connected new DPCM %s path %s %s %s\n",
stream ? "capture" : "playback", fe->dai_link->name, stream ? "capture" : "playback", fe->dai_link->name,
@ -1294,8 +1296,10 @@ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream)
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
debugfs_remove(dpcm->debugfs_state); debugfs_remove(dpcm->debugfs_state);
#endif #endif
spin_lock(&fe->card->dpcm_lock);
list_del(&dpcm->list_be); list_del(&dpcm->list_be);
list_del(&dpcm->list_fe); list_del(&dpcm->list_fe);
spin_unlock(&fe->card->dpcm_lock);
kfree(dpcm); kfree(dpcm);
} }
} }
@ -1548,9 +1552,11 @@ void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream)
{ {
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
spin_lock(&fe->card->dpcm_lock);
for_each_dpcm_be(fe, stream, dpcm) for_each_dpcm_be(fe, stream, dpcm)
dpcm->be->dpcm[stream].runtime_update = dpcm->be->dpcm[stream].runtime_update =
SND_SOC_DPCM_UPDATE_NO; SND_SOC_DPCM_UPDATE_NO;
spin_unlock(&fe->card->dpcm_lock);
} }
static void dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe, static void dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe,
@ -2640,11 +2646,13 @@ close:
dpcm_be_dai_shutdown(fe, stream); dpcm_be_dai_shutdown(fe, stream);
disconnect: disconnect:
/* disconnect any non started BEs */ /* disconnect any non started BEs */
spin_lock(&fe->card->dpcm_lock);
for_each_dpcm_be(fe, stream, dpcm) { for_each_dpcm_be(fe, stream, dpcm) {
struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_soc_pcm_runtime *be = dpcm->be;
if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START)
dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE;
} }
spin_unlock(&fe->card->dpcm_lock);
return ret; return ret;
} }
@ -3221,7 +3229,9 @@ int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe,
{ {
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
int state; int state;
int ret = 1;
spin_lock(&fe->card->dpcm_lock);
for_each_dpcm_fe(be, stream, dpcm) { for_each_dpcm_fe(be, stream, dpcm) {
if (dpcm->fe == fe) if (dpcm->fe == fe)
@ -3230,12 +3240,15 @@ int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe,
state = dpcm->fe->dpcm[stream].state; state = dpcm->fe->dpcm[stream].state;
if (state == SND_SOC_DPCM_STATE_START || if (state == SND_SOC_DPCM_STATE_START ||
state == SND_SOC_DPCM_STATE_PAUSED || state == SND_SOC_DPCM_STATE_PAUSED ||
state == SND_SOC_DPCM_STATE_SUSPEND) state == SND_SOC_DPCM_STATE_SUSPEND) {
return 0; ret = 0;
break;
}
} }
spin_unlock(&fe->card->dpcm_lock);
/* it's safe to free/stop this BE DAI */ /* it's safe to free/stop this BE DAI */
return 1; return ret;
} }
EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_free_stop); EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_free_stop);
@ -3248,7 +3261,9 @@ int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe,
{ {
struct snd_soc_dpcm *dpcm; struct snd_soc_dpcm *dpcm;
int state; int state;
int ret = 1;
spin_lock(&fe->card->dpcm_lock);
for_each_dpcm_fe(be, stream, dpcm) { for_each_dpcm_fe(be, stream, dpcm) {
if (dpcm->fe == fe) if (dpcm->fe == fe)
@ -3258,12 +3273,15 @@ int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe,
if (state == SND_SOC_DPCM_STATE_START || if (state == SND_SOC_DPCM_STATE_START ||
state == SND_SOC_DPCM_STATE_PAUSED || state == SND_SOC_DPCM_STATE_PAUSED ||
state == SND_SOC_DPCM_STATE_SUSPEND || state == SND_SOC_DPCM_STATE_SUSPEND ||
state == SND_SOC_DPCM_STATE_PREPARE) state == SND_SOC_DPCM_STATE_PREPARE) {
return 0; ret = 0;
break;
}
} }
spin_unlock(&fe->card->dpcm_lock);
/* it's safe to change hw_params */ /* it's safe to change hw_params */
return 1; return ret;
} }
EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_params); EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_params);
@ -3329,6 +3347,7 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe,
goto out; goto out;
} }
spin_lock(&fe->card->dpcm_lock);
for_each_dpcm_be(fe, stream, dpcm) { for_each_dpcm_be(fe, stream, dpcm) {
struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_soc_pcm_runtime *be = dpcm->be;
params = &dpcm->hw_params; params = &dpcm->hw_params;
@ -3349,7 +3368,7 @@ static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe,
params_channels(params), params_channels(params),
params_rate(params)); params_rate(params));
} }
spin_unlock(&fe->card->dpcm_lock);
out: out:
return offset; return offset;
} }