Skip to content

Firecracker config always emits an empty vsock device (omitempty no-op on struct field) #811

Description

@naman79820

Description

When generating the Firecracker JSON config, the vsock device is always written into the config, even when vAccel/vsock is not in use. The FirecrackerConfig.VSock field is tagged json:"vsock,omitempty", but omitempty has no effect on a struct value in Go's encoding/json a struct is never considered "empty" so the key is always marshalled.

As a result, every non-vAccel Firecracker boot emits:

"vsock":{"guest_cid":0,"uds_path":"","vsock_id":""}

This is an invalid/meaningless vsock device: guest CID 0 is reserved (valid guest CIDs start at 3) and uds_path is empty. The code intends the opposite, the field is only populated under if args.VAccelType == "vsock" and tagged omitempty so the section should only appear when vAccel is configured.

Note: this is incorrect config generation, not a boot failure, Firecracker currently tolerates the empty section (the non-vAccel e2e tests pass on main). The issue is that the generated config contradicts the code's clear intent.

Relevant code: pkg/unikontainers/hypervisors/firecracker.go:

  • Line 76: VSock FirecrackerVSockDev \json:"vsock,omitempty"`` (struct value)
  • Lines 178-193: the vsock struct is left as its zero value unless args.VAccelType == "vsock", but is always assigned into the config.

The sibling NetIfs []FirecrackerNet field works correctly because omitempty does apply to empty slices. The same "don't emit config that isn't needed" issue was already fixed for the network section in #310 this is the same class of bug for vsock.

Introduced in commit 1c9c720 ("feat: add vAccel support to urunc").

System info

  • urunc version: 0.7.0 (also present on main)
  • Arch: any (config-generation logic, architecture-independent)
  • VMM: Firecracker
  • Unikernel: any (reproduces for any Firecracker guest not using vAccel)

Steps to reproduce

The bug is deterministic and can be shown without running Firecracker, since it's purely in JSON config generation.

  1. Run any non-vAccel Firecracker container (e.g. a hello/nginx Firecracker image) and inspect the generated config (the "Firecracker json config" debug log line). The vsock key is present with guest_cid: 0 and an empty uds_path.

  2. Minimal standalone reproduction of the marshalling behaviour:

    package main

    import (
        "encoding/json"
        "fmt"
    )

    type VSockDev struct {
        GuestCID int    `json:"guest_cid"`
        UDSPath  string `json:"uds_path"`
        VSockID  string `json:"vsock_id"`
    }
    type Config struct {
        VSock VSockDev `json:"vsock,omitempty"`
    }

    func main() {
        b, _ := json.Marshal(Config{}) // no vAccel -> zero value
        fmt.Println(string(b))
    }
Output: `{"vsock":{"guest_cid":0,"uds_path":"","vsock_id":""}}`
Expected with a working `omitempty`: `{}`

Suggested fix

Make VSock a pointer so omitempty takes effect, and only set it in the vAccel branch:

// struct
VSock *FirecrackerVSockDev `json:"vsock,omitempty"`

// in BuildExecCmd
var FCVSockDev *FirecrackerVSockDev
if args.VAccelType == "vsock" {
    FCVSockDev = &FirecrackerVSockDev{ ... }
}
// leave nil otherwise -> vsock key omitted

Verified: with the pointer change, the no-vsock case marshals to {}, and the vAccel case still emits the full vsock section.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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