## Summary Zeroes written outside the heap buffer in RAR5 handler may lead to memory corruption.
## Tested Version [7-Zip 24.09](https://sourceforge.net/p/sevenzip/discussion/45797/thread/b95432c7ac/) ## Details ### Multi byte write heap buffer overflow in `NCompress::NRar5::CDecoder` RAR5 decoder attempts to fix corrupted items by filling them with zeroes. However a miscalculation [2] of the `rem` value in `My_ZeroMemory(_window + _winPos, (size_t)rem);` [1] call leads to zeroes written past the allocated buffer. ```cpp Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress)) { ... #define Z7_RAR_RECOVER_SOLID_LIMIT (1 << 20) ... { const UInt64 lzSize = _lzSize + _winPos; ... #if Z7_RAR_RECOVER_SOLID_LIMIT != 0 else if (lzSize < _lzEnd) { ... // we can report that recovering was made: // _lzError = LZ_ERROR_TYPE_HEADER; // We write zeros to area after corruption: if (_window) { UInt64 rem = _lzEnd - lzSize; // <------- 2 const size_t ws = _winSize; if (rem >= ws) { My_ZeroMemory(_window, ws); _lzSize = ws; _winPos = 0; } else { const size_t cur = ws - _winPos; if (cur <= rem) { rem -= cur; My_ZeroMemory(_window + _winPos, cur); _lzSize += _winPos; _winPos = 0; } My_ZeroMemory(_window + _winPos, (size_t)rem); // <-------- 1 _winPos += (size_t)rem; } } ... } #endif } ... _unpackSize = 0; _unpackSize_Defined = (outSize != NULL); if (_unpackSize_Defined) _unpackSize = *outSize; if ((Int64)_unpackSize >= 0) _lzEnd += _unpackSize; // known end after current file // <------------- 3 else _lzEnd = 0; // unknown end ... } ``` A PoC triggers heap buffer write overflow when `7zz` is compiled with ASAN and extracted, for example as `7zz e -so rar-crash.rar5`: ``` ==2188082==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7fc75fbcc844 at pc 0x5567af835070 bp 0x7fff7f71ce30 sp 0x7fff7f71c600 WRITE of size 9469 at 0x7fc75fbcc844 thread T0 #0 0x5567af83506f in __asan_memset /src/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:67:3 #1 0x5567b0167b0c in My_ZeroMemory(void*, unsigned long) /src/7-zip/CPP/7zip/Bundles/Alone2/../../Compress/Rar5Decoder.cpp:63:5 #2 0x5567b017c257 in NCompress::NRar5::CDecoder::Code(ISequentialInStream*, ISequentialOutStream*, unsigned long const*, unsigned long const*, ICompressProgressInfo*) /src/7-zip/CPP/7zip/Bundles/Alone2/../../Compress/Rar5Decoder.cpp:1905:11 #3 0x5567aff075c0 in NArchive::NRar5::CUnpacker::Code(NArchive::NRar5::CItem const&, NArchive::NRar5::CItem const&, unsigned long, ISequentialInStream*, ISequentialOutStream*, ICompressProgressInfo*, bool&) /src/7-zip/CPP/7zip/Bundles/Alone2/../../Archive/Rar/Rar5Handler.cpp:1165:24 #4 0x5567aff24721 in NArchive::NRar5::CHandler::Extract(unsigned int const*, unsigned int, int, IArchiveExtractCallback*) /src/7-zip/CPP/7zip/Bundles/Alone2/../../Archive/Rar/Rar5Handler.cpp:3293:25 #5 0x5567b0244c0b in DecompressArchive(CCodecs*, CArchiveLink const&, unsigned long, NWildcard::CCensorNode const&, CExtractOptions const&, bool, IExtractCallbackUI*, IFolderArchiveExtractCallback*, CArchiveExtractCallback*, UString&, unsigned long&) /src/7-zip/CPP/7zip/Bundles/Alone2/../../UI/Common/Extract.cpp:235:23 #6 0x5567b023fe41 in Extract(CCodecs*, CObjectVector<COpenType> const&, CRecordVector<int> const&, CObjectVector<UString>&, CObjectVector<UString>&, NWildcard::CCensorNode const&, CExtractOptions const&, IOpenCallbackUI*, IExtractCallbackUI*, IFolderArchiveExtractCallback*, IHashCalc*, UString&, CDecompressStat&) /src/7-zip/CPP/7zip/Bundles/Alone2/../../UI/Common/Extract.cpp:542:5 #7 0x5567b02f9d8a in Main2(int, char**) /src/7-zip/CPP/7zip/Bundles/Alone2/../../UI/Console/Main.cpp:1378:21 #8 0x5567b0305b34 in main /src/7-zip/CPP/7zip/Bundles/Alone2/../../UI/Console/MainAr.cpp:162:11 ``` On Windows the same PoC was tested to crash the official 7-Zip build even without ASAN. #### Impact The bytes past the allocated heap buffer are always overwritten with zeroes: `My_ZeroMemory(_window + _winPos, (size_t)rem)` [1], but `rem` is calculated as `UInt64 rem = _lzEnd - lzSize;` [2] where `_lzEnd` depends on the size of the previous item in archive which is attacker controlled: `_lzEnd += _unpackSize` [3]. Thus the attacker may control how many bytes to overwrite. It is unlikely it could lead to arbitrary code execution, but it may lead to denial of service because of the memory corruption. ## Credit This issue was discovered and reported by GHSL team member [@JarLob (Jaroslav Lobačevski)](https://github.com/JarLob). ## Coordinated Disclosure Timeline - 2025-04-24: Reported as a private issue - 2025-04-29: Report acknowledged - 2025-07-05: Fixed in v25.00