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 }