diff mbox

[FFmpeg-devel,2/2] avcodec/vp8: Check for bitsteam end in decode_mb_row_no_filter()

Message ID 20170228032830.3094-2-michael@niedermayer.cc
State Accepted
Commit 7b5ff7d57355dc608f0fd86e3ab32a2fda65e752
Headers show

Commit Message

Michael Niedermayer Feb. 28, 2017, 3:28 a.m. UTC
Fixes: 686/clusterfuzz-testcase-5853946876788736

Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/targets/ffmpeg
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
---
 libavcodec/vp8.c | 20 ++++++++++++++------
 libavcodec/vp8.h |  2 +-
 2 files changed, 15 insertions(+), 7 deletions(-)

Comments

Ronald S. Bultje Feb. 28, 2017, 12:44 p.m. UTC | #1
Hi,

On Mon, Feb 27, 2017 at 10:28 PM, Michael Niedermayer <
michael@niedermayer.cc> wrote:

> Fixes: 686/clusterfuzz-testcase-5853946876788736
>
> Found-by: continuous fuzzing process https://github.com/google/oss-
> fuzz/tree/master/targets/ffmpeg
> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
> ---
>  libavcodec/vp8.c | 20 ++++++++++++++------
>  libavcodec/vp8.h |  2 +-
>  2 files changed, 15 insertions(+), 7 deletions(-)
>
> diff --git a/libavcodec/vp8.c b/libavcodec/vp8.c
> index c1c3eb7072..cc158528ef 100644
> --- a/libavcodec/vp8.c
> +++ b/libavcodec/vp8.c
> @@ -2275,7 +2275,7 @@ static void vp8_decode_mv_mb_modes(AVCodecContext
> *avctx, VP8Frame *cur_frame,
>  #define update_pos(td, mb_y, mb_x) while(0)
>  #endif
>
> -static av_always_inline void decode_mb_row_no_filter(AVCodecContext
> *avctx, void *tdata,
> +static av_always_inline int decode_mb_row_no_filter(AVCodecContext
> *avctx, void *tdata,
>                                          int jobnr, int threadnr, int
> is_vp7)
>  {
>      VP8Context *s = avctx->priv_data;
> @@ -2291,6 +2291,10 @@ static av_always_inline void
> decode_mb_row_no_filter(AVCodecContext *avctx, void
>          curframe->tf.f->data[1] +  8 * mb_y * s->uvlinesize,
>          curframe->tf.f->data[2] +  8 * mb_y * s->uvlinesize
>      };
> +
> +    if (c->end <= c->buffer && c->bits >= 0)
> +         return AVERROR_INVALIDDATA;


From vp56.h:

    if(bits >= 0 && c->buffer < c->end) {
        code_word |= bytestream_get_be16(&c->buffer) << bits;
        bits -= 16;
    }

So this looks supicious, c->end should never be more than 1 byte beyond
c->buffer (which is padded by AV_INPUT_BUFFER_PADDING_SIZE). What is the
real issue?

Ronald
Michael Niedermayer Feb. 28, 2017, 4:29 p.m. UTC | #2
On Tue, Feb 28, 2017 at 07:44:12AM -0500, Ronald S. Bultje wrote:
> Hi,
> 
> On Mon, Feb 27, 2017 at 10:28 PM, Michael Niedermayer <
> michael@niedermayer.cc> wrote:
> 
> > Fixes: 686/clusterfuzz-testcase-5853946876788736
> >
> > Found-by: continuous fuzzing process https://github.com/google/oss-
> > fuzz/tree/master/targets/ffmpeg
> > Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
> > ---
> >  libavcodec/vp8.c | 20 ++++++++++++++------
> >  libavcodec/vp8.h |  2 +-
> >  2 files changed, 15 insertions(+), 7 deletions(-)
> >
> > diff --git a/libavcodec/vp8.c b/libavcodec/vp8.c
> > index c1c3eb7072..cc158528ef 100644
> > --- a/libavcodec/vp8.c
> > +++ b/libavcodec/vp8.c
> > @@ -2275,7 +2275,7 @@ static void vp8_decode_mv_mb_modes(AVCodecContext
> > *avctx, VP8Frame *cur_frame,
> >  #define update_pos(td, mb_y, mb_x) while(0)
> >  #endif
> >
> > -static av_always_inline void decode_mb_row_no_filter(AVCodecContext
> > *avctx, void *tdata,
> > +static av_always_inline int decode_mb_row_no_filter(AVCodecContext
> > *avctx, void *tdata,
> >                                          int jobnr, int threadnr, int
> > is_vp7)
> >  {
> >      VP8Context *s = avctx->priv_data;
> > @@ -2291,6 +2291,10 @@ static av_always_inline void
> > decode_mb_row_no_filter(AVCodecContext *avctx, void
> >          curframe->tf.f->data[1] +  8 * mb_y * s->uvlinesize,
> >          curframe->tf.f->data[2] +  8 * mb_y * s->uvlinesize
> >      };
> > +
> > +    if (c->end <= c->buffer && c->bits >= 0)
> > +         return AVERROR_INVALIDDATA;
> 
> 
> From vp56.h:
> 
>     if(bits >= 0 && c->buffer < c->end) {
>         code_word |= bytestream_get_be16(&c->buffer) << bits;
>         bits -= 16;
>     }
> 
> So this looks supicious, c->end should never be more than 1 byte beyond
> c->buffer (which is padded by AV_INPUT_BUFFER_PADDING_SIZE). What is the
> real issue?

The real issue is that the code runs with blindfolds and hands tied
behind its back, so it doesnt know when its running toward a wall
and in fact it doesnt stop running when it hits the wall not even
once it starved, its even running in its grave.

You can feed the code with approximately a header with huge resolution
and 0 bytes of actual image and it wont notice, and after a really long
time it still wont notice it is decoding a image out of a non existing
bitstream.

That check checks if the end of the bitstream is reached and returns
an error in that case. That way the test sample finished in 10sec
instead of not finishing within the time i waited

If you want i can also add an error message that says that the
bitstream end is reached before the image had finished decoding

[...]
Ronald S. Bultje March 1, 2017, 3:19 a.m. UTC | #3
Hi Michael,

On Tue, Feb 28, 2017 at 11:29 AM, Michael Niedermayer <
michael@niedermayer.cc> wrote:

> On Tue, Feb 28, 2017 at 07:44:12AM -0500, Ronald S. Bultje wrote:
> > Hi,
> >
> > On Mon, Feb 27, 2017 at 10:28 PM, Michael Niedermayer <
> > michael@niedermayer.cc> wrote:
> >
> > > Fixes: 686/clusterfuzz-testcase-5853946876788736
> > >
> > > Found-by: continuous fuzzing process https://github.com/google/oss-
> > > fuzz/tree/master/targets/ffmpeg
> > > Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
> > > ---
> > >  libavcodec/vp8.c | 20 ++++++++++++++------
> > >  libavcodec/vp8.h |  2 +-
> > >  2 files changed, 15 insertions(+), 7 deletions(-)
> > >
> > > diff --git a/libavcodec/vp8.c b/libavcodec/vp8.c
> > > index c1c3eb7072..cc158528ef 100644
> > > --- a/libavcodec/vp8.c
> > > +++ b/libavcodec/vp8.c
> > > @@ -2275,7 +2275,7 @@ static void vp8_decode_mv_mb_modes(
> AVCodecContext
> > > *avctx, VP8Frame *cur_frame,
> > >  #define update_pos(td, mb_y, mb_x) while(0)
> > >  #endif
> > >
> > > -static av_always_inline void decode_mb_row_no_filter(AVCodecContext
> > > *avctx, void *tdata,
> > > +static av_always_inline int decode_mb_row_no_filter(AVCodecContext
> > > *avctx, void *tdata,
> > >                                          int jobnr, int threadnr, int
> > > is_vp7)
> > >  {
> > >      VP8Context *s = avctx->priv_data;
> > > @@ -2291,6 +2291,10 @@ static av_always_inline void
> > > decode_mb_row_no_filter(AVCodecContext *avctx, void
> > >          curframe->tf.f->data[1] +  8 * mb_y * s->uvlinesize,
> > >          curframe->tf.f->data[2] +  8 * mb_y * s->uvlinesize
> > >      };
> > > +
> > > +    if (c->end <= c->buffer && c->bits >= 0)
> > > +         return AVERROR_INVALIDDATA;
> >
> >
> > From vp56.h:
> >
> >     if(bits >= 0 && c->buffer < c->end) {
> >         code_word |= bytestream_get_be16(&c->buffer) << bits;
> >         bits -= 16;
> >     }
> >
> > So this looks supicious, c->end should never be more than 1 byte beyond
> > c->buffer (which is padded by AV_INPUT_BUFFER_PADDING_SIZE). What is the
> > real issue?
>
> The real issue is that the code runs with blindfolds and hands tied
> behind its back, so it doesnt know when its running toward a wall
> and in fact it doesnt stop running when it hits the wall not even
> once it starved, its even running in its grave.
>
> You can feed the code with approximately a header with huge resolution
> and 0 bytes of actual image and it wont notice, and after a really long
> time it still wont notice it is decoding a image out of a non existing
> bitstream.
>
> That check checks if the end of the bitstream is reached and returns
> an error in that case. That way the test sample finished in 10sec
> instead of not finishing within the time i waited


Aaaaah, that explains things.

The commit message uses "fuzz" and "fix" in the same sentence construction,
suggesting a security issue. You're now telling me there is in fact no
security issue. That suggests the commit message is wrong.

I'm fine with the patch as is, but change the commit message. I would
suggest still referring to the filename, but avoiding the word "fix" since
it doesn't fix anything - since there is no bug. And then explain (in the
commit message) that this shortcuts (i.e. speeds up) the error and
return-to-user when decoding a truncated frame. No error message is
necessary, it'll be something silly like "truncated bitstream" which
clobbers my terminal when I'm fuzzing and is otherwise virtually useless.

Ronald
Michael Niedermayer March 1, 2017, 12:21 p.m. UTC | #4
On Tue, Feb 28, 2017 at 10:19:05PM -0500, Ronald S. Bultje wrote:
> Hi Michael,
> 
> On Tue, Feb 28, 2017 at 11:29 AM, Michael Niedermayer <
> michael@niedermayer.cc> wrote:
> 
> > On Tue, Feb 28, 2017 at 07:44:12AM -0500, Ronald S. Bultje wrote:
> > > Hi,
> > >
> > > On Mon, Feb 27, 2017 at 10:28 PM, Michael Niedermayer <
> > > michael@niedermayer.cc> wrote:
> > >
> > > > Fixes: 686/clusterfuzz-testcase-5853946876788736
> > > >
> > > > Found-by: continuous fuzzing process https://github.com/google/oss-
> > > > fuzz/tree/master/targets/ffmpeg
> > > > Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
> > > > ---
> > > >  libavcodec/vp8.c | 20 ++++++++++++++------
> > > >  libavcodec/vp8.h |  2 +-
> > > >  2 files changed, 15 insertions(+), 7 deletions(-)
> > > >
> > > > diff --git a/libavcodec/vp8.c b/libavcodec/vp8.c
> > > > index c1c3eb7072..cc158528ef 100644
> > > > --- a/libavcodec/vp8.c
> > > > +++ b/libavcodec/vp8.c
> > > > @@ -2275,7 +2275,7 @@ static void vp8_decode_mv_mb_modes(
> > AVCodecContext
> > > > *avctx, VP8Frame *cur_frame,
> > > >  #define update_pos(td, mb_y, mb_x) while(0)
> > > >  #endif
> > > >
> > > > -static av_always_inline void decode_mb_row_no_filter(AVCodecContext
> > > > *avctx, void *tdata,
> > > > +static av_always_inline int decode_mb_row_no_filter(AVCodecContext
> > > > *avctx, void *tdata,
> > > >                                          int jobnr, int threadnr, int
> > > > is_vp7)
> > > >  {
> > > >      VP8Context *s = avctx->priv_data;
> > > > @@ -2291,6 +2291,10 @@ static av_always_inline void
> > > > decode_mb_row_no_filter(AVCodecContext *avctx, void
> > > >          curframe->tf.f->data[1] +  8 * mb_y * s->uvlinesize,
> > > >          curframe->tf.f->data[2] +  8 * mb_y * s->uvlinesize
> > > >      };
> > > > +
> > > > +    if (c->end <= c->buffer && c->bits >= 0)
> > > > +         return AVERROR_INVALIDDATA;
> > >
> > >
> > > From vp56.h:
> > >
> > >     if(bits >= 0 && c->buffer < c->end) {
> > >         code_word |= bytestream_get_be16(&c->buffer) << bits;
> > >         bits -= 16;
> > >     }
> > >
> > > So this looks supicious, c->end should never be more than 1 byte beyond
> > > c->buffer (which is padded by AV_INPUT_BUFFER_PADDING_SIZE). What is the
> > > real issue?
> >
> > The real issue is that the code runs with blindfolds and hands tied
> > behind its back, so it doesnt know when its running toward a wall
> > and in fact it doesnt stop running when it hits the wall not even
> > once it starved, its even running in its grave.
> >
> > You can feed the code with approximately a header with huge resolution
> > and 0 bytes of actual image and it wont notice, and after a really long
> > time it still wont notice it is decoding a image out of a non existing
> > bitstream.
> >
> > That check checks if the end of the bitstream is reached and returns
> > an error in that case. That way the test sample finished in 10sec
> > instead of not finishing within the time i waited
> 
> 
> Aaaaah, that explains things.
> 
> The commit message uses "fuzz" and "fix" in the same sentence construction,
> suggesting a security issue. You're now telling me there is in fact no
> security issue. That suggests the commit message is wrong.
> 
> I'm fine with the patch as is, but change the commit message. I would
> suggest still referring to the filename, but avoiding the word "fix" since
> it doesn't fix anything - since there is no bug. And then explain (in the

ive added "Timeout" after fix as thats what is "fixed", should avoid
the ambiguity


> commit message) that this shortcuts (i.e. speeds up) the error and
> return-to-user when decoding a truncated frame. No error message is
> necessary, it'll be something silly like "truncated bitstream" which
> clobbers my terminal when I'm fuzzing and is otherwise virtually useless.

added the suggeted text

applied

thx

[...]
diff mbox

Patch

diff --git a/libavcodec/vp8.c b/libavcodec/vp8.c
index c1c3eb7072..cc158528ef 100644
--- a/libavcodec/vp8.c
+++ b/libavcodec/vp8.c
@@ -2275,7 +2275,7 @@  static void vp8_decode_mv_mb_modes(AVCodecContext *avctx, VP8Frame *cur_frame,
 #define update_pos(td, mb_y, mb_x) while(0)
 #endif
 
-static av_always_inline void decode_mb_row_no_filter(AVCodecContext *avctx, void *tdata,
+static av_always_inline int decode_mb_row_no_filter(AVCodecContext *avctx, void *tdata,
                                         int jobnr, int threadnr, int is_vp7)
 {
     VP8Context *s = avctx->priv_data;
@@ -2291,6 +2291,10 @@  static av_always_inline void decode_mb_row_no_filter(AVCodecContext *avctx, void
         curframe->tf.f->data[1] +  8 * mb_y * s->uvlinesize,
         curframe->tf.f->data[2] +  8 * mb_y * s->uvlinesize
     };
+
+    if (c->end <= c->buffer && c->bits >= 0)
+         return AVERROR_INVALIDDATA;
+
     if (mb_y == 0)
         prev_td = td;
     else
@@ -2394,18 +2398,19 @@  static av_always_inline void decode_mb_row_no_filter(AVCodecContext *avctx, void
             update_pos(td, mb_y, mb_x);
         }
     }
+    return 0;
 }
 
-static void vp7_decode_mb_row_no_filter(AVCodecContext *avctx, void *tdata,
+static int vp7_decode_mb_row_no_filter(AVCodecContext *avctx, void *tdata,
                                         int jobnr, int threadnr)
 {
-    decode_mb_row_no_filter(avctx, tdata, jobnr, threadnr, 1);
+    return decode_mb_row_no_filter(avctx, tdata, jobnr, threadnr, 1);
 }
 
-static void vp8_decode_mb_row_no_filter(AVCodecContext *avctx, void *tdata,
+static int vp8_decode_mb_row_no_filter(AVCodecContext *avctx, void *tdata,
                                         int jobnr, int threadnr)
 {
-    decode_mb_row_no_filter(avctx, tdata, jobnr, threadnr, 0);
+    return decode_mb_row_no_filter(avctx, tdata, jobnr, threadnr, 0);
 }
 
 static av_always_inline void filter_mb_row(AVCodecContext *avctx, void *tdata,
@@ -2488,13 +2493,16 @@  int vp78_decode_mb_row_sliced(AVCodecContext *avctx, void *tdata, int jobnr,
     VP8ThreadData *next_td = NULL, *prev_td = NULL;
     VP8Frame *curframe = s->curframe;
     int mb_y, num_jobs = s->num_jobs;
+    int ret;
 
     td->thread_nr = threadnr;
     for (mb_y = jobnr; mb_y < s->mb_height; mb_y += num_jobs) {
         if (mb_y >= s->mb_height)
             break;
         td->thread_mb_pos = mb_y << 16;
-        s->decode_mb_row_no_filter(avctx, tdata, jobnr, threadnr);
+        ret = s->decode_mb_row_no_filter(avctx, tdata, jobnr, threadnr);
+        if (ret < 0)
+            return ret;
         if (s->deblock_filter)
             s->filter_mb_row(avctx, tdata, jobnr, threadnr);
         update_pos(td, mb_y, INT_MAX & 0xFFFF);
diff --git a/libavcodec/vp8.h b/libavcodec/vp8.h
index 374e1388e2..6218fe0567 100644
--- a/libavcodec/vp8.h
+++ b/libavcodec/vp8.h
@@ -275,7 +275,7 @@  typedef struct VP8Context {
      */
     int mb_layout;
 
-    void (*decode_mb_row_no_filter)(AVCodecContext *avctx, void *tdata, int jobnr, int threadnr);
+    int (*decode_mb_row_no_filter)(AVCodecContext *avctx, void *tdata, int jobnr, int threadnr);
     void (*filter_mb_row)(AVCodecContext *avctx, void *tdata, int jobnr, int threadnr);
 
     int vp7;