We are currently migrating Bugzilla to GitHub issues.
Any changes made to the bug tracker now will be lost, so please do not post new bugs or make changes to them.
When we're done, all bug URLs will redirect to their equivalent location on the new bug tracker.

Bug 4905

Summary: reject vorbis comments with negative loop parameters
Product: SDL_mixer Reporter: Ozkan Sezer <sezeroz>
Component: miscAssignee: Ryan C. Gordon <icculus>
Status: RESOLVED FIXED QA Contact: Sam Lantinga <slouken>
Severity: normal    
Priority: P2 CC: admin, mikeguy42
Version: unspecified   
Hardware: x86   
OS: Linux   
Attachments: loop.patch
loop.patch
loop1.patch
loop2.patch -- fixed, v2
loop3.patch -- fixed, further simplified

Description Ozkan Sezer 2019-12-18 15:27:30 UTC
Created attachment 4114 [details]
loop.patch

Crafted or malformed files may have negative LOOP_START, LOOP_END, or
LOOP_LENGTH entries in their vorbis comments. From the 'spec' at zdoom
site https://zdoom.org/wiki/Audio_loop:  "Any characters other than
digits (0-9), colons (:), or a single decimal point for the seconds
portion will result in the tag being ignored."

We should just ignore any negative values, otherwise, we will have to
deal with broken loop parameters.  It is even worse with flac: all of
those parameters are unsigned (FLAC__uint64), the broken values shall
be even worse.

The attached patch does the following:

- Makes parse_time() to always return signed values, including flac.
- If parse_time() reads a negative value, it returns -1, which is
  later checked to indicate a bad tag to be ignored.
- Replaces strtoull() usage with strtoll()
- If any of music-> loop_start, loop_len, or loop_end is negative,
  they are all set to 0 and tag is ignored.

Flac side: (please review carefully):
- music-> loop_start, loop_len, and loop_end changed to signed type
  i.e. FLAC__int64:  No one will have anything greater than LLONG_MAX
  and the code is comparable to ogg/opus counterparts.
- music-> pcm_pos and full_length members are also changed to signed,
  which helps elsewhere in code in comparisons (e.g. no warnings) and
  it makes the code comparable to ogg/opus counterparts again.


Comments?  OK to apply?
Comment 1 Ozkan Sezer 2019-12-18 15:28:58 UTC
Added Vitaly Novichkov and Michael Day to CC list (they are authors
of the loop code.)
Comment 2 Vitaly Novichkov 2019-12-18 15:34:28 UTC
One thing you messed up: you made a memory leak: you forgot to clean-up the "param" in "break".

P.S. In MixerX I shouldn't break, because together with loop I also have to parse other tags such as title, artist, album and copyright for the tag retrieving API.
Comment 3 Vitaly Novichkov 2019-12-18 15:46:22 UTC
Anyway, about FLAC, I told some time ago that it may have a bigger size than max signed value.

However, after small computing:

The max signed value is 18446744073709551615. Divide it with 44100 to get a count of seconds: ~418293516410647. So, it allows storing 1,33x10^7 years which is totally unreal file size. So, it's should be fine with signed.
Comment 4 Ozkan Sezer 2019-12-18 15:47:19 UTC
Created attachment 4115 [details]
loop.patch

> One thing you messed up: you made a memory leak: you forgot to
> clean-up the "param" in "break".

Attaching updated patch which doesn't leak memory.
Comment 5 Ozkan Sezer 2019-12-18 15:48:35 UTC
(In reply to Vitaly Novichkov from comment #3)
> count of seconds: ~418293516410647. So, it allows storing 1,33x10^7 years
> which is totally unreal file size. So, it's should be fine with signed.

Indeed.

Michael Day:  What do you think?  (Flac loop code was yours.)
Comment 6 Vitaly Novichkov 2019-12-18 16:11:01 UTC
Also, be careful with SDL_atof() and SDL_strtod(), they have oddness:

- when a debugger is detached, they are using my local comma (comma a s a decimal separator like "1,2" is "1.2")
- when I attaching a debugger, it, by a wonder, works correctly

So, look a result without debugger:
ToF: 0 from str: .577
ToF: 0 from str: .091

And with debugger:
ToF: 0,577 from str: .577
ToF: 0,091 from str: .091

In MixerX I have in "utils.c" the workaround "str_to_float()" which allows to handle such cases.
Comment 7 Michael Day 2019-12-18 20:10:45 UTC
In general I think this an excellent improvement. If you are in a hurry I am okay with going ahead and applying this patch (I see nothing obviously wrong based on a simple review of the code) but if you are okay to wait a bit I will test this out tonight.
Comment 8 Ozkan Sezer 2019-12-18 20:29:08 UTC
(In reply to Michael Day from comment #7)
> In general I think this an excellent improvement. If you are in a hurry I am
> okay with going ahead and applying this patch (I see nothing obviously wrong
> based on a simple review of the code) but if you are okay to wait a bit I
> will test this out tonight.

I'll wait for you to test it.
Comment 9 Michael Day 2019-12-19 03:51:35 UTC
(In reply to Ozkan Sezer from comment #8)
> (In reply to Michael Day from comment #7)
> > In general I think this an excellent improvement. If you are in a hurry I am
> > okay with going ahead and applying this patch (I see nothing obviously wrong
> > based on a simple review of the code) but if you are okay to wait a bit I
> > will test this out tonight.
> 
> I'll wait for you to test it.

I found one issue. In each of the *CreateFromRW functions, loop_start is initialized to -1. This causes looping to break. I think it should instead be initialized to 0.
Comment 10 Ozkan Sezer 2019-12-19 06:34:17 UTC
Created attachment 4116 [details]
loop1.patch

> I found one issue. In each of the *CreateFromRW functions, loop_start is
> initialized to -1. This causes looping to break. I think it should instead
> be initialized to 0.

OK good catch. New patch attached which initializes music->loop to 0, and
not -1.

Doing that also allows simplifying the final loop decision. The interdiff
between the old and new patches is the like following,  same in all three
files in CreateFromRW:
     music->loop = -1;
-    music->loop_start = -1;
+    music->loop_start = 0;
     music->loop_end = 0;
     music->loop_len = 0;

and:

-    if (((music->loop_start >= 0) || (music->loop_end > 0)) &&
-        ((music->loop_start < music->loop_end) || (music->loop_end > 0)) &&
-         (music->loop_start < full_length) &&
-         (music->loop_end <= full_length)) {
-        if (music->loop_start < 0) music->loop_start = 0;
+    if ((music->loop_end > 0) && (music->loop_start < music->loop_end)
+        (music->loop_start < full_length) && (music->loop_end <= full_length)) {
         if (music->loop_end == 0)  music->loop_end = full_length;
         music->loop = 1;
     }


Please review.  OK to apply?
Comment 11 Ozkan Sezer 2019-12-19 08:29:22 UTC
Created attachment 4117 [details]
loop2.patch -- fixed, v2

Bad day, perhaps.  Here is a version that actually builds.
Changes after the original:
- removes all music->loop??? initializers from CreateFromRW:
  music is allocated using calloc().
- simplifies the final loop decision, as showed in previous
  comment.


Please review / test.  OK to apply?
Comment 12 Vitaly Novichkov 2019-12-19 12:46:41 UTC
Going to test/review and backport this into MixerX at evening of UTC+3
Comment 13 Michael Day 2019-12-19 15:27:03 UTC
I think the big if statement can be reduced further to something like:

if ((music->loop_end > 0) && (music->loop_end <= full_length) &&
    (music->loop_start < music->loop_end)) {
    music->loop = 1;
}

Basically, if:

1. loop_end is positive AND
2. loop_end occurs before the end of the track AND
3. loop_start occurs before loop_end (which implies that loop_start occurs before the end position because of condition #2)

...then the loop points are valid and we can use them for looping.

What do you think?
Comment 14 Ozkan Sezer 2019-12-19 15:49:09 UTC
Created attachment 4119 [details]
loop3.patch -- fixed, further simplified

(In reply to Michael Day from comment #13)
> I think the big if statement can be reduced further to something like:
> 
> if ((music->loop_end > 0) && (music->loop_end <= full_length) &&
>     (music->loop_start < music->loop_end)) {
>     music->loop = 1;
> }
> 
> Basically, if:
> 
> 1. loop_end is positive AND
> 2. loop_end occurs before the end of the track AND
> 3. loop_start occurs before loop_end (which implies that loop_start occurs
> before the end position because of condition #2)
> 
> ...then the loop points are valid and we can use them for looping.
> 
> What do you think?

Oh yes.  Will apply the attached version shortly.
Comment 15 Ozkan Sezer 2019-12-19 15:55:46 UTC
Applied the patch from comment #14, here:
https://hg.libsdl.org/SDL_mixer/rev/a3c637d1a698

Thanks for the reviews and suggestions. If there are
any issues with the change, or improvements can be made
to it, please drop a note here.  Closing as fixed.
Comment 16 Ozkan Sezer 2019-12-19 15:56:12 UTC
Closing as fixed.