Skip to content

Commit 32e841a

Browse files
committed
btf: decode from []byte
Parsing BTF still relies on io.Reader(At) in places, which makes it awkward to parse from a plain byte slice. Rework the internal plumbing to use []byte. This also deductes the endianness of a BTF blob from its header, which allows us to get rid of guessBTFByteOrder. Signed-off-by: Lorenz Bauer <[email protected]>
1 parent c60a53f commit 32e841a

File tree

9 files changed

+89
-106
lines changed

9 files changed

+89
-106
lines changed

btf/btf.go

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package btf
22

33
import (
4-
"bufio"
54
"debug/elf"
6-
"encoding/binary"
75
"errors"
86
"fmt"
97
"io"
@@ -69,11 +67,12 @@ func LoadSpec(file string) (*Spec, error) {
6967
func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
7068
file, err := internal.NewSafeELFFile(rd)
7169
if err != nil {
72-
if bo := guessRawBTFByteOrder(rd); bo != nil {
73-
return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil)
70+
raw, err := io.ReadAll(io.NewSectionReader(rd, 0, math.MaxInt64))
71+
if err != nil {
72+
return nil, fmt.Errorf("read raw BTF: %w", err)
7473
}
7574

76-
return nil, err
75+
return loadRawSpec(raw, nil)
7776
}
7877

7978
return loadSpecFromELF(file)
@@ -170,15 +169,20 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
170169
return nil, err
171170
}
172171

173-
if btfSection.ReaderAt == nil {
174-
return nil, fmt.Errorf("compressed BTF is not supported")
172+
rawBTF, err := btfSection.Data()
173+
if err != nil {
174+
return nil, fmt.Errorf("reading .BTF section: %w", err)
175175
}
176176

177-
spec, err := loadRawSpec(btfSection.ReaderAt, file.ByteOrder, nil)
177+
spec, err := loadRawSpec(rawBTF, nil)
178178
if err != nil {
179179
return nil, err
180180
}
181181

182+
if spec.decoder.byteOrder != file.ByteOrder {
183+
return nil, fmt.Errorf("BTF byte order %s does not match ELF byte order %s", spec.decoder.byteOrder, file.ByteOrder)
184+
}
185+
182186
spec.elf = &elfData{
183187
sectionSizes,
184188
offsets,
@@ -188,7 +192,7 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
188192
return spec, nil
189193
}
190194

191-
func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error) {
195+
func loadRawSpec(btf []byte, base *Spec) (*Spec, error) {
192196
var (
193197
baseDecoder *decoder
194198
baseStrings *stringTable
@@ -200,47 +204,39 @@ func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error
200204
baseStrings = base.strings
201205
}
202206

203-
buf := internal.NewBufferedSectionReader(btf, 0, math.MaxInt64)
204-
header, err := parseBTFHeader(buf, bo)
207+
header, bo, err := parseBTFHeader(btf)
205208
if err != nil {
206209
return nil, fmt.Errorf("parsing .BTF header: %v", err)
207210
}
208211

209-
stringsSection := io.NewSectionReader(btf, header.stringStart(), int64(header.StringLen))
210-
rawStrings, err := readStringTable(stringsSection, baseStrings)
212+
if header.HdrLen > uint32(len(btf)) {
213+
return nil, fmt.Errorf("BTF header length is out of bounds")
214+
}
215+
btf = btf[header.HdrLen:]
216+
217+
if int(header.StringOff+header.StringLen) > len(btf) {
218+
return nil, fmt.Errorf("string table is out of bounds")
219+
}
220+
stringsSection := btf[header.StringOff : header.StringOff+header.StringLen]
221+
222+
rawStrings, err := newStringTable(stringsSection, baseStrings)
211223
if err != nil {
212224
return nil, fmt.Errorf("read string section: %w", err)
213225
}
214226

215-
typesSection := io.NewSectionReader(btf, header.typeStart(), int64(header.TypeLen))
216-
rawTypes := make([]byte, header.TypeLen)
217-
if _, err := io.ReadFull(typesSection, rawTypes); err != nil {
218-
return nil, fmt.Errorf("read type section: %w", err)
227+
if int(header.TypeOff+header.TypeLen) > len(btf) {
228+
return nil, fmt.Errorf("types section is out of bounds")
219229
}
230+
typesSection := btf[header.TypeOff : header.TypeOff+header.TypeLen]
220231

221-
decoder, err := newDecoder(rawTypes, bo, rawStrings, baseDecoder)
232+
decoder, err := newDecoder(typesSection, bo, rawStrings, baseDecoder)
222233
if err != nil {
223234
return nil, err
224235
}
225236

226237
return &Spec{decoder, nil}, nil
227238
}
228239

229-
func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder {
230-
buf := new(bufio.Reader)
231-
for _, bo := range []binary.ByteOrder{
232-
binary.LittleEndian,
233-
binary.BigEndian,
234-
} {
235-
buf.Reset(io.NewSectionReader(r, 0, math.MaxInt64))
236-
if _, err := parseBTFHeader(buf, bo); err == nil {
237-
return bo
238-
}
239-
}
240-
241-
return nil
242-
}
243-
244240
// fixupDatasec attempts to patch up missing info in Datasecs and its members by
245241
// supplementing them with information from the ELF headers and symbol table.
246242
func (elf *elfData) fixupDatasec(typ Type) error {
@@ -519,7 +515,12 @@ func LoadSplitSpec(file string, base *Spec) (*Spec, error) {
519515
// Types from base are used to resolve references in the split BTF.
520516
// The returned Spec only contains types from the split BTF, not from the base.
521517
func LoadSplitSpecFromReader(r io.ReaderAt, base *Spec) (*Spec, error) {
522-
return loadRawSpec(r, internal.NativeEndian, base)
518+
raw, err := io.ReadAll(io.NewSectionReader(r, 0, math.MaxInt64))
519+
if err != nil {
520+
return nil, fmt.Errorf("read raw BTF: %w", err)
521+
}
522+
523+
return loadRawSpec(raw, base)
523524
}
524525

525526
// All iterates over all types.

btf/btf_test.go

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package btf
22

33
import (
44
"bytes"
5-
"encoding/binary"
65
"errors"
76
"fmt"
8-
"io"
97
"io/fs"
108
"os"
119
"runtime"
@@ -46,34 +44,34 @@ var vmlinuxTestdata = sync.OnceValues(func() (specAndRawBTF, error) {
4644
return specAndRawBTF{}, err
4745
}
4846

49-
spec, err := loadRawSpec(bytes.NewReader(b), binary.LittleEndian, nil)
47+
spec, err := loadRawSpec(b, nil)
5048
if err != nil {
5149
return specAndRawBTF{}, err
5250
}
5351

5452
return specAndRawBTF{b, spec}, nil
5553
})
5654

57-
func vmlinuxTestdataReader(tb testing.TB) *bytes.Reader {
55+
func vmlinuxTestdataSpec(tb testing.TB) *Spec {
5856
tb.Helper()
5957

6058
td, err := vmlinuxTestdata()
6159
if err != nil {
6260
tb.Fatal(err)
6361
}
6462

65-
return bytes.NewReader(td.raw)
63+
return td.spec.Copy()
6664
}
6765

68-
func vmlinuxTestdataSpec(tb testing.TB) *Spec {
66+
func vmlinuxTestdataBytes(tb testing.TB) []byte {
6967
tb.Helper()
7068

7169
td, err := vmlinuxTestdata()
7270
if err != nil {
7371
tb.Fatal(err)
7472
}
7573

76-
return td.spec.Copy()
74+
return td.raw
7775
}
7876

7977
func parseELFBTF(tb testing.TB, file string) *Spec {
@@ -211,32 +209,24 @@ func TestTypeByName(t *testing.T) {
211209
}
212210

213211
func BenchmarkParseVmlinux(b *testing.B) {
214-
rd := vmlinuxTestdataReader(b)
212+
vmlinux := vmlinuxTestdataBytes(b)
215213
b.ReportAllocs()
216214
b.ResetTimer()
217215

218216
for n := 0; n < b.N; n++ {
219-
if _, err := rd.Seek(0, io.SeekStart); err != nil {
220-
b.Fatal(err)
221-
}
222-
223-
if _, err := loadRawSpec(rd, binary.LittleEndian, nil); err != nil {
217+
if _, err := loadRawSpec(vmlinux, nil); err != nil {
224218
b.Fatal("Can't load BTF:", err)
225219
}
226220
}
227221
}
228222

229223
func BenchmarkIterateVmlinux(b *testing.B) {
230-
rd := vmlinuxTestdataReader(b)
224+
vmlinux := vmlinuxTestdataBytes(b)
231225
b.ReportAllocs()
232226
b.ResetTimer()
233227

234228
for range b.N {
235-
if _, err := rd.Seek(0, io.SeekStart); err != nil {
236-
b.Fatal(err)
237-
}
238-
239-
spec, err := loadRawSpec(rd, binary.LittleEndian, nil)
229+
spec, err := loadRawSpec(vmlinux, nil)
240230
if err != nil {
241231
b.Fatal("Can't load BTF:", err)
242232
}
@@ -325,13 +315,6 @@ func TestVerifierError(t *testing.T) {
325315
}
326316
}
327317

328-
func TestGuessBTFByteOrder(t *testing.T) {
329-
bo := guessRawBTFByteOrder(vmlinuxTestdataReader(t))
330-
if bo != binary.LittleEndian {
331-
t.Fatalf("Guessed %s instead of %s", bo, binary.LittleEndian)
332-
}
333-
}
334-
335318
func TestSpecCopy(t *testing.T) {
336319
qt.Check(t, qt.IsNil((*Spec)(nil).Copy()))
337320

btf/btf_types.go

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ import (
44
"encoding/binary"
55
"errors"
66
"fmt"
7-
"io"
87
"unsafe"
9-
10-
"github.com/cilium/ebpf/internal"
118
)
129

1310
//go:generate go run golang.org/x/tools/cmd/stringer@latest -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind
@@ -87,47 +84,49 @@ type btfHeader struct {
8784
StringLen uint32
8885
}
8986

90-
// typeStart returns the offset from the beginning of the .BTF section
91-
// to the start of its type entries.
92-
func (h *btfHeader) typeStart() int64 {
93-
return int64(h.HdrLen + h.TypeOff)
94-
}
95-
96-
// stringStart returns the offset from the beginning of the .BTF section
97-
// to the start of its string table.
98-
func (h *btfHeader) stringStart() int64 {
99-
return int64(h.HdrLen + h.StringOff)
100-
}
101-
10287
// parseBTFHeader parses the header of the .BTF section.
103-
func parseBTFHeader(r io.Reader, bo binary.ByteOrder) (*btfHeader, error) {
88+
func parseBTFHeader(buf []byte) (*btfHeader, binary.ByteOrder, error) {
10489
var header btfHeader
105-
if err := binary.Read(r, bo, &header); err != nil {
106-
return nil, fmt.Errorf("can't read header: %v", err)
90+
var bo binary.ByteOrder
91+
for _, order := range []binary.ByteOrder{binary.LittleEndian, binary.BigEndian} {
92+
n, err := binary.Decode(buf, order, &header)
93+
if err != nil {
94+
return nil, nil, fmt.Errorf("read header: %v", err)
95+
}
96+
97+
if header.Magic != btfMagic {
98+
continue
99+
}
100+
101+
buf = buf[n:]
102+
bo = order
103+
break
107104
}
108105

109-
if header.Magic != btfMagic {
110-
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
106+
if bo == nil {
107+
return nil, nil, fmt.Errorf("no valid BTF header")
111108
}
112109

113110
if header.Version != 1 {
114-
return nil, fmt.Errorf("unexpected version %v", header.Version)
111+
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
115112
}
116113

117114
if header.Flags != 0 {
118-
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
115+
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
119116
}
120117

121118
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
122119
if remainder < 0 {
123-
return nil, errors.New("header length shorter than btfHeader size")
120+
return nil, nil, errors.New("header length shorter than btfHeader size")
124121
}
125122

126-
if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil {
127-
return nil, fmt.Errorf("header padding: %v", err)
123+
for _, b := range buf[:remainder] {
124+
if b != 0 {
125+
return nil, nil, errors.New("header contains non-zero trailer")
126+
}
128127
}
129128

130-
return &header, nil
129+
return &header, bo, nil
131130
}
132131

133132
// btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst.

btf/fuzz_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func FuzzSpec(f *testing.F) {
2020
t.Skip("data is too short")
2121
}
2222

23-
spec, err := loadRawSpec(bytes.NewReader(data), internal.NativeEndian, nil)
23+
spec, err := loadRawSpec(data, nil)
2424
if err != nil {
2525
if spec != nil {
2626
t.Fatal("spec is not nil")

btf/handle.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package btf
22

33
import (
4-
"bytes"
54
"errors"
65
"fmt"
76
"math"
@@ -153,7 +152,7 @@ func (h *Handle) Spec(base *Spec) (*Spec, error) {
153152
return nil, fmt.Errorf("missing base types")
154153
}
155154

156-
return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, base)
155+
return loadRawSpec(btfBuffer, base)
157156
}
158157

159158
// Close destroys the handle.

btf/kernel.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func loadKernelSpec() (_ *Spec, _ error) {
119119
if err == nil {
120120
defer fh.Close()
121121

122-
spec, err := loadRawSpec(fh, internal.NativeEndian, nil)
122+
spec, err := LoadSplitSpecFromReader(fh, nil)
123123
return spec, err
124124
}
125125

@@ -149,7 +149,7 @@ func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) {
149149
}
150150
defer fh.Close()
151151

152-
return loadRawSpec(fh, internal.NativeEndian, base)
152+
return LoadSplitSpecFromReader(fh, base)
153153
}
154154

155155
// findVMLinux scans multiple well-known paths for vmlinux kernel images.

0 commit comments

Comments
 (0)