Skip to content

Commit 136ef0e

Browse files
tomassrnkaclaude
andcommitted
fix: gate UFFD write-protection on x86_64 for ARM64 compatibility
Commit 8fc760f ("feat: enable write-protection on guest memory") added UFFD write-protection support unconditionally. ARM64 Linux kernels do not implement UFFD write protection — they lack UFFD_FEATURE_WP_ASYNC, UFFDIO_REGISTER_MODE_WP, the UFFDIO_WRITEPROTECT ioctl, and UFFDIO_COPY_MODE_WP. This causes snapshot restore to fail on ARM64 (Apple Silicon / AWS Graviton) with ioctl errors from the kernel. Gate the three write-protection code paths in guest_memory_from_uffd() behind #[cfg(target_arch = "x86_64")]: 1. UFFD features: request WP_ASYNC only on x86_64. On other architectures, request only EVENT_REMOVE | MISSING_HUGETLBFS. 2. Register mode: include WRITE_PROTECT in the register mode only on x86_64. On other architectures, register with MISSING only. 3. write_protect ioctl: gate the hugetlbfs write-protection call behind cfg(target_arch = "x86_64") so it is never emitted on ARM64. The core UFFD snapshot restore functionality (page fault handling via MISSING mode) works identically on both architectures and is unaffected. Also update scripts/build.sh to auto-detect the target architecture and select the correct Rust target triple (aarch64-unknown-linux-musl) so the build script works on ARM64 hosts. Tested: built for aarch64-unknown-linux-musl, deployed to ARM64 VM, verified sandbox creation via UFFD snapshot restore succeeds with no write-protection errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a41d3fb commit 136ef0e

2 files changed

Lines changed: 29 additions & 5 deletions

File tree

scripts/build.sh

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,32 @@
22

33
set -euo pipefail
44

5+
# Determine target architecture
6+
ARCH="${ARCH:-$(uname -m)}"
7+
case "$ARCH" in
8+
x86_64)
9+
TARGET="x86_64-unknown-linux-musl"
10+
;;
11+
aarch64|arm64)
12+
TARGET="aarch64-unknown-linux-musl"
13+
;;
14+
*)
15+
echo "Unsupported architecture: $ARCH"
16+
exit 1
17+
;;
18+
esac
19+
520
# The format will be: v<major>.<minor>.<patch>_<commit_hash> — e.g. v1.7.2_8bb88311
621
# Extract full version from src/firecracker/swagger/firecracker.yaml
722
FC_VERSION=$(awk '/^info:/{flag=1} flag && /^ version:/{print $2; exit}' src/firecracker/swagger/firecracker.yaml)
823
commit_hash=$(git rev-parse --short=7 HEAD)
924
version_name="v${FC_VERSION}_${commit_hash}"
1025
echo "Version name: $version_name"
26+
echo "Target: $TARGET"
1127

1228
echo "Starting to build Firecracker version: $version_name"
1329
tools/devtool -y build --release
1430

1531
mkdir -p "./build/fc/${version_name}"
16-
cp ./build/cargo_target/x86_64-unknown-linux-musl/release/firecracker "./build/fc/${version_name}/firecracker"
32+
cp "./build/cargo_target/${TARGET}/release/firecracker" "./build/fc/${version_name}/firecracker"
1733
echo "Finished building Firecracker version: $version_name and copied to ./build/fc/${version_name}/firecracker"

src/vmm/src/persist.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,9 +492,12 @@ fn guest_memory_from_uffd(
492492
// because the only place the kernel checks this is in a hook from madvise, e.g. it doesn't
493493
// actively change the behavior of UFFD, only passively. Without balloon devices
494494
// we never call madvise anyway, so no need to put this into a conditional.
495-
uffd_builder.require_features(
496-
FeatureFlags::EVENT_REMOVE | FeatureFlags::MISSING_HUGETLBFS | FeatureFlags::WP_ASYNC,
497-
);
495+
#[cfg(target_arch = "x86_64")]
496+
let features =
497+
FeatureFlags::EVENT_REMOVE | FeatureFlags::MISSING_HUGETLBFS | FeatureFlags::WP_ASYNC;
498+
#[cfg(not(target_arch = "x86_64"))]
499+
let features = FeatureFlags::EVENT_REMOVE | FeatureFlags::MISSING_HUGETLBFS;
500+
uffd_builder.require_features(features);
498501

499502
let uffd = uffd_builder
500503
.close_on_exec(true)
@@ -504,10 +507,14 @@ fn guest_memory_from_uffd(
504507
.map_err(GuestMemoryFromUffdError::Create)?;
505508

506509
for mem_region in guest_memory.iter() {
510+
#[cfg(target_arch = "x86_64")]
511+
let mode = RegisterMode::MISSING | RegisterMode::WRITE_PROTECT;
512+
#[cfg(not(target_arch = "x86_64"))]
513+
let mode = RegisterMode::MISSING;
507514
uffd.register_with_mode(
508515
mem_region.as_ptr().cast(),
509516
mem_region.size() as _,
510-
RegisterMode::MISSING | RegisterMode::WRITE_PROTECT,
517+
mode,
511518
)
512519
.map_err(GuestMemoryFromUffdError::Register)?;
513520

@@ -516,6 +523,7 @@ fn guest_memory_from_uffd(
516523
// won't have any effect, as the write-protection bit for a page will be
517524
// wiped when the first page fault occurs. These cases need to be handled
518525
// directly from the UFFD handler.
526+
#[cfg(target_arch = "x86_64")]
519527
if huge_pages.is_hugetlbfs() {
520528
uffd.write_protect(mem_region.as_ptr().cast(), mem_region.size() as _)
521529
.map_err(GuestMemoryFromUffdError::WriteProtect)?;

0 commit comments

Comments
 (0)