From 954c459b2dbea68544700e7b597ce993e3cc21bc Mon Sep 17 00:00:00 2001 From: Aleksey Prykhodko Date: Mon, 1 Jun 2026 17:36:20 +0300 Subject: [PATCH 1/3] Fix integer overflow in block size computation in XADStuffItXIronHandle --- XADStuffItXIronHandle.m | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/XADStuffItXIronHandle.m b/XADStuffItXIronHandle.m index 0b5f5846..49f99c8d 100644 --- a/XADStuffItXIronHandle.m +++ b/XADStuffItXIronHandle.m @@ -23,6 +23,7 @@ #import "StuffItXUtilities.h" #import "CarrylessRangeCoder.h" #import "BWT.h" +#import @@ -93,12 +94,21 @@ -(int)produceBlockAtOffset:(off_t)pos if(CSInputNextBitLE(input)==1) return -1; - unsigned int blocksize=(unsigned int)CSInputNextSitxP2(input); + uint64_t rawblocksize=CSInputNextSitxP2(input); + // Values above INT_MAX overflow when cast to int later (e.g. decodeBlockWithLength:). + if(rawblocksize>INT_MAX) [XADException raiseIllegalDataException]; + unsigned int blocksize=(unsigned int)rawblocksize; if(blocksize>currsize) { + size_t allocsize; + bool allocOverflowed=__builtin_mul_overflow((size_t)blocksize,(size_t)6,&allocsize); + if(allocOverflowed) + { + [XADException raiseIllegalDataException]; + } free(block); - block=malloc(blocksize*6); + block=malloc(allocsize); if(!block) [XADException raiseOutOfMemoryException]; sorted=block+blocksize; table=(uint32_t *)(block+2*blocksize); From 7493682a0551795962d964c87e33f7b489e0c478 Mon Sep 17 00:00:00 2001 From: Aleksey Prykhodko Date: Mon, 1 Jun 2026 19:46:13 +0300 Subject: [PATCH 2/3] Add tests for block size overflow in XADStuffItXIronHandle --- XADMaster.xcodeproj/project.pbxproj | 12 +- .../Stuffit/XADStuffItXIronHandleTests.m | 103 ++++++++++++++++++ 2 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 XADMasterTests/Stuffit/XADStuffItXIronHandleTests.m diff --git a/XADMaster.xcodeproj/project.pbxproj b/XADMaster.xcodeproj/project.pbxproj index 8891aa8c..1999fb4c 100644 --- a/XADMaster.xcodeproj/project.pbxproj +++ b/XADMaster.xcodeproj/project.pbxproj @@ -1415,13 +1415,14 @@ 734C13B921C79C8500917EAA /* unpack_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 734C13B621C79C8400917EAA /* unpack_utils.c */; }; 734C13BA21C79C8500917EAA /* unpack_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 734C13B621C79C8400917EAA /* unpack_utils.c */; }; 7378D98B21CCD04000A4B11F /* CRCCalculationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7378D98A21CCD04000A4B11F /* CRCCalculationTests.m */; }; - 73B1C2012B4A100100ABCDEF /* XADRegexTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 73B1C2002B4A100100ABCDEF /* XADRegexTests.m */; }; - 73F0AA6402D14A5300A1B2C3 /* EndianConversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 73F0AA6302D14A5300A1B2C3 /* EndianConversionTests.m */; }; 7398C7AE2059248700D7B977 /* UniversalDetector.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B06D3770DDA5C2600D9C000 /* UniversalDetector.framework */; }; 73A91A542ADC64DE0059C423 /* StuffitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 73A91A4F2ADC64DE0059C423 /* StuffitTests.m */; }; 73A91A562ADC64EB0059C423 /* StuffitFixtures in Resources */ = {isa = PBXBuildFile; fileRef = 73A91A552ADC64EB0059C423 /* StuffitFixtures */; }; + 73B1C2012B4A100100ABCDEF /* XADRegexTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 73B1C2002B4A100100ABCDEF /* XADRegexTests.m */; }; 73DA3468206B6BF1006ADB42 /* XADPlatformOSXTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 73DA3467206B6BF1006ADB42 /* XADPlatformOSXTests.m */; }; 73DA346A206B6BF1006ADB42 /* XADMaster.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* XADMaster.framework */; }; + 73F0AA6402D14A5300A1B2C3 /* EndianConversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 73F0AA6302D14A5300A1B2C3 /* EndianConversionTests.m */; }; + BA5204602FCDDC450041EA37 /* XADStuffItXIronHandleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BA52045F2FCDDC450041EA37 /* XADStuffItXIronHandleTests.m */; }; C4C3241827AC25E9007919DB /* XADZippedBzip2LeakTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C4C3241727AC25E9007919DB /* XADZippedBzip2LeakTests.m */; }; C4C3241A27AC2655007919DB /* eicar.bz in Resources */ = {isa = PBXBuildFile; fileRef = C4C3241927AC2655007919DB /* eicar.bz */; }; DF05DCF92806F104001E9C52 /* XADMaster.h in Headers */ = {isa = PBXBuildFile; fileRef = DF05DCF52806EF40001E9C52 /* XADMaster.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -2146,14 +2147,15 @@ 734C13B121C79B1A00917EAA /* unpack_seek.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unpack_seek.c; sourceTree = ""; }; 734C13B621C79C8400917EAA /* unpack_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unpack_utils.c; sourceTree = ""; }; 7378D98A21CCD04000A4B11F /* CRCCalculationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CRCCalculationTests.m; sourceTree = ""; }; - 73B1C2002B4A100100ABCDEF /* XADRegexTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XADRegexTests.m; sourceTree = ""; }; - 73F0AA6302D14A5300A1B2C3 /* EndianConversionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EndianConversionTests.m; sourceTree = ""; }; 73A91A4F2ADC64DE0059C423 /* StuffitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StuffitTests.m; sourceTree = ""; }; 73A91A552ADC64EB0059C423 /* StuffitFixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; path = StuffitFixtures; sourceTree = ""; }; + 73B1C2002B4A100100ABCDEF /* XADRegexTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XADRegexTests.m; sourceTree = ""; }; 73DA3465206B6BF1006ADB42 /* XADMasterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XADMasterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 73DA3467206B6BF1006ADB42 /* XADPlatformOSXTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XADPlatformOSXTests.m; sourceTree = ""; }; 73DA3469206B6BF1006ADB42 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 73F0AA6302D14A5300A1B2C3 /* EndianConversionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EndianConversionTests.m; sourceTree = ""; }; 8DC2EF5B0486A6940098B216 /* XADMaster.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XADMaster.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BA52045F2FCDDC450041EA37 /* XADStuffItXIronHandleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XADStuffItXIronHandleTests.m; sourceTree = ""; }; C4C3241727AC25E9007919DB /* XADZippedBzip2LeakTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XADZippedBzip2LeakTests.m; sourceTree = ""; }; C4C3241927AC2655007919DB /* eicar.bz */ = {isa = PBXFileReference; lastKnownFileType = file; path = eicar.bz; sourceTree = ""; }; DF05DCF52806EF40001E9C52 /* XADMaster.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XADMaster.h; sourceTree = ""; }; @@ -3267,6 +3269,7 @@ children = ( 73A91A552ADC64EB0059C423 /* StuffitFixtures */, 73A91A4F2ADC64DE0059C423 /* StuffitTests.m */, + BA52045F2FCDDC450041EA37 /* XADStuffItXIronHandleTests.m */, ); path = Stuffit; sourceTree = ""; @@ -4782,6 +4785,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BA5204602FCDDC450041EA37 /* XADStuffItXIronHandleTests.m in Sources */, 7378D98B21CCD04000A4B11F /* CRCCalculationTests.m in Sources */, 73F0AA6402D14A5300A1B2C3 /* EndianConversionTests.m in Sources */, 73B1C2012B4A100100ABCDEF /* XADRegexTests.m in Sources */, diff --git a/XADMasterTests/Stuffit/XADStuffItXIronHandleTests.m b/XADMasterTests/Stuffit/XADStuffItXIronHandleTests.m new file mode 100644 index 00000000..8107df4b --- /dev/null +++ b/XADMasterTests/Stuffit/XADStuffItXIronHandleTests.m @@ -0,0 +1,103 @@ +/* + * XADStuffItXIronHandleTests.m + * + * Copyright (c) 2017-present, MacPaw Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#import +#import "../../CSMemoryHandle.h" +#import "../../XADException.h" +#import "../../XADStuffItXIronHandle.h" + +@interface XADStuffItXIronHandleTests : XCTestCase +@end + +@implementation XADStuffItXIronHandleTests + +- (void)testOversizedBlockRaisesIllegalData +{ + // Bit layout (LSB-first within each byte), encoding 2^31 via CSInputNextSitxP2: + // bit 0 = 0 stream bit — not end of stream + // bits 1-2 = 1,0 prefix: one leading 1-bit → n=2 + // bit 3 = 1 value loop: value |= 1, n→1 + // bits 4-33 = 0 30 zeros — bit counter advances to 2^31 + // bit 34 = 1 value loop: value |= 2^31, n→0 + // CSInputNextSitxP2 returns 2^31 = INT_MAX+1 + uint8_t bytes[] = {0x0A, 0x00, 0x00, 0x00, 0x04}; + + NSException *caught = + [self caughtExceptionProducingBlockWithBytes:bytes + length:sizeof(bytes)]; + + XCTAssertNotNil(caught, @"Expected XADException to be thrown"); + XCTAssertEqualObjects(caught.name, XADExceptionName); + XCTAssertEqual([caught.userInfo[@"XADError"] intValue], XADIllegalDataError); +} + +// Demonstrates the crash path where blocksize*6 overflows unsigned int to a tiny +// allocation. blocksize=715827883 is below INT_MAX so the rawblocksize > INT_MAX +// guard does not fire. Without the fix, unsigned int arithmetic wraps +// 715827883*6 → 2, so malloc(2) succeeds and sorted lands ~680 MB beyond the +// 2-byte block, causing EXC_BAD_ACCESS — a hardware signal that @try/@catch +// cannot catch, so the test runner crashes before reaching any assertion. +// With the fix, size_t arithmetic gives the correct ~4 GB allocation size. +// malloc either returns NULL (→ XADOutOfMemoryException) or succeeds and +// decoding hits EOF on the tiny input (→ CSEndOfFileException). Either way +// the process survives, which is the only assertion we can reliably make here. +- (void)testBlockSizeWithMultiplicationOverflow +{ + // blocksize=715827883: 715827883*6 mod 2^32 = 2 → malloc(2) without fix + // Bit layout: stream=0, prefix 14×1 + 0 (n=15), value loop = 0x2AAAAAAC bits, + // compressed=0, firstindex=0 + uint8_t bytes[] = { + 0xFE, 0x7F, // stream bit + prefix (14 ones → 0) + 0xAC, 0xAA, 0xAA, 0x2A, 0x01, // blocksize value loop + compressed + firstindex + 0x00, // skip byte in decodeBlockWithLength + 0x00, 0x00, 0x00, 0x00, // InitializeRangeCoder + 0x00, 0x00, 0x00, 0x00, // decode data (crash before any is consumed) + 0x00, 0x00, 0x00, 0x00, + }; + + NSException *caught = + [self caughtExceptionProducingBlockWithBytes:bytes + length:sizeof(bytes)]; + + XCTAssertNotNil(caught, @"Expected an exception, not a hard crash"); +} + +#pragma mark - Helpers + +- (NSException *)caughtExceptionProducingBlockWithBytes:(uint8_t *)bytes length:(size_t)length +{ + CSMemoryHandle *handle = + [CSMemoryHandle memoryHandleForReadingBuffer:bytes + length:(unsigned int)length]; + XADStuffItXIronHandle *ironHandle = + [[XADStuffItXIronHandle alloc] initWithHandle:handle + length:CSHandleMaxLength]; + NSException *caught = nil; + @try { + [ironHandle produceBlockAtOffset:0]; + } @catch (NSException *e) { + caught = e; + } + [ironHandle release]; + return caught; +} + +@end From 9fe60e457f47eec797536c3d76f8551a67f9c024 Mon Sep 17 00:00:00 2001 From: Aleksey Prykhodko Date: Tue, 2 Jun 2026 01:29:17 +0300 Subject: [PATCH 3/3] Use portable overflow check in XADStuffItXIronHandle --- XADStuffItXIronHandle.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/XADStuffItXIronHandle.m b/XADStuffItXIronHandle.m index 49f99c8d..f3845daa 100644 --- a/XADStuffItXIronHandle.m +++ b/XADStuffItXIronHandle.m @@ -101,14 +101,13 @@ -(int)produceBlockAtOffset:(off_t)pos if(blocksize>currsize) { - size_t allocsize; - bool allocOverflowed=__builtin_mul_overflow((size_t)blocksize,(size_t)6,&allocsize); - if(allocOverflowed) + // blocksize*6 must not overflow size_t. + if((size_t)blocksize>SIZE_MAX/6) { [XADException raiseIllegalDataException]; } free(block); - block=malloc(allocsize); + block=malloc((size_t)blocksize*6); if(!block) [XADException raiseOutOfMemoryException]; sorted=block+blocksize; table=(uint32_t *)(block+2*blocksize);