Skip to content

[EXTERNAL] fix(events): catch FileNotFoundException in openBufferedReader to prevent TOCTOU crash (#3704) contributed by @tsushanth#3719

Merged
tonidero merged 4 commits into
mainfrom
external/tsushanth/fix/filereader-toctou-fnfe
Jul 6, 2026
Merged

[EXTERNAL] fix(events): catch FileNotFoundException in openBufferedReader to prevent TOCTOU crash (#3704) contributed by @tsushanth#3719
tonidero merged 4 commits into
mainfrom
external/tsushanth/fix/filereader-toctou-fnfe

Conversation

@tonidero

@tonidero tonidero commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

Fixes #3696.

EventsFileHelper.readFile guards with fileIsEmpty() before opening the event store file, but FileHelper.openBufferedReader then calls FileInputStream(file) without re-checking existence. A concurrent flush/rotate that deletes ad_event_store.jsonl between fileIsEmpty() returning false and FileInputStream(file) throws an uncaught FileNotFoundException on 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:

Fatal Exception: java.io.FileNotFoundException: .../event_store/ad_event_store.jsonl: open failed: ENOENT
    at FileHelper.openBufferedReader(FileHelper.kt:80)
    at FileHelper.readFilePerLines(FileHelper.kt:39)
    at EventsFileHelper.readFile(EventsFileHelper.kt:61)
    at EventsManager.getStoredEvents(EventsManager.kt:422)
    at EventsManager.flushNextBatch(EventsManager.kt:277)

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 (line 58).

} catch (e: FileNotFoundException) {
    debugLog { "FileHelper: file not found when trying to read: $filePath. Treating as empty." }
}

Also adds a regression test that calls readFilePerLines on a nonexistent path and asserts no exception propagates and the block receives an empty sequence.

Testing

  • New unit test in FileHelperTest covers the missing-file path.
  • Existing tests all still pass.

Contributed by @tsushanth in #3704


Note

Low Risk
Narrow I/O hardening with empty-read semantics; affects event/diagnostics file reads but reduces crash risk rather than changing happy-path behavior.

Overview
Fixes a production crash when the ad/event store file disappears between an existence check and opening it for read (e.g. concurrent flush/rotate on the SDK background thread).

FileHelper.openBufferedReader now wraps the FileInputStream open in a try/catch for FileNotFoundException, logs at debug, and does not invoke the read callback—same “missing file = empty” semantics as fileIsEmpty(), aligned with how removeFirstLinesFromFile already handled missing files at a higher layer.

Adds a FileHelperTest regression that reads a nonexistent path and asserts no exception and no lines. Removes an EventsFileHelperTest that expected a REMOVE_LINES_EXCEPTION debug event when clearing a missing file, since that path no longer surfaces FileNotFoundException from the reader.

Reviewed by Cursor Bugbot for commit 9419fba. Bugbot is set up for automated code reviews on this repo. Configure here.

…vent TOCTOU crash (#3704)

## Problem

Fixes #3696.

`EventsFileHelper.readFile` guards with `fileIsEmpty()` before opening
the event store file, but `FileHelper.openBufferedReader` then calls
`FileInputStream(file)` without re-checking existence. A concurrent
flush/rotate that deletes `ad_event_store.jsonl` between `fileIsEmpty()`
returning `false` and `FileInputStream(file)` throws an **uncaught
`FileNotFoundException`** on 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:
```
Fatal Exception: java.io.FileNotFoundException: .../event_store/ad_event_store.jsonl: open failed: ENOENT
    at FileHelper.openBufferedReader(FileHelper.kt:80)
    at FileHelper.readFilePerLines(FileHelper.kt:39)
    at EventsFileHelper.readFile(EventsFileHelper.kt:61)
    at EventsManager.getStoredEvents(EventsManager.kt:422)
    at EventsManager.flushNextBatch(EventsManager.kt:277)
```

## 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` (line 58).

```kotlin
} catch (e: FileNotFoundException) {
    debugLog { "FileHelper: file not found when trying to read: $filePath. Treating as empty." }
}
```

Also adds a regression test that calls `readFilePerLines` on a
nonexistent path and asserts no exception propagates and the block
receives an empty sequence.

## Testing

- New unit test in `FileHelperTest` covers the missing-file path.
- Existing tests all still pass.

<!-- CURSOR_SUMMARY -->
---

> [!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()` then `readFilePerLines`, but a
concurrent flush/rotate can delete the file before `FileInputStream`
opens, yielding an uncaught `FileNotFoundException` on the SDK
background thread.
> 
> **`FileHelper.openBufferedReader`** now catches
`FileNotFoundException`, logs at debug, and treats a missing file as
empty (matching `fileIsEmpty()`), aligning with the existing pattern in
`removeFirstLinesFromFile`.
> 
> A **regression test** asserts `readFilePerLines` on a nonexistent path
does not throw and leaves the consumer with no lines.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
801a0b5. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@tonidero tonidero added the pr:fix A bug fix label Jul 6, 2026
@tonidero tonidero marked this pull request as ready for review July 6, 2026 08:26
@tonidero tonidero requested a review from a team as a code owner July 6, 2026 08:26
@tonidero tonidero changed the title fix(events): catch FileNotFoundException in openBufferedReader to prevent TOCTOU crash (#3704) contributed by @tsushanth [EXTERNAL] fix(events): catch FileNotFoundException in openBufferedReader to prevent TOCTOU crash (#3704) contributed by @tsushanth Jul 6, 2026
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.50%. Comparing base (ae737e8) to head (9419fba).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3719      +/-   ##
==========================================
- Coverage   80.54%   80.50%   -0.04%     
==========================================
  Files         400      400              
  Lines       16595    16598       +3     
  Branches     2370     2370              
==========================================
- Hits        13367    13363       -4     
- Misses       2291     2302      +11     
+ Partials      937      933       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tonidero tonidero enabled auto-merge July 6, 2026 09:41
@tonidero tonidero added this pull request to the merge queue Jul 6, 2026
Merged via the queue into main with commit f12d1f1 Jul 6, 2026
36 checks passed
@tonidero tonidero deleted the external/tsushanth/fix/filereader-toctou-fnfe branch July 6, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash: FileHelper.openBufferedReader (adTracker)

3 participants