Skip to content

Commit c221398

Browse files
terrellnfacebook-github-bot
authored andcommitted
Fix Zip64 extra field offset computation in readCentralDirectoryFileHeader (#731)
Summary: The Zip64 extended information extra field only includes entries for CDFH fields that overflow to 0xFFFFFFFF. The old code used hardcoded offsets (16 for header offset, 0 for compressed size), which assumed all preceding fields were always present. This caused corruption errors on zip files where only some fields used Zip64. Fix by computing the offset dynamically based on which preceding CDFH fields actually overflowed. Also thread `opCtx` through the zip lexer for proper error reporting. Differential Revision: D103841972
1 parent f014e51 commit c221398

5 files changed

Lines changed: 106 additions & 61 deletions

File tree

custom_parsers/pytorch_model_parser.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ static ZL_Report pytorchModelSegmenter(ZL_Segmenter* sctx)
172172
const ZL_GraphID functionGraph = customGraphs.graphids[0];
173173

174174
ZS2_ZipLexer lexer;
175-
ZL_ERR_IF_ERR(ZS2_ZipLexer_init(&lexer, ZL_Input_ptr(input), inputSize));
175+
ZL_ERR_IF_ERR(ZS2_ZipLexer_init(
176+
&lexer, ZL_Input_ptr(input), inputSize, ZL__errorContext.opCtx));
176177

177178
const size_t nbFiles = ZS2_ZipLexer_numFiles(&lexer);
178179
const size_t maxNbSegs = nbFiles * 4 + 2;

custom_parsers/tests/fuzz_zip_lexer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ FUZZ(ZipLexerTest, FuzzLexer)
1212
std::array<ZS2_ZipToken, 10> tokens;
1313

1414
ZS2_ZipLexer lexer;
15-
auto report = ZS2_ZipLexer_init(&lexer, data.data(), data.size());
15+
auto report = ZS2_ZipLexer_init(&lexer, data.data(), data.size(), NULL);
1616
if (ZL_isError(report)) {
1717
return;
1818
}

custom_parsers/tests/test_zip_lexer.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,24 @@ std::array<uint8_t, 152> kTestZip64 = {
110110
0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
111111
};
112112

113+
// Zip file where only the CDFH local file header offset is 0xFFFFFFFF.
114+
// Compressed and uncompressed sizes are normal, so the Zip64 extra field
115+
// contains only the 8-byte header offset. Exercises the dynamic offset
116+
// computation in readCentralDirectoryFileHeader (the old hardcoded offset
117+
// of 16 would fail here because the Zip64 extra field is only 8 bytes).
118+
std::array<uint8_t, 117> kTestZip64OffsetOnly = {
119+
0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
120+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00,
121+
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x61, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
122+
0x50, 0x4b, 0x01, 0x02, 0x14, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
123+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
124+
0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
125+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x61, 0x01,
126+
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50,
127+
0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x3b,
128+
0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00,
129+
};
130+
113131
std::array<uint8_t, 22> kTestEmptyZip = { 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00,
114132
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
115133
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -120,7 +138,8 @@ void testZipLexer(
120138
std::string_view data,
121139
bool empty = false)
122140
{
123-
ZL_REQUIRE_SUCCESS(ZS2_ZipLexer_init(lexer, data.data(), data.size()));
141+
ZL_REQUIRE_SUCCESS(
142+
ZS2_ZipLexer_init(lexer, data.data(), data.size(), NULL));
124143
const size_t numFiles = ZS2_ZipLexer_numFiles(lexer);
125144
if (empty) {
126145
ASSERT_EQ(numFiles, 0);
@@ -247,6 +266,15 @@ TEST(ZipLexerTest, Zip64)
247266
&lexer, { (const char*)kTestZip64.begin(), kTestZip64.size() });
248267
}
249268

269+
TEST(ZipLexerTest, Zip64OffsetOnly)
270+
{
271+
ZS2_ZipLexer lexer;
272+
testZipLexer(
273+
&lexer,
274+
{ (const char*)kTestZip64OffsetOnly.begin(),
275+
kTestZip64OffsetOnly.size() });
276+
}
277+
250278
TEST(ZipLexerTest, EmptyZip)
251279
{
252280
ZS2_ZipLexer lexer;

custom_parsers/zip_lexer.c

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,17 @@ static bool hasZip64Info(const char* extraFieldPtr, size_t extraFieldLength)
102102
* Reads a 64-bit field from @p offsetTrusted (does not include the 4-byte
103103
* header).
104104
*
105-
* @p offsetTrusted An offset to read from that is guaranteed to be <= 20.
105+
* @p offsetTrusted An offset to read from that is guaranteed to be <= 16.
106106
*/
107107
static ZL_RESULT_OF(uint64_t) readZip64Info(
108+
ZL_OperationContext* opCtx,
108109
const char* zip64InfoPtr,
109110
size_t zip64InfoSize,
110111
size_t offsetTrusted)
111112
{
112-
ZL_RESULT_DECLARE_SCOPE(uint64_t, NULL);
113+
ZL_RESULT_DECLARE_SCOPE(uint64_t, opCtx);
113114
const char* const zip64InfoEnd = zip64InfoPtr + zip64InfoSize;
114-
ZL_ASSERT_LE(offsetTrusted, 20);
115+
ZL_ASSERT_LE(offsetTrusted, 16);
115116
for (;;) {
116117
ZL_ERR_IF_LT(zip64InfoEnd - zip64InfoPtr, 4, corruption);
117118
const uint16_t id = ZL_readLE16(zip64InfoPtr);
@@ -133,12 +134,13 @@ static ZL_RESULT_OF(uint64_t) readZip64Info(
133134
* @returns The size of the CDFH entry or an error.
134135
*/
135136
static ZL_Report readCentralDirectoryFileHeader(
137+
ZL_OperationContext* opCtx,
136138
const char* cdfhPtr,
137139
const char* cdfhEnd,
138140
uint64_t* localFileHeaderOffset,
139141
uint64_t* compressedSize)
140142
{
141-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
143+
ZL_RESULT_DECLARE_SCOPE_REPORT(opCtx);
142144
// suppress -Wmaybe-uninitialized
143145
*localFileHeaderOffset = 0;
144146

@@ -155,22 +157,33 @@ static ZL_Report readCentralDirectoryFileHeader(
155157
const char* const extraFieldPtr =
156158
cdfhPtr + kMinCentralDirectoryFileHeaderSize + filenameLength;
157159

158-
*localFileHeaderOffset = read32OrNeg1(cdfhPtr + 42);
159-
if (*localFileHeaderOffset == (uint64_t)-1) {
160-
ZL_TRY_LET(
161-
uint64_t,
162-
offset,
163-
readZip64Info(extraFieldPtr, extraFieldLength, 16));
164-
*localFileHeaderOffset = offset;
160+
// Zip64 extra field stores values in order: uncompressed size,
161+
// compressed size, header offset. Each field is only present when
162+
// the corresponding CDFH field is 0xFFFFFFFF.
163+
size_t zip64Offset = 0;
164+
if (ZL_readLE32(cdfhPtr + 24) == (uint32_t)-1) {
165+
zip64Offset += 8;
165166
}
166167

167168
*compressedSize = read32OrNeg1(cdfhPtr + 20);
168169
if (*compressedSize == (uint64_t)-1) {
169170
ZL_TRY_LET(
170171
uint64_t,
171172
size,
172-
readZip64Info(extraFieldPtr, extraFieldLength, 0));
173+
readZip64Info(
174+
opCtx, extraFieldPtr, extraFieldLength, zip64Offset));
173175
*compressedSize = size;
176+
zip64Offset += 8;
177+
}
178+
179+
*localFileHeaderOffset = read32OrNeg1(cdfhPtr + 42);
180+
if (*localFileHeaderOffset == (uint64_t)-1) {
181+
ZL_TRY_LET(
182+
uint64_t,
183+
offset,
184+
readZip64Info(
185+
opCtx, extraFieldPtr, extraFieldLength, zip64Offset));
186+
*localFileHeaderOffset = offset;
174187
}
175188

176189
return ZL_returnValue(cdfhLength);
@@ -235,6 +248,7 @@ static bool ZS2_ZipLexer_validateCentralDirectory(
235248
uint64_t localFileHeaderOffset;
236249
uint64_t compressedSize;
237250
if (ZL_isError(readCentralDirectoryFileHeader(
251+
lexer->opCtx,
238252
centralDirectoryPtr,
239253
lexer->srcEnd,
240254
&localFileHeaderOffset,
@@ -297,7 +311,7 @@ static ZL_Report ZS2_ZipLexer_findZipBegin(
297311
const char* maxRecordEnd,
298312
bool (*validate)(const ZS2_ZipLexer*, const char*, const char*))
299313
{
300-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
314+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
301315
ZL_ERR_IF_LT(minRecordSize, 4, corruption);
302316
ZL_ERR_IF_GT(
303317
minRecordSize,
@@ -346,7 +360,7 @@ static ZL_Report ZS2_ZipLexer_findEOCD64(
346360
const char* eocdPtr,
347361
ZS2_ZipLexer_EndOfCentralDirectory* eocd)
348362
{
349-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
363+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
350364
ZL_ASSERT(ZS2_ZipLexer_EndOfCentralDirectory_needZip64(eocd));
351365
ZL_ASSERT_GE(eocdPtr, lexer->zipBegin);
352366
ZL_ASSERT_LE(eocdPtr, lexer->srcEnd);
@@ -402,7 +416,7 @@ static ZL_Report ZS2_ZipLexer_findEOCD64(
402416
*/
403417
static ZL_Report ZS2_ZipLexer_parseEOCD(ZS2_ZipLexer* lexer, size_t eocdOffset)
404418
{
405-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
419+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
406420
ZL_ERR_IF_GT(
407421
eocdOffset, (size_t)(lexer->srcEnd - lexer->zipBegin), corruption);
408422
const char* const eocdPtr = lexer->zipBegin + eocdOffset;
@@ -487,6 +501,29 @@ static ZL_Report ZS2_ZipLexer_parseEOCD(ZS2_ZipLexer* lexer, size_t eocdOffset)
487501

488502
static ZL_Report ZS2_ZipLexer_setFileState(ZS2_ZipLexer* lexer);
489503

504+
static ZL_Report ZS2_ZipLexer_initWithEOCD(
505+
ZS2_ZipLexer* lexer,
506+
const void* src,
507+
size_t srcSize,
508+
size_t eocdOffset)
509+
{
510+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
511+
lexer->zipBegin = src;
512+
lexer->srcPtr = src;
513+
lexer->srcEnd = lexer->zipBegin + srcSize;
514+
515+
memset(&lexer->fileState, 0, sizeof(lexer->fileState));
516+
517+
ZL_ERR_IF_ERR(ZS2_ZipLexer_parseEOCD(lexer, eocdOffset));
518+
519+
if (lexer->cdfhIdx < lexer->cdfhNum) {
520+
// Proactively initialize the file state to catch more invalid zip files
521+
// in the init() function.
522+
ZL_ERR_IF_ERR(ZS2_ZipLexer_setFileState(lexer));
523+
}
524+
return ZL_returnSuccess();
525+
}
526+
490527
static bool ZS2_ZipLexer_tryInit(
491528
ZS2_ZipLexer* lexer,
492529
const void* src,
@@ -507,10 +544,14 @@ static bool ZS2_ZipLexer_tryInit(
507544
return true;
508545
}
509546

510-
ZL_Report
511-
ZS2_ZipLexer_init(ZS2_ZipLexer* lexer, const void* src, size_t srcSize)
547+
ZL_Report ZS2_ZipLexer_init(
548+
ZS2_ZipLexer* lexer,
549+
const void* src,
550+
size_t srcSize,
551+
ZL_OperationContext* opCtx)
512552
{
513-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
553+
lexer->opCtx = opCtx;
554+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
514555
const size_t minReverseOffset = kMinEndOfCentralDirectoryRecordSize;
515556
// Maximum allowed offset if there is no garbage at the end
516557
const size_t maxLegalReverseOffset =
@@ -542,29 +583,6 @@ ZS2_ZipLexer_init(ZS2_ZipLexer* lexer, const void* src, size_t srcSize)
542583
return ZL_returnSuccess();
543584
}
544585

545-
ZL_Report ZS2_ZipLexer_initWithEOCD(
546-
ZS2_ZipLexer* lexer,
547-
const void* src,
548-
size_t srcSize,
549-
size_t eocdOffset)
550-
{
551-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
552-
lexer->zipBegin = src;
553-
lexer->srcPtr = src;
554-
lexer->srcEnd = lexer->zipBegin + srcSize;
555-
556-
memset(&lexer->fileState, 0, sizeof(lexer->fileState));
557-
558-
ZL_ERR_IF_ERR(ZS2_ZipLexer_parseEOCD(lexer, eocdOffset));
559-
560-
if (lexer->cdfhIdx < lexer->cdfhNum) {
561-
// Proactively initialize the file state to catch more invalid zip files
562-
// in the init() function.
563-
ZL_ERR_IF_ERR(ZS2_ZipLexer_setFileState(lexer));
564-
}
565-
return ZL_returnSuccess();
566-
}
567-
568586
/**
569587
* Emits an unknown token. This is used for bytes in the Zip file that are
570588
* otherwise unaccounted for. The Zip format, read loosely, allows for gaps in
@@ -578,7 +596,7 @@ static ZL_Report ZS2_ZipLexer_lexUnknown(
578596
ZS2_ZipToken* out,
579597
const char* nextPtr)
580598
{
581-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
599+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
582600
ZL_ASSERT_GT(nextPtr, lexer->srcPtr);
583601
ZL_ASSERT_LE(nextPtr, lexer->srcEnd);
584602

@@ -612,7 +630,7 @@ static ZL_Report ZS2_ZipLexer_lexSection(
612630
const char** sectionPtrPtr,
613631
size_t sectionSize)
614632
{
615-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
633+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
616634
// Ensure the srcPtr is at the beginning of the section
617635
if (lexer->srcPtr < *sectionPtrPtr) {
618636
return ZS2_ZipLexer_lexUnknown(lexer, out, *sectionPtrPtr);
@@ -634,7 +652,7 @@ static ZL_Report ZS2_ZipLexer_lexSection(
634652
/// Handles emitting tokens for all sections after the files.
635653
static ZL_Report ZS2_ZipLexer_lexTail(ZS2_ZipLexer* lexer, ZS2_ZipToken* out)
636654
{
637-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
655+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
638656
ZL_ERR_IF_NE(lexer->cdfhPtr, lexer->cdfhEnd, corruption);
639657

640658
memset(out, 0, sizeof(*out));
@@ -691,12 +709,13 @@ static ZL_Report ZS2_ZipLexer_readNextCentralDirectoryFileHeader(
691709
uint64_t* localFileHeaderOffset,
692710
uint64_t* compressedSize)
693711
{
694-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
712+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
695713
ZL_ASSERT_LT(lexer->cdfhIdx, lexer->cdfhNum);
696714
ZL_TRY_LET(
697715
size_t,
698716
cdfhLength,
699717
readCentralDirectoryFileHeader(
718+
lexer->opCtx,
700719
lexer->cdfhPtr,
701720
lexer->cdfhEnd,
702721
localFileHeaderOffset,
@@ -723,7 +742,7 @@ static bool ZS2_ZipLexer_FileState_empty(
723742
*/
724743
static ZL_Report ZS2_ZipLexer_setFileState(ZS2_ZipLexer* lexer)
725744
{
726-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
745+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
727746
ZL_ASSERT(ZS2_ZipLexer_FileState_empty(&lexer->fileState));
728747

729748
// Read fields from the CDFH. The compressed size is read from the CDFH,
@@ -796,7 +815,7 @@ static ZL_Report ZS2_ZipLexer_setFileState(ZS2_ZipLexer* lexer)
796815

797816
static ZL_Report ZS2_ZipLexer_lexFile(ZS2_ZipLexer* lexer, ZS2_ZipToken* out)
798817
{
799-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
818+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
800819
ZL_ASSERT_LE(lexer->cdfhIdx, lexer->cdfhNum);
801820
ZL_ASSERT_LE(lexer->cdfhPtr, lexer->cdfhEnd);
802821

@@ -859,7 +878,7 @@ static ZL_Report ZS2_ZipLexer_lexOne(ZS2_ZipLexer* lexer, ZS2_ZipToken* out)
859878
ZL_Report
860879
ZS2_ZipLexer_lex(ZS2_ZipLexer* lexer, ZS2_ZipToken* out, size_t outCapacity)
861880
{
862-
ZL_RESULT_DECLARE_SCOPE_REPORT(NULL);
881+
ZL_RESULT_DECLARE_SCOPE_REPORT(lexer->opCtx);
863882
size_t entries;
864883
for (entries = 0; !ZS2_ZipLexer_finished(lexer) && entries < outCapacity;
865884
++entries) {
@@ -886,6 +905,6 @@ size_t ZS2_ZipLexer_numFiles(const ZS2_ZipLexer* lexer)
886905
bool ZS2_isLikelyZipFile(const void* src, size_t srcSize)
887906
{
888907
ZS2_ZipLexer lexer;
889-
const ZL_Report report = ZS2_ZipLexer_init(&lexer, src, srcSize);
908+
const ZL_Report report = ZS2_ZipLexer_init(&lexer, src, srcSize, NULL);
890909
return !ZL_isError(report);
891910
}

custom_parsers/zip_lexer.h

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,20 @@ typedef struct {
4545
* Initializes a Zip lexer on the given input buffer. The lexer allows for
4646
* garbage data before & after the zip file.
4747
*
48+
* @param opCtx The operation context to use for error reporting or NULL.
49+
*
4850
* @returns Success if the input buffer may be a valid zip file,
4951
* or an error code if the input file is definitely not
5052
* a supported zip file.
5153
*
5254
* @note This lexer supports all zip files whose central directory
5355
* is listed in order of occurrence in the file.
5456
*/
55-
ZL_Report
56-
ZS2_ZipLexer_init(ZS2_ZipLexer* lexer, const void* src, size_t srcSize);
57-
58-
/**
59-
* Initializes a Zip lexer with a known offset to the EOCD.
60-
* @see ZS2_ZipLexer_init()
61-
*/
62-
ZL_Report ZS2_ZipLexer_initWithEOCD(
57+
ZL_Report ZS2_ZipLexer_init(
6358
ZS2_ZipLexer* lexer,
6459
const void* src,
6560
size_t srcSize,
66-
size_t eocdOffset);
61+
ZL_OperationContext* opCtx);
6762

6863
/**
6964
* Lexes the next @p outCapacity tokens from the input buffer.
@@ -139,6 +134,8 @@ struct ZS2_ZipLexer_s {
139134
uint32_t endOfCentralDirectoryRecordSize;
140135

141136
ZS2_ZipLexer_FileState fileState;
137+
138+
ZL_OperationContext* opCtx;
142139
};
143140

144141
ZL_END_C_DECLS

0 commit comments

Comments
 (0)