fix(events): catch FileNotFoundException in openBufferedReader to prevent TOCTOU crash#3704
Merged
tonidero merged 1 commit intoJul 6, 2026
Conversation
…vent TOCTOU crash EventsFileHelper.readFile guards with fileIsEmpty() before opening the event store file, but FileHelper.openBufferedReader opens it with FileInputStream(file) without re-checking existence. A concurrent flush/rotate that deletes ad_event_store.jsonl between the fileIsEmpty() check and the FileInputStream call throws an uncaught FileNotFoundException on the SDK background thread, killing the process. This is the RevenueCat#1 fatal crash on production builds that use AdMob/AppLovin ad trackers. Fix: catch FileNotFoundException in openBufferedReader and treat a missing file as empty — the same semantics fileIsEmpty() uses — logging at DEBUG level. The same pattern is already used in removeFirstLinesFromFile. Fixes RevenueCat#3696
tonidero
reviewed
Jul 6, 2026
tonidero
left a comment
Contributor
There was a problem hiding this comment.
Thank you so much for your contribution @tsushanth! This looks great! Just a very small nitpick comment and it should be good to get merged. Thank you again!
| // fileIsEmpty() before calling here, but a concurrent flush/rotate can delete | ||
| // the file between that check and FileInputStream(file), producing an uncaught | ||
| // FileNotFoundException on the SDK background thread that crashes the process. | ||
| // Treat a missing file as empty — the same semantics fileIsEmpty() uses. |
Contributor
There was a problem hiding this comment.
I believe we don't really need this comment, since the code is self-explanatory. Can you remove it?
Contributor
There was a problem hiding this comment.
Actually, we can handle it on our side on the merge PR :) We will get this merged now. Thanks again!
b71efbf
into
RevenueCat:external/tsushanth/fix/filereader-toctou-fnfe
3 checks passed
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.
Problem
Fixes #3696.
EventsFileHelper.readFileguards withfileIsEmpty()before opening the event store file, butFileHelper.openBufferedReaderthen callsFileInputStream(file)without re-checking existence. A concurrent flush/rotate that deletesad_event_store.jsonlbetweenfileIsEmpty()returningfalseandFileInputStream(file)throws an uncaughtFileNotFoundExceptionon the SDK background thread (PurchasesFactory$LowPriorityThreadFactory), crashing the app.From the reporter: this is the #1 fatal crash on production builds using AdMob/AppLovin ad trackers, with ~53 crashes / ~52 affected users over 30 days, reproduced on Android 14–16.
Crash stack:
Fix
Catch
FileNotFoundExceptioninopenBufferedReaderand treat a missing file as empty — the same semanticsfileIsEmpty()uses — logging atDEBUGlevel. The same pattern is already used inremoveFirstLinesFromFile(line 58).Also adds a regression test that calls
readFilePerLineson a nonexistent path and asserts no exception propagates and the block receives an empty sequence.Testing
FileHelperTestcovers the missing-file path.Note
Low Risk
Narrow defensive change in file I/O with empty-file semantics preserved; low blast radius beyond preventing a known production crash path.
Overview
Fixes a TOCTOU race where ad/event store reads could crash the app: callers check
fileIsEmpty()thenreadFilePerLines, but a concurrent flush/rotate can delete the file beforeFileInputStreamopens, yielding an uncaughtFileNotFoundExceptionon the SDK background thread.FileHelper.openBufferedReadernow catchesFileNotFoundException, logs at debug, and treats a missing file as empty (matchingfileIsEmpty()), aligning with the existing pattern inremoveFirstLinesFromFile.A regression test asserts
readFilePerLineson a nonexistent path does not throw and leaves the consumer with no lines.Reviewed by Cursor Bugbot for commit 801a0b5. Bugbot is set up for automated code reviews on this repo. Configure here.