Skip to content

Commit 0c9c939

Browse files
e2bclaude
authored andcommitted
feat: ARM64 runtime guards for SMT, UFFD WP, and seccomp
- client.go: disable SMT on ARM64 (no hyperthreading, Firecracker rejects SMT=true on ARM) - script_builder.go: disable seccomp on ARM64 (upstream FC aarch64 filter missing userfaultfd syscall) - userfaultfd.go: skip UFFD write-protection flag on ARM64 (kernel doesn't support it; KVM dirty log used instead for diff tracking) - machineinfo: ARM64 fallback for CPU Family/Model when gopsutil doesn't populate them - smoketest: use runtime.GOARCH instead of hardcoded amd64 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 36b1781 commit 0c9c939

5 files changed

Lines changed: 48 additions & 8 deletions

File tree

packages/orchestrator/cmd/smoketest/smoke_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"os/exec"
1010
"path/filepath"
11+
"runtime"
1112
"testing"
1213
"time"
1314

@@ -282,7 +283,7 @@ func findOrBuildEnvd(t *testing.T) string {
282283

283284
cmd := exec.CommandContext(t.Context(), "go", "build", "-o", binPath, ".") //nolint:gosec // trusted input
284285
cmd.Dir = envdDir
285-
cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GOOS=linux", "GOARCH=amd64")
286+
cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GOOS=linux", "GOARCH="+runtime.GOARCH)
286287
out, err := cmd.CombinedOutput()
287288
if err != nil {
288289
t.Skipf("failed to build envd: %v\n%s", err, out)

packages/orchestrator/pkg/sandbox/fc/client.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package fc
33
import (
44
"context"
55
"fmt"
6+
"runtime"
67

78
"github.com/bits-and-blooms/bitset"
89
"github.com/firecracker-microvm/firecracker-go-sdk"
@@ -326,7 +327,15 @@ func (c *apiClient) setMachineConfig(
326327
memoryMB int64,
327328
hugePages bool,
328329
) error {
329-
smt := true
330+
// SMT (Simultaneous Multi-Threading / Hyper-Threading) must be disabled on
331+
// ARM64 because ARM processors use a different core topology (big.LITTLE,
332+
// efficiency/performance cores) rather than hardware threads per core.
333+
// Firecracker validates this against the host CPU and rejects SMT=true on ARM.
334+
// See: https://github.com/firecracker-microvm/firecracker/blob/main/docs/cpu_templates/cpu-features.md
335+
// We use runtime.GOARCH (not TARGET_ARCH) because the orchestrator binary
336+
// always runs on the same architecture as Firecracker.
337+
const archARM64 = "arm64"
338+
smt := runtime.GOARCH != archARM64
330339
trackDirtyPages := false
331340
machineConfig := &models.MachineConfiguration{
332341
VcpuCount: &vCPUCount,

packages/orchestrator/pkg/sandbox/fc/script_builder.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"fmt"
66
"path/filepath"
7+
"runtime"
78
txtTemplate "text/template"
89

910
"github.com/e2b-dev/infra/packages/orchestrator/pkg/cfg"
@@ -25,6 +26,8 @@ type startScriptArgs struct {
2526
NamespaceID string
2627
FirecrackerPath string
2728
FirecrackerSocket string
29+
ExtraArgs string
30+
StracePfx string
2831
}
2932

3033
// StartScriptResult contains the generated script and computed paths
@@ -47,7 +50,7 @@ ln -s {{ .HostRootfsPath }} {{ .DeprecatedSandboxRootfsDir }}/{{ .SandboxRootfsF
4750
mount -t tmpfs tmpfs {{ .SandboxDir }}/{{ .SandboxKernelDir }} -o X-mount.mkdir &&
4851
ln -s {{ .HostKernelPath }} {{ .SandboxDir }}/{{ .SandboxKernelDir }}/{{ .SandboxKernelFile }} &&
4952
50-
ip netns exec {{ .NamespaceID }} {{ .FirecrackerPath }} --api-sock {{ .FirecrackerSocket }}`
53+
ip netns exec {{ .NamespaceID }} {{ .StracePfx }}{{ .FirecrackerPath }} --api-sock {{ .FirecrackerSocket }}{{ .ExtraArgs }}`
5154

5255
const startScriptV2 = `mount --make-rprivate / &&
5356
mount -t tmpfs tmpfs {{ .SandboxDir }} -o X-mount.mkdir &&
@@ -57,7 +60,7 @@ ln -s {{ .HostRootfsPath }} {{ .SandboxDir }}/{{ .SandboxRootfsFile }} &&
5760
mkdir -p {{ .SandboxDir }}/{{ .SandboxKernelDir }} &&
5861
ln -s {{ .HostKernelPath }} {{ .SandboxDir }}/{{ .SandboxKernelDir }}/{{ .SandboxKernelFile }} &&
5962
60-
ip netns exec {{ .NamespaceID }} {{ .FirecrackerPath }} --api-sock {{ .FirecrackerSocket }}`
63+
ip netns exec {{ .NamespaceID }} {{ .StracePfx }}{{ .FirecrackerPath }} --api-sock {{ .FirecrackerSocket }}{{ .ExtraArgs }}`
6164

6265
// StartScriptBuilder handles the creation and execution of firecracker start scripts
6366
type StartScriptBuilder struct {
@@ -85,6 +88,15 @@ func (sb *StartScriptBuilder) buildArgs(
8588
rootfsPaths RootfsPaths,
8689
namespaceID string,
8790
) startScriptArgs {
91+
// On ARM64, disable seccomp to allow userfaultfd syscall for snapshot restore.
92+
// The upstream Firecracker seccomp filter for aarch64 does not include the
93+
// userfaultfd syscall (nr 282), causing snapshot loading to fail with
94+
// "Failed to UFFD object: System error".
95+
var extraArgs, stracePfx string
96+
if runtime.GOARCH == "arm64" {
97+
extraArgs = " --no-seccomp"
98+
}
99+
88100
return startScriptArgs{
89101
// General
90102
SandboxDir: sb.builderConfig.SandboxDir,
@@ -103,6 +115,8 @@ func (sb *StartScriptBuilder) buildArgs(
103115
NamespaceID: namespaceID,
104116
FirecrackerPath: versions.FirecrackerPath(sb.builderConfig),
105117
FirecrackerSocket: files.SandboxFirecrackerSocketPath(),
118+
ExtraArgs: extraArgs,
119+
StracePfx: stracePfx,
106120
}
107121
}
108122

packages/orchestrator/pkg/sandbox/uffd/userfaultfd/userfaultfd.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"runtime"
78
"sync"
89
"syscall"
910
"unsafe"
@@ -351,7 +352,8 @@ func (u *Userfaultfd) faultPage(
351352
// Performing copy() on UFFD clears the WP bit unless we explicitly tell
352353
// it not to. We do that for faults caused by a read access. Write accesses
353354
// would anyways cause clear the write-protection bit.
354-
if accessType != block.Write {
355+
// ARM64 kernels do not support UFFD write protection, so skip the WP flag.
356+
if accessType != block.Write && runtime.GOARCH != "arm64" {
355357
copyMode |= UFFDIO_COPY_MODE_WP
356358
}
357359

packages/orchestrator/pkg/service/machineinfo/main.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,27 @@ func Detect() (MachineInfo, error) {
2222
}
2323

2424
if len(info) > 0 {
25-
if info[0].Family == "" || info[0].Model == "" {
25+
family := info[0].Family
26+
model := info[0].Model
27+
28+
// On ARM64, gopsutil doesn't populate Family/Model from /proc/cpuinfo.
29+
// Provide fallback values so callers don't get an error.
30+
if runtime.GOARCH == "arm64" {
31+
if family == "" {
32+
family = "arm64"
33+
}
34+
if model == "" {
35+
model = "0"
36+
}
37+
}
38+
39+
if family == "" || model == "" {
2640
return MachineInfo{}, fmt.Errorf("unable to detect CPU platform from CPU info: %+v", info[0])
2741
}
2842

2943
return MachineInfo{
30-
Family: info[0].Family,
31-
Model: info[0].Model,
44+
Family: family,
45+
Model: model,
3246
ModelName: info[0].ModelName,
3347
Flags: info[0].Flags,
3448
Arch: runtime.GOARCH,

0 commit comments

Comments
 (0)