Skip to content

🛡️ Sentinel: [HIGH] Fix OOM DoS and TOCTOU in JSON file adapters - #72

Open
ivangegovdve-sudo wants to merge 1 commit into
mainfrom
sentinel-fix-toctou-oom-dos-14765083783185029734
Open

🛡️ Sentinel: [HIGH] Fix OOM DoS and TOCTOU in JSON file adapters#72
ivangegovdve-sudo wants to merge 1 commit into
mainfrom
sentinel-fix-toctou-oom-dos-14765083783185029734

Conversation

@ivangegovdve-sudo

Copy link
Copy Markdown
Owner

🚨 Severity: HIGH
💡 Vulnerability: The application relied on path.stat().st_size to enforce a 10MB limit before calling json.loads(path.read_text()). This is vulnerable to Time-of-Check to Time-of-Use (TOCTOU) race conditions and can be completely bypassed by character device files (e.g., /dev/zero), which report a size of 0 but can stream infinite data, leading to unbounded memory consumption (OOM DoS).
🎯 Impact: An attacker or misconfiguration pointing the application to a device file or a rapidly growing file could cause the application to crash due to out-of-memory errors, leading to denial of service.
🔧 Fix:

  1. Replaced path.exists() with path.is_file() to explicitly reject device files and directories.
  2. Replaced path.read_text() with a secure bounded read pattern content = f.read(10 * 1024 * 1024 + 1).
  3. Validated len(content) > 10 * 1024 * 1024 after reading to enforce the limit safely in memory.
    ✅ Verification: Ran uv run pytest, uv run ruff format ., uv run ruff check ., and uv run mypy. All checks passed. Logged learning to .jules/sentinel.md.

PR created automatically by Jules for task 14765083783185029734 started by @ivangegovdve-sudo

Replaced vulnerable `path.stat().st_size` checks with secure bounded reads (`f.read(limit + 1)`) and added `path.is_file()` validation across all file-backed JSON adapters to prevent out-of-memory denial-of-service via device files like `/dev/zero`.

Co-authored-by: ivangegovdve-sudo <225339531+ivangegovdve-sudo@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a high-severity security vulnerability related to potential Out-of-Memory (OOM) Denial-of-Service (DoS) attacks and Time-of-Check to Time-of-Use (TOCTOU) race conditions in JSON file adapters. The changes fortify the application's file reading mechanisms by introducing more robust file type validation and secure, bounded memory reads, thereby preventing an attacker or misconfiguration from causing application crashes due to excessively large or malicious input files.

Highlights

  • Enhanced File Validation: Replaced path.exists() with path.is_file() to explicitly reject device files and directories, preventing potential bypasses that could lead to OOM DoS.
  • Secure Bounded Reads: Implemented a secure bounded read pattern (f.read(limit + 1)) across JSON file adapters instead of path.read_text() to prevent unbounded memory consumption from large or malicious files.
  • Post-Read Size Validation: Moved the file size limit validation to after reading the content into a bounded buffer, ensuring the limit is enforced safely in memory and mitigating Time-of-Check to Time-of-Use (TOCTOU) vulnerabilities.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

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

Copy link
Copy Markdown

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 enhances the security of file-reading operations across several adapter classes by implementing a more robust mechanism to prevent Denial-of-Service (DoS) attacks. The changes involve replacing path.exists() with path.is_file() and path.stat().st_size checks with a bounded read approach (f.read(limit + 1)) to mitigate Time-of-Check to Time-of-Use (TOCTOU) vulnerabilities and safely handle device files. A new entry in .jules/sentinel.md documents this vulnerability and its prevention. The review suggests adding error handling for OSError and json.JSONDecodeError in the _read_json function for consistency and robustness, and extracting the hardcoded file size limit into a module-level constant across all affected files to improve maintainability.

Comment on lines +236 to 241
with path.open("r", encoding="utf-8") as f:
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:
raise ValueError(f"Checkpoint file {path} exceeds 10MB size limit")
parsed = json.loads(path.read_text(encoding="utf-8"))
parsed = json.loads(content)
return parsed if isinstance(parsed, dict) else {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This _read_json function does not handle potential OSError or json.JSONDecodeError, which could lead to unhandled exceptions if the file is corrupted or a read error occurs. Other file adapters in this PR, such as JsonFileProgressSnapshotStore, handle these exceptions by returning a default value. For consistency and robustness, it would be beneficial to wrap the logic in a try...except (OSError, json.JSONDecodeError) block and return {} in case of an error.

Comment on lines +237 to +238
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The file size limit 10 * 1024 * 1024 is used twice here. To improve maintainability and avoid magic numbers, consider defining it as a module-level constant. This would make the code cleaner and easier to update if the limit needs to change.

For example:

_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024  # 10MB

# ... inside _read_json
with path.open("r", encoding="utf-8") as f:
    content = f.read(_MAX_FILE_SIZE_BYTES + 1)
if len(content) > _MAX_FILE_SIZE_BYTES:
    # ...

This principle applies to the other file adapters modified in this pull request as well.

Comment on lines +111 to +112
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The file size limit 10 * 1024 * 1024 is hardcoded here. To improve maintainability and avoid magic numbers, it's recommended to define this value as a module-level constant and reuse it. This makes the code cleaner and simplifies future updates to the size limit.

For example:

_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024  # 10MB

# ... inside _load_storage
with self._file_path.open("r", encoding="utf-8") as f:
    content = f.read(_MAX_FILE_SIZE_BYTES + 1)
if len(content) > _MAX_FILE_SIZE_BYTES:
    # ...

Comment on lines +48 to +49
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The magic number 10 * 1024 * 1024 is used for the file size limit. It's better to extract this into a module-level constant to improve code clarity and make it easier to manage the value in one place.

For example:

_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024  # 10MB

# ... inside _load_storage
with self._file_path.open("r", encoding="utf-8") as f:
    content = f.read(_MAX_FILE_SIZE_BYTES + 1)
if len(content) > _MAX_FILE_SIZE_BYTES:
    # ...

Comment on lines +41 to +42
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The file size limit 10 * 1024 * 1024 is hardcoded. To enhance maintainability, this value should be defined as a constant at the module level. This avoids repetition and makes the limit's purpose clearer.

Example:

_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024  # 10MB

# ... inside _load_payload
with self._file_path.open("r", encoding="utf-8") as f:
    content = f.read(_MAX_FILE_SIZE_BYTES + 1)
if len(content) > _MAX_FILE_SIZE_BYTES:
    # ...

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant