1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
package ebpf
import (
"errors"
"fmt"
"io"
"runtime"
"github.com/cilium/ebpf/internal/unix"
)
// Memory is the building block for accessing the memory of specific bpf map
// types (Array and Arena at the time of writing) without going through the bpf
// syscall interface.
//
// Given the fd of a bpf map created with the BPF_F_MMAPABLE flag, a shared
// 'file'-based memory-mapped region can be allocated in the process' address
// space, exposing the bpf map's memory by simply accessing a memory location.
var ErrReadOnly = errors.New("resource is read-only")
// Memory implements accessing a Map's memory without making any syscalls.
// Pay attention to the difference between Go and C struct alignment rules. Use
// [structs.HostLayout] on supported Go versions to help with alignment.
//
// Note on memory coherence: avoid using packed structs in memory shared between
// user space and eBPF C programs. This drops a struct's memory alignment to 1,
// forcing the compiler to use single-byte loads and stores for field accesses.
// This may lead to partially-written data to be observed from user space.
//
// On most architectures, the memmove implementation used by Go's copy() will
// access data in word-sized chunks. If paired with a matching access pattern on
// the eBPF C side (and if using default memory alignment), accessing shared
// memory without atomics or other synchronization primitives should be sound
// for individual values. For accesses beyond a single value, the usual
// concurrent programming rules apply.
type Memory struct {
b []byte
ro bool
}
func newMemory(fd, size int) (*Memory, error) {
// Typically, maps created with BPF_F_RDONLY_PROG remain writable from user
// space until frozen. As a security precaution, the kernel doesn't allow
// mapping bpf map memory as read-write into user space if the bpf map was
// frozen, or if it was created using the RDONLY_PROG flag.
//
// The user would be able to write to the map after freezing (since the kernel
// can't change the protection mode of an already-mapped page), while the
// verifier assumes the contents to be immutable.
b, err := unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
// If the map is frozen when an rw mapping is requested, expect EPERM. If the
// map was created with BPF_F_RDONLY_PROG, expect EACCES.
var ro bool
if errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) {
ro = true
b, err = unix.Mmap(fd, 0, size, unix.PROT_READ, unix.MAP_SHARED)
}
if err != nil {
return nil, fmt.Errorf("setting up memory-mapped region: %w", err)
}
mm := &Memory{
b,
ro,
}
runtime.SetFinalizer(mm, (*Memory).close)
return mm, nil
}
func (mm *Memory) close() {
if err := unix.Munmap(mm.b); err != nil {
panic(fmt.Errorf("unmapping memory: %w", err))
}
mm.b = nil
}
// Size returns the size of the memory-mapped region in bytes.
func (mm *Memory) Size() int {
return len(mm.b)
}
// ReadOnly returns true if the memory-mapped region is read-only.
func (mm *Memory) ReadOnly() bool {
return mm.ro
}
// bounds returns true if an access at off of the given size is within bounds.
func (mm *Memory) bounds(off uint64, size uint64) bool {
return off+size < uint64(len(mm.b))
}
// ReadAt implements [io.ReaderAt]. Useful for creating a new [io.OffsetWriter].
//
// See [Memory] for details around memory coherence.
func (mm *Memory) ReadAt(p []byte, off int64) (int, error) {
if mm.b == nil {
return 0, fmt.Errorf("memory-mapped region closed")
}
if p == nil {
return 0, fmt.Errorf("input buffer p is nil")
}
if off < 0 || off >= int64(len(mm.b)) {
return 0, fmt.Errorf("read offset out of range")
}
n := copy(p, mm.b[off:])
if n < len(p) {
return n, io.EOF
}
return n, nil
}
// WriteAt implements [io.WriterAt]. Useful for creating a new
// [io.SectionReader].
//
// See [Memory] for details around memory coherence.
func (mm *Memory) WriteAt(p []byte, off int64) (int, error) {
if mm.b == nil {
return 0, fmt.Errorf("memory-mapped region closed")
}
if mm.ro {
return 0, fmt.Errorf("memory-mapped region not writable: %w", ErrReadOnly)
}
if p == nil {
return 0, fmt.Errorf("output buffer p is nil")
}
if off < 0 || off >= int64(len(mm.b)) {
return 0, fmt.Errorf("write offset out of range")
}
n := copy(mm.b[off:], p)
if n < len(p) {
return n, io.EOF
}
return n, nil
}
|