Skip to content

fix(encode): create temp files in a private directory - #237

Closed
LeadcodeDev wants to merge 1 commit into
fix/audio-signal-specfrom
fix/encode-temp-dir
Closed

LeadcodeDev wants to merge 1 commit into
fix/audio-signal-specfrom
fix/encode-temp-dir

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Severity Low, category security. Location: crates/rustmotion/src/encode/video/ffmpeg.rs:452

Impact

The path is fully derived from PID plus a counter starting at 0, in a shared, world-writable directory. create_dir_all succeeds on an already-existing directory (or a symlink to one) and fs::write follows symlinks, so a local attacker who pre-creates /tmp/rustmotion_audio_<pid>_0/audio.raw as a symlink gets an arbitrary file truncated and overwritten with PCM bytes under the rendering user's identity; PID space is small enough to pre-seed exhaustively. The same class exists in video_audio.rs:258, std::env::temp_dir().join(format!("rustmotion_vidaud_{:016x}.wav", hash)), where hash comes from DefaultHasher::new() (SipHash seeded with fixed keys 0,0 — deterministic across processes and machines), so the path is exactly predictable. There, if wav_path.exists() { return Some(wav_path) } (line 298) means a pre-planted file is used verbatim as the render's audio, and the .partial.wav sibling is handed to ffmpeg -y (line 339-340), which follows a symlink at that path.

Fix

Create the scratch directory with std::fs::create_dir (fails with AlreadyExists rather than adopting a hostile one) plus a random suffix, or adopt the tempfile crate for O_EXCL creation with a random name and RAII cleanup. For the video-audio WAV cache, put it under a per-user, non-world-writable directory (e.g. dirs::cache_dir()) instead of temp_dir(), and verify the cache entry is a regular file rather than trusting exists().

Evidence the audit read

Some(std::env::temp_dir().join(format!("rustmotion_audio_{}_{seq}", std::process::id())))
...
            std::fs::create_dir_all(tmp_dir)?;
...
            let audio_path = tmp_dir.join("audio.raw");
            std::fs::write(&audio_path, pcm)?;

Stacked on fix/audio-signal-spec, which carries the previous finding of this workstream. GitHub shows only this finding's diff; merge in order.

Part of the September 2026 audit remediation chantier. Refs #220 (RM-44).

The path is fully derived from PID plus a counter starting at 0, in a shared, world-writable directory. `create_dir_all` succeeds on an already-existing directory (or a symlink to one) and `fs::write` follows symlinks, so a local attacker who pre-creates `/tmp/rustmotion_audio_<pid>_0/audio.raw` as a symlink gets an arbitrary file truncated and overwritten with PCM bytes under the rendering user's identity; PID space is small enough to pre-seed exhaustively. The same class exists in `video_audio.rs:258`, `std::env::temp_dir().join(format!("rustmotion_vidaud_{:016x}.wav", hash))`, where `hash` comes from `DefaultHasher::new()` (SipHash seeded with fixed keys 0,0 — deterministic across processes and machines), so the path is exactly predictable. There, `if wav_path.exists() { return Some(wav_path) }` (line 298) means a pre-planted file is used verbatim as the render's audio, and the `.partial.wav` sibling is handed to `ffmpeg -y` (line 339-340), which follows a symlink at that path.

Refs #220
@LeadcodeDev

Copy link
Copy Markdown
Owner Author

Closed and reopened as a fresh pull request: GitHub registered this one as part of a stack, and a stacked pull request cannot be merged through either the GraphQL or the REST merge endpoint. The finding itself is unchanged.

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant