carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "sort"
8)
9
10// ErrELFBounds is every way CheckELFBounds refuses an artefact.
11var ErrELFBounds = errors.New("release: artefact is not a bounded linux-arm64 ELF")
12
13// ELF limits for CheckELFBounds. A Go linux-arm64 binary has a few dozen
14// sections and about ten program headers; these leave ample room and bound
15// the metadata any later parser can be made to walk.
16const (
17 elfMaxSections = 256
18 elfMaxPrograms = 64
19 elfHeaderSize = 64
20 elfPhentSize = 56
21 elfShentSize = 64
22 elfMachAArch64 = 183
23 elfStrTab = 3 // SHT_STRTAB
24 elfNoBits = 8 // SHT_NOBITS: no file contents
25 elfMaxNameTab = 64 << 10
26 elfMaxName = 256
27 elfCompressed = 0x800 // SHF_COMPRESSED
28 elfXIndex = 0xffff
29)
30
31// CheckELFBounds is the bounded pre-check run before debug/buildinfo parses
32// an artefact in a root process (controller ruling F1). It reads only the
33// fixed-size headers, by offset, with every count and offset checked
34// against len(b), and accepts only:
35// - ELF64, little-endian, version 1, EM_AARCH64;
36// - a program header table of at most 64 entries of 56 bytes, and a
37// section header table of 1 to 256 entries of 64 bytes, each inside the
38// file, not overlapping each other or the ELF header, with no extended
39// numbering (e_shnum 0, e_shstrndx 0xffff);
40// - every segment's file range inside the file;
41// - every section's file range inside the file, no section flagged
42// SHF_COMPRESSED (whose decompression is unbounded by the file size),
43// and no two sections with contents overlapping;
44// - a section-name table (e_shstrndx) of type SHT_STRTAB, 1 byte to
45// 64 KiB, every section's name offset inside it, and every name at
46// most 256 bytes up to its NUL.
47//
48// debug/elf copies each section's name out of the name table, so names
49// that all point at one huge string would multiply it by the section count
50// (Astra's re-review, controller ruling F1). With the caps, the names it
51// copies are at most 256 x 256 bytes, no section is decompressed, and every
52// range lies inside the bounded file. No further parser hardening is in
53// scope for this branch.
54func CheckELFBounds(b []byte) error {
55 n := uint64(len(b))
56 if n < elfHeaderSize || string(b[:4]) != "\x7fELF" || b[4] != 2 || b[5] != 1 || b[6] != 1 {
57 return ErrELFBounds
58 }
59 le := binary.LittleEndian
60 if le.Uint16(b[18:]) != elfMachAArch64 || le.Uint32(b[20:]) != 1 || le.Uint16(b[52:]) != elfHeaderSize {
61 return ErrELFBounds
62 }
63 phoff, shoff := le.Uint64(b[32:]), le.Uint64(b[40:])
64 phentsize, phnum := uint64(le.Uint16(b[54:])), uint64(le.Uint16(b[56:]))
65 shentsize, shnum := uint64(le.Uint16(b[58:])), uint64(le.Uint16(b[60:]))
66 shstrndx := uint64(le.Uint16(b[62:]))
67 if phnum > elfMaxPrograms || phnum > 0 && phentsize != elfPhentSize || shnum == 0 || shnum > elfMaxSections || shentsize != elfShentSize || shstrndx == elfXIndex || shstrndx >= shnum {
68 return ErrELFBounds
69 }
70 within := func(off, size uint64) bool { return off <= n && size <= n-off }
71 phEnd, shEnd := phoff+phnum*elfPhentSize, shoff+shnum*elfShentSize
72 if phnum > 0 && (!within(phoff, phnum*elfPhentSize) || phoff < elfHeaderSize) {
73 return ErrELFBounds
74 }
75 if !within(shoff, shnum*elfShentSize) || shoff < elfHeaderSize || phnum > 0 && shoff < phEnd && phoff < shEnd {
76 return ErrELFBounds
77 }
78 for i := uint64(0); i < phnum; i++ {
79 h := b[phoff+i*elfPhentSize:]
80 if !within(le.Uint64(h[8:]), le.Uint64(h[32:])) { // p_offset, p_filesz
81 return ErrELFBounds
82 }
83 }
84 type span struct{ off, end uint64 }
85 var spans []span
86 for i := uint64(0); i < shnum; i++ {
87 h := b[shoff+i*elfShentSize:]
88 typ, flags, off, size := le.Uint32(h[4:]), le.Uint64(h[8:]), le.Uint64(h[24:]), le.Uint64(h[32:])
89 if flags&elfCompressed != 0 {
90 return ErrELFBounds
91 }
92 if typ == elfNoBits || size == 0 {
93 continue
94 }
95 if !within(off, size) {
96 return ErrELFBounds
97 }
98 spans = append(spans, span{off, off + size})
99 }
100 names := b[shoff+shstrndx*elfShentSize:]
101 if le.Uint32(names[4:]) != elfStrTab {
102 return ErrELFBounds
103 }
104 nameOff, nameSize := le.Uint64(names[24:]), le.Uint64(names[32:])
105 if nameSize == 0 || nameSize > elfMaxNameTab || !within(nameOff, nameSize) {
106 return ErrELFBounds
107 }
108 table := b[nameOff : nameOff+nameSize]
109 for i := uint64(0); i < shnum; i++ {
110 at := uint64(le.Uint32(b[shoff+i*elfShentSize:]))
111 if at >= nameSize {
112 return ErrELFBounds
113 }
114 name := table[at:]
115 if len(name) > elfMaxName+1 {
116 name = name[:elfMaxName+1]
117 }
118 if bytes.IndexByte(name, 0) < 0 {
119 return ErrELFBounds
120 }
121 }
122 sort.Slice(spans, func(i, j int) bool { return spans[i].off < spans[j].off })
123 for i := 1; i < len(spans); i++ {
124 if spans[i].off < spans[i-1].end {
125 return ErrELFBounds
126 }
127 }
128 return nil
129}
130