Skip to content

Commit bc55cfd

Browse files
committed
ALSA: pcm: Fix potential AB/BA lock with buffer_mutex and mmap_lock
syzbot caught a potential deadlock between the PCM runtime->buffer_mutex and the mm->mmap_lock. It was brought by the recent fix to cover the racy read/write and other ioctls, and in that commit, I overlooked a (hopefully only) corner case that may take the revert lock, namely, the OSS mmap. The OSS mmap operation exceptionally allows to re-configure the parameters inside the OSS mmap syscall, where mm->mmap_mutex is already held. Meanwhile, the copy_from/to_user calls at read/write operations also take the mm->mmap_lock internally, hence it may lead to a AB/BA deadlock. A similar problem was already seen in the past and we fixed it with a refcount (in commit b248371). The former fix covered only the call paths with OSS read/write and OSS ioctls, while we need to cover the concurrent access via both ALSA and OSS APIs now. This patch addresses the problem above by replacing the buffer_mutex lock in the read/write operations with a refcount similar as we've used for OSS. The new field, runtime->buffer_accessing, keeps the number of concurrent read/write operations. Unlike the former buffer_mutex protection, this protects only around the copy_from/to_user() calls; the other codes are basically protected by the PCM stream lock. The refcount can be a negative, meaning blocked by the ioctls. If a negative value is seen, the read/write aborts with -EBUSY. In the ioctl side, OTOH, they check this refcount, too, and set to a negative value for blocking unless it's already being accessed. Reported-by: [email protected] Fixes: dca947d ("ALSA: pcm: Fix races among concurrent read/write and buffer changes") Cc: <[email protected]> Link: https://lore.kernel.org/r/[email protected] Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Takashi Iwai <[email protected]>
1 parent 21b5954 commit bc55cfd

File tree

4 files changed

+39
-11
lines changed

4 files changed

+39
-11
lines changed

include/sound/pcm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ struct snd_pcm_runtime {
402402
struct fasync_struct *fasync;
403403
bool stop_operating; /* sync_stop will be called */
404404
struct mutex buffer_mutex; /* protect for buffer changes */
405+
atomic_t buffer_accessing; /* >0: in r/w operation, <0: blocked */
405406

406407
/* -- private section -- */
407408
void *private_data;

sound/core/pcm.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,7 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
970970

971971
runtime->status->state = SNDRV_PCM_STATE_OPEN;
972972
mutex_init(&runtime->buffer_mutex);
973+
atomic_set(&runtime->buffer_accessing, 0);
973974

974975
substream->runtime = runtime;
975976
substream->private_data = pcm->private_data;

sound/core/pcm_lib.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,11 +1906,9 @@ static int wait_for_avail(struct snd_pcm_substream *substream,
19061906
if (avail >= runtime->twake)
19071907
break;
19081908
snd_pcm_stream_unlock_irq(substream);
1909-
mutex_unlock(&runtime->buffer_mutex);
19101909

19111910
tout = schedule_timeout(wait_time);
19121911

1913-
mutex_lock(&runtime->buffer_mutex);
19141912
snd_pcm_stream_lock_irq(substream);
19151913
set_current_state(TASK_INTERRUPTIBLE);
19161914
switch (runtime->status->state) {
@@ -2221,7 +2219,6 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
22212219

22222220
nonblock = !!(substream->f_flags & O_NONBLOCK);
22232221

2224-
mutex_lock(&runtime->buffer_mutex);
22252222
snd_pcm_stream_lock_irq(substream);
22262223
err = pcm_accessible_state(runtime);
22272224
if (err < 0)
@@ -2276,6 +2273,10 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
22762273
err = -EINVAL;
22772274
goto _end_unlock;
22782275
}
2276+
if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) {
2277+
err = -EBUSY;
2278+
goto _end_unlock;
2279+
}
22792280
snd_pcm_stream_unlock_irq(substream);
22802281
if (!is_playback)
22812282
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU);
@@ -2284,6 +2285,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
22842285
if (is_playback)
22852286
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
22862287
snd_pcm_stream_lock_irq(substream);
2288+
atomic_dec(&runtime->buffer_accessing);
22872289
if (err < 0)
22882290
goto _end_unlock;
22892291
err = pcm_accessible_state(runtime);
@@ -2313,7 +2315,6 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
23132315
if (xfer > 0 && err >= 0)
23142316
snd_pcm_update_state(substream, runtime);
23152317
snd_pcm_stream_unlock_irq(substream);
2316-
mutex_unlock(&runtime->buffer_mutex);
23172318
return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
23182319
}
23192320
EXPORT_SYMBOL(__snd_pcm_lib_xfer);

sound/core/pcm_native.c

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,24 @@ static int snd_pcm_hw_params_choose(struct snd_pcm_substream *pcm,
685685
return 0;
686686
}
687687

688+
/* acquire buffer_mutex; if it's in r/w operation, return -EBUSY, otherwise
689+
* block the further r/w operations
690+
*/
691+
static int snd_pcm_buffer_access_lock(struct snd_pcm_runtime *runtime)
692+
{
693+
if (!atomic_dec_unless_positive(&runtime->buffer_accessing))
694+
return -EBUSY;
695+
mutex_lock(&runtime->buffer_mutex);
696+
return 0; /* keep buffer_mutex, unlocked by below */
697+
}
698+
699+
/* release buffer_mutex and clear r/w access flag */
700+
static void snd_pcm_buffer_access_unlock(struct snd_pcm_runtime *runtime)
701+
{
702+
mutex_unlock(&runtime->buffer_mutex);
703+
atomic_inc(&runtime->buffer_accessing);
704+
}
705+
688706
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
689707
#define is_oss_stream(substream) ((substream)->oss.oss)
690708
#else
@@ -695,14 +713,16 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
695713
struct snd_pcm_hw_params *params)
696714
{
697715
struct snd_pcm_runtime *runtime;
698-
int err = 0, usecs;
716+
int err, usecs;
699717
unsigned int bits;
700718
snd_pcm_uframes_t frames;
701719

702720
if (PCM_RUNTIME_CHECK(substream))
703721
return -ENXIO;
704722
runtime = substream->runtime;
705-
mutex_lock(&runtime->buffer_mutex);
723+
err = snd_pcm_buffer_access_lock(runtime);
724+
if (err < 0)
725+
return err;
706726
snd_pcm_stream_lock_irq(substream);
707727
switch (runtime->status->state) {
708728
case SNDRV_PCM_STATE_OPEN:
@@ -820,7 +840,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
820840
snd_pcm_lib_free_pages(substream);
821841
}
822842
unlock:
823-
mutex_unlock(&runtime->buffer_mutex);
843+
snd_pcm_buffer_access_unlock(runtime);
824844
return err;
825845
}
826846

@@ -865,7 +885,9 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
865885
if (PCM_RUNTIME_CHECK(substream))
866886
return -ENXIO;
867887
runtime = substream->runtime;
868-
mutex_lock(&runtime->buffer_mutex);
888+
result = snd_pcm_buffer_access_lock(runtime);
889+
if (result < 0)
890+
return result;
869891
snd_pcm_stream_lock_irq(substream);
870892
switch (runtime->status->state) {
871893
case SNDRV_PCM_STATE_SETUP:
@@ -884,7 +906,7 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
884906
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
885907
cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
886908
unlock:
887-
mutex_unlock(&runtime->buffer_mutex);
909+
snd_pcm_buffer_access_unlock(runtime);
888910
return result;
889911
}
890912

@@ -1369,12 +1391,15 @@ static int snd_pcm_action_nonatomic(const struct action_ops *ops,
13691391

13701392
/* Guarantee the group members won't change during non-atomic action */
13711393
down_read(&snd_pcm_link_rwsem);
1372-
mutex_lock(&substream->runtime->buffer_mutex);
1394+
res = snd_pcm_buffer_access_lock(substream->runtime);
1395+
if (res < 0)
1396+
goto unlock;
13731397
if (snd_pcm_stream_linked(substream))
13741398
res = snd_pcm_action_group(ops, substream, state, false);
13751399
else
13761400
res = snd_pcm_action_single(ops, substream, state);
1377-
mutex_unlock(&substream->runtime->buffer_mutex);
1401+
snd_pcm_buffer_access_unlock(substream->runtime);
1402+
unlock:
13781403
up_read(&snd_pcm_link_rwsem);
13791404
return res;
13801405
}

0 commit comments

Comments
 (0)