The Pedigree Project 0.1
elf-validation-regressions.cc
1/*
2 * Copyright (c) 2026 Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Log.h"
9#include "pedigree/kernel/TargetInfo.h"
10#include "pedigree/kernel/linker/Elf.h"
11#include "pedigree/kernel/utilities/utility.h"
12
13namespace {
14bool fail(const char* detail) {
15 ERROR("HOSTED-ELF-VALIDATION-TEST: FAIL: " << detail);
16 return false;
17}
18
19class ExecutableValidationFixture final : public Elf {
20 public:
21 using Header = ElfHeader_t;
22 using ProgramHeader = ElfProgramHeader_t;
23 using Metadata = ExecutableMetadata;
24 using Result = ExecutableValidationResult;
25
26 static constexpr size_t ImageSize = 8192;
27 static constexpr size_t ProgramHeaderCapacity = 3;
28
29 ExecutableValidationFixture() {
30 reset();
31 }
32
33 void reset(Elf_Half type = ET_EXEC, size_t programHeaderCount = 1) {
34 ByteSet(&header, 0, sizeof(header));
35 ByteSet(programHeaders, 0, sizeof(programHeaders));
36 ByteSet(image, 0, sizeof(image));
37 terminateInterpreter = true;
38
39 header.ident[0] = 0x7f;
40 header.ident[1] = 'E';
41 header.ident[2] = 'L';
42 header.ident[3] = 'F';
43 header.ident[4] = BITS_32 ? 1 : 2;
44 header.ident[5] = TargetInfo::isLittleEndian() ? 1 : 2;
45 header.ident[6] = 1;
46 header.type = type;
47#if X64 || defined(MACH_HOSTED)
48 header.machine = 62; // EM_X86_64
49#endif
50 header.version = 1;
51 header.entry = type == ET_DYN ? 0x100 : 0x400100;
52 header.phoff = sizeof(header);
53 header.ehsize = sizeof(header);
54 header.phentsize = sizeof(programHeaders[0]);
55 header.phnum = programHeaderCount;
56
57 ProgramHeader& load = programHeaders[0];
58 load.type = PT_LOAD;
59 load.flags = PF_R | PF_X;
60 load.offset = 0;
61 load.vaddr = type == ET_DYN ? 0 : 0x400000;
62 load.filesz = ImageSize;
63 load.memsz = ImageSize;
64 load.align = TargetInfo::getPageSize();
65 }
66
67 void addInterpreter(size_t index = 1, size_t offset = 512, size_t size = sizeof(Interpreter)) {
68 header.phnum = index + 1;
69 ProgramHeader& interpreter = programHeaders[index];
70 ByteSet(&interpreter, 0, sizeof(interpreter));
71 interpreter.type = PT_INTERP;
72 interpreter.offset = offset;
73 interpreter.filesz = size;
74 interpreter.memsz = size;
75 interpreter.align = 1;
76 }
77
78 Result validate(size_t programHeaderLength = ~size_t{0}, size_t interpreterLength = ~size_t{0}) {
79 Metadata metadata{};
80 return validate(metadata, programHeaderLength, interpreterLength);
81 }
82
83 Result validate(Metadata& metadata, size_t programHeaderLength = ~size_t{0},
84 size_t interpreterLength = ~size_t{0}) {
85 sync();
86 metadata = Metadata{};
87 Result result = Elf::validateExecutableHeader(image + 1, sizeof(Header), ImageSize, metadata);
88 if (result != Result::Valid) {
89 return result;
90 }
91
92 const size_t actualProgramHeaderLength =
93 programHeaderLength == ~size_t{0} ? metadata.programHeaderSize : programHeaderLength;
94 result = Elf::validateExecutableProgramHeaders(image + 1 + metadata.programHeaderOffset,
95 actualProgramHeaderLength, ImageSize, metadata);
96 if (result != Result::Valid || !metadata.hasInterpreter) {
97 return result;
98 }
99
100 const size_t actualInterpreterLength =
101 interpreterLength == ~size_t{0} ? metadata.interpreterSize : interpreterLength;
102 return Elf::validateExecutableInterpreter(image + 1 + metadata.interpreterOffset,
103 actualInterpreterLength, metadata);
104 }
105
106 Result validateHeader(size_t length = sizeof(Header), size_t fileSize = ImageSize) {
107 sync();
108 Metadata metadata{};
109 return Elf::validateExecutableHeader(image + 1, length, fileSize, metadata);
110 }
111
112 Header header;
113 ProgramHeader programHeaders[ProgramHeaderCapacity];
114 bool terminateInterpreter;
115
116 private:
117 static constexpr char Interpreter[] = "/lib/ld.so";
118
119 void sync() {
120 ByteSet(image, 0, sizeof(image));
121 uint8_t* const data = image + 1;
122 MemoryCopy(data, &header, sizeof(header));
123
124 const size_t count =
125 header.phnum < ProgramHeaderCapacity ? header.phnum : ProgramHeaderCapacity;
126 const size_t programHeaderSize = count * sizeof(programHeaders[0]);
127 if (header.phoff <= ImageSize && programHeaderSize <= (ImageSize - header.phoff)) {
128 MemoryCopy(data + header.phoff, programHeaders, programHeaderSize);
129 }
130
131 for (size_t i = 0; i < count; ++i) {
132 const ProgramHeader& programHeader = programHeaders[i];
133 if (programHeader.type != PT_INTERP || programHeader.offset >= ImageSize) {
134 continue;
135 }
136
137 const size_t available = ImageSize - programHeader.offset;
138 const size_t copySize = sizeof(Interpreter) < available ? sizeof(Interpreter) : available;
139 MemoryCopy(data + programHeader.offset, Interpreter, copySize);
140 if (!terminateInterpreter && programHeader.filesz && programHeader.filesz <= available) {
141 data[programHeader.offset + programHeader.filesz - 1] = 'x';
142 }
143 }
144 }
145
146 // Every validation stage is deliberately exercised with unaligned input.
147 alignas(Elf_Xword) uint8_t image[ImageSize + 1];
148};
149
150constexpr char ExecutableValidationFixture::Interpreter[];
151
152bool expect(Elf::ExecutableValidationResult actual, Elf::ExecutableValidationResult expected,
153 const char* detail) {
154 return actual == expected || fail(detail);
155}
156
157#define EXPECT_RESULT(actual, expected, detail) \
158 do { \
159 if (!expect((actual), (expected), (detail))) \
160 return false; \
161 } while (0)
162
163bool validImagesAndMetadata() {
164 using Fixture = ExecutableValidationFixture;
165 using Result = Elf::ExecutableValidationResult;
166
167 Fixture fixture;
168 Fixture::Metadata metadata{};
169 EXPECT_RESULT(fixture.validate(metadata), Result::Valid, "valid ET_EXEC rejected");
170 if (metadata.type != ET_EXEC || metadata.entryPoint != 0x400100 ||
171 metadata.programHeaderOffset != sizeof(Fixture::Header) || metadata.programHeaderCount != 1 ||
172 metadata.programHeaderSize != sizeof(Fixture::ProgramHeader) ||
173 metadata.loadStart != 0x400000 || metadata.loadEnd != 0x400000 + Fixture::ImageSize ||
174 metadata.hasInterpreter) {
175 return fail("valid ET_EXEC metadata was incorrect");
176 }
177
178 fixture.reset(ET_DYN);
179 EXPECT_RESULT(fixture.validate(metadata), Result::Valid, "valid zero-based ET_DYN rejected");
180 if (metadata.loadStart != 0 || metadata.loadEnd != Fixture::ImageSize) {
181 return fail("valid ET_DYN metadata was incorrect");
182 }
183
184 fixture.reset(ET_EXEC, 2);
185 fixture.programHeaders[0].filesz = TargetInfo::getPageSize();
186 fixture.programHeaders[0].memsz = TargetInfo::getPageSize();
187 fixture.programHeaders[1].type = PT_LOAD;
188 fixture.programHeaders[1].flags = PF_R;
189 fixture.programHeaders[1].offset = TargetInfo::getPageSize();
190 fixture.programHeaders[1].vaddr = 0x400000 + TargetInfo::getPageSize();
191 fixture.programHeaders[1].filesz = TargetInfo::getPageSize();
192 fixture.programHeaders[1].memsz = TargetInfo::getPageSize();
193 fixture.programHeaders[1].align = TargetInfo::getPageSize();
194 EXPECT_RESULT(fixture.validate(), Result::Valid, "non-overlapping load segments rejected");
195 return true;
196}
197
198bool headerValidation() {
199 using Fixture = ExecutableValidationFixture;
200 using Result = Elf::ExecutableValidationResult;
201
202 Fixture fixture;
203 Fixture::Metadata metadata{};
204 EXPECT_RESULT(
205 Elf::validateExecutableHeader(nullptr, sizeof(Fixture::Header), Fixture::ImageSize, metadata),
206 Result::Malformed, "null ELF header accepted");
207 EXPECT_RESULT(fixture.validateHeader(sizeof(Fixture::Header) - 1), Result::Malformed,
208 "truncated ELF header accepted");
209 EXPECT_RESULT(fixture.validateHeader(sizeof(Fixture::Header), sizeof(Fixture::Header) - 1),
210 Result::Malformed, "ELF header outside file accepted");
211
212 fixture.reset();
213 fixture.header.ident[0] = 0;
214 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed, "bad ELF magic accepted");
215 fixture.reset();
216 fixture.header.ident[4] = BITS_32 ? 2 : 1;
217 EXPECT_RESULT(fixture.validateHeader(), Result::WrongArchitecture, "wrong ELF class accepted");
218 fixture.reset();
219 fixture.header.ident[5] = TargetInfo::isLittleEndian() ? 2 : 1;
220 EXPECT_RESULT(fixture.validateHeader(), Result::WrongArchitecture,
221 "wrong ELF byte order accepted");
222 fixture.reset();
223 fixture.header.ident[6] = 0;
224 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed, "bad ident version accepted");
225 fixture.reset();
226 fixture.header.version = 0;
227 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed, "bad ELF version accepted");
228 fixture.reset();
229 fixture.header.type = ET_REL;
230 EXPECT_RESULT(fixture.validateHeader(), Result::UnsupportedType, "ET_REL accepted for execution");
231 fixture.reset();
232 fixture.header.machine = 0;
233 EXPECT_RESULT(fixture.validateHeader(), Result::WrongArchitecture, "wrong ELF machine accepted");
234 fixture.reset();
235 --fixture.header.ehsize;
236 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed, "bad ELF header size accepted");
237 fixture.reset();
238 --fixture.header.phentsize;
239 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed, "bad program header size accepted");
240 fixture.reset();
241 fixture.header.phnum = 0;
242 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed, "empty program header table accepted");
243 fixture.reset();
244 fixture.header.phnum = (Elf::MaximumProgramHeaderTableSize / sizeof(Fixture::ProgramHeader)) + 1;
245 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed,
246 "oversized program header table accepted");
247 fixture.reset();
248 fixture.header.phoff = sizeof(Fixture::Header) - 1;
249 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed,
250 "overlapping ELF and program headers accepted");
251 fixture.reset();
252 fixture.header.phoff = Fixture::ImageSize - 1;
253 EXPECT_RESULT(fixture.validateHeader(), Result::Malformed,
254 "program header table past EOF accepted");
255 fixture.reset();
256 fixture.header.phoff = ~Elf_Off{0} - 8;
257 EXPECT_RESULT(fixture.validateHeader(sizeof(Fixture::Header), ~size_t{0}), Result::Malformed,
258 "overflowing program header table accepted");
259 return true;
260}
261
262bool loadSegmentValidation() {
263 using Fixture = ExecutableValidationFixture;
264 using Result = Elf::ExecutableValidationResult;
265
266 Fixture fixture;
267 EXPECT_RESULT(fixture.validate(sizeof(Fixture::ProgramHeader) - 1), Result::Malformed,
268 "truncated program header buffer accepted");
269 fixture.reset();
270 fixture.programHeaders[0].type = PT_NOTE;
271 EXPECT_RESULT(fixture.validate(), Result::Malformed, "ELF without a load segment accepted");
272 fixture.reset();
273 fixture.programHeaders[0].filesz = 0;
274 fixture.programHeaders[0].memsz = 0;
275 EXPECT_RESULT(fixture.validate(), Result::Malformed, "ELF without a nonempty load accepted");
276 fixture.reset();
277 fixture.programHeaders[0].memsz = Fixture::ImageSize - 1;
278 EXPECT_RESULT(fixture.validate(), Result::Malformed, "load filesz greater than memsz accepted");
279 fixture.reset();
280 fixture.programHeaders[0].offset = Fixture::ImageSize - 8;
281 fixture.programHeaders[0].filesz = 16;
282 fixture.programHeaders[0].memsz = 16;
283 EXPECT_RESULT(fixture.validate(), Result::Malformed, "load segment past EOF accepted");
284 fixture.reset(ET_EXEC, 2);
285 fixture.programHeaders[1].type = PT_NOTE;
286 fixture.programHeaders[1].offset = Fixture::ImageSize - 8;
287 fixture.programHeaders[1].filesz = 16;
288 EXPECT_RESULT(fixture.validate(), Result::Malformed,
289 "file-backed non-load segment past EOF accepted");
290 fixture.reset();
291 fixture.programHeaders[0].vaddr = ~Elf_Addr{0} - 16;
292 fixture.programHeaders[0].offset =
293 fixture.programHeaders[0].vaddr & TargetInfo::getPageOffsetMask();
294 fixture.programHeaders[0].filesz = 0;
295 fixture.programHeaders[0].memsz = 32;
296 fixture.programHeaders[0].align = 1;
297 fixture.header.entry = fixture.programHeaders[0].vaddr;
298 EXPECT_RESULT(fixture.validate(), Result::Malformed, "overflowing load address accepted");
299 fixture.reset();
300 fixture.programHeaders[0].vaddr = ~Elf_Addr{0} - TargetInfo::getPageOffsetMask();
301 fixture.programHeaders[0].filesz = 0;
302 fixture.programHeaders[0].memsz = TargetInfo::getPageOffsetMask();
303 fixture.header.entry = fixture.programHeaders[0].vaddr;
304 EXPECT_RESULT(fixture.validate(), Result::Malformed, "overflowing page rounding accepted");
305 fixture.reset();
306 fixture.programHeaders[0].align = 3;
307 EXPECT_RESULT(fixture.validate(), Result::Malformed, "non-power-of-two load alignment accepted");
308 fixture.reset();
309 fixture.programHeaders[0].offset = 1;
310 fixture.programHeaders[0].filesz = 128;
311 fixture.programHeaders[0].memsz = TargetInfo::getPageSize();
312 EXPECT_RESULT(fixture.validate(), Result::Malformed, "misaligned load segment accepted");
313 fixture.reset();
314 fixture.programHeaders[0].align = 1;
315 fixture.programHeaders[0].offset = 1;
316 fixture.programHeaders[0].filesz = 128;
317 fixture.programHeaders[0].memsz = TargetInfo::getPageSize();
318 EXPECT_RESULT(fixture.validate(), Result::Malformed, "page-incongruent load segment accepted");
319 fixture.reset();
320 fixture.header.entry = 0x500000;
321 EXPECT_RESULT(fixture.validate(), Result::Malformed, "unmapped entry point accepted");
322 fixture.reset();
323 fixture.programHeaders[0].flags = PF_R;
324 EXPECT_RESULT(fixture.validate(), Result::Malformed, "non-executable entry point accepted");
325
326 fixture.reset(ET_DYN);
327 fixture.programHeaders[0].vaddr = TargetInfo::getPageSize();
328 fixture.header.entry = TargetInfo::getPageSize() + 0x100;
329 EXPECT_RESULT(fixture.validate(), Result::UnsupportedLayout, "nonzero-based ET_DYN accepted");
330
331 fixture.reset(ET_EXEC, 2);
332 fixture.programHeaders[0].filesz = TargetInfo::getPageSize() / 2;
333 fixture.programHeaders[0].memsz = TargetInfo::getPageSize() / 2;
334 fixture.programHeaders[0].align = 1;
335 fixture.programHeaders[1].type = PT_LOAD;
336 fixture.programHeaders[1].flags = PF_R;
337 fixture.programHeaders[1].offset = TargetInfo::getPageSize() / 2;
338 fixture.programHeaders[1].vaddr = 0x400000 + (TargetInfo::getPageSize() / 2);
339 fixture.programHeaders[1].filesz = TargetInfo::getPageSize() / 2;
340 fixture.programHeaders[1].memsz = TargetInfo::getPageSize() / 2;
341 fixture.programHeaders[1].align = 1;
342 EXPECT_RESULT(fixture.validate(), Result::UnsupportedLayout,
343 "page-overlapping load segments accepted");
344 return true;
345}
346
347bool interpreterValidation() {
348 using Fixture = ExecutableValidationFixture;
349 using Result = Elf::ExecutableValidationResult;
350
351 Fixture fixture;
352 Fixture::Metadata metadata{};
353 fixture.addInterpreter();
354 EXPECT_RESULT(fixture.validate(metadata), Result::Valid, "valid PT_INTERP rejected");
355 if (!metadata.hasInterpreter || metadata.interpreterOffset != 512 || !metadata.interpreterSize) {
356 return fail("valid PT_INTERP metadata was incorrect");
357 }
358
359 fixture.reset();
360 fixture.addInterpreter(1, 512, 1);
361 EXPECT_RESULT(fixture.validate(), Result::Malformed, "one-byte PT_INTERP accepted");
362 fixture.reset();
363 fixture.addInterpreter(1, 512, Elf::MaximumInterpreterSize + 1);
364 EXPECT_RESULT(fixture.validate(), Result::Malformed, "oversized PT_INTERP accepted");
365 fixture.reset();
366 fixture.addInterpreter(1, Fixture::ImageSize - 1, 2);
367 EXPECT_RESULT(fixture.validate(), Result::Malformed, "PT_INTERP past EOF accepted");
368 fixture.reset();
369 fixture.addInterpreter();
370 fixture.addInterpreter(2, 640);
371 EXPECT_RESULT(fixture.validate(), Result::MultipleInterpreters, "duplicate PT_INTERP accepted");
372 fixture.reset();
373 fixture.addInterpreter();
374 fixture.terminateInterpreter = false;
375 EXPECT_RESULT(fixture.validate(), Result::Malformed, "unterminated PT_INTERP accepted");
376 fixture.reset();
377 fixture.addInterpreter();
378 EXPECT_RESULT(fixture.validate(~size_t{0}, 1), Result::Malformed,
379 "truncated PT_INTERP buffer accepted");
380 fixture.reset();
381 fixture.addInterpreter(1, 512, Elf::MaximumInterpreterSize);
382 EXPECT_RESULT(fixture.validate(), Result::Valid, "maximum-size PT_INTERP rejected");
383 return true;
384}
385
386#undef EXPECT_RESULT
387} // namespace
388
389bool runHostedElfValidationRegressions() {
390 if (!validImagesAndMetadata() || !headerValidation() || !loadSegmentValidation() ||
391 !interpreterValidation()) {
392 return false;
393 }
394
395 NOTICE("HOSTED-ELF-VALIDATION-TEST: PASS");
396 return true;
397}
Definition Elf.h:201
static ExecutableValidationResult validateExecutableProgramHeaders(const uint8_t *pBuffer, size_t length, size_t fileSize, ExecutableMetadata &metadata)
static ExecutableValidationResult validateExecutableInterpreter(const uint8_t *pBuffer, size_t length, const ExecutableMetadata &metadata)
static ExecutableValidationResult validateExecutableHeader(const uint8_t *pBuffer, size_t length, size_t fileSize, ExecutableMetadata &metadata)
bool validate(uint8_t *pBuffer, size_t length)
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40