Skip to content

🛡️ Sentinel: [CRITICAL] Fix TOCTOU vulnerability and unbound memory DoS in file reads - #62

Open
ivangegovdve-sudo wants to merge 1 commit into
mainfrom
sentinel-fix-file-read-dos-and-toctou-9459008440490358241
Open

🛡️ Sentinel: [CRITICAL] Fix TOCTOU vulnerability and unbound memory DoS in file reads#62
ivangegovdve-sudo wants to merge 1 commit into
mainfrom
sentinel-fix-file-read-dos-and-toctou-9459008440490358241

Conversation

@ivangegovdve-sudo

Copy link
Copy Markdown
Owner

🚨 Severity

CRITICAL

💡 Vulnerability

A Time-of-Check to Time-of-Use (TOCTOU) vulnerability existed across four JSON file-backed adapters (CheckpointStore, JsonFilePracticeRepository, JsonFileProgressRepository, JsonFileProgressSnapshotStore). Before reading file contents, the codebase checked file size using path.stat().st_size and existence using path.exists(), followed by an unbounded read_text() operation. This is insecure because path.exists() allows non-regular device files (like /dev/zero) which incorrectly report 0 bytes, bypassing the size check entirely. Once the size check is bypassed, the subsequent unbounded read_text() continuously pulls data into memory, creating a critical Denial-of-Service (DoS) and out-of-memory vulnerability.

🎯 Impact

An attacker (or misconfigured process) could point a repository path to a continuous data stream or device file. Because the size check would pass trivially (returning 0 bytes), the application would attempt to read the entire infinite file into memory via read_text(), resulting in process crashes, unbounded memory consumption, and a full application Denial of Service.

🔧 Fix

  1. Replaced path.exists() with path.is_file() to explicitly restrict reads to regular files, completely rejecting device files.
  2. Eliminated the TOCTOU-prone st_size pre-check.
  3. Implemented a secure bounded read pattern: content = f.read(10 * 1024 * 1024 + 1) within a context manager.
  4. Manually enforced the limit by checking len(content) > 10 * 1024 * 1024 post-read.
  5. Logged this pattern in the .jules/sentinel.md journal to avoid future occurrences.

✅ Verification

  • All tests pass: uv run pytest
  • Formatting, typing, and linting rules satisfied: uv run ruff check . / uv run ruff format . / uv run mypy
  • Changes localized strictly to the 4 impacted adapter files (kept within 50 line modification limits).

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

…oS in file reads

Replaces naive `path.stat().st_size` and `path.exists()` checks with `path.is_file()` combined with a strict bounded read (`f.read(limit + 1)`) in JSON file-backed adapters. This prevents attackers from bypassing size limits using device files (e.g. `/dev/zero`) and completely mitigates out-of-memory DoS vulnerabilities caused by streaming large amounts of unconstrained data into memory via `read_text()`.

Updated `.jules/sentinel.md` journal with details on this specific pattern.

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@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 resolves a critical TOCTOU vulnerability and prevents potential Denial-of-Service attacks related to unbounded file reads in several JSON file-backed adapters. It enhances the security and stability of the application by ensuring that only regular files are processed with a strict size limit, mitigating the risk of out-of-memory errors and application crashes.

Highlights

  • TOCTOU Vulnerability Fix: Addressed a Time-of-Check to Time-of-Use (TOCTOU) vulnerability in JSON file-backed adapters by replacing path.exists() with path.is_file() and eliminating the st_size pre-check.
  • Denial-of-Service Prevention: Prevented potential Denial-of-Service (DoS) attacks by implementing a secure bounded read pattern with a 10MB limit for file reads.
  • Secure File Handling: Ensured that only regular files are processed, preventing the use of device files that could bypass size checks and lead to unbounded memory consumption.
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 provides a critical fix for a TOCTOU vulnerability that could lead to a Denial-of-Service attack. The approach of switching from path.exists() to path.is_file() and implementing a secure bounded read is excellent and effectively mitigates the risk. The changes are consistently applied across all four affected adapters.

I've added a few suggestions to improve maintainability by replacing the hardcoded file size limit with a constant. This is a minor point, but it will make the code cleaner and easier to manage in the long run. Overall, this is a great and important security enhancement.

Comment on lines +238 to +239
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

To improve maintainability and avoid magic numbers, it's a good practice to define the file size limit as a module-level constant. This makes it easier to find and update the value if needed, and ensures consistency across the codebase.

You could define _MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024 at the top of the file and use it here.

Suggested change
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:
content = f.read(_MAX_FILE_SIZE_BYTES + 1)
if len(content) > _MAX_FILE_SIZE_BYTES:

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

Similar to the other adapters, let's replace the magic number 10 * 1024 * 1024 with a shared constant like _MAX_FILE_SIZE_BYTES for better maintainability. This will centralize the configuration for the file size limit.

Suggested change
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:
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

To maintain consistency and avoid magic numbers, please consider defining 10 * 1024 * 1024 as a module-level constant (e.g., _MAX_FILE_SIZE_BYTES) and using it here. This improves readability and makes the limit explicit.

Suggested change
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:
content = f.read(_MAX_FILE_SIZE_BYTES + 1)
if len(content) > _MAX_FILE_SIZE_BYTES:

Comment on lines +42 to +43
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

Let's replace the hardcoded size limit 10 * 1024 * 1024 with a module-level constant (e.g., _MAX_FILE_SIZE_BYTES) to improve code clarity and make future changes easier. This should be applied consistently across all modified adapters.

Suggested change
content = f.read(10 * 1024 * 1024 + 1)
if len(content) > 10 * 1024 * 1024:
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