Fix crash: DecompressedData caches a dangling withUnsafeBytes pointer (SIGABRT, heap corruption)#2220
Open
ankuper wants to merge 1 commit into
Open
Conversation
DecompressedData.init derives src_ptr/src_size from a Data.withUnsafeBytes closure and stashes it into stream.pointee for later use across many read(bytes:count:) calls. Data's documentation is explicit that the pointer handed to withUnsafeBytes is only valid for the duration of that closure — Data's backing storage can move or be deallocated once it returns, leaving src_ptr dangling for every read() call after init. compression_stream_process then reads through that dangling pointer, corrupting the heap; malloc's own consistency checks catch the corruption later and abort() (confirmed on-device: SIGABRT inside libsystem_malloc.dylib, queue DCTMultiAnimationRenderer-FirstFrame, build 33196, ~40s after launch — loading the first frame of an animated sticker/emoji). Re-derive a live pointer inside a fresh withUnsafeBytes call on every read(), tracking how many source bytes compression_stream_process has already consumed (consumedSrcBytes) so each call resumes from the right offset. compression_stream_process's own src_size/dst_size bookkeeping is unchanged; only the pointer's lifetime handling is fixed.
rayessadat6-sys
approved these changes
Jul 2, 2026
| // Bytes of dataRange already consumed by compression_stream_process. src_ptr | ||
| // cannot be cached across calls: it must be re-derived from a live | ||
| // withUnsafeBytes pointer every time (see read(bytes:count:) below). | ||
| private var consumedSrcBytes = 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
DecompressedData.initderivesstream.pointee.src_ptr/src_sizefrom aData.withUnsafeBytesclosure and stashes the pointer for later use across many subsequentread(bytes:count:)calls.Data's documentation is explicit that the pointer handed towithUnsafeBytesis only valid for the duration of that closure — the backing storage can move or be deallocated once it returns, leavingsrc_ptrdangling for everyread()call afterinitreturns.compression_stream_processthen reads through that dangling pointer, corrupting the heap; malloc's own consistency checks catch the corruption later andabort(). Confirmed on-device:SIGABRTinsidelibsystem_malloc.dylib, on theDCTMultiAnimationRenderer-FirstFramequeue, ~40s after launch while loading the first frame of an animated sticker/emoji.Fix
Re-derive a live pointer inside a fresh
withUnsafeBytescall on everyread(), tracking how many source bytescompression_stream_processhas already consumed (consumedSrcBytes) so each call resumes from the correct offset.compression_stream_process's ownsrc_size/dst_sizebookkeeping is unchanged; only the pointer's lifetime handling is fixed.Testing
Verified against the crash report that triggered this (
SIGABRTvialibsystem_malloc.dylib, stack going throughDecompressedData.init/deinitandAnimationCacheItemAccessor.loadNextFrame()). Built and ran on-device (iPhone, iOS 26.5) with animated stickers/emoji loading normally post-fix, no regression observed.