Bug 77595 - memblockq: Assertion 'length % bq->base == 0' failed, pulseaudio aborts
Summary: memblockq: Assertion 'length % bq->base == 0' failed, pulseaudio aborts
Status: RESOLVED FIXED
Alias: None
Product: PulseAudio
Classification: Unclassified
Component: daemon (show other bugs)
Version: unspecified
Hardware: x86-64 (AMD64) Linux (All)
: medium normal
Assignee: pulseaudio-bugs
QA Contact: pulseaudio-bugs
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 75721
  Show dependency treegraph
 
Reported: 2014-04-17 21:52 UTC by Felipe Sateler
Modified: 2014-10-24 13:12 UTC (History)
2 users (show)

See Also:
i915 platform:
i915 features:


Attachments
A test program causing the daemon to abort (3.34 KB, text/plain)
2014-04-17 21:52 UTC, Felipe Sateler
Details

Description Felipe Sateler 2014-04-17 21:52:05 UTC
Created attachment 97540 [details]
A test program causing the daemon to abort

Forwarded from the debian bug tracker:

> I found that pa_stream_write with an offset that's not a multiple of
> the base sample size causes pulseaudio to assert and abort.  This is
> not the case for the nbytes parameter of how many bytes to write, that
> is allowed to not be a multiple of the sample size, but the seek
> offset parameter must be a multiple of the sample size or it asserts.

And he provided a test program

> I modified a test (attached) to
> demonstrate it is still a problem.  There might be a shorter setup
> sequence, but it comes down to giving SEEK as something other than a
> multiple of the sample size.
> pa_stream_write(p, buffer, nbytes, NULL, SEEK, PA_SEEK_RELATIVE);


David tested against pa 2.0, but I can reproduce the problem with 5.0 as well.

Please find attached the test program that causes the daemon to abort.
Comment 1 David Fries 2014-04-19 17:14:15 UTC
I was the original reporter.  This is not a new problem as I originally reported it on version 0.9.21.  I've since fixed my program, but since PulseAudio is a shared resource of multiple programs it seems to me that it should be able to handle bad input like this without dying, especially since a write doesn't have to be a multiple of a sample size.
Comment 2 Tanu Kaskinen 2014-04-25 09:40:11 UTC
6.0 blocker.
Comment 3 David Henningsson 2014-08-29 12:41:25 UTC
Okay, so we're writing 64K, starting at index 1. This later fails when we're trying to drop 1 byte to get to the first package.

Why do we allow writing there at all? Looking at pa_memblockq_push, there is already a check for length, so we should probably add one for index too. There is no point in allowing an unaligned index if we don't allow unaligned lengths (right?), so the first step should be:

=============
@@ -287,7 +287,8 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) {
     pa_assert(uchunk->length > 0);
     pa_assert(uchunk->index + uchunk->length <= pa_memblock_get_length(uchunk->memblock));
 
-    pa_assert_se(uchunk->length % bq->base == 0);
+    pa_assert(uchunk->length % bq->base == 0);
+    pa_assert(uchunk->index % bq->base == 0);
 
     if (!can_push(bq, uchunk->length))
         return -1;
=============

(This assertion has no side effect, so remove the _se suffix.)

...however, this only makes us crash slightly earlier.

So, the desired client behaviour is probably to return with an error message in case the client tries to do something we don't allow, like this:

==============
diff --git a/src/pulse/stream.c b/src/pulse/stream.c
index 8e35c29..bb94180 100644
--- a/src/pulse/stream.c
+++ b/src/pulse/stream.c
@@ -1481,6 +1481,8 @@ int pa_stream_write(
     PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
     PA_CHECK_VALIDITY(s->context, seek <= PA_SEEK_RELATIVE_END, PA_ERR_INVALID);
     PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || (seek == PA_SEEK_RELATIVE && offset == 0), PA_ERR_INVALID);
+    PA_CHECK_VALIDITY(s->context, offset % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID);
+    PA_CHECK_VALIDITY(s->context, length % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID);
     PA_CHECK_VALIDITY(s->context,
                       !s->write_memblock ||
                       ((data >= s->write_data) &&
==============


However, that still won't stop us from malicious clients trying to crash the daemon...
Comment 4 Arun Raghavan 2014-10-12 12:14:58 UTC
David, if I understand correctly, your concern is that someone not using libpulse could emulate our protocol and cause a server crash? There are probably a few more instances where this can happen, not the least of which is writing past the SHM boundary which I believe can cause a SIGBUS.

One thing I can think of is that if we find such a situation, the assert could be converted into an "exception" that causes the sink input to be killed.

I would say that this would need to be part of a larger effort to eliminate ways in which malicious clients could kill the daemon (which would need to include a definition of our trust model, etc.).

In the mean time, I think we should unblock the release by patching both sides as you suggest. Do you want to write up patches (if you agree)?
Comment 5 David Henningsson 2014-10-16 10:14:10 UTC
(In reply to Arun Raghavan from comment #4)
> David, if I understand correctly, your concern is that someone not using
> libpulse could emulate our protocol and cause a server crash?

Yup.

I've sent three patches to the ML now, the third one addressing this concern as well. Waiting for review.
Comment 6 David Henningsson 2014-10-24 13:12:51 UTC
Patches now pushed.


Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.