Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a929999
Wrap uffd fd in a struct with corresponding methods; Move the low lev…
ValentaTomas Aug 7, 2025
58a4d41
Unify receiver name
ValentaTomas Aug 7, 2025
e3d6716
[WIP] Add uffd tests
ValentaTomas Aug 7, 2025
56e43c8
Add disabled wp
ValentaTomas Aug 7, 2025
acc2d66
[WIP] WP
ValentaTomas Aug 8, 2025
1c4b8ea
[WIP] Check multicopy
ValentaTomas Aug 10, 2025
9d7f5e5
[WIP] Multicopy
ValentaTomas Aug 12, 2025
66a1bbe
Remove experiments
ValentaTomas Aug 19, 2025
e774a92
Add the page alignment check (only in method)
ValentaTomas Aug 19, 2025
f240542
Remove commented out part
ValentaTomas Aug 19, 2025
f31cb47
Remove unused return field
ValentaTomas Aug 19, 2025
bfc5935
Cleanup
ValentaTomas Aug 19, 2025
f732d5a
Remove WP parts
ValentaTomas Aug 19, 2025
855369b
Cleanup
ValentaTomas Aug 19, 2025
f981d76
Fix compile errors
ValentaTomas Oct 3, 2025
ccd8396
Cleanup
ValentaTomas Oct 4, 2025
fbf258a
Simplify offset check
ValentaTomas Oct 4, 2025
67b1bfb
Clarify comment
ValentaTomas Oct 4, 2025
28c48a7
Add test constant
ValentaTomas Oct 4, 2025
1ecd0a1
Fix incorrect log
ValentaTomas Oct 4, 2025
4fe40a0
Fix format
ValentaTomas Oct 4, 2025
bd8dc0b
Remove conversion
ValentaTomas Oct 4, 2025
f0d17ef
Make method public
ValentaTomas Oct 4, 2025
c4691b5
Add command context
ValentaTomas Oct 4, 2025
9581305
Fix error formatting
ValentaTomas Oct 4, 2025
fa72b74
Fix error formatting
ValentaTomas Oct 4, 2025
8230e4f
Improve error comparison
ValentaTomas Oct 4, 2025
1454794
Fix error formatting
ValentaTomas Oct 4, 2025
ac484d2
Change process exit
ValentaTomas Oct 4, 2025
f2d01de
Enable unpriviledged uffd mode in GH PR tests
ValentaTomas Oct 4, 2025
d27b2d4
Trigger build
ValentaTomas Oct 4, 2025
5e83359
Merge branch 'uffd-extract' of https://github.com/e2b-dev/infra into …
ValentaTomas Oct 4, 2025
05ae2e9
Fix uffd unpriviledged enable
ValentaTomas Oct 4, 2025
512fd04
Use 4k pages in uffd cross process test
ValentaTomas Oct 4, 2025
1ecc810
Add access checks to tests
ValentaTomas Oct 5, 2025
fd77d22
Cleanup
ValentaTomas Oct 5, 2025
b1a6d53
Remove unused constants for now
ValentaTomas Oct 5, 2025
2a329c8
Fix lint issue
ValentaTomas Oct 5, 2025
44984ea
Clarify naming
ValentaTomas Oct 5, 2025
c5e55e3
Clarify comment
ValentaTomas Oct 5, 2025
b04e552
Remove yet non-relevant diagram
ValentaTomas Oct 5, 2025
9607180
Remove comment
ValentaTomas Oct 5, 2025
3cf3bec
Remove write protection field
ValentaTomas Oct 5, 2025
ac9bfdf
Add explicit mmap cleanup
ValentaTomas Oct 6, 2025
3ab7848
Merge branch 'main' into uffd-extract
ValentaTomas Oct 6, 2025
69954c5
Improve test names
ValentaTomas Oct 6, 2025
4ce39ab
Fix test error message
ValentaTomas Oct 6, 2025
e8b9330
Cleanup tests
ValentaTomas Oct 6, 2025
e366753
Merge branch 'main' into uffd-extract
ValentaTomas Oct 6, 2025
5ef4373
Fix lint error
ValentaTomas Oct 6, 2025
bb951ba
Add uffd write protect constants
ValentaTomas Oct 5, 2025
8906575
Add UFFD write protection event diagram
ValentaTomas Oct 5, 2025
d89b81f
Add userfaultfd write protection methods
ValentaTomas Oct 5, 2025
f2a012a
Add tests todo
ValentaTomas Oct 5, 2025
e522612
[WIP] Add dirty tracking via WP
ValentaTomas Oct 6, 2025
64e2b13
Clarify WP flow
ValentaTomas Oct 7, 2025
bd8988e
[WIP] Add WP to the serve loop
ValentaTomas Oct 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ jobs:
with:
job_key: unit-tests-${{ matrix.package }}

- name: Enable unprivileged uffd mode
run: |
echo 1 | sudo tee /proc/sys/vm/unprivileged_userfaultfd
- name: Enable hugepages
run: |
sudo mkdir -p /mnt/hugepages
sudo mount -t hugetlbfs none /mnt/hugepages
echo 128 | sudo tee /proc/sys/vm/nr_hugepages
- name: Run tests
working-directory: ${{ matrix.package }}
run: go test -v ${{ matrix.test_path }}
66 changes: 0 additions & 66 deletions packages/orchestrator/internal/sandbox/block/tracker.go

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
package mapping
package memory

import "fmt"

type MemfileMap []GuestRegionUffdMapping

// GetOffset returns the relative offset and the page size of the mapped range for a given address
func (m MemfileMap) GetOffset(hostVirtAddr uintptr) (int64, uint64, error) {
for _, m := range m {
if hostVirtAddr >= m.BaseHostVirtAddr && hostVirtAddr < m.BaseHostVirtAddr+m.Size {
return m.relativeOffset(hostVirtAddr), uint64(m.PageSize), nil
}
}

return 0, 0, fmt.Errorf("address %d not found in any mapping", hostVirtAddr)
}

type GuestRegionUffdMapping struct {
BaseHostVirtAddr uintptr `json:"base_host_virt_addr"`
Size uintptr `json:"size"`
Expand All @@ -14,19 +27,3 @@ type GuestRegionUffdMapping struct {
func (m *GuestRegionUffdMapping) relativeOffset(addr uintptr) int64 {
return int64(m.Offset + addr - m.BaseHostVirtAddr)
}

type FcMappings []GuestRegionUffdMapping

// Returns the relative offset and the page size of the mapped range for a given address
func (m FcMappings) GetRange(addr uintptr) (int64, int64, error) {
for _, m := range m {
if addr < m.BaseHostVirtAddr || m.BaseHostVirtAddr+m.Size <= addr {
// Outside of this mapping
continue
}

return m.relativeOffset(addr), int64(m.PageSize), nil
}

return 0, 0, fmt.Errorf("address %d not found in any mapping", addr)
}
5 changes: 5 additions & 0 deletions packages/orchestrator/internal/sandbox/uffd/memory/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package memory

type MemoryMap interface {
GetOffset(hostVirtAddr uintptr) (offset int64, pagesize uint64, err error)
}
43 changes: 43 additions & 0 deletions packages/orchestrator/internal/sandbox/uffd/memory/tracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package memory

import (
"sync"

"github.com/bits-and-blooms/bitset"
"github.com/e2b-dev/infra/packages/shared/pkg/storage/header"
)

type Tracker struct {
bitset *bitset.BitSet
blockSize int64
mu sync.RWMutex
}

func NewTracker(size, blockSize int64) *Tracker {
return &Tracker{
bitset: bitset.New(uint(header.TotalBlocks(size, blockSize))),
blockSize: blockSize,
mu: sync.RWMutex{},
}
}

func (t *Tracker) Mark(offset int64) {
t.mu.Lock()
defer t.mu.Unlock()

t.bitset.Set(uint(header.BlockIdx(offset, t.blockSize)))
}

func (t *Tracker) BitSet() *bitset.BitSet {
t.mu.RLock()
defer t.mu.RUnlock()

return t.bitset.Clone()
}

func (t *Tracker) Check(offset int64) bool {
t.mu.RLock()
defer t.mu.RUnlock()

return t.bitset.Test(uint(header.BlockIdx(offset, t.blockSize)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
)

type MemoryBackend interface {
Disable() error
Dirty() *bitset.BitSet

Start(ctx context.Context, sandboxId string) error
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package testutils

import "context"

// contentSlicer exposes byte slice via the Slicer interface.
// This is used for testing purposes.
type contentSlicer struct {
content []byte
}

func newContentSlicer(content []byte) *contentSlicer {
return &contentSlicer{content: content}
}

func (s *contentSlicer) Slice(_ context.Context, offset, size int64) ([]byte, error) {
return s.content[offset : offset+size], nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package testutils

import (
"sync"

"github.com/e2b-dev/infra/packages/shared/pkg/utils"
)

// ContiguousMap is a mapping that is contiguous in the host virtual address space.
// This is used for testing purposes.
type ContiguousMap struct {
start uintptr
size uint64
pagesize uint64

accessedOffsets map[uint64]struct{}
mu sync.RWMutex
}

func NewContiguousMap(start uintptr, size, pagesize uint64) *ContiguousMap {
return &ContiguousMap{
start: start,
size: size,
pagesize: pagesize,
accessedOffsets: make(map[uint64]struct{}),
}
}

func (m *ContiguousMap) GetOffset(addr uintptr) (int64, uint64, error) {
offset := addr - m.start
pagesize := m.pagesize

m.mu.Lock()
m.accessedOffsets[uint64(offset)] = struct{}{}
m.mu.Unlock()

return int64(offset), pagesize, nil
}

func (m *ContiguousMap) Map() map[uint64]struct{} {
return m.accessedOffsets
}

func (m *ContiguousMap) Keys() []uint64 {
return utils.MapKeys(m.accessedOffsets)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package testutils

import (
"fmt"
"math"
"syscall"
"unsafe"

"golang.org/x/sys/unix"

"github.com/e2b-dev/infra/packages/shared/pkg/storage/header"
)

func NewPageMmap(size, pagesize uint64) ([]byte, uintptr, func() error, error) {
if pagesize == header.PageSize {
return newMmap(size, header.PageSize, 0)
}

if pagesize == header.HugepageSize {
return newMmap(size, header.HugepageSize, unix.MAP_HUGETLB|unix.MAP_HUGE_2MB)
}

return nil, 0, nil, fmt.Errorf("unsupported page size: %d", pagesize)
}

func newMmap(size, pagesize uint64, flags int) ([]byte, uintptr, func() error, error) {
l := int(math.Ceil(float64(size)/float64(pagesize)) * float64(pagesize))
b, err := syscall.Mmap(
-1,
0,
l,
syscall.PROT_READ|syscall.PROT_WRITE,
syscall.MAP_PRIVATE|syscall.MAP_ANONYMOUS|flags,
)
if err != nil {
return nil, 0, nil, err
}

close := func() error {
return syscall.Munmap(b)
}

return b, uintptr(unsafe.Pointer(&b[0])), close, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package testutils

import (
"crypto/rand"
)

func RandomPages(pagesize, numberOfPages uint64) (data *contentSlicer, size uint64) {
size = pagesize * numberOfPages

n := int(size)
buf := make([]byte, n)
if _, err := rand.Read(buf); err != nil {
panic(err)
}

data = newContentSlicer(buf)

return data, size
}

// DiffByte returns the first byte index where a and b differ.
// It also returns the differing byte values (want, got).
// If slices are identical, it returns -1.
func DiffByte(a, b []byte) (idx int, want, got byte) {
minLen := len(a)
if len(b) < minLen {
minLen = len(b)
}

for i := range minLen {
if a[i] != b[i] {
return i, b[i], a[i]
}
}

if len(a) != len(b) {
return minLen, 0, 0
}

return -1, 0, 0
}
Loading
Loading