Conversation
|
Hi @daledah! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
react/react-native#58566 is now open, so replace the 'not filed' placeholder with the link and keep the note that the change is still absent from every released RN version. Co-authored-by: daledah <daledah@users.noreply.github.com>
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
Summary:
RCTBlobManager'sresolve:offset:size:slices the storedNSDatawith the offset and size that come from the JS-side blob descriptor, without ever range-checking them against the data it actually holds:When the descriptor and the stored bytes disagree,
subdataWithRange:raisesNSRangeException. That is fatal rather than recoverable, becausecreateFromParts:withId:runs on the TurboModule method queue (com.meta.react.turbomodulemanager.queue) with no JS frame on the stack to catch it, so the app is terminated.This has been reported repeatedly since 2018 and is still unfixed on
main:range {0, 1769328} exceeds data length 458(aws-sdk uploads). Closed by the bot in 2022 with the code unchanged.fetch/blob uploads.What makes it worth guarding at the slice itself is that the reports come from at least three unrelated producers:
sizeexceeded the stored bytes - fixed separately in Fix Text truncation at NULL character on iOS and Android (#24129) #57906.Each of those was (or will be) fixed one at a time, and each time the symptom was a hard crash in the same three lines. A descriptor that disagrees with the stored bytes is a data bug; it should not be a fatal one.
What this changes
resolve:offset:size:clamps the range to the bytes that are actually stored. An out-of-bounds offset returnsnil; a size that overruns the available bytes is truncated to what is there. Both cases log viaRCTLogWarnwith the blob ID and the two lengths, so the underlying desync is visible instead of silent.size < 0still means "the rest of the blob", preserving the existing-1contract.createFromParts:withId:gainsnilguards with the same logging. A part that fails to resolve, or a string part that fails to encode, previously contributed zero bytes with no signal at all - which is itself one of the ways a blob ends up shorter than its descriptor.This also fixes a latent crash on the existing code path: with
offset > 0andsize == -1, the old condition entered the branch and calledNSMakeRange(offset, (NSUInteger)-1), which is an enormous length.resolveURL:defaultssizeto-1when the query parameter is absent, so ablob:URL built by hand rather than byURL.createObjectURLwould hit it.Truncate rather than zero-pad
Android is already forgiving here -
BlobModule.ktusesArrays.copyOfRange, which zero-pads when the requested end is past the array, which is why this crash is iOS-only. This change deliberately truncates instead of matching that zero-padding: padding a short read with NUL bytes silently corrupts the payload with plausible-looking data, whereas a short read is at least a length mismatch that a server or a checksum can detect. Happy to switch to zero-padding for cross-platform symmetry if maintainers prefer it.Changelog:
[IOS] [FIXED] - Clamp blob ranges in
RCTBlobManagerinstead of raisingNSRangeExceptionTest Plan:
The two producers above that have already been fixed cannot be used to reproduce this on
mainany more, so the repro below creates the descriptor/data disagreement directly. It is deterministic and does not depend on which producer caused it.In RNTester, or any app:
Before: the app terminates.
After: the app stays up, the blob resolves to the 318 bytes that are actually stored, and the console shows:
Formatting: