Skip to content

Commit da669f6

Browse files
Refactor UFFD (#1409)
Co-authored-by: Joseph Lombrozo <joe.lombrozo@e2b.dev>
1 parent acb8f0c commit da669f6

File tree

16 files changed

+795
-511
lines changed

16 files changed

+795
-511
lines changed

packages/orchestrator/internal/sandbox/uffd/mapping/firecracker.go

Lines changed: 0 additions & 32 deletions
This file was deleted.

packages/orchestrator/internal/sandbox/uffd/mapping/mapping.go

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package memory
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
type AddressNotFoundError struct {
8+
hostVirtAddr uintptr
9+
}
10+
11+
func (e AddressNotFoundError) Error() string {
12+
return fmt.Sprintf("address %d not found in any mapping", e.hostVirtAddr)
13+
}
14+
15+
type Mapping struct {
16+
Regions []Region
17+
}
18+
19+
func NewMapping(regions []Region) *Mapping {
20+
return &Mapping{Regions: regions}
21+
}
22+
23+
// GetOffset returns the relative offset and the page size of the mapped range for a given address.
24+
func (m *Mapping) GetOffset(hostVirtAddr uintptr) (int64, uint64, error) {
25+
for _, r := range m.Regions {
26+
if hostVirtAddr >= r.BaseHostVirtAddr && hostVirtAddr < r.endHostVirtAddr() {
27+
return r.shiftedOffset(hostVirtAddr), uint64(r.PageSize), nil
28+
}
29+
}
30+
31+
return 0, 0, AddressNotFoundError{hostVirtAddr: hostVirtAddr}
32+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package memory
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/e2b-dev/infra/packages/shared/pkg/storage/header"
10+
)
11+
12+
func TestMapping_GetOffset(t *testing.T) {
13+
regions := []Region{
14+
{
15+
BaseHostVirtAddr: 0x1000,
16+
Size: 0x2000,
17+
Offset: 0x5000,
18+
PageSize: header.PageSize,
19+
},
20+
{
21+
BaseHostVirtAddr: 0x5000,
22+
Size: 0x1000,
23+
Offset: 0x8000,
24+
PageSize: header.PageSize,
25+
},
26+
}
27+
mapping := NewMapping(regions)
28+
29+
tests := []struct {
30+
name string
31+
hostVirtAddr uintptr
32+
expectedOffset int64
33+
expectedSize uint64
34+
expectError error
35+
}{
36+
{
37+
name: "valid address in first region",
38+
hostVirtAddr: 0x1500,
39+
expectedOffset: 0x5500, // 0x5000 + (0x1500 - 0x1000)
40+
expectedSize: 0x1000,
41+
},
42+
{
43+
name: "valid address at start of first region",
44+
hostVirtAddr: 0x1000,
45+
expectedOffset: 0x5000,
46+
expectedSize: 0x1000,
47+
},
48+
{
49+
name: "valid address at end-1 of first region",
50+
hostVirtAddr: 0x2FFF, // 0x1000 + 0x2000 - 1
51+
expectedOffset: 0x6FFF, // 0x5000 + (0x2FFF - 0x1000)
52+
expectedSize: 0x1000,
53+
},
54+
{
55+
name: "valid address in second region",
56+
hostVirtAddr: 0x5500,
57+
expectedOffset: 0x8500, // 0x8000 + (0x5500 - 0x5000)
58+
expectedSize: 0x1000,
59+
},
60+
{
61+
name: "valid address at start of second region",
62+
hostVirtAddr: 0x5000,
63+
expectedOffset: 0x8000,
64+
expectedSize: 0x1000,
65+
},
66+
{
67+
name: "valid address at end-1 of second region",
68+
hostVirtAddr: 0x5FFF,
69+
expectedOffset: 0x8FFF, // 0x8000 + (0x5FFF - 0x5000)
70+
expectedSize: 0x1000,
71+
},
72+
{
73+
name: "address before first region",
74+
hostVirtAddr: 0x500,
75+
expectError: AddressNotFoundError{hostVirtAddr: 0x500},
76+
},
77+
{
78+
name: "address after last region",
79+
hostVirtAddr: 0x7000,
80+
expectError: AddressNotFoundError{hostVirtAddr: 0x7000},
81+
},
82+
{
83+
name: "address in gap between regions",
84+
hostVirtAddr: 0x4000,
85+
expectError: AddressNotFoundError{hostVirtAddr: 0x4000},
86+
},
87+
{
88+
name: "address at exact end of first region (exclusive)",
89+
hostVirtAddr: 0x3000, // 0x1000 + 0x2000
90+
expectError: AddressNotFoundError{hostVirtAddr: 0x3000},
91+
},
92+
{
93+
name: "address at exact end of second region (exclusive)",
94+
hostVirtAddr: 0x6000, // 0x5000 + 0x1000
95+
expectError: AddressNotFoundError{hostVirtAddr: 0x6000},
96+
},
97+
}
98+
99+
for _, tt := range tests {
100+
t.Run(tt.name, func(t *testing.T) {
101+
offset, size, err := mapping.GetOffset(tt.hostVirtAddr)
102+
if tt.expectError != nil {
103+
require.ErrorIs(t, err, tt.expectError)
104+
} else {
105+
require.NoError(t, err)
106+
assert.Equal(t, tt.expectedOffset, offset)
107+
assert.Equal(t, tt.expectedSize, size)
108+
}
109+
})
110+
}
111+
}
112+
113+
func TestMapping_EmptyRegions(t *testing.T) {
114+
mapping := NewMapping([]Region{})
115+
116+
// Test GetOffset with empty regions
117+
_, _, err := mapping.GetOffset(0x1000)
118+
require.Error(t, err)
119+
}
120+
121+
func TestMapping_OverlappingRegions(t *testing.T) {
122+
// Test with overlapping regions (edge case)
123+
regions := []Region{
124+
{
125+
BaseHostVirtAddr: 0x1000,
126+
Size: 0x2000,
127+
Offset: 0x5000,
128+
PageSize: header.PageSize,
129+
},
130+
{
131+
BaseHostVirtAddr: 0x2000, // Overlaps with first region
132+
Size: 0x1000,
133+
Offset: 0x8000,
134+
PageSize: header.PageSize,
135+
},
136+
}
137+
mapping := NewMapping(regions)
138+
139+
// The first matching region should be returned
140+
offset, size, err := mapping.GetOffset(0x2500) // In overlap area
141+
require.NoError(t, err)
142+
143+
// Should get result from first region
144+
require.Equal(t, int64(0x5000+(0x2500-0x1000)), offset) // 0x6500
145+
require.Equal(t, uint64(header.PageSize), size)
146+
147+
// Also test that the underlying implementation prefers the first region if both regions contain the address
148+
offset2, size2, err2 := mapping.GetOffset(0x2000)
149+
require.NoError(t, err2)
150+
require.Equal(t, int64(0x5000+(0x2000-0x1000)), offset2) // 0x6000 from first region
151+
require.Equal(t, uint64(header.PageSize), size2)
152+
}
153+
154+
func TestMapping_BoundaryConditions(t *testing.T) {
155+
regions := []Region{
156+
{
157+
BaseHostVirtAddr: 0x1000,
158+
Size: 0x2000,
159+
Offset: 0x5000,
160+
PageSize: header.PageSize,
161+
},
162+
}
163+
mapping := NewMapping(regions)
164+
165+
// Test exact start boundary
166+
offset, _, err := mapping.GetOffset(0x1000)
167+
require.NoError(t, err)
168+
require.Equal(t, int64(0x5000), offset) // 0x5000 + (0x1000 - 0x1000)
169+
170+
// Test just before end boundary (exclusive)
171+
offset, _, err = mapping.GetOffset(0x2FFF) // 0x1000 + 0x2000 - 1
172+
require.NoError(t, err)
173+
require.Equal(t, int64(0x5000+(0x2FFF-0x1000)), offset) // 0x6FFF
174+
175+
// Test exact end boundary (should fail - exclusive)
176+
_, _, err = mapping.GetOffset(0x3000) // 0x1000 + 0x2000
177+
require.Error(t, err)
178+
179+
// Test below start boundary (should fail)
180+
_, _, err = mapping.GetOffset(0x0FFF)
181+
require.Error(t, err)
182+
}
183+
184+
func TestMapping_SingleLargeRegion(t *testing.T) {
185+
// Entire 64-bit address space region
186+
regions := []Region{
187+
{
188+
BaseHostVirtAddr: 0x0,
189+
Size: ^uintptr(0), // Max uintptr
190+
Offset: 0x100,
191+
PageSize: header.PageSize,
192+
},
193+
}
194+
mapping := NewMapping(regions)
195+
196+
offset, size, err := mapping.GetOffset(0xABCDEF)
197+
require.NoError(t, err)
198+
require.Equal(t, int64(0x100+0xABCDEF), offset)
199+
require.Equal(t, uint64(header.PageSize), size)
200+
}
201+
202+
func TestMapping_ZeroSizeRegion(t *testing.T) {
203+
regions := []Region{
204+
{
205+
BaseHostVirtAddr: 0x2000,
206+
Size: 0,
207+
Offset: 0x1000,
208+
PageSize: header.PageSize,
209+
},
210+
}
211+
mapping := NewMapping(regions)
212+
_, _, err := mapping.GetOffset(0x2000)
213+
require.Error(t, err)
214+
}
215+
216+
func TestMapping_MultipleRegionsSparse(t *testing.T) {
217+
regions := []Region{
218+
{
219+
BaseHostVirtAddr: 0x100,
220+
Size: 0x100,
221+
Offset: 0x1000,
222+
PageSize: header.PageSize,
223+
},
224+
{
225+
BaseHostVirtAddr: 0x10000,
226+
Size: 0x100,
227+
Offset: 0x2000,
228+
PageSize: header.PageSize,
229+
},
230+
}
231+
mapping := NewMapping(regions)
232+
// Should succeed for start of first region
233+
offset, size, err := mapping.GetOffset(0x100)
234+
require.NoError(t, err)
235+
require.Equal(t, int64(0x1000), offset)
236+
require.Equal(t, uint64(header.PageSize), size)
237+
238+
// Should succeed for start of second region
239+
offset, size, err = mapping.GetOffset(0x10000)
240+
require.NoError(t, err)
241+
require.Equal(t, int64(0x2000), offset)
242+
require.Equal(t, uint64(header.PageSize), size)
243+
244+
// In gap
245+
_, _, err = mapping.GetOffset(0x5000)
246+
require.Error(t, err)
247+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package memory
2+
3+
// Region is a mapping of a region of memory of the guest to a region of memory on the host.
4+
// The serialization is based on the Firecracker UFFD protocol communication.
5+
// https://github.com/firecracker-microvm/firecracker/blob/ceeca6a14284537ae0b2a192cd2ffef10d3a81e2/src/vmm/src/persist.rs#L96
6+
type Region struct {
7+
BaseHostVirtAddr uintptr `json:"base_host_virt_addr"`
8+
Size uintptr `json:"size"`
9+
Offset uintptr `json:"offset"`
10+
// This field is deprecated in the newer version of the Firecracker with a new field `page_size`.
11+
PageSize uintptr `json:"page_size_kib"`
12+
}
13+
14+
// endHostVirtAddr returns the end address of the region in host virtual address.
15+
// The end address is exclusive.
16+
func (r *Region) endHostVirtAddr() uintptr {
17+
return r.BaseHostVirtAddr + r.Size
18+
}
19+
20+
// shiftedOffset returns the offset of the given address in the region.
21+
func (r *Region) shiftedOffset(addr uintptr) int64 {
22+
return int64(addr - r.BaseHostVirtAddr + r.Offset)
23+
}

0 commit comments

Comments
 (0)