Skip to content

Commit 83c2b4c

Browse files
authored
Merge branch 'main' into pvh-rebased
2 parents cb790e4 + a8f38cb commit 83c2b4c

File tree

6 files changed

+122
-123
lines changed

6 files changed

+122
-123
lines changed

resources/overlay/usr/local/bin/fast_page_fault_helper.c

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,27 @@
2020
int main(int argc, char *const argv[]) {
2121
sigset_t set;
2222
int signal;
23+
void *ptr;
2324

2425
sigemptyset(&set);
25-
if(sigaddset(&set, SIGUSR1) == -1) {
26+
if (sigaddset(&set, SIGUSR1) == -1) {
2627
perror("sigaddset");
27-
return -1;
28+
return 1;
29+
}
30+
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
31+
perror("sigprocmask");
32+
return 1;
2833
}
2934

30-
void *ptr = mmap(NULL, MEM_SIZE_MIB, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
31-
32-
memset(ptr, 1, MEM_SIZE_MIB);
35+
ptr = mmap(NULL, MEM_SIZE_MIB, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
3336

34-
if(MAP_FAILED == ptr) {
37+
if (MAP_FAILED == ptr) {
3538
perror("mmap");
36-
return -1;
39+
return 1;
3740
}
3841

42+
memset(ptr, 1, MEM_SIZE_MIB);
43+
3944
sigwait(&set, &signal);
4045

4146
memset(ptr, 2, MEM_SIZE_MIB);

tests/conftest.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -252,16 +252,6 @@ def bin_seccomp_paths():
252252
yield demos
253253

254254

255-
@pytest.fixture
256-
def uffd_handler_paths():
257-
"""Build UFFD handler binaries."""
258-
handlers = {
259-
f"{handler}_handler": build_tools.get_example(f"uffd_{handler}_handler")
260-
for handler in ["malicious", "valid", "fault_all"]
261-
}
262-
yield handlers
263-
264-
265255
@pytest.fixture(scope="session")
266256
def netns_factory(worker_id):
267257
"""A network namespace factory

tests/framework/utils.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import re
1111
import select
1212
import signal
13-
import stat
1413
import subprocess
1514
import time
1615
import typing
@@ -133,69 +132,6 @@ def chroot(path):
133132
os.chdir(working_dir)
134133

135134

136-
class UffdHandler:
137-
"""Describe the UFFD page fault handler process."""
138-
139-
def __init__(self, name, socket_path, mem_file, chroot_path, log_file_name):
140-
"""Instantiate the handler process with arguments."""
141-
self._proc = None
142-
self._handler_name = name
143-
self._socket_path = socket_path
144-
self._mem_file = mem_file
145-
self._chroot = chroot_path
146-
self._log_file = log_file_name
147-
148-
def spawn(self, uid, gid):
149-
"""Spawn handler process using arguments provided."""
150-
151-
with chroot(self._chroot):
152-
st = os.stat(self._handler_name)
153-
os.chmod(self._handler_name, st.st_mode | stat.S_IEXEC)
154-
155-
chroot_log_file = Path("/") / self._log_file
156-
with open(chroot_log_file, "w", encoding="utf-8") as logfile:
157-
args = [f"/{self._handler_name}", self._socket_path, self._mem_file]
158-
self._proc = subprocess.Popen(
159-
args, stdout=logfile, stderr=subprocess.STDOUT
160-
)
161-
162-
# Give it time start and fail, if it really has too (bad things happen).
163-
time.sleep(1)
164-
if not self.is_running():
165-
print(chroot_log_file.read_text(encoding="utf-8"))
166-
assert False, "Could not start PF handler!"
167-
168-
# The page fault handler will create the socket path with root rights.
169-
# Change rights to the jailer's.
170-
os.chown(self._socket_path, uid, gid)
171-
172-
@property
173-
def proc(self):
174-
"""Return UFFD handler process."""
175-
return self._proc
176-
177-
def is_running(self):
178-
"""Check if UFFD process is running"""
179-
return self.proc is not None and self.proc.poll() is None
180-
181-
@property
182-
def log_file(self):
183-
"""Return the path to the UFFD handler's log file"""
184-
return Path(self._chroot) / Path(self._log_file)
185-
186-
@property
187-
def log_data(self):
188-
"""Return the log data of the UFFD handler"""
189-
if self.log_file is None:
190-
return ""
191-
return self.log_file.read_text(encoding="utf-8")
192-
193-
def __del__(self):
194-
"""Tear down the UFFD handler process."""
195-
if self.proc is not None:
196-
self.proc.kill()
197-
198-
199135
class CpuMap:
200136
"""Cpu map from real cpu cores to containers visible cores.
201137

tests/framework/utils_uffd.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""UFFD related utility functions"""
4+
5+
import os
6+
import stat
7+
import subprocess
8+
import time
9+
from pathlib import Path
10+
11+
from framework.utils import chroot
12+
from host_tools import cargo_build
13+
14+
SOCKET_PATH = "/firecracker-uffd.sock"
15+
16+
17+
class UffdHandler:
18+
"""Describe the UFFD page fault handler process."""
19+
20+
def __init__(self, name, socket_path, mem_file, chroot_path, log_file_name):
21+
"""Instantiate the handler process with arguments."""
22+
self._proc = None
23+
self._handler_name = name
24+
self._socket_path = socket_path
25+
self._mem_file = mem_file
26+
self._chroot = chroot_path
27+
self._log_file = log_file_name
28+
29+
def spawn(self, uid, gid):
30+
"""Spawn handler process using arguments provided."""
31+
32+
with chroot(self._chroot):
33+
st = os.stat(self._handler_name)
34+
os.chmod(self._handler_name, st.st_mode | stat.S_IEXEC)
35+
36+
chroot_log_file = Path("/") / self._log_file
37+
with open(chroot_log_file, "w", encoding="utf-8") as logfile:
38+
args = [f"/{self._handler_name}", self._socket_path, self._mem_file]
39+
self._proc = subprocess.Popen(
40+
args, stdout=logfile, stderr=subprocess.STDOUT
41+
)
42+
43+
# Give it time start and fail, if it really has too (bad things happen).
44+
time.sleep(1)
45+
if not self.is_running():
46+
print(chroot_log_file.read_text(encoding="utf-8"))
47+
assert False, "Could not start PF handler!"
48+
49+
# The page fault handler will create the socket path with root rights.
50+
# Change rights to the jailer's.
51+
os.chown(self._socket_path, uid, gid)
52+
53+
@property
54+
def proc(self):
55+
"""Return UFFD handler process."""
56+
return self._proc
57+
58+
def is_running(self):
59+
"""Check if UFFD process is running"""
60+
return self.proc is not None and self.proc.poll() is None
61+
62+
@property
63+
def log_file(self):
64+
"""Return the path to the UFFD handler's log file"""
65+
return Path(self._chroot) / Path(self._log_file)
66+
67+
@property
68+
def log_data(self):
69+
"""Return the log data of the UFFD handler"""
70+
if self.log_file is None:
71+
return ""
72+
return self.log_file.read_text(encoding="utf-8")
73+
74+
def __del__(self):
75+
"""Tear down the UFFD handler process."""
76+
if self.proc is not None:
77+
self.proc.kill()
78+
79+
80+
def spawn_pf_handler(vm, handler_path, mem_path):
81+
"""Spawn page fault handler process."""
82+
# Copy snapshot memory file into chroot of microVM.
83+
jailed_mem = vm.create_jailed_resource(mem_path)
84+
# Copy the valid page fault binary into chroot of microVM.
85+
jailed_handler = vm.create_jailed_resource(handler_path)
86+
handler_name = os.path.basename(jailed_handler)
87+
88+
uffd_handler = UffdHandler(
89+
handler_name, SOCKET_PATH, jailed_mem, vm.chroot(), "uffd.log"
90+
)
91+
uffd_handler.spawn(vm.jailer.uid, vm.jailer.gid)
92+
93+
return uffd_handler
94+
95+
96+
def uffd_handler(handler_name):
97+
"""Retrieves the uffd handler with the given name"""
98+
return cargo_build.get_example(f"uffd_{handler_name}_handler")

tests/integration_tests/functional/test_uffd.py

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
import pytest
99
import requests
1010

11-
from framework.utils import Timeout, UffdHandler, check_output
12-
13-
SOCKET_PATH = "/firecracker-uffd.sock"
11+
from framework.utils import Timeout, check_output
12+
from framework.utils_uffd import SOCKET_PATH, spawn_pf_handler, uffd_handler
1413

1514

1615
@pytest.fixture(scope="function", name="snapshot")
@@ -36,22 +35,6 @@ def snapshot_fxt(microvm_factory, guest_kernel_linux_5_10, rootfs):
3635
yield snapshot
3736

3837

39-
def spawn_pf_handler(vm, handler_path, mem_path):
40-
"""Spawn page fault handler process."""
41-
# Copy snapshot memory file into chroot of microVM.
42-
jailed_mem = vm.create_jailed_resource(mem_path)
43-
# Copy the valid page fault binary into chroot of microVM.
44-
jailed_handler = vm.create_jailed_resource(handler_path)
45-
handler_name = os.path.basename(jailed_handler)
46-
47-
uffd_handler = UffdHandler(
48-
handler_name, SOCKET_PATH, jailed_mem, vm.chroot(), "uffd.log"
49-
)
50-
uffd_handler.spawn(vm.jailer.uid, vm.jailer.gid)
51-
52-
return uffd_handler
53-
54-
5538
def test_bad_socket_path(uvm_plain, snapshot):
5639
"""
5740
Test error scenario when socket path does not exist.
@@ -100,7 +83,7 @@ def test_unbinded_socket(uvm_plain, snapshot):
10083
vm.mark_killed()
10184

10285

103-
def test_valid_handler(uvm_plain, snapshot, uffd_handler_paths):
86+
def test_valid_handler(uvm_plain, snapshot):
10487
"""
10588
Test valid uffd handler scenario.
10689
"""
@@ -109,9 +92,7 @@ def test_valid_handler(uvm_plain, snapshot, uffd_handler_paths):
10992
vm.spawn()
11093

11194
# Spawn page fault handler process.
112-
_pf_handler = spawn_pf_handler(
113-
vm, uffd_handler_paths["valid_handler"], snapshot.mem
114-
)
95+
_pf_handler = spawn_pf_handler(vm, uffd_handler("valid"), snapshot.mem)
11596

11697
vm.restore_from_snapshot(snapshot, resume=True, uffd_path=SOCKET_PATH)
11798

@@ -128,7 +109,7 @@ def test_valid_handler(uvm_plain, snapshot, uffd_handler_paths):
128109
vm.ssh.check_output("true")
129110

130111

131-
def test_malicious_handler(uvm_plain, snapshot, uffd_handler_paths):
112+
def test_malicious_handler(uvm_plain, snapshot):
132113
"""
133114
Test malicious uffd handler scenario.
134115
@@ -144,9 +125,7 @@ def test_malicious_handler(uvm_plain, snapshot, uffd_handler_paths):
144125
vm.spawn()
145126

146127
# Spawn page fault handler process.
147-
_pf_handler = spawn_pf_handler(
148-
vm, uffd_handler_paths["malicious_handler"], snapshot.mem
149-
)
128+
_pf_handler = spawn_pf_handler(vm, uffd_handler("malicious"), snapshot.mem)
150129

151130
# We expect Firecracker to freeze while resuming from a snapshot
152131
# due to the malicious handler's unavailability.

tests/integration_tests/performance/test_huge_pages.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from framework.microvm import HugePagesConfig
1111
from framework.properties import global_props
1212
from framework.utils_ftrace import ftrace_events
13-
from integration_tests.functional.test_uffd import SOCKET_PATH, spawn_pf_handler
13+
from framework.utils_uffd import SOCKET_PATH, spawn_pf_handler, uffd_handler
1414

1515

1616
def check_hugetlbfs_in_use(pid: int, allocation_name: str):
@@ -69,9 +69,7 @@ def test_hugetlbfs_boot(uvm_plain):
6969
)
7070

7171

72-
def test_hugetlbfs_snapshot(
73-
microvm_factory, guest_kernel_linux_5_10, rootfs, uffd_handler_paths
74-
):
72+
def test_hugetlbfs_snapshot(microvm_factory, guest_kernel_linux_5_10, rootfs):
7573
"""
7674
Test hugetlbfs snapshot restore via uffd
7775
"""
@@ -95,16 +93,14 @@ def test_hugetlbfs_snapshot(
9593
vm.spawn()
9694

9795
# Spawn page fault handler process.
98-
_pf_handler = spawn_pf_handler(
99-
vm, uffd_handler_paths["valid_handler"], snapshot.mem
100-
)
96+
_pf_handler = spawn_pf_handler(vm, uffd_handler("valid"), snapshot.mem)
10197

10298
vm.restore_from_snapshot(snapshot, resume=True, uffd_path=SOCKET_PATH)
10399

104100
check_hugetlbfs_in_use(vm.firecracker_pid, "/anon_hugepage")
105101

106102

107-
def test_hugetlbfs_diff_snapshot(microvm_factory, uvm_plain, uffd_handler_paths):
103+
def test_hugetlbfs_diff_snapshot(microvm_factory, uvm_plain):
108104
"""
109105
Test hugetlbfs differential snapshot support.
110106
@@ -139,9 +135,7 @@ def test_hugetlbfs_diff_snapshot(microvm_factory, uvm_plain, uffd_handler_paths)
139135
vm.spawn()
140136

141137
# Spawn page fault handler process.
142-
_pf_handler = spawn_pf_handler(
143-
vm, uffd_handler_paths["valid_handler"], snapshot_merged.mem
144-
)
138+
_pf_handler = spawn_pf_handler(vm, uffd_handler("valid"), snapshot_merged.mem)
145139

146140
vm.restore_from_snapshot(snapshot_merged, resume=True, uffd_path=SOCKET_PATH)
147141

@@ -153,7 +147,6 @@ def test_ept_violation_count(
153147
microvm_factory,
154148
guest_kernel_linux_5_10,
155149
rootfs,
156-
uffd_handler_paths,
157150
metrics,
158151
huge_pages,
159152
):
@@ -200,9 +193,7 @@ def test_ept_violation_count(
200193
vm.spawn()
201194

202195
# Spawn page fault handler process.
203-
_pf_handler = spawn_pf_handler(
204-
vm, uffd_handler_paths["fault_all_handler"], snapshot.mem
205-
)
196+
_pf_handler = spawn_pf_handler(vm, uffd_handler("fault_all"), snapshot.mem)
206197

207198
with ftrace_events("kvm:*"):
208199
vm.restore_from_snapshot(snapshot, resume=True, uffd_path=SOCKET_PATH)

0 commit comments

Comments
 (0)