Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fbeb4926 authored by Takashi Iwai's avatar Takashi Iwai Committed by Greg Kroah-Hartman
Browse files

ALSA: pcm: Fix races among concurrent hw_params and hw_free calls



commit 92ee3c60ec9fe64404dc035e7c41277d74aa26cb upstream.

Currently we have neither proper check nor protection against the
concurrent calls of PCM hw_params and hw_free ioctls, which may result
in a UAF.  Since the existing PCM stream lock can't be used for
protecting the whole ioctl operations, we need a new mutex to protect
those racy calls.

This patch introduced a new mutex, runtime->buffer_mutex, and applies
it to both hw_params and hw_free ioctl code paths.  Along with it, the
both functions are slightly modified (the mmap_count check is moved
into the state-check block) for code simplicity.

Reported-by: default avatarHu Jiahui <kirin.say@gmail.com>
Cc: <stable@vger.kernel.org>
Reviewed-by: default avatarJaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20220322170720.3529-2-tiwai@suse.de


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
[OP: backport to 5.4: adjusted context]
Signed-off-by: default avatarOvidiu Panait <ovidiu.panait@windriver.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f098f8b9
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -395,6 +395,7 @@ struct snd_pcm_runtime {
	wait_queue_head_t sleep;	/* poll sleep */
	wait_queue_head_t sleep;	/* poll sleep */
	wait_queue_head_t tsleep;	/* transfer sleep */
	wait_queue_head_t tsleep;	/* transfer sleep */
	struct fasync_struct *fasync;
	struct fasync_struct *fasync;
	struct mutex buffer_mutex;	/* protect for buffer changes */


	/* -- private section -- */
	/* -- private section -- */
	void *private_data;
	void *private_data;
+2 −0
Original line number Original line Diff line number Diff line
@@ -969,6 +969,7 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
	init_waitqueue_head(&runtime->tsleep);
	init_waitqueue_head(&runtime->tsleep);


	runtime->status->state = SNDRV_PCM_STATE_OPEN;
	runtime->status->state = SNDRV_PCM_STATE_OPEN;
	mutex_init(&runtime->buffer_mutex);


	substream->runtime = runtime;
	substream->runtime = runtime;
	substream->private_data = pcm->private_data;
	substream->private_data = pcm->private_data;
@@ -1000,6 +1001,7 @@ void snd_pcm_detach_substream(struct snd_pcm_substream *substream)
	substream->runtime = NULL;
	substream->runtime = NULL;
	if (substream->timer)
	if (substream->timer)
		spin_unlock_irq(&substream->timer->lock);
		spin_unlock_irq(&substream->timer->lock);
	mutex_destroy(&runtime->buffer_mutex);
	kfree(runtime);
	kfree(runtime);
	put_pid(substream->pid);
	put_pid(substream->pid);
	substream->pid = NULL;
	substream->pid = NULL;
+36 −19
Original line number Original line Diff line number Diff line
@@ -630,33 +630,40 @@ static int snd_pcm_hw_params_choose(struct snd_pcm_substream *pcm,
	return 0;
	return 0;
}
}


#if IS_ENABLED(CONFIG_SND_PCM_OSS)
#define is_oss_stream(substream)	((substream)->oss.oss)
#else
#define is_oss_stream(substream)	false
#endif

static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *params)
			     struct snd_pcm_hw_params *params)
{
{
	struct snd_pcm_runtime *runtime;
	struct snd_pcm_runtime *runtime;
	int err, usecs;
	int err = 0, usecs;
	unsigned int bits;
	unsigned int bits;
	snd_pcm_uframes_t frames;
	snd_pcm_uframes_t frames;


	if (PCM_RUNTIME_CHECK(substream))
	if (PCM_RUNTIME_CHECK(substream))
		return -ENXIO;
		return -ENXIO;
	runtime = substream->runtime;
	runtime = substream->runtime;
	mutex_lock(&runtime->buffer_mutex);
	snd_pcm_stream_lock_irq(substream);
	snd_pcm_stream_lock_irq(substream);
	switch (runtime->status->state) {
	switch (runtime->status->state) {
	case SNDRV_PCM_STATE_OPEN:
	case SNDRV_PCM_STATE_OPEN:
	case SNDRV_PCM_STATE_SETUP:
	case SNDRV_PCM_STATE_SETUP:
	case SNDRV_PCM_STATE_PREPARED:
	case SNDRV_PCM_STATE_PREPARED:
		if (!is_oss_stream(substream) &&
		    atomic_read(&substream->mmap_count))
			err = -EBADFD;
		break;
		break;
	default:
	default:
		snd_pcm_stream_unlock_irq(substream);
		err = -EBADFD;
		return -EBADFD;
		break;
	}
	}
	snd_pcm_stream_unlock_irq(substream);
	snd_pcm_stream_unlock_irq(substream);
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
	if (err)
	if (!substream->oss.oss)
		goto unlock;
#endif
		if (atomic_read(&substream->mmap_count))
			return -EBADFD;


	params->rmask = ~0U;
	params->rmask = ~0U;
	err = snd_pcm_hw_refine(substream, params);
	err = snd_pcm_hw_refine(substream, params);
@@ -733,14 +740,19 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
	if ((usecs = period_to_usecs(runtime)) >= 0)
	if ((usecs = period_to_usecs(runtime)) >= 0)
		pm_qos_add_request(&substream->latency_pm_qos_req,
		pm_qos_add_request(&substream->latency_pm_qos_req,
				   PM_QOS_CPU_DMA_LATENCY, usecs);
				   PM_QOS_CPU_DMA_LATENCY, usecs);
	return 0;
	err = 0;
 _error:
 _error:
	if (err) {
		/* hardware might be unusable from this time,
		/* hardware might be unusable from this time,
	   so we force application to retry to set
		 * so we force application to retry to set
	   the correct hardware parameter settings */
		 * the correct hardware parameter settings
		 */
		snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
		snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
		if (substream->ops->hw_free != NULL)
		if (substream->ops->hw_free != NULL)
			substream->ops->hw_free(substream);
			substream->ops->hw_free(substream);
	}
 unlock:
	mutex_unlock(&runtime->buffer_mutex);
	return err;
	return err;
}
}


@@ -773,22 +785,27 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
	if (PCM_RUNTIME_CHECK(substream))
	if (PCM_RUNTIME_CHECK(substream))
		return -ENXIO;
		return -ENXIO;
	runtime = substream->runtime;
	runtime = substream->runtime;
	mutex_lock(&runtime->buffer_mutex);
	snd_pcm_stream_lock_irq(substream);
	snd_pcm_stream_lock_irq(substream);
	switch (runtime->status->state) {
	switch (runtime->status->state) {
	case SNDRV_PCM_STATE_SETUP:
	case SNDRV_PCM_STATE_SETUP:
	case SNDRV_PCM_STATE_PREPARED:
	case SNDRV_PCM_STATE_PREPARED:
		if (atomic_read(&substream->mmap_count))
			result = -EBADFD;
		break;
		break;
	default:
	default:
		snd_pcm_stream_unlock_irq(substream);
		result = -EBADFD;
		return -EBADFD;
		break;
	}
	}
	snd_pcm_stream_unlock_irq(substream);
	snd_pcm_stream_unlock_irq(substream);
	if (atomic_read(&substream->mmap_count))
	if (result)
		return -EBADFD;
		goto unlock;
	if (substream->ops->hw_free)
	if (substream->ops->hw_free)
		result = substream->ops->hw_free(substream);
		result = substream->ops->hw_free(substream);
	snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
	snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
	pm_qos_remove_request(&substream->latency_pm_qos_req);
	pm_qos_remove_request(&substream->latency_pm_qos_req);
 unlock:
	mutex_unlock(&runtime->buffer_mutex);
	return result;
	return result;
}
}