lib/closure/src/binary/elf.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const binary = @import("root.zig");
2 const read = @import("read.zig");
3 const std = @import("std");
4
5 pub const program_headers_max: usize = 128;
6 pub const section_headers_max: usize = 1_024;
7
8 const InspectError = error{
9 UnsupportedElf,
10 ElfHeaderCapacityExceeded,
11 RangeCapacityExceeded,
12 EvidenceCapacityExceeded,
13 };
14
15 pub const Error = read.Error || InspectError;
16
17 const elf_header_bytes: usize = 64;
18 const program_header_bytes: usize = 56;
19 const section_header_bytes: usize = 64;
20 const program_load: u32 = 1;
21 const program_execute: u32 = 1;
22 const section_symtab: u32 = 2;
23 const section_rela: u32 = 4;
24 const section_rel: u32 = 9;
25 const section_dynsym: u32 = 11;
26
27 pub fn matches(bytes: []const u8) bool {
28 return bytes.len >= 4 and
29 std.mem.eql(u8, bytes[0..4], "\x7fELF");
30 }
31
32 pub fn inspect(
33 bytes: []const u8,
34 scratch: binary.Scratch,
35 ) Error!binary.Report {
36 const header = try headerFor(bytes);
37 var range_count: usize = 0;
38 var evidence_count: usize = 0;
39 var work: u64 = 1;
40 try survey(
41 bytes,
42 header,
43 scratch.ranges.len,
44 scratch.evidence.len,
45 &range_count,
46 &evidence_count,
47 &work,
48 null,
49 );
50 range_count = 0;
51 evidence_count = 0;
52 work = 1;
53 try survey(
54 bytes,
55 header,
56 scratch.ranges.len,
57 scratch.evidence.len,
58 &range_count,
59 &evidence_count,
60 &work,
61 scratch,
62 );
63 return .{
64 .kind = .elf64,
65 .ranges = scratch.ranges[0..range_count],
66 .evidence = scratch.evidence[0..evidence_count],
67 .work = work,
68 };
69 }
70
71 const Header = struct {
72 program_offset: usize,
73 program_count: usize,
74 section_offset: usize,
75 section_count: usize,
76 };
77
78 fn headerFor(bytes: []const u8) Error!Header {
79 _ = try read.take(bytes, 0, elf_header_bytes);
80 if (!matches(bytes) or
81 bytes[4] != 2 or
82 bytes[5] != 1 or
83 bytes[6] != 1 or
84 try read.u16le(bytes, 18) != 0x3e)
85 {
86 return error.UnsupportedElf;
87 }
88 if (try read.u16le(bytes, 52) != elf_header_bytes or
89 try read.u16le(bytes, 54) != program_header_bytes or
90 try read.u16le(bytes, 58) != section_header_bytes)
91 {
92 return error.UnsupportedElf;
93 }
94 const program_count = try read.u16le(bytes, 56);
95 const section_count = try read.u16le(bytes, 60);
96 if (program_count > program_headers_max or
97 section_count > section_headers_max)
98 {
99 return error.ElfHeaderCapacityExceeded;
100 }
101 const program_offset = try read.toOffset(try read.u64le(bytes, 32));
102 const section_offset = try read.toOffset(try read.u64le(bytes, 40));
103 if (program_count > 0) {
104 const last = try read.indexed(
105 program_offset,
106 program_count - 1,
107 program_header_bytes,
108 );
109 _ = try read.take(bytes, last, program_header_bytes);
110 }
111 if (section_count > 0) {
112 const last = try read.indexed(
113 section_offset,
114 section_count - 1,
115 section_header_bytes,
116 );
117 _ = try read.take(bytes, last, section_header_bytes);
118 }
119 return .{
120 .program_offset = program_offset,
121 .program_count = program_count,
122 .section_offset = section_offset,
123 .section_count = section_count,
124 };
125 }
126
127 fn survey(
128 bytes: []const u8,
129 header: Header,
130 ranges_max: usize,
131 evidence_max: usize,
132 range_count: *usize,
133 evidence_count: *usize,
134 work: *u64,
135 output: ?binary.Scratch,
136 ) Error!void {
137 try surveyPrograms(
138 bytes,
139 header,
140 ranges_max,
141 range_count,
142 work,
143 output,
144 );
145 try surveySections(
146 bytes,
147 header,
148 evidence_max,
149 evidence_count,
150 work,
151 output,
152 );
153 }
154
155 fn surveyPrograms(
156 bytes: []const u8,
157 header: Header,
158 ranges_max: usize,
159 range_count: *usize,
160 work: *u64,
161 output: ?binary.Scratch,
162 ) Error!void {
163 for (0..header.program_count) |index| {
164 work.* += 1;
165 const offset = try read.indexed(
166 header.program_offset,
167 index,
168 program_header_bytes,
169 );
170 const kind = try read.u32le(bytes, offset);
171 const flags = try read.u32le(bytes, offset + 4);
172 if (kind != program_load or flags & program_execute == 0) continue;
173 const file_offset = try read.u64le(bytes, offset + 8);
174 const file_length = try read.u64le(bytes, offset + 32);
175 _ = try read.take(
176 bytes,
177 try read.toOffset(file_offset),
178 try read.toLength(file_length),
179 );
180 if (file_length == 0) continue;
181 if (range_count.* == ranges_max) {
182 return error.RangeCapacityExceeded;
183 }
184 if (output) |present| {
185 present.ranges[range_count.*] = .{
186 .offset = file_offset,
187 .length = file_length,
188 };
189 }
190 range_count.* += 1;
191 }
192 }
193
194 fn surveySections(
195 bytes: []const u8,
196 header: Header,
197 evidence_max: usize,
198 evidence_count: *usize,
199 work: *u64,
200 output: ?binary.Scratch,
201 ) Error!void {
202 for (0..header.section_count) |index| {
203 work.* += 1;
204 const offset = try read.indexed(
205 header.section_offset,
206 index,
207 section_header_bytes,
208 );
209 const kind = try read.u32le(bytes, offset + 4);
210 const evidence_kind: binary.EvidenceKind = switch (kind) {
211 section_symtab, section_dynsym => .symbols,
212 section_rela, section_rel => .relocations,
213 else => continue,
214 };
215 const file_offset = try read.u64le(bytes, offset + 24);
216 const file_length = try read.u64le(bytes, offset + 32);
217 _ = try read.take(
218 bytes,
219 try read.toOffset(file_offset),
220 try read.toLength(file_length),
221 );
222 if (file_length == 0) continue;
223 if (evidence_count.* == evidence_max) {
224 return error.EvidenceCapacityExceeded;
225 }
226 if (output) |present| {
227 present.evidence[evidence_count.*] = .{
228 .kind = evidence_kind,
229 .offset = file_offset,
230 .length = file_length,
231 };
232 }
233 evidence_count.* += 1;
234 }
235 }
236
237 fn put16(bytes: []u8, offset: usize, value: u16) void {
238 std.mem.writeInt(u16, bytes[offset..][0..2], value, .little);
239 }
240
241 fn put32(bytes: []u8, offset: usize, value: u32) void {
242 std.mem.writeInt(u32, bytes[offset..][0..4], value, .little);
243 }
244
245 fn put64(bytes: []u8, offset: usize, value: u64) void {
246 std.mem.writeInt(u64, bytes[offset..][0..8], value, .little);
247 }
248
249 test "ELF inspector reports executable ranges and owned evidence tables" {
250 var bytes: [384]u8 = @splat(0);
251 @memcpy(bytes[0..4], "\x7fELF");
252 bytes[4] = 2;
253 bytes[5] = 1;
254 bytes[6] = 1;
255 put16(&bytes, 18, 0x3e);
256 put64(&bytes, 32, 64);
257 put64(&bytes, 40, 128);
258 put16(&bytes, 52, 64);
259 put16(&bytes, 54, 56);
260 put16(&bytes, 56, 1);
261 put16(&bytes, 58, 64);
262 put16(&bytes, 60, 2);
263 put32(&bytes, 64, program_load);
264 put32(&bytes, 68, program_execute);
265 put64(&bytes, 72, 256);
266 put64(&bytes, 96, 32);
267 put32(&bytes, 128 + 64 + 4, section_symtab);
268 put64(&bytes, 128 + 64 + 24, 288);
269 put64(&bytes, 128 + 64 + 32, 24);
270 var ranges: [1]binary.Range = undefined;
271 var evidence: [1]binary.Evidence = undefined;
272 const report = try inspect(&bytes, .{
273 .ranges = &ranges,
274 .evidence = &evidence,
275 });
276 try std.testing.expectEqual(binary.Kind.elf64, report.kind);
277 try std.testing.expectEqual(@as(usize, 1), report.ranges.len);
278 try std.testing.expectEqual(@as(u64, 256), report.ranges[0].offset);
279 try std.testing.expectEqual(
280 binary.EvidenceKind.symbols,
281 report.evidence[0].kind,
282 );
283 }
284
285 test "ELF inspector preflights output capacity without mutation" {
286 var bytes: [256]u8 = @splat(0);
287 @memcpy(bytes[0..4], "\x7fELF");
288 bytes[4] = 2;
289 bytes[5] = 1;
290 bytes[6] = 1;
291 put16(&bytes, 18, 0x3e);
292 put64(&bytes, 32, 64);
293 put64(&bytes, 40, 120);
294 put16(&bytes, 52, 64);
295 put16(&bytes, 54, 56);
296 put16(&bytes, 56, 1);
297 put16(&bytes, 58, 64);
298 put16(&bytes, 60, 0);
299 put32(&bytes, 64, program_load);
300 put32(&bytes, 68, program_execute);
301 put64(&bytes, 72, 192);
302 put64(&bytes, 96, 8);
303 var sentinel: [1]binary.Range = .{.{
304 .offset = 99,
305 .length = 77,
306 }};
307 try std.testing.expectError(
308 error.RangeCapacityExceeded,
309 inspect(&bytes, .{
310 .ranges = sentinel[0..0],
311 .evidence = &.{},
312 }),
313 );
314 try std.testing.expectEqual(@as(u64, 99), sentinel[0].offset);
315 try std.testing.expectEqual(@as(u64, 77), sentinel[0].length);
316 }