Hardening Rust-Written Linux Kernel Drivers: Supply Chain Verification and Module Signing
The Problem
The Rust-for-Linux project crossed a critical threshold with Linux 6.8: the first Rust-written networking PHY driver (rust_phy) merged into the drivers/net/phy subtree, followed by GPU memory-management primitives in 6.11 and NVMe queue scaffolding experiments in 6.13. By 2026, several hundred files under drivers/ carry a .rs extension, and distributions including Fedora, openSUSE, and Arch now ship kernels built with CONFIG_RUST=y by default.
This creates a supply-chain exposure that the kernel community has not faced before. Traditional C kernel drivers have no build-time dependency graph beyond the kernel headers themselves. A Rust kernel crate, by contrast, can legitimately depend on crates from crates.io — and the rust/ subdirectory of the kernel ships a curated set of “kernel crates” (vendored under rust/kernel/) that drivers import. The boundary between the kernel-curated vendored crates and external crates is enforced only by convention, not by a hard build-system gate.
The concrete risks are:
Vendored crate drift. The kernel’s rust/vendor/ tree is updated manually when maintainers run cargo vendor. If a crate receives a security patch upstream but the kernel vendor snapshot lags, the kernel tree carries the vulnerable version without any automatic CVE signal flowing to kernel security lists.
Out-of-tree driver crates. Enterprise and hardware-vendor kernel modules (DKMS, KMODS) are beginning to ship in Rust. Unlike C DKMS modules — which are straightforward to audit with static analysis — Rust DKMS crates arrive with a Cargo.toml that may pull transitive dependencies not present in the kernel vendor tree.
cargo audit blindness. The cargo audit tool queries the RustSec advisory database, which lags CVE publication by days to weeks and has incomplete coverage of crates used exclusively in no-std (kernel) contexts. A crate with an advisory marked “only affects std” may still have an affected code path exercised in a kernel context.
Module signing gaps. Linux kernel module signing (CONFIG_MODULE_SIG_ALL) works identically for Rust-compiled .ko objects — but CI pipelines that were set up for C modules frequently skip Rust modules when the build system compiles them as a separate pass.
Minimising attack surface. Distributions shipping CONFIG_RUST=y may also enable experimental Rust subsystems that have not yet received the same review depth as the equivalent C code. The kernel’s CONFIG_RUST_IS_AVAILABLE guard exists precisely because Rust abstractions are still being hardened.
Target systems: Linux 6.8+ with CONFIG_RUST=y; distributions shipping Rust kernel crates (Fedora 40+, openSUSE Tumbleweed 2024+, custom enterprise kernel builds).
Threat Model
1. Supply-chain attacker with crates.io write access (indirect, no direct kernel access). Objective: inject malicious code into a crate depended on by a kernel module; wait for a vendor snapshot update to pull it into tree. Impact: arbitrary kernel code execution on hosts that load the affected module.
2. Malicious out-of-tree DKMS crate author (package repository write access). Objective: publish a Rust DKMS module that appears to implement a legitimate hardware driver but includes a backdoor in a transitive dependency. Impact: kernel-level persistence on any host that installs the package.
3. Insider attacker with kernel-tree commit access (authenticated). Objective: introduce a subtle memory-safety bug in a Rust kernel crate that bypasses the compiler’s safe/unsafe boundary; wait for the module to be loaded on production hosts. Impact: local privilege escalation; kernel memory disclosure.
4. Build-system attacker (CI/CD pipeline access). Objective: replace a signed kernel module .ko with a backdoored version post-build; exploit a CI pipeline that signs modules from an untrusted artifact store. Impact: the signed module passes modprobe signature verification but executes attacker code.
Without the hardening described below, attackers 1 and 2 face no automated detection; attacker 4 is the most realistic path given the frequency of CI/CD supply-chain compromises in 2025-2026.
Hardening Configuration
Auditing the Kernel Vendor Tree
# Clone the kernel source and enter the rust/ directory
cd /usr/src/linux-6.8
cd rust/vendor
# Enumerate all vendored crates and their versions
find . -name "Cargo.toml" -maxdepth 2 | xargs grep -h "^version" | sort -u
# Cross-reference against the RustSec advisory database
# Install cargo-audit if not present
cargo install cargo-audit
# Run audit against the kernel vendor tree
# cargo-audit requires a Cargo.lock; the kernel ships one at rust/Cargo.lock
cargo audit --file ../Cargo.lock 2>&1 | tee /tmp/kernel-rust-audit.txt
# Filter for CVSS HIGH/CRITICAL
grep -A5 "CVSS.*[89]\.[0-9]\|CVSS.*10\." /tmp/kernel-rust-audit.txt
Automate this in a CI check that runs on every kernel version bump:
# .github/workflows/kernel-rust-audit.yml
name: Kernel Rust Crate Audit
on:
schedule:
- cron: "0 6 * * 1" # weekly Monday
push:
paths:
- "rust/Cargo.lock"
jobs:
audit:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
working-directory: rust/
Pinning Vendor Snapshots and Diffing Updates
When the kernel vendor tree is updated, diff the changeset against the previous snapshot before merging:
# Before pulling a new vendor update, record the current hashes
find rust/vendor -name "*.rs" | sort | xargs sha256sum > /tmp/vendor-before.txt
# After the update
find rust/vendor -name "*.rs" | sort | xargs sha256sum > /tmp/vendor-after.txt
# Diff — any new or changed files warrant manual review
diff /tmp/vendor-before.txt /tmp/vendor-after.txt
# Focus on unsafe blocks introduced in the diff
git diff HEAD~1 -- rust/vendor/ | grep -A5 "^+.*unsafe"
Policy: any crate update that introduces new unsafe blocks requires sign-off from at least one kernel security reviewer, not just the subsystem maintainer.
Enforcing Module Signing for Rust Modules
Check that the build system signs Rust .ko files with the same key used for C modules:
# Verify kernel config
grep CONFIG_MODULE_SIG_ALL /boot/config-$(uname -r)
# Expected: CONFIG_MODULE_SIG_ALL=y
grep CONFIG_MODULE_SIG_KEY /boot/config-$(uname -r)
# Expected: CONFIG_MODULE_SIG_KEY="certs/signing_key.pem"
# After building, verify a Rust module is signed
modinfo drivers/net/phy/rust_phy.ko | grep ^sig
# Expected: signer: <your build key CN>
# sig_key: <fingerprint>
# sig_hashalgo: sha512
# Check signature verification at load time
strace -e trace=finit_module modprobe rust_phy 2>&1 | grep EKEYREJECTED
# Should produce no output if signing is working
For distributions that compile Rust modules in a separate pass from C modules, verify the scripts/sign-file utility is called for .ko files regardless of source language:
# Grep the kernel Makefile for sign-file invocation covering Rust targets
grep -r "sign-file" scripts/ Makefile rust/Makefile
Locking Down Module Loading with IMA/EVM
Pair module signing with IMA (Integrity Measurement Architecture) appraisal so the kernel refuses to load any module whose measurement doesn’t match a stored reference:
# /etc/ima/ima-policy — add to existing IMA policy
# Appraise all kernel modules loaded from /lib/modules
appraise func=MODULE_CHECK appraise_type=imasig|modsig
# Verify IMA is enforcing
cat /sys/kernel/security/ima/policy | grep MODULE_CHECK
# Check that a newly built Rust module carries an IMA signature
evmctl ima_verify drivers/net/phy/rust_phy.ko
# Expected: IMA signature verification passed
Scanning Out-of-Tree Rust DKMS Modules
For vendor-supplied Rust DKMS modules, run cargo audit and cargo deny before installation:
# Extract the DKMS source for a Rust module (example: vendor-gpu-rust-dkms)
dpkg -x vendor-gpu-rust-dkms_1.0.deb /tmp/dkms-inspect/
cd /tmp/dkms-inspect/usr/src/vendor-gpu-rust-1.0/
# Audit direct and transitive deps
cargo audit
# Use cargo-deny for license and ban-list enforcement
cat << 'EOF' > deny.toml
[advisories]
vulnerability = "deny"
unmaintained = "warn"
[licenses]
allow = ["MIT", "Apache-2.0", "GPL-2.0-only"]
[bans]
deny = [
# Crates with known soundness issues in kernel/no_std contexts
{ name = "smallvec", version = "<1.11.2" },
]
EOF
cargo deny check
Restricting Which Rust Modules Can Load via Kernel Lockdown
Linux kernel lockdown mode (available since 5.4) restricts unsigned module loading even when CONFIG_MODULE_SIG_FORCE is not set:
# Enable lockdown at boot (add to kernel command line)
# lockdown=integrity — prevents loading unsigned modules
# lockdown=confidentiality — additionally restricts /dev/mem, kprobes, etc.
grep lockdown /proc/cmdline
# Or set at runtime (cannot be lifted without reboot)
echo integrity > /sys/kernel/security/lockdown
# Verify
cat /sys/kernel/security/lockdown
# Expected: [none] integrity confidentiality
# (brackets show current mode)
Pair lockdown with Secure Boot and the CONFIG_SECURITY_LOCKDOWN_LSM_EARLY option to ensure lockdown is enforced before the first kernel module is loaded.
Continuous Scanning with cargo-vet
The cargo vet tool (Mozilla) allows teams to maintain an audited record of crates they’ve reviewed:
cargo vet init # initialise audit store in supply-chain/
cargo vet # fail if any dep lacks an audit entry
# Add an audit for a crate after reviewing it
cargo vet certify allocator-api2 0.2.18 safe-to-deploy
For in-kernel crate audits, maintain a supply-chain/audits.toml in the kernel repository’s rust/ subdirectory and require CI to pass cargo vet before any Cargo.lock change merges.
Expected Behaviour After Hardening
| Check | Before Hardening | After Hardening |
|---|---|---|
cargo audit on kernel vendor tree |
Not run | Run weekly; blocks PR on HIGH/CRITICAL |
Unsigned Rust .ko load attempt |
Succeeds (warning only) | EKEYREJECTED — module rejected |
| New unsafe block in vendor crate | Merges silently | Requires explicit security sign-off |
| Out-of-tree Rust DKMS install | No dep scan | cargo deny scan required; fails on vulnerable deps |
| Lockdown mode | Not enforced | integrity lockdown active; unsigned modules blocked |
| IMA module appraisal | Not configured | Failed IMA check blocks module load |
Verification snippet:
# Build a test Rust module without signing and attempt to load it
make -C /lib/modules/$(uname -r)/build M=$PWD modules
# Strip signature
strip --strip-debug my_test_module.ko
insmod my_test_module.ko
# Expected output with hardening:
# insmod: ERROR: could not insert module my_test_module.ko: Key was rejected by service
Trade-offs and Operational Considerations
| Aspect | Benefit | Cost | Mitigation |
|---|---|---|---|
Weekly cargo audit CI |
Early detection of RustSec advisories | Advisory lag vs. NVD; false negatives in no-std contexts | Supplement with manual review of crate changelogs |
| Module signing enforcement | Prevents loading of tampered Rust .ko |
Requires HSM or secure key storage for signing key | Store signing key in a hardware HSM; rotate annually |
cargo vet audits |
Traceable human review of every crate dep | Significant upfront effort to audit existing crates | Use cargo vet suggest to prioritise high-risk crates first |
| Kernel lockdown integrity | Blocks unsigned modules system-wide | Breaks legitimate unsigned out-of-tree modules (DKMS without sig) | Ensure all DKMS modules are signed; use mokutil for user-enrolled keys |
| IMA module appraisal | Cryptographic proof of module integrity at load time | IMA policy must be present before first module load | Bake IMA policy into initramfs; use ima_canonical_fmt for reproducibility |
Failure Modes
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| Signing key lost or expired | modprobe fails with EKEYREJECTED for all Rust modules |
/proc/keys shows expired key; keyctl list empty |
Re-enrol new key via mokutil --import; rebuild modules |
| Vendored crate update pulls vulnerable transitive dep | cargo audit finds advisory after merge |
Weekly CI audit run; check cargo audit --json output |
Pin affected crate to patched version in Cargo.lock; submit kernel patch |
| IMA policy missing from initramfs | Modules load without appraisal after reboot | cat /sys/kernel/security/ima/policy empty |
Add IMA policy to /etc/initramfs-tools/scripts/ and run update-initramfs -u |
| Lockdown mode blocks legitimate debug module | insmod fails for development .ko |
Kernel log: Lockdown: insmod: unsigned module loading is restricted |
Switch to integrity mode on dev boxes; use signed debug modules |
cargo vet blocks vendor update |
CI fails on new crate dep with no audit | cargo vet output lists missing audits |
Add exemption with time-boxed review deadline; assign crate reviewer |