-
Notifications
You must be signed in to change notification settings - Fork 4
π‘οΈ Sentinel: [CRITICAL] Fix symlink vulnerability in atomic writes #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ## 2025-02-18 - Fix Predictable Temporary File Symlink Vulnerability | ||
|
|
||
| **Vulnerability:** The code previously used string concatenation (e.g., `path + ".tmp"`) with `os.WriteFile` and `os.Rename` for atomic file writes in shared directories like `os.TempDir()`. This pattern is vulnerable to symlink attacks, where an attacker could pre-create a symlink with the predictable name pointing to an arbitrary file they want to overwrite. | ||
|
|
||
| **Learning:** Predictable temporary file names in shared directories (like `/tmp`) are inherently insecure for atomic writes. Even if the content being written isn't malicious, the *location* being written to can be controlled via symlinks, leading to arbitrary file overwrite or corruption issues. Furthermore, creating a file with `os.CreateTemp` ensures the OS opens it with `O_EXCL`, preventing any prior symlink from being followed. Finally, on Windows, file handles must be explicitly closed before the `os.Rename` step, or it will fail. | ||
|
|
||
| **Prevention:** For atomic writes in shared directories, *always* use `os.CreateTemp` with an unpredictable pattern (like `prism-*.tmp`). Ensure the temporary file is created in the same directory as the target file (using `filepath.Dir()`) to avoid cross-device rename errors. Explicitly `Chmod` to match required permissions, handle the error gracefully by closing and removing the temporary file, and explicitly close the file descriptor before calling `os.Rename`. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -138,10 +138,31 @@ func Save(sessionID, metric string, b *Buffer) { | |||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| tmp := path + ".tmp" | ||||||||||||||||||||||||||||||||||||||
| if err := os.WriteFile(tmp, data, 0644); err != nil { | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Use os.CreateTemp to avoid symlink vulnerability with predictable names | ||||||||||||||||||||||||||||||||||||||
| tmpFile, err := os.CreateTemp(filepath.Dir(path), "prism-spark-*.tmp") | ||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| tmp := tmpFile.Name() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if _, err := tmpFile.Write(data); err != nil { | ||||||||||||||||||||||||||||||||||||||
| tmpFile.Close() | ||||||||||||||||||||||||||||||||||||||
| os.Remove(tmp) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // explicit close is important especially on Windows before rename | ||||||||||||||||||||||||||||||||||||||
| if err := tmpFile.Close(); err != nil { | ||||||||||||||||||||||||||||||||||||||
| os.Remove(tmp) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if err := os.Chmod(tmp, 0644); err != nil { | ||||||||||||||||||||||||||||||||||||||
| os.Remove(tmp) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+156
to
164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the improvement in
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| os.Rename(tmp, path) | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error returned by if err := os.Rename(tmp, path); err != nil {
os.Remove(tmp)
} |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid a potential TOCTOU (Time-of-check to time-of-use) race condition, it is safer to use
tmpFile.Chmod(0644)on the open file descriptor before closing it, rather than callingos.Chmod(tmpPath, 0644)on the file path after it has been closed. This ensures the permissions are applied to the exact file that was created, which is consistent with the security goals of this PR.