fix(security): resolve TOCTOU race condition in atomic writes#402
fix(security): resolve TOCTOU race condition in atomic writes#402alysajad wants to merge 2 commits intogoogleworkspace:mainfrom
Conversation
🦋 Changeset detectedLatest 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 |
|
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. |
Summary of ChangesHello, 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
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| 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)?; |
There was a problem hiding this comment.
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.
| 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)?; |
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.