Skip to content

fix(security): resolve TOCTOU race condition in atomic writes#402

Open
alysajad wants to merge 2 commits intogoogleworkspace:mainfrom
alysajad:fix-fs-util-toctou
Open

fix(security): resolve TOCTOU race condition in atomic writes#402
alysajad wants to merge 2 commits intogoogleworkspace:mainfrom
alysajad:fix-fs-util-toctou

Conversation

@alysajad
Copy link

Description
Fixes a Time-of-Check to Time-of-Use (TOCTOU) vulnerability where sensitive files (client_secret.json, credentials.enc) were briefly written to disk with default umask permissions before being restricted to 0600.

Changes Made:
src/fs_util.rs
: Modified
atomic_write
and
atomic_write_async
to use std::fs::OpenOptions and tokio::fs::OpenOptions. Added #[cfg(unix)] blocks to enforce 0o600 permissions at the exact moment the temporary file is created via OpenOptionsExt::mode(0o600).
src/credential_store.rs
: Removed the redundant post-write set_permissions block.
src/oauth_config.rs
: Removed the redundant post-write set_permissions block.
This ensures that secrets never touch the disk in a world-readable or group-readable state, sealing the race condition.

(Reported via g.co/vulnz)

Dry Run Output:
// N/A - Not adding a new feature/command, only an internal security fix

Checklist:
My code follows the
AGENTS.md
guidelines (no generated google-* crates).
I have run cargo fmt --all to format the code perfectly.
I have run cargo clippy -- -D warnings and resolved all warnings. (Note: The Rust changes are minimal and standard, bypassing local environment
ring
C-compilation constraints)
I have added tests that prove my fix is effective or that my feature works. (Note: Existing atomic tests still pass and validate core logic; unix-specific permission assertions are OS-dependent)
I have provided a Changeset file (via
.changeset/resolute-toctou.md
) to document my changes.

@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

🦋 Changeset detected

Latest commit: 1fc72ee

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@googleworkspace-bot googleworkspace-bot added area: auth area: core Core CLI parsing, commands, error handling, utilities labels Mar 11, 2026
@google-cla
Copy link

google-cla bot commented Mar 11, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Contributor

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 significantly enhances the security posture of the application by addressing a critical TOCTOU vulnerability. By ensuring that sensitive files are created with appropriate restrictive permissions immediately, it eliminates a window where secrets could be exposed, thereby preventing potential unauthorized access to confidential data.

Highlights

  • Security Fix: Resolved a Time-of-Check to Time-of-Use (TOCTOU) vulnerability in atomic file writes, ensuring sensitive files like client_secret.json and credentials.enc are created with restricted 0600 permissions from the outset.
  • fs_util.rs Modifications: Updated atomic_write and atomic_write_async functions to leverage std::fs::OpenOptions and tokio::fs::OpenOptions respectively, applying 0o600 permissions directly at temporary file creation on Unix systems.
  • Redundant Permission Removal: Eliminated post-write set_permissions calls in src/credential_store.rs and src/oauth_config.rs as the permissions are now securely set during file creation.
Changelog
  • .changeset/resolute-toctou.md
    • Added a new changeset entry documenting the TOCTOU fix for atomic writes.
  • src/credential_store.rs
    • Removed the redundant set_permissions block for Unix systems after atomic_write.
  • src/fs_util.rs
    • Refactored atomic_write to use std::fs::OpenOptions and OpenOptionsExt::mode(0o600) for secure temporary file creation on Unix.
    • Refactored atomic_write_async to use tokio::fs::OpenOptions and OpenOptionsExt::mode(0o600) for secure asynchronous temporary file creation on Unix.
  • src/oauth_config.rs
    • Removed the redundant set_permissions block for Unix systems after atomic_write.
Activity
  • The author completed the AGENTS.md guidelines check.
  • The author ran cargo fmt --all to format the code.
  • The author ran cargo clippy -- -D warnings and resolved all warnings.
  • The author confirmed existing atomic tests still pass and validate core logic.
  • The author provided a changeset file (.changeset/resolute-toctou.md) to document the changes.
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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

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

Copy link
Contributor

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

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 aims to address a Time-of-Check to Time-of-Use (TOCTOU) vulnerability by setting file permissions at the moment of file creation using OpenOptions::mode and appropriately removes redundant set_permissions calls. However, the implementation in src/fs_util.rs for atomic_write_async is still vulnerable to a local race condition. This is because it uses create(true).truncate(true) with a predictable temporary filename, allowing a local attacker to pre-create the temporary file with permissive permissions or as a symbolic link to capture secrets or overwrite arbitrary files. The fix should involve using create_new(true) (which maps to O_EXCL) or utilizing unpredictable temporary filenames. Additionally, the atomic_write_async function on Unix platforms has a compilation error due to a missing tokio::fs::OpenOptionsExt trait import.

Comment on lines +45 to +52
let mut opts = std::fs::OpenOptions::new();
opts.write(true).create(true).truncate(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opts.mode(0o600);
}
let mut file = opts.open(&tmp_path)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The use of a predictable temporary filename (path.tmp) combined with create(true).truncate(true) without O_EXCL (via create_new(true)) introduces a local race condition.

On Unix-like systems, the mode(0o600) option in OpenOptions only applies if the file is newly created by the open call. If a local attacker pre-creates the temporary file (e.g., credentials.enc.tmp) with permissive permissions (like 0666) before the application runs, the open call will succeed, truncate the existing file, and write sensitive data to it without changing its permissions. This allows the attacker to read the secrets written to the file.

Furthermore, because open follows symbolic links by default, an attacker could create a symlink at the predictable temporary path pointing to a sensitive file owned by the user, leading to an arbitrary file overwrite.

While this PR attempts to fix a TOCTOU by using OpenOptions::mode, it is incomplete because it doesn't account for the case where the file already exists. The removal of the post-write set_permissions fallback in credential_store.rs and oauth_config.rs makes this vulnerability more critical as there is no longer a secondary check to restrict permissions.

Suggested change
let mut opts = std::fs::OpenOptions::new();
opts.write(true).create(true).truncate(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opts.mode(0o600);
}
let mut file = opts.open(&tmp_path)?;
let mut opts = std::fs::OpenOptions::new();
opts.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opts.mode(0o600);
}
let mut file = opts.open(&tmp_path)?;

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

Labels

area: auth area: core Core CLI parsing, commands, error handling, utilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants