Skip to content

Firecracker config written to predictable /tmp/fc.json — symlink attack and concurrent container corruption #689

@MayankSharmaCSE

Description

@MayankSharmaCSE

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:

  1. 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.

  1. 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.

  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateThis issue or pull request already existsinvalidThis doesn't seem right

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions