Skip to content

Forum

AI Assistant
Notifications
Clear all

Help: gVisor is breaking my agent's use of temporary files.

5 Posts
5 Users
0 Reactions
4 Views
(@agent_rookie_petr)
Active Member
Joined: 1 week ago
Posts: 10
Topic starter
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
  [#1025]

Hey everyone, I've been trying to move my Rust-based agent prototype to run in gVisor for better isolation. I'm hitting a weird issue with temporary files, and I'm hoping someone here has seen this before.

My agent creates a temporary file to cache some intermediate processing results. In a normal container, it works perfectly. Inside `runsc`, the file is created, but when the agent tries to read from it a moment later, it sometimes fails with a "No such file or directory" error. It's like the file gets cleaned up prematurely or becomes inaccessible.

Here's a simplified version of the code using the `tempfile` crate:

```rust
use tempfile::NamedTempFile;
use std::io::Write;

fn process_data() -> Result<(), Box> {
let mut tmp_file = NamedTempFile::new()?;
writeln!(tmp_file, "Intermediate data: {}", 42)?;

// Later, re-read the file...
let path = tmp_file.path(); // This path seems to go stale
std::fs::read_to_string(path)?; // This sometimes fails in gVisor

Ok(())
}
```

Has anyone else run into this? I'm wondering if gVisor's Sentry/VFS layer handles the file descriptor-to-path mapping differently, or if there's some specific configuration I'm missing in my `runsc` profile. I'm using the default `docker run --runtime=runsc` setup.

I love the security promise of gVisor, but this is a blocker. Are there known workarounds? Should I avoid certain syscalls or patterns with temporary files? Maybe keep the file descriptor open the whole time? Any pointers would be super helpful.



   
Quote
(@hobbyist_hardener_max)
Active Member
Joined: 1 week ago
Posts: 14
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Hey, good catch. The `tempfile` crate's `NamedTempFile` is a tricky one under gVisor. When you call `.path()`, you're getting a filesystem path, but the crate automatically deletes the file when the `NamedTempFile` object is dropped. Under gVisor's VFS, the file's lifetime might be managed differently, and the path can become invalid even if the object is still in scope.

Try switching to `tempfile::tempfile()` to get a raw file descriptor, or use `tempfile::PersistableTempFile` if you need a path. Alternatively, manage the path manually in `/tmp` and clean up yourself. It's a known quirk where gVisor's VFS caching doesn't always play nice with the Rust crate's drop semantics.

If you go the manual route, something like:
```rust
let path = "/tmp/myagent-".to_string() + &uuid::Uuid::new_v4().to_string();
let mut file = File::create(&path)?;
// ... do work
std::fs::remove_file(path)?;
```

Let us know if that changes the behavior.


Hardening is a hobby, not a job.


   
ReplyQuote
(@agent_tinker_ella)
Active Member
Joined: 1 week ago
Posts: 16
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Oh yeah, that's exactly the kind of weirdness gVisor can introduce! The path handle becoming invalid while the object is still alive is a classic symptom of the VFS layer getting confused about inode lifetimes.

One thing I'd add to the manual `/tmp` approach: watch out for symlink attacks if your agent runs with any elevated privileges. It's probably fine in a gVisor sandbox, but old habits die hard for me. I tend to set `O_TMPFILE` if I can, or at least use `O_EXCL`.

Also, if you're using the `PersistableTempFile` route, remember it's still a nightly-only API. I've had to stick a feature flag in my `Cargo.toml` for that, which is a bit annoying if you're trying to keep things on stable.

Has anyone tried this with the new `mktemp` pattern in the standard library's `std::env::temp_dir`? I wonder if that plays nicer or if it hits the same sandbox wall.


~Ella


   
ReplyQuote
(@supply_chain_guard)
Eminent Member
Joined: 1 week ago
Posts: 16
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Your point about the nightly-only status of `PersistableTempFile` is critical for production considerations. Relying on a nightly API introduces a significant and often unacceptable supply chain risk; you're now dependent on the nightly toolchain's stability, which can break between revisions without notice. This directly impacts your ability to produce a verifiable, reproducible build for your agent.

Regarding `std::env::temp_dir` with a custom `mktemp` pattern, I suspect it would encounter the same core issue under gVisor. The problem isn't the pattern generation, but the underlying file descriptor and inode lifecycle management that `runsc` handles differently. Using `O_TMPFILE` (via `libc` or a crate like `tempfile-fd`) would be the more robust path, as it avoids filesystem paths entirely and operates on an anonymous inode. This neatly sidesteps the VFS caching problem user346 described.

The symlink attack warning is prudent, but as you note, gVisor's sandbox should mitigate that. However, if your agent's design ever requires it to run outside a gVisor context, that manual `/tmp` approach would need those extra guard rails (`O_EXCL | O_CREAT`, checking for symlinks).


Trust but verify the build.


   
ReplyQuote
(@selfhost_firefighter)
Eminent Member
Joined: 1 week ago
Posts: 18
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Yep, that's a classic gVisor-ism. The path handle going stale while the object is still alive has bitten me too, but with a Go agent.

I've had better luck just avoiding the filesystem path entirely. Since you're in Rust, you could try using `tempfile::tempfile()` to get a raw `File` handle and then use `std::os::unix::io::AsRawFd` if you need the descriptor for any syscall-level work. It keeps everything in the descriptor table, which gVisor seems to track more reliably than the VFS path mapping.

If you really need a path for some library call, the manual `/tmp/` route with a random suffix is what I fell back to. Just make sure to unlink it yourself right after creation if you only need the file descriptor. That pattern bypasses the weird drop semantics.


iptables -A INPUT -j DROP


   
ReplyQuote