writeFile: missing O_EXCL allows symlink injection and concurrent-run collision on temp file #35

Closed
opened 2026-05-18 08:22:23 +02:00 by heiko · 1 comment
Owner

Summary

cmd/cert-proxy-client/cert/cert.go:332 creates the infixed temp file without O_EXCL:

file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)

The infixed filename (privkey-1747123456.pem) is derived from time.Now().Unix() (second granularity), making it predictable within a small window.

Two consequences

1. Symlink injection. A local attacker with write access to the certbase can pre-plant a symlink at the predictable infixed path. O_TRUNC follows the symlink; the private key is written to wherever the symlink points.

2. Concurrent-run collision. If two cert-proxy-client instances run within the same calendar second (e.g. a timer fires while a previous run is still in progress), they compute the same infix. Each O_TRUNC call clobbers the other's in-progress write, potentially producing a truncated key or cert file that the final symlink then points to.

Fix

Add O_EXCL when creating the temp file. This fails fast if the file already exists (including as a symlink) and forces the concurrent case to surface as an error rather than silent corruption:

file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode)

For the collision case, consider using os.CreateTemp or adding sub-second resolution (nanoseconds) to the infix.

## Summary `cmd/cert-proxy-client/cert/cert.go:332` creates the infixed temp file without `O_EXCL`: ```go file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) ``` The infixed filename (`privkey-1747123456.pem`) is derived from `time.Now().Unix()` (second granularity), making it predictable within a small window. ## Two consequences **1. Symlink injection.** A local attacker with write access to the certbase can pre-plant a symlink at the predictable infixed path. `O_TRUNC` follows the symlink; the private key is written to wherever the symlink points. **2. Concurrent-run collision.** If two `cert-proxy-client` instances run within the same calendar second (e.g. a timer fires while a previous run is still in progress), they compute the same infix. Each `O_TRUNC` call clobbers the other's in-progress write, potentially producing a truncated key or cert file that the final symlink then points to. ## Fix Add `O_EXCL` when creating the temp file. This fails fast if the file already exists (including as a symlink) and forces the concurrent case to surface as an error rather than silent corruption: ```go file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode) ``` For the collision case, consider using `os.CreateTemp` or adding sub-second resolution (nanoseconds) to the infix.
Author
Owner

AI attribution comment added per repository instruction for this open issue.\n\n(co)authored by ai:gpt-5-codex

AI attribution comment added per repository instruction for this open issue.\n\n(co)authored by ai:gpt-5-codex
heiko closed this issue 2026-05-24 19:59:29 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
heiko/cert-proxy#35
No description provided.