Lab R4: Contribute to rust-vmm
Background
The previous three labs taught you the building blocks — kvm-ioctls, linux-loader,
virtio-queue — as standalone crates. Those crates are not Firecracker's; they belong to rust-vmm,
a separate open-source organization that maintains shared virtualization components consumed by
Firecracker, Cloud Hypervisor, crosvm-adjacent projects, and others. This is a contribution lab, not
a build-it lab. Its subject is a skill that separates a Firecracker contributor from a Firecracker
maintainer: knowing when a change belongs upstream in rust-vmm rather than in Firecracker, and
being able to land it through rust-vmm's distinct process — different repos, different CI (the
rust-vmm-ci coverage gate), the same DCO discipline, and a review model tuned for "this API must
serve several VMMs, not just one."
Firecracker has a deliberately close but arms-length relationship with rust-vmm. It consumes
kvm-ioctls, kvm-bindings, vm-memory, linux-loader, vm-superio, event-manager, vm-fdt,
and vmm-sys-util; it donated seccompiler, event-manager, and vm-superio upstream; and it
forks-in-tree some pieces (its own Queue, its own seccomp compiler tooling) where it needs control
the shared crate cannot give. Every serious Firecracker contributor eventually hits a change that
"should really be fixed upstream," and the ones who can carry that change across the org boundary —
and then coordinate the version bump back into Firecracker — are the ones who become maintainers.
Note: You do not need to land a real upstream PR to complete this lab (though that is the ultimate stretch goal). You need to understand the workflow well enough to do it and to walk one realistic example end to end. The deliverable is a written contribution plan plus a forked, green-CI branch — the work right up to the moment of opening the PR.
Why This Lab Matters for Contributors
- The what-is-rust-vmm chapter explains the relationship conceptually; this lab makes it operational — you will navigate the actual repos and CI.
- "Should this go in Firecracker or upstream?" is a real review question. Proposing a Firecracker change that belongs upstream (or vice versa) is a sign you do not understand the boundary; getting it right is a sign you do. The compatibility chapter and maintainership chapter lean on this judgment.
- A coordinated rust-vmm bump is one of the trickier PRs in the Firecracker repo (it can shift behavior across every device). Understanding the upstream side makes you the person who can drive it.
- The capstone may surface a root cause that lives in a rust-vmm crate. You need to know what to do when "the bug is not in Firecracker."
Prerequisites
- Labs R1–R3 complete: you have used
kvm-ioctls,linux-loader, andvirtio-queuedirectly and know what these crates do. - Level 2 complete: you can prepare a DCO-signed PR and know the Firecracker contribution flow, which you will now contrast with rust-vmm's.
git, a GitHub account, andgh(the GitHub CLI) configured.
gh --version
git config user.name && git config user.email # both set, for DCO sign-off
Step-by-Step Tasks
Step 1: Learn the rust-vmm org layout
Unlike Firecracker (one monorepo), rust-vmm is one GitHub repo per crate under the
rust-vmm org. Browse it and build a mental map:
# List the org's repos (each is a crate or shared infra):
gh repo list rust-vmm --limit 100 --no-archived
The repos you have already touched, plus the shared infrastructure:
| Repo | What it is | You used it in |
|---|---|---|
rust-vmm/kvm-ioctls, rust-vmm/kvm-bindings | KVM safe wrappers / raw bindings | Lab R1 |
rust-vmm/linux-loader | kernel load + boot protocol | Lab R2 |
rust-vmm/vm-virtio (contains virtio-queue) | virtqueue logic | Lab R3 |
rust-vmm/vm-memory | GuestMemoryMmap, GuestAddress | Labs R2–R3 |
rust-vmm/vm-superio | serial/i8042/RTC (donated by Firecracker) | serial console deep dive |
rust-vmm/event-manager | epoll loop (donated by Firecracker) | the VMM thread |
rust-vmm/vmm-sys-util | EventFd, ioctl macros | everywhere |
rust-vmm/rust-vmm-ci | the shared CI every crate pulls in as a submodule | (the gate you must pass) |
rust-vmm/community | governance, maintainer docs, the new-crate process | (read this) |
Note: Some crates are meta-repos:
virtio-queue,virtio-bindings, andvirtio-vsockall live insiderust-vmm/vm-virtio. When you changevirtio-queue, you open a PR againstvm-virtio. Confirm withgh repo view rust-vmm/vm-virtio.
Crates also have a maturity state — empty (under review), rust-vmm (WIP, not production), and
crates.io (published, production). Firecracker only depends on crates.io-state crates. A change to
a published crate is held to a higher bar than a change to a WIP one.
Step 2: Decide where a change belongs — the boundary test
Before writing anything, answer: Firecracker or upstream? Use this test.
| The change… | Belongs… | Because |
|---|---|---|
| Fixes the generic virtqueue/KVM/loader logic, useful to any VMM | rust-vmm | the abstraction is shared; other VMMs benefit and FC just bumps the version |
| Adds/clarifies docs or a small, generally-useful API on a shared crate | rust-vmm | low-risk, high-leverage upstream wins |
| Implements a Firecracker-specific device or policy (e.g. its block request parsing, its seccomp filters) | Firecracker | it is product behavior, not a shared primitive |
| Needs control rust-vmm won't grant (no-panic guarantees, FC-specific perf) | Firecracker (in-tree fork) | this is why FC forks its own Queue |
| Bumps a crate version to pick up an upstream fix | Firecracker (but the fix itself was upstream) | a coordinated two-step: land upstream, then bump in FC |
Warning — the classic mistake. Proposing in Firecracker a fix that is actually a bug in a shared crate. Reviewers will (rightly) ask you to fix it upstream and bump. Conversely, proposing in rust-vmm a change that encodes a Firecracker-specific assumption will be rejected as "too specific — this must serve multiple VMMs." Getting the boundary right before you write code saves a round trip and signals maturity.
Step 3: Find a real good-first-issue across the crates
rust-vmm issues are spread across repos, so search the whole org:
# Good-first-issues across every rust-vmm repo:
gh search issues --owner rust-vmm --label "good first issue" --state open --limit 40
# Or scope to crates Firecracker actually uses:
gh search issues --owner rust-vmm --state open "documentation" --limit 40
gh issue list --repo rust-vmm/vm-virtio --state open --limit 40
gh issue list --repo rust-vmm/linux-loader --state open --limit 40
Look for: doc gaps, a missing Debug/Display impl, an error message that drops context, a small
ergonomic accessor, or a clippy-flagged pattern. Doc and small-API improvements on a crate
Firecracker uses are the ideal first upstream contribution — high value, low risk, and they force
you to understand the public API surface.
Step 4: Walk a realistic example — improving a doc/API on linux-loader
Pick a concrete, low-risk target. Worked example: the KernelLoaderResult you used in Lab R2 has
fields (kernel_load, kernel_end, setup_header, pvh_boot_cap) whose doc comments could be
clearer about units and meaning — exactly the kind of confusion you hit in Lab R2. (Verify the
current state first; it may already be improved — never assume the gap still exists.)
git clone https://github.com/rust-vmm/linux-loader.git && cd linux-loader
# Confirm the doc/API state on the current tip before proposing anything:
rg -n "KernelLoaderResult|kernel_end|pvh_boot_cap" src/
gh issue list --repo rust-vmm/linux-loader --search "KernelLoaderResult OR docs"
The change shape (illustrative — adapt to whatever real gap you find):
#![allow(unused)] fn main() { /// The end of the kernel image in guest memory, as a guest-physical address. /// /// This is the first byte *after* the loaded kernel; the device tree blob and/or /// initrd are typically placed starting here. It is an absolute `GuestAddress`, /// not an offset relative to `kernel_load`. pub kernel_end: GuestAddress, }
Then make it pass locally and via the shared CI.
Step 5: Pass the rust-vmm-ci gate
Every rust-vmm crate vendors rust-vmm-ci as a git submodule and runs the same Buildkite
pipeline: build, unit tests, clippy/rustfmt style checks, and a code-coverage gate. The coverage
gate is the part that surprises Firecracker contributors:
git submodule update --init # pull rust-vmm-ci
# Local equivalents of the CI steps:
cargo fmt -- --check
cargo clippy --all --all-targets --all-features -- -D warnings
cargo test --all-features
The coverage gate is controlled by coverage_config_x86_64.json in the repo root, which records
the current coverage_score and exclude_path patterns. CI rejects any PR that lowers overall
coverage. Practical consequences:
- New functionality must come with tests, or coverage drops and CI fails. (Same spirit as Firecracker's "new functionality needs integration tests," enforced numerically.)
- If your change raises coverage, you may need to bump the
coverage_scorenumber up incoverage_config_x86_64.jsonso the new floor is recorded. If it legitimately lowers it (rare, e.g. you deleted tested code), you adjust it down with justification.
rg -n "coverage_score|exclude_path" coverage_config_x86_64.json
Tip: A docs-only change usually does not move coverage, which is part of why it is the ideal first PR. An API change almost always needs a test to keep coverage green.
Step 6: Sign off and open the PR (the DCO + review model)
rust-vmm uses the same DCO discipline as Firecracker — every commit signed off, enforced by a bot — and no CLA:
git checkout -b docs/clarify-kernel-loader-result
# ... make the change ...
git commit -s -m "loader: clarify KernelLoaderResult field docs" # -s adds Signed-off-by
git push origin docs/clarify-kernel-loader-result
gh pr create --repo rust-vmm/linux-loader --fill
How rust-vmm review differs from Firecracker review:
| Firecracker | rust-vmm | |
|---|---|---|
| Repos | one monorepo | one repo per crate |
| Sign-off | DCO git commit -s, no CLA | same |
| Reviewers | Firecracker maintainers (AWS team) | that crate's maintainers (cross-company: AWS, Intel, Red Hat, …) |
| Approval bar | ≥2 maintainer approvals | per-crate maintainers (see the repo's MAINTAINERS.md/CODEOWNERS) |
| CI | tools/devtool (pytest + cargo) | rust-vmm-ci (Buildkite, coverage gate) |
| Key review lens | minimal-device-model, FC threat model | "is this API general enough for multiple VMMs?" |
The dominant rust-vmm review question is generality: a reviewer from a different VMM will ask whether your API change works for their consumer, not just Firecracker's. Frame your PR that way.
Step 7: Coordinate the Firecracker bump (closing the loop)
A landed upstream fix is only half the job if Firecracker needs it. The second step is a coordinated version bump in Firecracker:
# In your Firecracker checkout, find the dependency and its current version:
rg -n "^linux-loader|^virtio-queue|^kvm-ioctls" ~/firecracker/Cargo.toml
# Bump it to the new release, then regenerate the lockfile and run the full gate:
# (edit Cargo.toml to the new version)
cd ~/firecracker
tools/devtool build
tools/devtool checkbuild --all
tools/devtool test # the bump must not regress the integration suite
A crate bump PR in Firecracker is reviewed for behavioral impact: did the new crate version change defaults, error types, or panics on any path Firecracker exercises? This is why bumps are sometimes held until a release window. Note the CHANGELOG entry Firecracker expects for a dependency bump.
Note: The two-PR dance (land upstream → release the crate → bump in Firecracker) can span days to weeks because it depends on an upstream release, not just a merge. Plan for it; do not promise a Firecracker reviewer a fix that is still unreleased upstream.
Implementation Requirements / Deliverables
- A written boundary analysis (5–10 sentences) for one concrete change, applying the Step 2 test to argue Firecracker vs. upstream, with the reasoning.
- A located, currently-open rust-vmm good-first-issue (or a verified real gap) on a crate Firecracker uses, with the repo and issue/PR link.
-
A forked branch of that crate with your change committed with DCO sign-off (
git commit -s), and localcargo fmt/clippy/testall green. -
Evidence you understand the coverage gate: the crate's
coverage_config_x86_64.jsoncoverage_scorevalue and whether your change moves it. -
A written coordination plan: if this fix needed to reach Firecracker, the exact two-step
(upstream PR → crate release → Firecracker
Cargo.tomlbump + CHANGELOG) and what the FC reviewer would check. - (Stretch) the upstream PR actually opened.
Troubleshooting
gh search issues --owner rust-vmm returns too much / too little
Scope it: add --repo rust-vmm/<crate> for one crate, or --label "good first issue", or a keyword
like documentation. Remember virtio-queue issues live under rust-vmm/vm-virtio.
CI fails on coverage even though tests pass
Your change added code paths without tests, lowering the percentage below the recorded
coverage_score. Add tests for the new path, or — if you genuinely added no logic (docs only) and CI
still complains — confirm you did not delete a tested example. Adjust coverage_config_x86_64.json
only with justification in the PR.
The DCO bot fails the PR
A commit lacks Signed-off-by. Fix with git commit --amend -s (or git rebase --signoff for
several commits) and force-push. Same flow as Firecracker.
Reviewer says "this is too Firecracker-specific"
You encoded an FC assumption into a shared API. Re-frame the change to be VMM-agnostic, or accept that it belongs in Firecracker's in-tree fork instead. This is the boundary test (Step 2) failing in review — exactly what you were trying to avoid.
My Firecracker bump compiles but a test regresses
The new crate version changed behavior. Read the crate's CHANGELOG between the old and new versions, identify the behavioral delta, and decide whether Firecracker code must adapt. This is the real risk in a bump PR and why reviewers scrutinize them.
Expected Output
There is no program output for this lab; the artifacts are a contribution plan and a green-CI branch. A successful local CI run looks like:
$ cargo fmt -- --check
$ cargo clippy --all --all-targets --all-features -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s)
$ cargo test --all-features
...
test result: ok. NN passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
$ git log --format='%h %s%n%b' -1
abc1234 loader: clarify KernelLoaderResult field docs
Signed-off-by: Your Name <you@example.com>
Stretch Goals
-
Actually open the PR. Take the branch from Step 6 all the way to an open PR against the crate's repo, respond to review, and (if accepted) see it merged. This is a real open-source contribution — add it to your portfolio.
-
Trace a real Firecracker bump in git history.
cd ~/firecracker && git log --oneline -- Cargo.toml | rg -i "bump|kvm-ioctls|virtio-queue|vm-memory|linux-loader". Read one such commit and its PR: what upstream change motivated it, and what did the FC reviewer check? -
Find a fork-vs-consume decision. Compare Firecracker's in-tree
src/vmm/src/devices/virtio/queue.rswith upstreamvirtio-queue(you used both in Lab R3). Write a one-paragraph argument for why Firecracker forks rather than consumes it — then check whether any upstream issue tracks closing that gap. -
Map the donation flow. Firecracker donated
seccompiler,event-manager, andvm-superioupstream. Pick one, read its early rust-vmm history, and summarize how a Firecracker-internal module became a shared crate. This is the reverse of consuming — and a path a maintainer sometimes drives.
Validation / Self-check
You are done when you can answer these without notes:
- How is rust-vmm's repo structure different from Firecracker's, and which repo would you open a
virtio-queuePR against? - State the boundary test: give two concrete changes, one that belongs in Firecracker and one upstream, and justify each.
- What is rust-vmm-ci, what does the coverage gate enforce, and which file records the coverage score?
- How does the rust-vmm review model differ from Firecracker's, and what is the dominant review question on a shared-crate PR?
- What is the same between the two projects' contribution flows (DCO, no CLA, tests-with-features)?
- Describe the coordinated two-step to get an upstream fix into Firecracker, and why it can span weeks.
- Name three crates Firecracker consumes and three it donated upstream, and one piece it forks in-tree and why.
You have now learned the building blocks beneath Firecracker — KVM via kvm-ioctls, kernel loading
via linux-loader, virtqueues via virtio-queue — and how to contribute to the ecosystem that
maintains them. That is the full rust-vmm picture: Firecracker is one consumer of a shared commons,
and the strongest contributors move fluently across the boundary. Carry this into the
Capstone Project: when you do your full contribution cycle there, a root
cause may live upstream, and you will now know exactly what to do — fix it in rust-vmm, land it, and
coordinate the bump back into Firecracker like a maintainer.