Skip to content

Commit

Permalink
Handle size 0 in avifROStreamReadBoxHeaderPartial
Browse files Browse the repository at this point in the history
Move ReadFile() from avifincrtest to aviftest_helpers.
Delete unused GetWhiteSinglePixelAvif().
Add avifsize0test.
  • Loading branch information
y-guyon committed Mar 28, 2024
1 parent 374e622 commit 9057c2d
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 69 deletions.
2 changes: 1 addition & 1 deletion include/avif/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ AVIF_NODISCARD avifBool avifROStreamReadUX8(avifROStream * stream, uint64_t * v,
AVIF_NODISCARD avifBool avifROStreamReadU64(avifROStream * stream, uint64_t * v);
AVIF_NODISCARD avifBool avifROStreamReadString(avifROStream * stream, char * output, size_t outputSize);
AVIF_NODISCARD avifBool avifROStreamReadBoxHeader(avifROStream * stream, avifBoxHeader * header); // This fails if the size reported by the header cannot fit in the stream
AVIF_NODISCARD avifBool avifROStreamReadBoxHeaderPartial(avifROStream * stream, avifBoxHeader * header); // This doesn't require that the full box can fit in the stream
AVIF_NODISCARD avifBool avifROStreamReadBoxHeaderPartial(avifROStream * stream, avifBoxHeader * header, avifBool topLevel); // This doesn't require that the full box can fit in the stream
AVIF_NODISCARD avifBool avifROStreamReadVersionAndFlags(avifROStream * stream, uint8_t * version, uint32_t * flags); // version and flags ptrs are both optional
AVIF_NODISCARD avifBool avifROStreamReadAndEnforceVersion(avifROStream * stream, uint8_t enforcedVersion); // currently discards flags
// The following functions can write non-aligned bits.
Expand Down
26 changes: 22 additions & 4 deletions src/read.c
Original file line number Diff line number Diff line change
Expand Up @@ -3807,7 +3807,7 @@ static avifResult avifParse(avifDecoder * decoder)
// Parse the header, and find out how many bytes it actually was
BEGIN_STREAM(headerStream, headerContents.data, headerContents.size, &decoder->diag, "File-level box header");
avifBoxHeader header;
AVIF_CHECKERR(avifROStreamReadBoxHeaderPartial(&headerStream, &header), AVIF_RESULT_BMFF_PARSE_FAILED);
AVIF_CHECKERR(avifROStreamReadBoxHeaderPartial(&headerStream, &header, /*topLevel=*/AVIF_TRUE), AVIF_RESULT_BMFF_PARSE_FAILED);
parseOffset += headerStream.offset;
AVIF_ASSERT_OR_RETURN(decoder->io->sizeHint == 0 || parseOffset <= decoder->io->sizeHint);

Expand All @@ -3823,11 +3823,24 @@ static avifResult avifParse(avifDecoder * decoder)
if (!memcmp(header.type, "ftyp", 4) || !memcmp(header.type, "meta", 4) || !memcmp(header.type, "moov", 4)) {
#endif
boxOffset = parseOffset;
readResult = decoder->io->read(decoder->io, 0, parseOffset, header.size, &boxContents);
size_t sizeToRead;
if (header.size == 0) {
// A size of 0 means the box body goes till the end of the file.
if (decoder->io->sizeHint != 0 && decoder->io->sizeHint - parseOffset < SIZE_MAX) {
sizeToRead = decoder->io->sizeHint - parseOffset;
} else {
sizeToRead = SIZE_MAX; // This will get truncated. See the documentation of avifIOReadFunc.
}
} else {
sizeToRead = header.size;
}
readResult = decoder->io->read(decoder->io, 0, parseOffset, sizeToRead, &boxContents);
if (readResult != AVIF_RESULT_OK) {
return readResult;
}
if (boxContents.size != header.size) {
if (header.size == 0) {
header.size = boxContents.size;
} else if (boxContents.size != header.size) {
// A truncated box, bail out
return AVIF_RESULT_TRUNCATED_DATA;
}
Expand Down Expand Up @@ -3935,7 +3948,12 @@ avifBool avifPeekCompatibleFileType(const avifROData * input)
BEGIN_STREAM(s, input->data, input->size, NULL, NULL);

avifBoxHeader header;
if (!avifROStreamReadBoxHeader(&s, &header) || memcmp(header.type, "ftyp", 4)) {
if (!avifROStreamReadBoxHeaderPartial(&s, &header, /*topLevel=*/AVIF_TRUE) || memcmp(header.type, "ftyp", 4)) {
return AVIF_FALSE;
}
if (header.size == 0) {
// The size of the 'ftyp' box is 0, which means it goes till the end of the file.
// Either there is no brand requiring anything in the file but a FileTypebox (so not AVIF), or it is invalid.
return AVIF_FALSE;
}

Expand Down
31 changes: 25 additions & 6 deletions src/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,24 +245,43 @@ avifBool avifROStreamReadString(avifROStream * stream, char * output, size_t out
return AVIF_TRUE;
}

avifBool avifROStreamReadBoxHeaderPartial(avifROStream * stream, avifBoxHeader * header)
avifBool avifROStreamReadBoxHeaderPartial(avifROStream * stream, avifBoxHeader * header, avifBool topLevel)
{
// Section 4.2.2 of ISO/IEC 14496-12.
size_t startOffset = stream->offset;

uint32_t smallSize;
AVIF_CHECK(avifROStreamReadU32(stream, &smallSize));
AVIF_CHECK(avifROStreamRead(stream, header->type, 4));
AVIF_CHECK(avifROStreamReadU32(stream, &smallSize)); // unsigned int(32) size;
AVIF_CHECK(avifROStreamRead(stream, header->type, 4)); // unsigned int(32) type = boxtype;

uint64_t size = smallSize;
if (size == 1) {
AVIF_CHECK(avifROStreamReadU64(stream, &size));
AVIF_CHECK(avifROStreamReadU64(stream, &size)); // unsigned int(64) largesize;
}

if (!memcmp(header->type, "uuid", 4)) {
AVIF_CHECK(avifROStreamSkip(stream, 16));
AVIF_CHECK(avifROStreamSkip(stream, 16)); // unsigned int(8) usertype[16] = extended_type;
}

size_t bytesRead = stream->offset - startOffset;
if (size == 0) {
// Section 4.2.2 of ISO/IEC 14496-12.
// if size is 0, then this box shall be in a top-level box (i.e. not contained in another
// box), and be the last box in its 'file', and its payload extends to the end of that
// enclosing 'file'. This is normally only used for a MediaDataBox.
if (!topLevel) {
avifDiagnosticsPrintf(stream->diag, "%s: Non-top-level box with size 0", stream->diagContext);
return AVIF_FALSE;
}

// The given stream may be incomplete and there is no guarantee that sizeHint is available and accurate.
// Otherwise size could be set to avifROStreamRemainingBytes(stream) + (stream->offset - startOffset) right now.

// Wait for avifIOReadFunc() to return AVIF_RESULT_OK.
header->size = 0;
return AVIF_TRUE;
}

if ((size < bytesRead) || ((size - bytesRead) > SIZE_MAX)) {
avifDiagnosticsPrintf(stream->diag, "%s: Header size overflow check failure", stream->diagContext);
return AVIF_FALSE;
Expand All @@ -273,7 +292,7 @@ avifBool avifROStreamReadBoxHeaderPartial(avifROStream * stream, avifBoxHeader *

avifBool avifROStreamReadBoxHeader(avifROStream * stream, avifBoxHeader * header)
{
AVIF_CHECK(avifROStreamReadBoxHeaderPartial(stream, header));
AVIF_CHECK(avifROStreamReadBoxHeaderPartial(stream, header, /*topLevel=*/AVIF_FALSE));
if (header->size > avifROStreamRemainingBytes(stream)) {
avifDiagnosticsPrintf(stream->diag, "%s: Child box too large, possibly truncated data", stream->diagContext);
return AVIF_FALSE;
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ if(AVIF_ENABLE_GTEST)
add_avif_gtest(avifrgbtoyuvtest)
add_avif_gtest(avifrgbtoyuvthreadingtest)
add_avif_gtest_with_data(avifscaletest)
add_avif_gtest_with_data(avifsize0test)
add_avif_internal_gtest(avifstreamtest)
add_avif_internal_gtest(aviftilingtest)
add_avif_internal_gtest(avifutilstest)
Expand Down
2 changes: 1 addition & 1 deletion tests/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ It is of color type 3 (PNG_COLOR_TYPE_PALETTE) and has a tRNS chunk.

License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE)

Source: from `GetWhiteSinglePixelAvif()` in `avif_fuzztest_helpers.cc`.
Source: single white pixel encoded with libavif at default quality.

### Files `kodim*`

Expand Down
32 changes: 0 additions & 32 deletions tests/gtest/avif_fuzztest_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -159,38 +159,6 @@ size_t GetNumSamples(size_t width, size_t height, avifPixelFormat pixel_format,
return num_luma_samples + num_chroma_samples + num_alpha_samples;
}

//------------------------------------------------------------------------------

std::vector<uint8_t> GetWhiteSinglePixelAvif() {
return {
0x0, 0x0, 0x0, 0x20, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66,
0x0, 0x0, 0x0, 0x0, 0x61, 0x76, 0x69, 0x66, 0x6d, 0x69, 0x66, 0x31,
0x6d, 0x69, 0x61, 0x66, 0x4d, 0x41, 0x31, 0x41, 0x0, 0x0, 0x0, 0xf2,
0x6d, 0x65, 0x74, 0x61, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28,
0x68, 0x64, 0x6c, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x70, 0x69, 0x63, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x6c, 0x69, 0x62, 0x61, 0x76, 0x69, 0x66, 0x0,
0x0, 0x0, 0x0, 0xe, 0x70, 0x69, 0x74, 0x6d, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1, 0x0, 0x0, 0x0, 0x1e, 0x69, 0x6c, 0x6f, 0x63, 0x0, 0x0,
0x0, 0x0, 0x44, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1,
0x0, 0x0, 0x1, 0x1a, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x28,
0x69, 0x69, 0x6e, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0,
0x0, 0x1a, 0x69, 0x6e, 0x66, 0x65, 0x2, 0x0, 0x0, 0x0, 0x0, 0x1,
0x0, 0x0, 0x61, 0x76, 0x30, 0x31, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x0,
0x0, 0x0, 0x0, 0x6a, 0x69, 0x70, 0x72, 0x70, 0x0, 0x0, 0x0, 0x4b,
0x69, 0x70, 0x63, 0x6f, 0x0, 0x0, 0x0, 0x14, 0x69, 0x73, 0x70, 0x65,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1,
0x0, 0x0, 0x0, 0x10, 0x70, 0x69, 0x78, 0x69, 0x0, 0x0, 0x0, 0x0,
0x3, 0x8, 0x8, 0x8, 0x0, 0x0, 0x0, 0xc, 0x61, 0x76, 0x31, 0x43,
0x81, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0x63, 0x6f, 0x6c, 0x72,
0x6e, 0x63, 0x6c, 0x78, 0x0, 0x1, 0x0, 0xd, 0x0, 0x6, 0x80, 0x0,
0x0, 0x0, 0x17, 0x69, 0x70, 0x6d, 0x61, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1, 0x0, 0x1, 0x4, 0x1, 0x2, 0x83, 0x4, 0x0, 0x0,
0x0, 0x1f, 0x6d, 0x64, 0x61, 0x74, 0x12, 0x0, 0xa, 0x7, 0x38, 0x0,
0x6, 0x10, 0x10, 0xd0, 0x69, 0x32, 0xa, 0x1f, 0xf0, 0x3f, 0xff, 0xff,
0xc4, 0x0, 0x0, 0xaf, 0x70};
}

//------------------------------------------------------------------------------
// Environment setup

Expand Down
5 changes: 0 additions & 5 deletions tests/gtest/avif_fuzztest_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,6 @@ inline auto ArbitraryAvifDecoder() {
inline auto ArbitraryAvifDecoder() { return ArbitraryBaseAvifDecoder(); }
#endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP

//------------------------------------------------------------------------------

// Returns a white pixel compressed with AVIF.
std::vector<uint8_t> GetWhiteSinglePixelAvif();

//------------------------------------------------------------------------------
// Environment setup

Expand Down
24 changes: 4 additions & 20 deletions tests/gtest/avifincrtest.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause

#include <fstream>
#include <iostream>
#include <string>
#include <tuple>
Expand All @@ -18,32 +17,16 @@ using testing::Values;
namespace avif {
namespace {

//------------------------------------------------------------------------------

// Used to pass the data folder path to the GoogleTest suites.
const char* data_path = nullptr;

// Reads the file with file_name into bytes and returns them.
testutil::AvifRwData ReadFile(const char* file_name) {
std::ifstream file(std::string(data_path) + "/" + file_name,
std::ios::binary | std::ios::ate);
testutil::AvifRwData bytes;
if (avifRWDataRealloc(&bytes, file.good() ? static_cast<size_t>(file.tellg())
: 0) != AVIF_RESULT_OK) {
return {};
}
file.seekg(0, std::ios::beg);
file.read(reinterpret_cast<char*>(bytes.data),
static_cast<std::streamsize>(bytes.size));
return bytes;
}

//------------------------------------------------------------------------------

// Check that non-incremental and incremental decodings of a grid AVIF produce
// the same pixels.
TEST(IncrementalTest, Decode) {
const testutil::AvifRwData encoded_avif = ReadFile("sofa_grid1x5_420.avif");
const testutil::AvifRwData encoded_avif =
testutil::ReadFile(std::string(data_path) + "sofa_grid1x5_420.avif");
ASSERT_NE(encoded_avif.size, 0u);
ImagePtr reference(avifImageCreateEmpty());
ASSERT_NE(reference, nullptr);
Expand Down Expand Up @@ -87,7 +70,8 @@ TEST_P(IncrementalTest, EncodeDecode) {
// Load an image. It does not matter that it comes from an AVIF file.
ImagePtr image(avifImageCreateEmpty());
ASSERT_NE(image, nullptr);
const testutil::AvifRwData image_bytes = ReadFile("sofa_grid1x5_420.avif");
const testutil::AvifRwData image_bytes =
testutil::ReadFile(std::string(data_path) + "sofa_grid1x5_420.avif");
ASSERT_NE(image_bytes.size, 0u);
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
Expand Down
107 changes: 107 additions & 0 deletions tests/gtest/avifsize0test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2024 Google LLC
// SPDX-License-Identifier: BSD-2-Clause

#include <algorithm>
#include <iostream>
#include <string>

#include "avif/avif.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"

namespace avif {
namespace {

// Used to pass the data folder path to the GoogleTest suites.
const char* data_path = nullptr;

//------------------------------------------------------------------------------

TEST(AvifDecodeTest, SingleWhitePixel) {
const char* file_name = "white_1x1.avif";
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderSetIOFile(decoder.get(),
(std::string(data_path) + file_name).c_str()),
AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
if (testutil::Av1DecoderAvailable()) {
EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
}
}

TEST(AvifDecodeTest, MdatSize0) {
testutil::AvifRwData avif =
testutil::ReadFile(std::string(data_path) + "white_1x1.avif");
// Edit the file to simulate an 'mdat' box with size 0 (meaning ending at EOF)
const uint8_t* kMdat = reinterpret_cast<const uint8_t*>("mdat");
uint8_t* mdat_position =
std::search(avif.data, avif.data + avif.size, kMdat, kMdat + 4);
ASSERT_NE(mdat_position, avif.data + avif.size);
mdat_position[-1] = '\0';

DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), avif.data, avif.size),
AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
if (testutil::Av1DecoderAvailable()) {
EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
}
}

TEST(AvifDecodeTest, MetaSize0) {
testutil::AvifRwData avif =
testutil::ReadFile(std::string(data_path) + "white_1x1.avif");
// Edit the file to simulate a 'meta' box with size 0 (invalid).
const uint8_t* kMeta = reinterpret_cast<const uint8_t*>("meta");
uint8_t* meta_position =
std::search(avif.data, avif.data + avif.size, kMeta, kMeta + 4);
ASSERT_NE(meta_position, avif.data + avif.size);
meta_position[-1] = '\0';

DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), avif.data, avif.size),
AVIF_RESULT_OK);

// This should fail because the meta box contains the mdat box.
// However, the section 8.11.3.1 of ISO/IEC 14496-12 does not explicitly
// require the coded image item extents to be read from the MediaDataBox if
// the construction_method is 0.
// Maybe another section or specification enforces that.
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
if (testutil::Av1DecoderAvailable()) {
EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
}
}

TEST(AvifDecodeTest, FtypSize0) {
testutil::AvifRwData avif =
testutil::ReadFile(std::string(data_path) + "white_1x1.avif");
// Edit the file to simulate a 'ftyp' box with size 0 (invalid).
avif.data[3] = '\0';

DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder, nullptr);
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), avif.data, avif.size),
AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_BMFF_PARSE_FAILED);
}

//------------------------------------------------------------------------------

} // namespace
} // namespace avif

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
if (argc != 2) {
std::cerr << "There must be exactly one argument containing the path to "
"the test data folder"
<< std::endl;
return 1;
}
avif::data_path = argv[1];
return RUN_ALL_TESTS();
}
16 changes: 16 additions & 0 deletions tests/gtest/aviftest_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <limits>
#include <string>
#include <vector>
Expand Down Expand Up @@ -456,6 +457,21 @@ avifResult MergeGrid(int grid_cols, int grid_rows,

//------------------------------------------------------------------------------

testutil::AvifRwData ReadFile(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
testutil::AvifRwData bytes;
if (avifRWDataRealloc(&bytes, file.good() ? static_cast<size_t>(file.tellg())
: 0) != AVIF_RESULT_OK) {
return {};
}
file.seekg(0, std::ios::beg);
file.read(reinterpret_cast<char*>(bytes.data),
static_cast<std::streamsize>(bytes.size));
return bytes;
}

//------------------------------------------------------------------------------

ImagePtr ReadImage(const char* folder_path, const char* file_name,
avifPixelFormat requested_format, int requested_depth,
avifChromaDownsampling chroma_downsampling,
Expand Down
5 changes: 5 additions & 0 deletions tests/gtest/aviftest_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ avifResult MergeGrid(int grid_cols, int grid_rows,
const std::vector<const avifImage*>& cells,
avifImage* merged);

//------------------------------------------------------------------------------

// Reads the file at file_path into bytes and returns them.
testutil::AvifRwData ReadFile(const std::string& file_path);

//------------------------------------------------------------------------------
// Shorter versions of libavif functions

Expand Down

0 comments on commit 9057c2d

Please sign in to comment.