Skip to content

Commit dc30338

Browse files
committed
Fix ARM64 seccomp and busybox cross-compilation bugs
Bug 1: ARM64 seccomp security issue - Instead of completely disabling seccomp with --no-seccomp on ARM64, now checks for a custom seccomp filter (seccomp-filter.bpf) that includes the userfaultfd syscall (nr 282) needed for UFFD snapshot restore - Falls back to --no-seccomp only if custom filter is not available - Added SeccompFilterPath() method to look up the filter in the same location as the firecracker binary (arch-prefixed or legacy flat path) Bug 2: fetch-busybox cross-compilation issue - Changed apt-get download to explicitly request arm64 architecture (busybox-static:arm64) instead of relying on host architecture - Added architecture validation after extraction to verify the binary is actually aarch64, failing with clear error if not - Updated error message to mention arm64 architecture may not be configured
1 parent c9c2f76 commit dc30338

4 files changed

Lines changed: 112 additions & 8 deletions

File tree

‎packages/orchestrator/Makefile‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,17 @@ fetch-busybox:
146146
elif command -v apt-get >/dev/null 2>&1 && command -v dpkg-deb >/dev/null 2>&1; then \
147147
echo "Fetching arm64 busybox via apt..."; \
148148
TMPDIR=$$(mktemp -d); \
149-
apt-get download busybox-static 2>/dev/null && \
150-
dpkg-deb -x busybox-static_*.deb "$$TMPDIR" && \
149+
apt-get download busybox-static:arm64 2>/dev/null && \
150+
dpkg-deb -x busybox-static_*_arm64.deb "$$TMPDIR" && \
151+
if ! file "$$TMPDIR/bin/busybox" 2>/dev/null | grep -q 'aarch64\|ARM aarch64'; then \
152+
rm -rf "$$TMPDIR" busybox-static_*.deb; \
153+
echo "⚠ Downloaded busybox is not arm64 architecture"; \
154+
exit 1; \
155+
fi && \
151156
cp "$$TMPDIR/bin/busybox" "$$BUSYBOX_TARGET" && \
152157
rm -rf "$$TMPDIR" busybox-static_*.deb && \
153158
echo "✓ Replaced embedded busybox with arm64 binary (from busybox-static package)" || \
154-
{ rm -rf "$$TMPDIR" busybox-static_*.deb; echo "⚠ apt-get download failed"; exit 1; }; \
159+
{ rm -rf "$$TMPDIR" busybox-static_*.deb; echo "⚠ apt-get download failed (arm64 architecture may not be configured)"; exit 1; }; \
155160
else \
156161
echo "⚠ ARM64 busybox required but no method available to fetch it."; \
157162
echo " Options:"; \

‎packages/orchestrator/pkg/sandbox/fc/config.go‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ const (
1313

1414
FirecrackerBinaryName = "firecracker"
1515

16+
// SeccompFilterName is the name of the custom seccomp filter BPF file.
17+
// On aarch64, the default Firecracker seccomp filter does not include the
18+
// userfaultfd syscall (nr 282), which is required for UFFD-based snapshot
19+
// restore. A custom filter that adds userfaultfd can be placed at:
20+
// {FirecrackerVersionsDir}/{version}/[{arch}/]seccomp-filter.bpf
21+
SeccompFilterName = "seccomp-filter.bpf"
22+
1623
envsDisk = "/mnt/disks/fc-envs/v1"
1724
buildDirName = "builds"
1825

@@ -55,6 +62,25 @@ func (t Config) FirecrackerPath(config cfg.BuilderConfig) string {
5562
return filepath.Join(config.FirecrackerVersionsDir, t.FirecrackerVersion, FirecrackerBinaryName)
5663
}
5764

65+
// SeccompFilterPath returns the path to a custom seccomp filter BPF file if it exists.
66+
// Returns empty string if no custom filter is found. The custom filter should include
67+
// the userfaultfd syscall for UFFD-based snapshot restore on aarch64.
68+
func (t Config) SeccompFilterPath(config cfg.BuilderConfig) string {
69+
// Check arch-prefixed path first ({version}/{arch}/seccomp-filter.bpf)
70+
archPath := filepath.Join(config.FirecrackerVersionsDir, t.FirecrackerVersion, utils.TargetArch(), SeccompFilterName)
71+
if _, err := os.Stat(archPath); err == nil {
72+
return archPath
73+
}
74+
75+
// Fall back to legacy flat path ({version}/seccomp-filter.bpf)
76+
flatPath := filepath.Join(config.FirecrackerVersionsDir, t.FirecrackerVersion, SeccompFilterName)
77+
if _, err := os.Stat(flatPath); err == nil {
78+
return flatPath
79+
}
80+
81+
return ""
82+
}
83+
5884
type RootfsPaths struct {
5985
TemplateVersion uint64
6086
TemplateID string

‎packages/orchestrator/pkg/sandbox/fc/config_test.go‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,69 @@ func TestHostKernelPath_PrefersArchOverLegacy(t *testing.T) {
111111
// Should prefer the arch-prefixed path
112112
assert.Equal(t, filepath.Join(dir, "vmlinux-6.1.102", arch, "vmlinux.bin"), result)
113113
}
114+
115+
func TestSeccompFilterPath_ArchPrefixed(t *testing.T) {
116+
t.Parallel()
117+
dir := t.TempDir()
118+
arch := utils.TargetArch()
119+
120+
// Create the arch-prefixed seccomp filter
121+
archDir := filepath.Join(dir, "v1.12.0", arch)
122+
require.NoError(t, os.MkdirAll(archDir, 0o755))
123+
require.NoError(t, os.WriteFile(filepath.Join(archDir, "seccomp-filter.bpf"), []byte("bpf"), 0o644))
124+
125+
config := cfg.BuilderConfig{FirecrackerVersionsDir: dir}
126+
fc := Config{FirecrackerVersion: "v1.12.0"}
127+
128+
result := fc.SeccompFilterPath(config)
129+
130+
assert.Equal(t, filepath.Join(dir, "v1.12.0", arch, "seccomp-filter.bpf"), result)
131+
}
132+
133+
func TestSeccompFilterPath_LegacyFallback(t *testing.T) {
134+
t.Parallel()
135+
dir := t.TempDir()
136+
137+
// Only create the legacy flat seccomp filter
138+
require.NoError(t, os.MkdirAll(filepath.Join(dir, "v1.12.0"), 0o755))
139+
require.NoError(t, os.WriteFile(filepath.Join(dir, "v1.12.0", "seccomp-filter.bpf"), []byte("bpf"), 0o644))
140+
141+
config := cfg.BuilderConfig{FirecrackerVersionsDir: dir}
142+
fc := Config{FirecrackerVersion: "v1.12.0"}
143+
144+
result := fc.SeccompFilterPath(config)
145+
146+
assert.Equal(t, filepath.Join(dir, "v1.12.0", "seccomp-filter.bpf"), result)
147+
}
148+
149+
func TestSeccompFilterPath_NoneExists(t *testing.T) {
150+
t.Parallel()
151+
dir := t.TempDir()
152+
153+
// No seccomp filter — should return empty string
154+
config := cfg.BuilderConfig{FirecrackerVersionsDir: dir}
155+
fc := Config{FirecrackerVersion: "v1.12.0"}
156+
157+
result := fc.SeccompFilterPath(config)
158+
159+
assert.Equal(t, "", result)
160+
}
161+
162+
func TestSeccompFilterPath_PrefersArchOverLegacy(t *testing.T) {
163+
t.Parallel()
164+
dir := t.TempDir()
165+
arch := utils.TargetArch()
166+
167+
// Create BOTH arch-prefixed and legacy flat seccomp filters
168+
require.NoError(t, os.MkdirAll(filepath.Join(dir, "v1.12.0", arch), 0o755))
169+
require.NoError(t, os.WriteFile(filepath.Join(dir, "v1.12.0", arch, "seccomp-filter.bpf"), []byte("arch-bpf"), 0o644))
170+
require.NoError(t, os.WriteFile(filepath.Join(dir, "v1.12.0", "seccomp-filter.bpf"), []byte("legacy-bpf"), 0o644))
171+
172+
config := cfg.BuilderConfig{FirecrackerVersionsDir: dir}
173+
fc := Config{FirecrackerVersion: "v1.12.0"}
174+
175+
result := fc.SeccompFilterPath(config)
176+
177+
// Should prefer the arch-prefixed path
178+
assert.Equal(t, filepath.Join(dir, "v1.12.0", arch, "seccomp-filter.bpf"), result)
179+
}

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,20 @@ func (sb *StartScriptBuilder) buildArgs(
8787
rootfsPaths RootfsPaths,
8888
namespaceID string,
8989
) startScriptArgs {
90-
// On ARM64, disable seccomp to allow userfaultfd syscall for snapshot restore.
91-
// The upstream Firecracker seccomp filter for aarch64 does not include the
92-
// userfaultfd syscall (nr 282), causing snapshot loading to fail with
93-
// "Failed to UFFD object: System error".
90+
// On ARM64, we need to handle seccomp specially because the upstream Firecracker
91+
// seccomp filter for aarch64 does not include the userfaultfd syscall (nr 282),
92+
// which is required for UFFD-based snapshot restore.
93+
//
94+
// If a custom seccomp filter is available (seccomp-filter.bpf), use it via
95+
// --seccomp-filter. This custom filter should be the default aarch64 filter
96+
// with userfaultfd added. If no custom filter exists, fall back to --no-seccomp.
9497
var extraArgs string
9598
if runtime.GOARCH == "arm64" {
96-
extraArgs = " --no-seccomp"
99+
if filterPath := versions.SeccompFilterPath(sb.builderConfig); filterPath != "" {
100+
extraArgs = " --seccomp-filter " + filterPath
101+
} else {
102+
extraArgs = " --no-seccomp"
103+
}
97104
}
98105

99106
return startScriptArgs{

0 commit comments

Comments
 (0)