diff mbox series

[FFmpeg-devel,v5,3/7] libavformat/asfdec: Fix type of value_len

Message ID MN2PR04MB5981C190AAD2D247A6A08428BAAA9@MN2PR04MB5981.namprd04.prod.outlook.com
State Superseded, archived
Headers show
Series [FFmpeg-devel,v5,1/7] libavformat/asf: Fix handling of byte array length values | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished
andriy/make_ppc success Make finished
andriy/make_fate_ppc success Make fate finished

Commit Message

Soft Works Sept. 30, 2021, 2:58 a.m. UTC
The value_len is an uint32 not an int32 per spec. That
value must not be truncated, neither by casting to int, nor by any
conditional checks, because at the end of get_tag, this value is
needed to move forward in parsing. When the len value gets
modified, the parsing may break.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
v5: Split into pieces as requested

 libavformat/asfdec_f.c | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

Comments

James Almer Sept. 30, 2021, 3:52 a.m. UTC | #1
On 9/29/2021 11:58 PM, Soft Works wrote:
> The value_len is an uint32 not an int32 per spec. That
> value must not be truncated, neither by casting to int, nor by any
> conditional checks, because at the end of get_tag, this value is
> needed to move forward in parsing. When the len value gets
> modified, the parsing may break.
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
> v5: Split into pieces as requested
> 
>   libavformat/asfdec_f.c | 24 +++++++++++-------------
>   1 file changed, 11 insertions(+), 13 deletions(-)
> 
> diff --git a/libavformat/asfdec_f.c b/libavformat/asfdec_f.c
> index 076b5ab147..d017fae019 100644
> --- a/libavformat/asfdec_f.c
> +++ b/libavformat/asfdec_f.c
> @@ -218,7 +218,7 @@ static uint64_t get_value(AVIOContext *pb, int type, int type2_size)
>       }
>   }
>   
> -static void get_tag(AVFormatContext *s, const char *key, int type, int len, int type2_size)
> +static void get_tag(AVFormatContext *s, const char *key, int type, uint32_t len, int type2_size)

There's an av_assert0() in this function to ensure len will never be 
bigger than (INT_MAX - 22) / 2. And len is used as argument for 
avio_read(), which takes an int, not an unsigned int. So this change is 
not ok.

>   {
>       ASFContext *asf = s->priv_data;
>       char *value = NULL;
> @@ -528,7 +528,7 @@ static int asf_read_ext_stream_properties(AVFormatContext *s, int64_t size)
>   static int asf_read_content_desc(AVFormatContext *s, int64_t size)
>   {
>       AVIOContext *pb = s->pb;
> -    int len1, len2, len3, len4, len5;
> +    uint32_t len1, len2, len3, len4, len5;
>   
>       len1 = avio_rl16(pb);
>       len2 = avio_rl16(pb);
> @@ -602,25 +602,23 @@ static int asf_read_metadata(AVFormatContext *s, int64_t size)
>   {
>       AVIOContext *pb = s->pb;
>       ASFContext *asf = s->priv_data;
> -    int n, stream_num, name_len_utf16, name_len_utf8, value_len;
> +    int n, name_len_utf8;
> +    uint16_t stream_num, name_len_utf16, value_type;
> +    uint32_t value_len;
>       int ret, i;
>       n = avio_rl16(pb);
>   
>       for (i = 0; i < n; i++) {
>           uint8_t *name;
> -        int value_type;
>   
>           avio_rl16(pb);  // lang_list_index
> -        stream_num = avio_rl16(pb);
> -        name_len_utf16 = avio_rl16(pb);
> -        value_type = avio_rl16(pb); /* value_type */
> -        value_len  = avio_rl32(pb);
> +        stream_num     = (uint16_t)avio_rl16(pb);
> +        name_len_utf16 = (uint16_t)avio_rl16(pb);
> +        value_type     = (uint16_t)avio_rl16(pb); /* value_type */
> +        value_len      = avio_rl32(pb);
>   
> -        if (value_len < 0 || value_len > UINT16_MAX)
> -            return AVERROR_INVALIDDATA;
> -
> -        name_len_utf8 = 2*name_len_utf16 + 1;
> -        name          = av_malloc(name_len_utf8);
> +        name_len_utf8  = 2 * name_len_utf16 + 1;
> +        name           = av_malloc(name_len_utf8);

Unrelated cosmetics.

>           if (!name)
>               return AVERROR(ENOMEM);
>   
>
Soft Works Sept. 30, 2021, 4:16 a.m. UTC | #2
> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> James Almer
> Sent: Thursday, 30 September 2021 05:53
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH v5 3/7] libavformat/asfdec: Fix
> type of value_len
> 
> On 9/29/2021 11:58 PM, Soft Works wrote:
> > The value_len is an uint32 not an int32 per spec. That
> > value must not be truncated, neither by casting to int, nor by any
> > conditional checks, because at the end of get_tag, this value is
> > needed to move forward in parsing. When the len value gets
> > modified, the parsing may break.
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> > v5: Split into pieces as requested
> >
> >   libavformat/asfdec_f.c | 24 +++++++++++-------------
> >   1 file changed, 11 insertions(+), 13 deletions(-)
> >
> > diff --git a/libavformat/asfdec_f.c b/libavformat/asfdec_f.c
> > index 076b5ab147..d017fae019 100644
> > --- a/libavformat/asfdec_f.c
> > +++ b/libavformat/asfdec_f.c
> > @@ -218,7 +218,7 @@ static uint64_t get_value(AVIOContext *pb, int
> type, int type2_size)
> >       }
> >   }
> >
> > -static void get_tag(AVFormatContext *s, const char *key, int type,
> int len, int type2_size)
> > +static void get_tag(AVFormatContext *s, const char *key, int type,
> uint32_t len, int type2_size)
> 
> There's an av_assert0() in this function to ensure len will never be
> bigger than (INT_MAX - 22) / 2. And len is used as argument for
> avio_read(), which takes an int, not an unsigned int. So this change
> is
> not ok.


You missed this part:

    if (required_bufferlen > INT32_MAX) {
        av_log(s, AV_LOG_VERBOSE, "Unable to handle values > INT32_MAX  in tag %s.\n", key);
        goto finish;
    }

which comes before avio_read.

goto finish doesn't mean to error out. It just means that value cannot 
be processed (even though it's valid from the ASF spec).

But parsing continues now without erroring, because now we can handle 
those values > INT32_MAX because we supply the correct value to
avio_seek() (the last statement).

Also:

The division by two applies to Unicode only, that's why I'm applying it
only to the Unicode case. The subtraction of '22' was not required, that's
why it's removed.


Kind regards,
softworkz
Soft Works Sept. 30, 2021, 4:28 a.m. UTC | #3
> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> James Almer
> Sent: Thursday, 30 September 2021 05:53
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH v5 3/7] libavformat/asfdec: Fix
> type of value_len
> 
> On 9/29/2021 11:58 PM, Soft Works wrote:
> > The value_len is an uint32 not an int32 per spec. That
> > value must not be truncated, neither by casting to int, nor by any
> > conditional checks, because at the end of get_tag, this value is
> > needed to move forward in parsing. When the len value gets
> > modified, the parsing may break.
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> > v5: Split into pieces as requested
> >
> >   libavformat/asfdec_f.c | 24 +++++++++++-------------
> >   1 file changed, 11 insertions(+), 13 deletions(-)
> >
> > diff --git a/libavformat/asfdec_f.c b/libavformat/asfdec_f.c
> > index 076b5ab147..d017fae019 100644
> > --- a/libavformat/asfdec_f.c
> > +++ b/libavformat/asfdec_f.c
> > @@ -218,7 +218,7 @@ static uint64_t get_value(AVIOContext *pb, int
> type, int type2_size)
> >       }
> >   }
> >
> > -static void get_tag(AVFormatContext *s, const char *key, int type,
> int len, int type2_size)
> > +static void get_tag(AVFormatContext *s, const char *key, int type,
> uint32_t len, int type2_size)
> 
> There's an av_assert0() in this function to ensure len will never be
> bigger than (INT_MAX - 22) / 2. And len is used as argument for
> avio_read(), which takes an int, not an unsigned int. So this change
> is
> not ok.

The general situation is this:

The ASF spec allows sizes of various elements in its format up to a 
maximum of UINT32_MAX.

As you have correctly noted: we can't process those sizes because 
some of the internal functions are taking int32 parameters rather than
uint32. (e.g. with avio_read, get_id3_tag, asf_read_picture, ...)


This patch can't fix that part. But what it fixes is that such values
between INT32_MAX and UINT32_MAX won't break ASF parsing anymore.
We emit a message that there's some metadata value that we can't
read, but we skip over it and continue parsing.

That's one part of what this patch does.

Kind regards,
softworkz
Soft Works Sept. 30, 2021, 6:21 a.m. UTC | #4
> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Soft Works
> Sent: Thursday, 30 September 2021 04:59
> To: ffmpeg-devel@ffmpeg.org
> Subject: [FFmpeg-devel] [PATCH v5 3/7] libavformat/asfdec: Fix type
> of value_len
> 
> The value_len is an uint32 not an int32 per spec. That
> value must not be truncated, neither by casting to int, nor by any
> conditional checks, because at the end of get_tag, this value is
> needed to move forward in parsing. When the len value gets
> modified, the parsing may break.
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
> v5: Split into pieces as requested
> 
>  libavformat/asfdec_f.c | 24 +++++++++++-------------
>  1 file changed, 11 insertions(+), 13 deletions(-)
> 
> diff --git a/libavformat/asfdec_f.c b/libavformat/asfdec_f.c
> index 076b5ab147..d017fae019 100644
> --- a/libavformat/asfdec_f.c
> +++ b/libavformat/asfdec_f.c
> @@ -218,7 +218,7 @@ static uint64_t get_value(AVIOContext *pb, int
> type, int type2_size)
>      }
>  }
> 
> -static void get_tag(AVFormatContext *s, const char *key, int type,
> int len, int type2_size)
> +static void get_tag(AVFormatContext *s, const char *key, int type,
> uint32_t len, int type2_size)
>  {
>      ASFContext *asf = s->priv_data;
>      char *value = NULL;
> @@ -528,7 +528,7 @@ static int
> asf_read_ext_stream_properties(AVFormatContext *s, int64_t size)
>  static int asf_read_content_desc(AVFormatContext *s, int64_t size)
>  {
>      AVIOContext *pb = s->pb;
> -    int len1, len2, len3, len4, len5;
> +    uint32_t len1, len2, len3, len4, len5;
> 
>      len1 = avio_rl16(pb);
>      len2 = avio_rl16(pb);
> @@ -602,25 +602,23 @@ static int asf_read_metadata(AVFormatContext
> *s, int64_t size)
>  {
>      AVIOContext *pb = s->pb;
>      ASFContext *asf = s->priv_data;
> -    int n, stream_num, name_len_utf16, name_len_utf8, value_len;
> +    int n, name_len_utf8;
> +    uint16_t stream_num, name_len_utf16, value_type;
> +    uint32_t value_len;
>      int ret, i;
>      n = avio_rl16(pb);
> 
>      for (i = 0; i < n; i++) {
>          uint8_t *name;
> -        int value_type;
> 
>          avio_rl16(pb);  // lang_list_index
> -        stream_num = avio_rl16(pb);
> -        name_len_utf16 = avio_rl16(pb);
> -        value_type = avio_rl16(pb); /* value_type */
> -        value_len  = avio_rl32(pb);
> +        stream_num     = (uint16_t)avio_rl16(pb);
> +        name_len_utf16 = (uint16_t)avio_rl16(pb);
> +        value_type     = (uint16_t)avio_rl16(pb); /* value_type */
> +        value_len      = avio_rl32(pb);
> 
> -        if (value_len < 0 || value_len > UINT16_MAX)
> -            return AVERROR_INVALIDDATA;

The previous two lines, which were added about a year ago, are actually 
the origin of this patchset:

A while ago after rebasing, I noticed that album image extraction from 
.wma didn't work anymore in certain cases, namely: 
image attachments > 32kB

I found those two lines and wondered, checked against the ASF spec and 
noticed the other flaws.

Regarding those two lines, I would suppose that they might have slipped in 
accidentally - at least the check against UINT_16_MAX.


Kind regards,
softworkz
diff mbox series

Patch

diff --git a/libavformat/asfdec_f.c b/libavformat/asfdec_f.c
index 076b5ab147..d017fae019 100644
--- a/libavformat/asfdec_f.c
+++ b/libavformat/asfdec_f.c
@@ -218,7 +218,7 @@  static uint64_t get_value(AVIOContext *pb, int type, int type2_size)
     }
 }
 
-static void get_tag(AVFormatContext *s, const char *key, int type, int len, int type2_size)
+static void get_tag(AVFormatContext *s, const char *key, int type, uint32_t len, int type2_size)
 {
     ASFContext *asf = s->priv_data;
     char *value = NULL;
@@ -528,7 +528,7 @@  static int asf_read_ext_stream_properties(AVFormatContext *s, int64_t size)
 static int asf_read_content_desc(AVFormatContext *s, int64_t size)
 {
     AVIOContext *pb = s->pb;
-    int len1, len2, len3, len4, len5;
+    uint32_t len1, len2, len3, len4, len5;
 
     len1 = avio_rl16(pb);
     len2 = avio_rl16(pb);
@@ -602,25 +602,23 @@  static int asf_read_metadata(AVFormatContext *s, int64_t size)
 {
     AVIOContext *pb = s->pb;
     ASFContext *asf = s->priv_data;
-    int n, stream_num, name_len_utf16, name_len_utf8, value_len;
+    int n, name_len_utf8;
+    uint16_t stream_num, name_len_utf16, value_type;
+    uint32_t value_len;
     int ret, i;
     n = avio_rl16(pb);
 
     for (i = 0; i < n; i++) {
         uint8_t *name;
-        int value_type;
 
         avio_rl16(pb);  // lang_list_index
-        stream_num = avio_rl16(pb);
-        name_len_utf16 = avio_rl16(pb);
-        value_type = avio_rl16(pb); /* value_type */
-        value_len  = avio_rl32(pb);
+        stream_num     = (uint16_t)avio_rl16(pb);
+        name_len_utf16 = (uint16_t)avio_rl16(pb);
+        value_type     = (uint16_t)avio_rl16(pb); /* value_type */
+        value_len      = avio_rl32(pb);
 
-        if (value_len < 0 || value_len > UINT16_MAX)
-            return AVERROR_INVALIDDATA;
-
-        name_len_utf8 = 2*name_len_utf16 + 1;
-        name          = av_malloc(name_len_utf8);
+        name_len_utf8  = 2 * name_len_utf16 + 1;
+        name           = av_malloc(name_len_utf8);
         if (!name)
             return AVERROR(ENOMEM);