This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit f5f31760f81f15db09d25c84cfdd9c7d87ef12fd Author: Jun Zhao <[email protected]> AuthorDate: Sat Oct 24 16:05:24 2026 +0800 Commit: Jun Zhao <[email protected]> CommitDate: Sat Oct 24 15:15:50 2026 +0000 tests/fate: cover chapter subframe alignment and non-synchsafe v2.4 CHAP Extend fate-id3v2chap to verify that: - unread trailing bytes after a NUL-terminated text subframe (TPE1) are skipped so both TPE1 ("artist") and the following embedded subframe (TIT2) are parsed; - a non-conforming v2.4 CHAP frame with a plain 32-bit size whose high bit is set (0x000000b6) is not truncated even when the stripped offset contains a fitting empty frame header; - a valid synchsafe v2.4 CHAP frame followed by an empty TPE1 frame and a TALB frame preserves the subsequent frames; - an ambiguous plain 256-byte v2.4 CHAP size (0x00000100, all high bits clear) rejects the 128-byte synchsafe candidate whose fake header exceeds the remaining tag length. Signed-off-by: Jun Zhao <[email protected]> --- libavformat/tests/id3v2chap.c | 109 ++++++++++++++++++++++++++++++++++++------ tests/ref/fate/id3v2chap | 29 +++++++++++ 2 files changed, 124 insertions(+), 14 deletions(-) diff --git a/libavformat/tests/id3v2chap.c b/libavformat/tests/id3v2chap.c index 760255bc17..536b219419 100644 --- a/libavformat/tests/id3v2chap.c +++ b/libavformat/tests/id3v2chap.c @@ -34,6 +34,7 @@ static int readback(const uint8_t *buf, int size) FFIOContext read_pb; ID3v2ExtraMeta *extra = NULL; AVDictionary *meta = NULL; + const AVDictionaryEntry *album; int ret = 1; if (!in) @@ -44,14 +45,22 @@ static int readback(const uint8_t *buf, int size) if (ff_id3v2_parse_chapters(in, extra) < 0) goto done; - printf("chapters=%u\n", in->nb_chapters); + printf("chapters=%u", in->nb_chapters); + album = av_dict_get(meta, "album", NULL, 0); + if (album) + printf(" album=%s", album->value); + printf("\n"); for (unsigned i = 0; i < in->nb_chapters; i++) { const AVChapter *ch = in->chapters[i]; - const AVDictionaryEntry *title = av_dict_get(ch->metadata, "title", NULL, 0); + const AVDictionaryEntry *title = av_dict_get(ch->metadata, "title", NULL, 0); + const AVDictionaryEntry *artist = av_dict_get(ch->metadata, "artist", NULL, 0); - printf("chapter=%u time_base=%d/%d start=%"PRId64" end=%"PRId64" title=%s\n", + printf("chapter=%u time_base=%d/%d start=%"PRId64" end=%"PRId64" title=%s", i, ch->time_base.num, ch->time_base.den, ch->start, ch->end, title ? title->value : "(missing)"); + if (artist) + printf(" artist=%s", artist->value); + printf("\n"); } ret = 0; @@ -64,17 +73,18 @@ done: /* The first chapter title is long enough that its TIT2 frame is at least * 128 bytes, so a plain size and a synchsafe size differ. */ -static int roundtrip(int version) +static int roundtrip(int version, int ambiguous) { - char long_title[151]; + char long_title[225]; + int title_len = ambiguous ? 224 : 150; AVFormatContext *out = NULL; AVIOContext *pb = NULL; ID3v2EncContext id3 = { 0 }; uint8_t *buf = NULL; int size, chap_pos = -1, ret = 1; - memset(long_title, 'L', sizeof(long_title) - 1); - long_title[sizeof(long_title) - 1] = 0; + memset(long_title, 'L', title_len); + long_title[title_len] = 0; out = avformat_alloc_context(); if (!out || @@ -86,7 +96,7 @@ static int roundtrip(int version) ff_id3v2_start(&id3, pb, version, ID3v2_DEFAULT_MAGIC); if (ff_id3v2_write_metadata(out, pb, &id3) < 0) goto done; - ff_id3v2_finish(&id3, pb, 10); + ff_id3v2_finish(&id3, pb, ambiguous ? 300 : 10); size = avio_close_dyn_buf(pb, &buf); pb = NULL; if (size < ID3v2_HEADER_SIZE) @@ -104,12 +114,82 @@ static int roundtrip(int version) if (readback(buf, size)) goto done; - /* Locate the first embedded TIT2 header inside the generated chapter. - * Give it an oversized length, leaving the chapter and following CHAP - * intact. The reader should retain both chapter timings. */ + /* A 224-byte title makes the CHAP payload 256 bytes. First test a valid + * synchsafe 256-byte CHAP (00 00 02 00, raw 512) followed by an empty + * TPE1 frame and a TALB frame inside the 300-byte padding area, so + * check_tag() must accept nlen == 0 at the 256-byte boundary rather than + * falling through to the raw 512-byte offset in zero padding. Then test + * the non-conforming plain size 00 00 01 00, whose high bits are clear + * so the synchsafe candidate at 128 bytes must be rejected by checking + * the oversized LLLL frame header inside the title. */ + if (ambiguous) { + if (chap_pos >= 0 && chap_pos + 10 + 256 + 45 + 27 <= size) { + uint8_t ch1_copy[45]; + memcpy(ch1_copy, buf + chap_pos + 10 + 256, sizeof(ch1_copy)); + memcpy(buf + chap_pos + 10 + 256, "TPE1\x00\x00\x00\x00\x00\x00", 10); + memcpy(buf + chap_pos + 10 + 266, "TALB\x00\x00\x00\x07\x00\x00\x03World", 17); + memcpy(buf + chap_pos + 10 + 283, ch1_copy, sizeof(ch1_copy)); + printf("empty next frame version=4\n"); + if (readback(buf, size)) + goto done; + memcpy(buf + chap_pos + 10 + 256, ch1_copy, sizeof(ch1_copy)); + memset(buf + chap_pos + 10 + 301, 0, 27); + + buf[chap_pos + 6] = 0x01; + buf[chap_pos + 7] = 0x00; + printf("ambiguous plain CHAP version=4\n"); + if (readback(buf, size)) + goto done; + } + ret = 0; + goto done; + } + + /* Non-conforming v2.4 CHAP with a plain 32-bit size (0x000000b6) whose + * high bit is set and whose payload contains uppercase ASCII at the + * stripped offset (0x36). Also zero the 4 size bytes after "LLLL" at + * offset 0x36 once to verify that is_syncsafe() rejects the 0x36 + * candidate even when check_tag() would accept an empty frame header. */ + if (version == 4 && chap_pos >= 0) { + uint8_t saved[4]; + buf[chap_pos + 6] = 0x00; + buf[chap_pos + 7] = 0xb6; + printf("non-synchsafe CHAP version=4\n"); + if (readback(buf, size)) + goto done; + + memcpy(saved, buf + chap_pos + 10 + 0x36 + 4, sizeof(saved)); + memset(buf + chap_pos + 10 + 0x36 + 4, 0, sizeof(saved)); + printf("high-bit plain CHAP with fitting fake header version=4\n"); + if (readback(buf, size)) + goto done; + memcpy(buf + chap_pos + 10 + 0x36 + 4, saved, sizeof(saved)); + buf[chap_pos + 6] = 0x01; + buf[chap_pos + 7] = 0x36; + } + + /* Split the 162-byte TIT2 subframe into a 22-byte TPE1 subframe with + * unread trailing bytes after its first NUL terminator, followed by a + * 140-byte TIT2 subframe, then test an oversized subframe length. */ if (chap_pos >= 0) { - for (int i = chap_pos + 10; i + 10 <= size; i++) { + for (int i = chap_pos + 10; i + 33 <= size; i++) { if (!memcmp(buf + i, "TIT2", 4)) { + uint8_t enc = buf[i + 10]; + memcpy(buf + i, "TPE1", 4); + buf[i + 6] = 0; + buf[i + 7] = 12; + buf[i + 13] = 0; + memcpy(buf + i + 22, "TIT2\x00\x00", 6); + buf[i + 28] = version == 4 ? 0x01 : 0x00; + buf[i + 29] = version == 4 ? 0x02 : 0x82; + buf[i + 30] = 0; + buf[i + 31] = 0; + buf[i + 32] = enc; + printf("trailing text subframe version=%d\n", version); + if (readback(buf, size)) + goto done; + + memcpy(buf + i, "TIT2", 4); memset(buf + i + 4, 0x7f, 4); printf("invalid subframe version=%d\n", version); if (readback(buf, size)) @@ -129,8 +209,9 @@ done: int main(void) { - int ret = roundtrip(3); + int ret = roundtrip(3, 0); - ret |= roundtrip(4); + ret |= roundtrip(4, 0); + ret |= roundtrip(4, 1); return ret; } diff --git a/tests/ref/fate/id3v2chap b/tests/ref/fate/id3v2chap index 512fbd3bd7..e711726f5f 100644 --- a/tests/ref/fate/id3v2chap +++ b/tests/ref/fate/id3v2chap @@ -3,6 +3,10 @@ CHAP size bytes=00 00 00 b6 chapters=2 chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL chapter=1 time_base=1/1000 start=1000 end=2000 title=End +trailing text subframe version=3 +chapters=2 +chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL artist=LL +chapter=1 time_base=1/1000 start=1000 end=2000 title=End invalid subframe version=3 chapters=2 chapter=0 time_base=1/1000 start=0 end=1000 title=ch0 @@ -12,7 +16,32 @@ CHAP size bytes=00 00 01 36 chapters=2 chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL chapter=1 time_base=1/1000 start=1000 end=2000 title=End +non-synchsafe CHAP version=4 +chapters=2 +chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +chapter=1 time_base=1/1000 start=1000 end=2000 title=End +high-bit plain CHAP with fitting fake header version=4 +chapters=2 +chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLL +chapter=1 time_base=1/1000 start=1000 end=2000 title=End +trailing text subframe version=4 +chapters=2 +chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL artist=LL +chapter=1 time_base=1/1000 start=1000 end=2000 title=End invalid subframe version=4 chapters=2 chapter=0 time_base=1/1000 start=0 end=1000 title=ch0 chapter=1 time_base=1/1000 start=1000 end=2000 title=End +version=4 declared=645 actual=645 +CHAP size bytes=00 00 02 00 +chapters=2 +chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +chapter=1 time_base=1/1000 start=1000 end=2000 title=End +empty next frame version=4 +chapters=2 album=World +chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +chapter=1 time_base=1/1000 start=1000 end=2000 title=End +ambiguous plain CHAP version=4 +chapters=2 +chapter=0 time_base=1/1000 start=0 end=1000 title=LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +chapter=1 time_base=1/1000 start=1000 end=2000 title=End -- To stop receiving notification emails like this one, please contact [email protected]. _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
