Hardening Rust-Written Linux Kernel Drivers: Supply Chain Verification and Module Signing

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