When urunc launches a Firecracker VM, it writes the JSON config to a hardcoded path /tmp/fc.json every single time, regardless of which container is being started.
This causes three problems:
- Symlink attack (local privilege escalation)
Any process on the host can create a symlink at /tmp/fc.json pointing to an arbitrary file:
ln -s /etc/shadow /tmp/fc.json
When urunc starts the next Firecracker container, it follows the symlink and overwrites the target file with the VM config. Since os.WriteFile follows symlinks by default and the code doesn't check for them, an attacker can overwrite any file the urunc process has access to.
- Concurrent containers clobber each other
Every Firecracker container writes to the exact same path. If two containers start at roughly the same time, one overwrites the other's config before Firecracker reads it. A container could end up booting with a completely different kernel, rootfs, or network configuration than intended.
- Sensitive info left world-readable
The config file is created with 0o644 permissions (readable by everyone) and is never cleaned up after Firecracker starts. It contains kernel paths, boot arguments, network interfaces, MAC addresses, and vsock socket paths — all readable by any local user.
Where the problem is
pkg/unikontainers/hypervisors/firecracker.go, lines 112 and 198:
// predictable path, same for every container
JSONConfigFile := filepath.Join("/tmp/", FCJsonFilename) // always /tmp/fc.json
// no symlink check, no O_NOFOLLOW, no O_EXCL, world-readable
os.WriteFile(JSONConfigFile, FCConfigJSON, 0o644)
The //nolint: gosec comment on line 198 shows the linter was already flagging this.
Why this matters
This is a container runtime — it runs on shared hosts, Kubernetes nodes, and CI runners where other untrusted workloads may be present. A local attacker doesn't need root to exploit this; they just need to race the symlink creation before the next Firecracker container starts.
Suggested fix
Use os.CreateTemp or a container-specific temp directory instead of the shared /tmp. The codebase already has atomicWriteFile in pkg/unikontainers/utils.go (used for state.json) — the same pattern should apply here. Also add O_NOFOLLOW to prevent symlink following, and clean up the file after Firecracker reads it.
When urunc launches a Firecracker VM, it writes the JSON config to a hardcoded path
/tmp/fc.jsonevery single time, regardless of which container is being started.This causes three problems:
Any process on the host can create a symlink at
/tmp/fc.jsonpointing to an arbitrary file:ln -s /etc/shadow /tmp/fc.json
When urunc starts the next Firecracker container, it follows the symlink and overwrites the target file with the VM config. Since os.WriteFile follows symlinks by default and the code doesn't check for them, an attacker can overwrite any file the urunc process has access to.
Every Firecracker container writes to the exact same path. If two containers start at roughly the same time, one overwrites the other's config before Firecracker reads it. A container could end up booting with a completely different kernel, rootfs, or network configuration than intended.
The config file is created with 0o644 permissions (readable by everyone) and is never cleaned up after Firecracker starts. It contains kernel paths, boot arguments, network interfaces, MAC addresses, and vsock socket paths — all readable by any local user.
Where the problem is
pkg/unikontainers/hypervisors/firecracker.go, lines 112 and 198:
// predictable path, same for every container
JSONConfigFile := filepath.Join("/tmp/", FCJsonFilename) // always /tmp/fc.json
// no symlink check, no O_NOFOLLOW, no O_EXCL, world-readable
os.WriteFile(JSONConfigFile, FCConfigJSON, 0o644)
The //nolint: gosec comment on line 198 shows the linter was already flagging this.
Why this matters
This is a container runtime — it runs on shared hosts, Kubernetes nodes, and CI runners where other untrusted workloads may be present. A local attacker doesn't need root to exploit this; they just need to race the symlink creation before the next Firecracker container starts.
Suggested fix
Use os.CreateTemp or a container-specific temp directory instead of the shared /tmp. The codebase already has atomicWriteFile in pkg/unikontainers/utils.go (used for state.json) — the same pattern should apply here. Also add O_NOFOLLOW to prevent symlink following, and clean up the file after Firecracker reads it.