diff mbox

ALSA: pcm: Fix possible inconsistent appl_ptr update via mmap

Message ID 20170616204055.31036-1-tiwai@suse.de (mailing list archive)
State New, archived
Headers show

Commit Message

Takashi Iwai June 16, 2017, 8:40 p.m. UTC
The ALSA PCM core refers to the appl_ptr value stored on the mmapped
page that is shared between kernel and user-space.  Although the
reference is performed in the PCM stream lock, it doesn't guarantee
the atomic access when the value gets updated concurrently from the
user-space on another CPU.

In most of codes, this is no big problem, but still there are a few
places that may result in slight inconsistencies because they access
runtime->control->appl_ptr multiple times; that is, the second read
might be a different value from the first value.  It can be even
backward or jumping, as we have no control for it.  Hence, the
calculation may give an unexpected value.  Luckily, there is no
security vulnerability by that, as far as I've checked.  But still we
should address it.

This patch tries to reduce such possible cases.  The fix is simple --
we just read once, store it to a local variable and use it for the
rest calculations.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 sound/core/pcm_lib.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

Comments

Takashi Sakamoto June 18, 2017, 10:49 a.m. UTC | #1
Hi,

On Jun 17 2017 05:40, Takashi Iwai wrote:
> The ALSA PCM core refers to the appl_ptr value stored on the mmapped
> page that is shared between kernel and user-space.  Although the
> reference is performed in the PCM stream lock, it doesn't guarantee
> the atomic access when the value gets updated concurrently from the
> user-space on another CPU.
> 
> In most of codes, this is no big problem, but still there are a few
> places that may result in slight inconsistencies because they access
> runtime->control->appl_ptr multiple times; that is, the second read
> might be a different value from the first value.  It can be even
> backward or jumping, as we have no control for it.  Hence, the
> calculation may give an unexpected value.  Luckily, there is no
> security vulnerability by that, as far as I've checked.  But still we
> should address it.
> 
> This patch tries to reduce such possible cases.  The fix is simple --
> we just read once, store it to a local variable and use it for the
> rest calculations.
> 
> Signed-off-by: Takashi Iwai <tiwai@suse.de>
> ---
>   sound/core/pcm_lib.c | 13 +++++++------
>   1 file changed, 7 insertions(+), 6 deletions(-)

This looks good to me by itself.

Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>

This function, 'snd_pcm_playback_silence()' can be called without 
acquiring stream lock by 'snd_pcm_reset()' when runtime of PCM substream 
is configured to have a larger value than zero to its 'silence_size' 
member. When a task or any IRQ handler operates the runtime and another 
task operates the reset in the same time, both of them got expected result.

I'll post a patch to take 'snd_pcm_reset()' to use 
'snd_pcm_action_lock_irq()' instead of 'snd_pcm_action_nonatomic()'. How 
do you think about it?

> diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
> index 631cd598ba6c..cda258d91490 100644
> --- a/sound/core/pcm_lib.c
> +++ b/sound/core/pcm_lib.c
> @@ -65,15 +65,16 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram
>   
>   	if (runtime->silence_size < runtime->boundary) {
>   		snd_pcm_sframes_t noise_dist, n;
> -		if (runtime->silence_start != runtime->control->appl_ptr) {
> -			n = runtime->control->appl_ptr - runtime->silence_start;
> +		snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
> +		if (runtime->silence_start != appl_ptr) {
> +			n = appl_ptr - runtime->silence_start;
>   			if (n < 0)
>   				n += runtime->boundary;
>   			if ((snd_pcm_uframes_t)n < runtime->silence_filled)
>   				runtime->silence_filled -= n;
>   			else
>   				runtime->silence_filled = 0;
> -			runtime->silence_start = runtime->control->appl_ptr;
> +			runtime->silence_start = appl_ptr;
>   		}
>   		if (runtime->silence_filled >= runtime->buffer_size)
>   			return;
> @@ -2224,7 +2225,9 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
>   				continue; /* draining */
>   		}
>   		frames = size > avail ? avail : size;
> -		cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
> +		appl_ptr = runtime->control->appl_ptr;
> +		appl_ofs = appl_ptr % runtime->buffer_size;
> +		cont = runtime->buffer_size - appl_ofs;
>   		if (frames > cont)
>   			frames = cont;
>   		if (snd_BUG_ON(!frames)) {
> @@ -2232,8 +2235,6 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
>   			snd_pcm_stream_unlock_irq(substream);
>   			return -EINVAL;
>   		}
> -		appl_ptr = runtime->control->appl_ptr;
> -		appl_ofs = appl_ptr % runtime->buffer_size;
>   		snd_pcm_stream_unlock_irq(substream);
>   		err = writer(substream, appl_ofs, data, offset, frames,
>   			     transfer);

Regards

Takashi Sakamoto
Takashi Iwai June 18, 2017, 5:23 p.m. UTC | #2
On Sun, 18 Jun 2017 12:49:24 +0200,
Takashi Sakamoto wrote:
> 
> Hi,
> 
> On Jun 17 2017 05:40, Takashi Iwai wrote:
> > The ALSA PCM core refers to the appl_ptr value stored on the mmapped
> > page that is shared between kernel and user-space.  Although the
> > reference is performed in the PCM stream lock, it doesn't guarantee
> > the atomic access when the value gets updated concurrently from the
> > user-space on another CPU.
> >
> > In most of codes, this is no big problem, but still there are a few
> > places that may result in slight inconsistencies because they access
> > runtime->control->appl_ptr multiple times; that is, the second read
> > might be a different value from the first value.  It can be even
> > backward or jumping, as we have no control for it.  Hence, the
> > calculation may give an unexpected value.  Luckily, there is no
> > security vulnerability by that, as far as I've checked.  But still we
> > should address it.
> >
> > This patch tries to reduce such possible cases.  The fix is simple --
> > we just read once, store it to a local variable and use it for the
> > rest calculations.
> >
> > Signed-off-by: Takashi Iwai <tiwai@suse.de>
> > ---
> >   sound/core/pcm_lib.c | 13 +++++++------
> >   1 file changed, 7 insertions(+), 6 deletions(-)
> 
> This looks good to me by itself.
> 
> Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
> 
> This function, 'snd_pcm_playback_silence()' can be called without
> acquiring stream lock by 'snd_pcm_reset()' when runtime of PCM
> substream is configured to have a larger value than zero to its
> 'silence_size' member. When a task or any IRQ handler operates the
> runtime and another task operates the reset in the same time, both of
> them got expected result.

Good catch.  The whole snd_pcm_reset() behavior is a bit whacky.

> I'll post a patch to take 'snd_pcm_reset()' to use
> 'snd_pcm_action_lock_irq()' instead of
> 'snd_pcm_action_nonatomic()'. How do you think about it?

Conceptually seen, that's not right.  snd_pcm_reset() calls PCM ioctl
ops, and this is supposed to be always non-atomic (it's a kind of
ioctl wrapper, after all).  So the correct fix would be simply to wrap
only the snd_pcm_playback_silence() call with the stream lock.


thanks,

Takashi
Clemens Ladisch June 18, 2017, 6:03 p.m. UTC | #3
Takashi Iwai wrote:
> The ALSA PCM core refers to the appl_ptr value stored on the mmapped
> page that is shared between kernel and user-space.  Although the
> reference is performed in the PCM stream lock, it doesn't guarantee
> the atomic access when the value gets updated concurrently from the
> user-space on another CPU.
>
> In most of codes, this is no big problem, but still there are a few
> places that may result in slight inconsistencies because they access
> runtime->control->appl_ptr multiple times; that is, the second read
> might be a different value from the first value.  It can be even
> backward or jumping, as we have no control for it.  Hence, the
> calculation may give an unexpected value.  Luckily, there is no
> security vulnerability by that, as far as I've checked.  But still we
> should address it.
>
> This patch tries to reduce such possible cases.  The fix is simple --
> we just read once, store it to a local variable and use it for the
> rest calculations.

It is likely that the compiler's optimizer already merged multiple
accesses and used a temporary register.

However, if the compiler can do this transformation, it can also do the
reverse transformation.  So this new code does not actually guarantee
that there is a single atomic access.  That can be enforced only with
READ_ONCE():

>  	if (runtime->silence_size < runtime->boundary) {
>  		snd_pcm_sframes_t noise_dist, n;
> -		if (runtime->silence_start != runtime->control->appl_ptr) {
> -			n = runtime->control->appl_ptr - runtime->silence_start;
> +		snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;

		snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
etc.


Regards,
Clemens
Takashi Iwai June 18, 2017, 7:07 p.m. UTC | #4
On Sun, 18 Jun 2017 20:03:34 +0200,
Clemens Ladisch wrote:
> 
> Takashi Iwai wrote:
> > The ALSA PCM core refers to the appl_ptr value stored on the mmapped
> > page that is shared between kernel and user-space.  Although the
> > reference is performed in the PCM stream lock, it doesn't guarantee
> > the atomic access when the value gets updated concurrently from the
> > user-space on another CPU.
> >
> > In most of codes, this is no big problem, but still there are a few
> > places that may result in slight inconsistencies because they access
> > runtime->control->appl_ptr multiple times; that is, the second read
> > might be a different value from the first value.  It can be even
> > backward or jumping, as we have no control for it.  Hence, the
> > calculation may give an unexpected value.  Luckily, there is no
> > security vulnerability by that, as far as I've checked.  But still we
> > should address it.
> >
> > This patch tries to reduce such possible cases.  The fix is simple --
> > we just read once, store it to a local variable and use it for the
> > rest calculations.
> 
> It is likely that the compiler's optimizer already merged multiple
> accesses and used a temporary register.
>
> However, if the compiler can do this transformation, it can also do the
> reverse transformation.  So this new code does not actually guarantee
> that there is a single atomic access.  That can be enforced only with
> READ_ONCE():

Good point, we need to assure by that.
Will respin the patch.


thanks,

Takashi


> >  	if (runtime->silence_size < runtime->boundary) {
> >  		snd_pcm_sframes_t noise_dist, n;
> > -		if (runtime->silence_start != runtime->control->appl_ptr) {
> > -			n = runtime->control->appl_ptr - runtime->silence_start;
> > +		snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
> 
> 		snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
> etc.
> 
> 
> Regards,
> Clemens
>
diff mbox

Patch

diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 631cd598ba6c..cda258d91490 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -65,15 +65,16 @@  void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram
 
 	if (runtime->silence_size < runtime->boundary) {
 		snd_pcm_sframes_t noise_dist, n;
-		if (runtime->silence_start != runtime->control->appl_ptr) {
-			n = runtime->control->appl_ptr - runtime->silence_start;
+		snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
+		if (runtime->silence_start != appl_ptr) {
+			n = appl_ptr - runtime->silence_start;
 			if (n < 0)
 				n += runtime->boundary;
 			if ((snd_pcm_uframes_t)n < runtime->silence_filled)
 				runtime->silence_filled -= n;
 			else
 				runtime->silence_filled = 0;
-			runtime->silence_start = runtime->control->appl_ptr;
+			runtime->silence_start = appl_ptr;
 		}
 		if (runtime->silence_filled >= runtime->buffer_size)
 			return;
@@ -2224,7 +2225,9 @@  snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
 				continue; /* draining */
 		}
 		frames = size > avail ? avail : size;
-		cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
+		appl_ptr = runtime->control->appl_ptr;
+		appl_ofs = appl_ptr % runtime->buffer_size;
+		cont = runtime->buffer_size - appl_ofs;
 		if (frames > cont)
 			frames = cont;
 		if (snd_BUG_ON(!frames)) {
@@ -2232,8 +2235,6 @@  snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
 			snd_pcm_stream_unlock_irq(substream);
 			return -EINVAL;
 		}
-		appl_ptr = runtime->control->appl_ptr;
-		appl_ofs = appl_ptr % runtime->buffer_size;
 		snd_pcm_stream_unlock_irq(substream);
 		err = writer(substream, appl_ofs, data, offset, frames,
 			     transfer);