Skip to content

[unified_analytics] Guard logFileStats against oversized or corrupted log files - #2575

Open
hkarmoush wants to merge 1 commit into
dart-lang:mainfrom
hkarmoush:unified-analytics-guard-log-file-stats-275
Open

[unified_analytics] Guard logFileStats against oversized or corrupted log files#2575
hkarmoush wants to merge 1 commit into
dart-lang:mainfrom
hkarmoush:unified-analytics-guard-log-file-stats-275

Conversation

@hkarmoush

Copy link
Copy Markdown
Contributor

Summary

Fixes #275.

LogHandler.logFileStats() read the entire telemetry log file into memory via readAsLinesSync() with no size check and no exception handling. This is the same file LogHandler.save() already guards against via kMaxLogFileSize, but logFileStats() had no such protection.

An abnormally large or corrupted log file (see the crash report linked from the issue, flutter/flutter#150137) could cause an OOM ("Exhausted heap space") or an uncaught FileSystemException (e.g. invalid UTF-8 during decode) when read this way.

This PR:

  1. Returns null from logFileStats() without reading the file when logFile.statSync().size > kMaxLogFileSize, mirroring the existing guard in save().
  2. Wraps the read in a try/on FileSystemException and records an Event.analyticsException in errorSet, following the existing pattern already used in this method for per-record FormatException/TypeError handling, so the failure remains observable via the package's existing error-reporting mechanism.

logFileStats() already returns null in other failure modes (empty file, all-malformed records), so this doesn't introduce a new contract surprise for callers — AnalyticsImpl.fetchAvailableSurveys() and the public Analytics.logFileStats() API already null-check the result.

Test plan

  • Added a test asserting the size guard short-circuits before ever calling readAsLinesSync() (using the existing _FakeFile test double already used for the equivalent save() tests).
  • Added a test asserting a FileSystemException thrown during read is caught, logFileStats() returns null, and the expected Event.analyticsException is recorded in errorSet.
  • dart test — all 186 tests pass.
  • dart analyze — no issues.
  • dart format — no changes needed.

… log files

LogHandler.logFileStats() read the entire log file into memory with no
size check and no exception handling, unlike LogHandler.save() which
already guards against files over kMaxLogFileSize. An abnormally large
or corrupted telemetry log (e.g. invalid UTF-8) could exhaust the heap
or throw an uncaught FileSystemException, as seen in flutter/flutter#150137.

Fixes dart-lang#275

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates LogHandler.logFileStats to avoid reading log files into memory if they exceed kMaxLogFileSize and gracefully handles FileSystemExceptions by logging them as analytics exceptions. Feedback on the changes suggests that if the log file does not exist, statSync().size returns -1, which would bypass the size check and throw an expected FileSystemException that gets incorrectly logged as an error. A code suggestion is provided to handle this case by returning null when size < 0.

// Avoid reading abnormally large (or corrupted) log files into
// memory, which can exhaust the heap; see
// https://github.com/dart-lang/tools/issues/275.
if (logFile.statSync().size > kMaxLogFileSize) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the log file does not exist (which is a common and expected state, e.g., when a user is opted out or before any events are logged), logFile.statSync().size returns -1 per the Dart SDK specification.

Currently, the code only checks if the size is greater than kMaxLogFileSize. Since -1 is not greater, it proceeds to call logFile.readAsLinesSync(), which throws a FileSystemException (file not found). This exception is then caught and logged as an analyticsException in errorSet, leading to false-positive error reports being sent to Google Analytics.

Checking if size < 0 allows us to safely and quietly return null when the file does not exist, avoiding unnecessary exceptions and false-positive error reports. This approach is also fully compatible with the existing _FakeFile and _FakeFileStat test doubles since they already implement size.

Suggested change
if (logFile.statSync().size > kMaxLogFileSize) return null;
final size = logFile.statSync().size;
if (size < 0 || size > kMaxLogFileSize) return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be addressed.

Event.analyticsException(
workflow: 'LogHandler.logFileStats',
error: err.runtimeType.toString(),
description: 'message: ${err.message}\npath: ${err.path}',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file path can't be sent. It might include PII or proprietary information.

// Avoid reading abnormally large (or corrupted) log files into
// memory, which can exhaust the heap; see
// https://github.com/dart-lang/tools/issues/275.
if (logFile.statSync().size > kMaxLogFileSize) return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

package:unified_analytics can OOM opening log file

2 participants