• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Alan-Jowett / ebpf-verifier / 27778108035

07 Jun 2026 06:51PM UTC coverage: 86.386% (-2.5%) from 88.93%
27778108035

push

github

elazarg
Release v0.2.5

Bump project version to 0.2.5 and add a CHANGELOG entry covering ELF loader hardening, numeric-domain soundness fixes, and the writable helper output initialization documentation update since v0.2.4. Also updates the using_installed_package example version requirement.

Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

9125 of 10563 relevant lines covered (86.39%)

6334294.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.46
/src/io/elf_reader.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <algorithm>
4
#include <array>
5
#include <cerrno>
6
#include <concepts>
7
#include <cstddef>
8
#include <cstdint>
9
#include <cstring>
10
#include <deque>
11
#include <filesystem>
12
#include <fstream>
13
#include <functional>
14
#include <iostream>
15
#include <map>
16
#include <optional>
17
#include <set>
18
#include <string>
19
#include <sys/stat.h>
20
#include <type_traits>
21
#include <variant>
22
#include <vector>
23

24
#include <elfio/elfio.hpp>
25

26
#include "crab_utils/num_safety.hpp"
27
#include "io/elf_reader.hpp"
28

29
namespace prevail {
30

31
// ---------------------------------------------------------------------------
32
// Utility functions
33
// ---------------------------------------------------------------------------
34

35
static_assert(sizeof(ELFIO::Elf32_Ehdr) == 52);
36
static_assert(offsetof(ELFIO::Elf32_Ehdr, e_shoff) == 32);
37
static_assert(offsetof(ELFIO::Elf32_Ehdr, e_shentsize) == 46);
38
static_assert(offsetof(ELFIO::Elf32_Ehdr, e_shnum) == 48);
39
static_assert(sizeof(ELFIO::Elf64_Ehdr) == 64);
40
static_assert(offsetof(ELFIO::Elf64_Ehdr, e_shoff) == 40);
41
static_assert(offsetof(ELFIO::Elf64_Ehdr, e_shentsize) == 58);
42
static_assert(offsetof(ELFIO::Elf64_Ehdr, e_shnum) == 60);
43
static_assert(sizeof(ELFIO::Elf32_Shdr) == 40);
44
static_assert(offsetof(ELFIO::Elf32_Shdr, sh_size) == 20);
45
static_assert(sizeof(ELFIO::Elf64_Shdr) == 64);
46
static_assert(offsetof(ELFIO::Elf64_Shdr, sh_size) == 32);
47
static_assert(sizeof(ELFIO::Elf32_Rel) == 8);
48
static_assert(offsetof(ELFIO::Elf32_Rel, r_offset) == 0);
49
static_assert(offsetof(ELFIO::Elf32_Rel, r_info) == 4);
50
static_assert(sizeof(ELFIO::Elf32_Rela) == 12);
51
static_assert(offsetof(ELFIO::Elf32_Rela, r_addend) == 8);
52
static_assert(sizeof(ELFIO::Elf64_Rel) == 16);
53
static_assert(offsetof(ELFIO::Elf64_Rel, r_offset) == 0);
54
static_assert(offsetof(ELFIO::Elf64_Rel, r_info) == 8);
55
static_assert(sizeof(ELFIO::Elf64_Rela) == 24);
56
static_assert(offsetof(ELFIO::Elf64_Rela, r_addend) == 16);
57

58
std::pair<std::reference_wrapper<EbpfInst>, std::reference_wrapper<EbpfInst>>
59
validate_and_get_lddw_pair(std::vector<EbpfInst>& instructions, const size_t location, const std::string& context) {
12,414✔
60
    constexpr uint8_t BPF_LDDW = 0x18;
12,414✔
61
    constexpr uint8_t BPF_LDDW_HI = 0x00;
12,414✔
62

63
    if (instructions.size() <= location + 1) {
12,414✔
64
        throw UnmarshalError("Invalid relocation: " + std::string(context) + " reference at instruction boundary");
×
65
    }
66

67
    auto& lo_inst = instructions[location];
12,414✔
68
    auto& hi_inst = instructions[location + 1];
12,414✔
69

70
    if (lo_inst.opcode != BPF_LDDW) {
12,414✔
71
        throw UnmarshalError("Invalid relocation: expected LDDW first slot (opcode 0x18) for " + std::string(context) +
×
72
                             ", found opcode 0x" + std::to_string(static_cast<int>(lo_inst.opcode)));
×
73
    }
74
    if (hi_inst.opcode != BPF_LDDW_HI) {
12,414✔
75
        throw UnmarshalError("Invalid relocation: expected LDDW second slot (opcode 0x00) for " + std::string(context) +
×
76
                             ", found opcode 0x" + std::to_string(static_cast<int>(hi_inst.opcode)));
×
77
    }
78

79
    return {std::ref(lo_inst), std::ref(hi_inst)};
12,414✔
80
}
81

82
bool is_map_section(const std::string& name) {
122,456✔
83
    return name == "maps" || name == ".maps" || name.starts_with("maps/") || name.starts_with(".maps/");
243,105✔
84
}
85

86
bool is_global_section(const std::string& name) {
87,710✔
87
    return name == ".data" || name == ".rodata" || name == ".bss" || name.starts_with(".data.") ||
218,321✔
88
           name.starts_with(".rodata.") || name.starts_with(".bss.");
261,478✔
89
}
90

91
std::string bad_reloc_value(const size_t reloc_value) {
×
92
    return "Bad reloc value (" + std::to_string(reloc_value) + "). " + "Make sure to compile with -O2.";
×
93
}
94

95
bool is_supported_bpf_relocation_type(const unsigned type) {
1,298,612✔
96
    return type == R_BPF_NONE_TYPE || type == R_BPF_64_64_TYPE || type == R_BPF_64_32_TYPE;
1,298,612✔
97
}
98

99
template <std::unsigned_integral T>
100
T read_reloc_uint(const char* data, const size_t offset, const bool little_endian) {
2,600,122✔
101
    T value = 0;
2,600,122✔
102
    for (size_t i = 0; i < sizeof(T); ++i) {
23,401,098✔
103
        const size_t byte_index = little_endian ? i : sizeof(T) - 1U - i;
20,800,976✔
104
        value |= static_cast<T>(static_cast<unsigned char>(data[offset + byte_index])) << (8U * i);
20,800,976✔
105
    }
106
    return value;
2,600,122✔
107
}
108

109
template <std::signed_integral T>
110
T read_reloc_int(const char* data, const size_t offset, const bool little_endian) {
2,898✔
111
    const auto raw = read_reloc_uint<std::make_unsigned_t<T>>(data, offset, little_endian);
2,898✔
112
    T value{};
1,449✔
113
    std::memcpy(&value, &raw, sizeof(value));
1,449✔
114
    return value;
1,449✔
115
}
116

117
struct RelocationEntry {
118
    ELFIO::Elf64_Addr offset;
119
    ELFIO::Elf_Word symbol_index;
120
    unsigned type;
121
    ELFIO::Elf_Sxword addend;
122
};
123

124
struct RelocationSectionLayout {
125
    unsigned char elf_class;
126
    ELFIO::Elf_Word section_type;
127
    size_t entry_size;
128
    ELFIO::Elf_Xword entries_num;
129
    bool little_endian;
130
};
131

132
size_t expected_relocation_entry_size(const unsigned char elf_class, const ELFIO::Elf_Word section_type) {
29,928✔
133
    if (elf_class == ELFIO::ELFCLASS64) {
14,964✔
134
        return section_type == ELFIO::SHT_RELA ? sizeof(ELFIO::Elf64_Rela) : sizeof(ELFIO::Elf64_Rel);
29,928✔
135
    }
136
    return section_type == ELFIO::SHT_RELA ? sizeof(ELFIO::Elf32_Rela) : sizeof(ELFIO::Elf32_Rel);
×
137
}
138

139
RelocationSectionLayout relocation_section_layout(const ELFIO::elfio& reader, const ELFIO::section& section,
29,928✔
140
                                                  const std::string& section_name) {
141
    const auto elf_class = reader.get_class();
29,928✔
142
    const auto section_type = section.get_type();
29,928✔
143
    if (section_type != ELFIO::SHT_REL && section_type != ELFIO::SHT_RELA) {
29,928✔
144
        throw UnmarshalError("Malformed relocation section " + section_name);
×
145
    }
146

147
    const size_t expected_entry_size = expected_relocation_entry_size(elf_class, section_type);
29,928✔
148
    const size_t entry_size = section.get_entry_size();
29,928✔
149
    // ELFIO's relocation accessor silently treats zero entry size as no
150
    // entries and then uses typed loads for non-zero entries. Validate the raw
151
    // table first so every indexed byte read below is inside one complete entry.
152
    if (entry_size != expected_entry_size || section.get_data() == nullptr || section.get_size() % entry_size != 0) {
29,928✔
153
        throw UnmarshalError("Malformed relocation section " + section_name);
3✔
154
    }
155
    return {
14,963✔
156
        .elf_class = elf_class,
157
        .section_type = section_type,
158
        .entry_size = entry_size,
159
        .entries_num = section.get_size() / entry_size,
59,852✔
160
        .little_endian = reader.get_encoding() == ELFIO::ELFDATA2LSB,
59,852✔
161
    };
29,926✔
162
}
163

164
RelocationEntry read_relocation_entry(const ELFIO::section& section, const ELFIO::Elf_Xword index,
1,298,612✔
165
                                      const RelocationSectionLayout& layout) {
166
    const char* data = section.get_data() + index * layout.entry_size;
1,298,612✔
167
    if (layout.elf_class == ELFIO::ELFCLASS64) {
1,298,612✔
168
        const uint64_t info =
649,306✔
169
            read_reloc_uint<ELFIO::Elf_Xword>(data, offsetof(ELFIO::Elf64_Rel, r_info), layout.little_endian);
1,298,612✔
170
        return {
649,306✔
171
            .offset =
172
                read_reloc_uint<ELFIO::Elf64_Addr>(data, offsetof(ELFIO::Elf64_Rel, r_offset), layout.little_endian),
1,298,612✔
173
            .symbol_index = static_cast<ELFIO::Elf_Word>(info >> 32U),
1,298,612✔
174
            .type = static_cast<unsigned>(info & 0xffffffffULL),
175
            .addend = layout.section_type == ELFIO::SHT_RELA
1,298,612✔
176
                          ? read_reloc_int<ELFIO::Elf_Sxword>(data, offsetof(ELFIO::Elf64_Rela, r_addend),
649,306✔
177
                                                              layout.little_endian)
1,449✔
178
                          : 0,
179
        };
1,949,367✔
180
    }
181

182
    const uint32_t info =
183
        read_reloc_uint<ELFIO::Elf_Word>(data, offsetof(ELFIO::Elf32_Rel, r_info), layout.little_endian);
×
184
    return {
185
        .offset = read_reloc_uint<ELFIO::Elf32_Addr>(data, offsetof(ELFIO::Elf32_Rel, r_offset), layout.little_endian),
×
186
        .symbol_index = static_cast<ELFIO::Elf_Word>(info >> 8U),
×
187
        .type = static_cast<unsigned>(info & 0xffU),
×
188
        .addend =
189
            layout.section_type == ELFIO::SHT_RELA
×
190
                ? read_reloc_int<ELFIO::Elf_Sword>(data, offsetof(ELFIO::Elf32_Rela, r_addend), layout.little_endian)
×
191
                : 0,
192
    };
×
193
}
194

195
symbol_details_t get_symbol_details(const ELFIO::const_symbol_section_accessor& symbols, const ELFIO::Elf_Xword index) {
230,584,076✔
196
    symbol_details_t details;
230,584,076✔
197
    if (!symbols.get_symbol(index, details.name, details.value, details.size, details.bind, details.type,
230,584,076✔
198
                            details.section_index, details.other)) {
230,584,076✔
199
        throw UnmarshalError("Invalid symbol index in ELF symbol table: " + std::to_string(index));
×
200
    }
201
    return details;
230,584,076✔
202
}
×
203

204
std::tuple<std::string, ELFIO::Elf_Xword>
205
get_program_name_and_size(const ELFIO::section& sec, const ELFIO::Elf_Xword start,
33,558✔
206
                          const ELFIO::const_symbol_section_accessor& symbols) {
207
    const ELFIO::Elf_Xword symbol_count = symbols.get_symbols_num();
33,558✔
208
    const ELFIO::Elf_Half section_index = sec.get_index();
33,558✔
209
    std::string program_name = sec.get_name();
33,558✔
210
    ELFIO::Elf_Xword size = sec.get_size() - start;
33,558✔
211
    for (ELFIO::Elf_Xword index = 0; index < symbol_count; index++) {
224,658,888✔
212
        auto symbol_details = get_symbol_details(symbols, index);
224,625,332✔
213
        if (symbol_details.section_index == section_index && !symbol_details.name.empty()) {
224,625,332✔
214
            if (symbol_details.type != ELFIO::STT_FUNC) {
1,198,490✔
215
                continue;
1,144,266✔
216
            }
217
            const auto relocation_offset = symbol_details.value;
54,224✔
218
            if (relocation_offset % sizeof(EbpfInst) != 0) {
54,224✔
219
                throw UnmarshalError("Non-instruction-aligned FUNC symbol '" + symbol_details.name + "' at offset " +
5✔
220
                                     std::to_string(relocation_offset) + " in section " + sec.get_name());
7✔
221
            }
222
            if (relocation_offset == start) {
54,222✔
223
                program_name = symbol_details.name;
111,769,166✔
224
            } else if (relocation_offset > start && relocation_offset < start + size) {
25,588✔
225
                size = relocation_offset - start;
2,266✔
226
            }
227
        }
228
    }
224,625,332✔
229
    return {program_name, size};
67,112✔
230
}
33,557✔
231

232
std::optional<std::string> find_function_symbol_at_offset(const ELFIO::const_symbol_section_accessor& symbols,
392✔
233
                                                          const ELFIO::Elf_Half section_index,
234
                                                          const ELFIO::Elf_Xword offset) {
235
    const ELFIO::Elf_Xword symbol_count = symbols.get_symbols_num();
392✔
236
    for (ELFIO::Elf_Xword index = 0; index < symbol_count; index++) {
2,894✔
237
        const auto symbol = get_symbol_details(symbols, index);
2,894✔
238
        if (symbol.section_index != section_index || symbol.type != ELFIO::STT_FUNC || symbol.name.empty()) {
2,894✔
239
            continue;
1,404✔
240
        }
241
        if (symbol.value == offset) {
1,490✔
242
            return symbol.name;
392✔
243
        }
244
    }
2,894✔
245
    return std::nullopt;
×
246
}
247

248
ELFIO::Elf_Xword compute_reachable_program_span(const std::vector<EbpfInst>& section_instructions,
30,918✔
249
                                                const ELFIO::Elf_Xword program_offset,
250
                                                const ELFIO::Elf_Xword initial_size) {
251
    if (section_instructions.empty()) {
30,918✔
252
        return initial_size;
253
    }
254

255
    const size_t total = section_instructions.size();
30,918✔
256
    const size_t start = program_offset / sizeof(EbpfInst);
30,918✔
257
    size_t initial_end = (program_offset + initial_size) / sizeof(EbpfInst);
30,918✔
258
    if (start >= total || initial_end <= start) {
30,918✔
259
        return initial_size;
260
    }
261
    initial_end = std::min(initial_end, total);
30,918✔
262

263
    auto mark = [&](const int64_t index, std::vector<bool>& seen, std::deque<size_t>& work) {
35,373,053✔
264
        if (index < 0 || index >= gsl::narrow<int64_t>(total)) {
35,357,594✔
265
            return;
3,793,432✔
266
        }
267
        const size_t idx = gsl::narrow<size_t>(index);
35,357,448✔
268
        if (seen[idx]) {
35,357,448✔
269
            return;
1,896,643✔
270
        }
271
        seen[idx] = true;
31,564,162✔
272
        work.push_back(idx);
31,564,162✔
273
    };
30,918✔
274

275
    std::vector<bool> seen(total, false);
30,918✔
276
    std::deque<size_t> work;
30,918✔
277
    mark(gsl::narrow<int64_t>(start), seen, work);
30,918✔
278

279
    size_t max_reachable = initial_end - 1;
30,918✔
280
    while (!work.empty()) {
31,595,080✔
281
        const size_t pc = work.front();
31,564,162✔
282
        work.pop_front();
31,564,162✔
283
        max_reachable = std::max(max_reachable, pc);
31,564,162✔
284

285
        const EbpfInst& inst = section_instructions[pc];
31,564,162✔
286
        const bool is_lddw = inst.opcode == INST_OP_LDDW_IMM;
31,564,162✔
287
        const size_t fallthrough = pc + (is_lddw ? 2 : 1);
31,564,162✔
288

289
        // LDDW is a two-slot instruction, so keep the high slot in range.
290
        if (is_lddw && pc + 1 < total) {
31,564,162✔
291
            mark(gsl::narrow<int64_t>(pc + 1), seen, work);
1,207,286✔
292
            max_reachable = std::max(max_reachable, pc + 1);
1,207,340✔
293
        }
294

295
        const uint8_t cls = inst.opcode & INST_CLS_MASK;
31,564,162✔
296
        if (cls == INST_CLS_JMP || cls == INST_CLS_JMP32) {
31,564,162✔
297
            const uint8_t op = (inst.opcode >> 4) & 0xF;
4,275,534✔
298
            if (op == INST_EXIT) {
4,275,534✔
299
                continue;
4,275,534✔
300
            }
301
            if (op == INST_CALL) {
4,244,470✔
302
                if (inst.opcode == INST_OP_CALL && inst.src == INST_CALL_LOCAL) {
1,220,820✔
303
                    const int64_t target = gsl::narrow<int64_t>(pc) + 1 + inst.imm;
233,640✔
304
                    mark(target, seen, work);
233,640✔
305
                }
306
                mark(gsl::narrow<int64_t>(fallthrough), seen, work);
1,220,820✔
307
                continue;
1,220,820✔
308
            }
1,220,820✔
309

310
            const int64_t target = gsl::narrow<int64_t>(pc) + 1 + inst.offset;
3,023,650✔
311
            mark(target, seen, work);
3,023,650✔
312
            if (op != INST_JA) {
3,023,650✔
313
                mark(gsl::narrow<int64_t>(fallthrough), seen, work);
2,352,652✔
314
            }
315
            continue;
3,023,650✔
316
        }
3,023,650✔
317

318
        mark(gsl::narrow<int64_t>(fallthrough), seen, work);
27,288,628✔
319
    }
320

321
    const size_t span_end = std::max(initial_end, max_reachable + 1);
30,918✔
322
    return gsl::narrow<ELFIO::Elf_Xword>((span_end - start) * sizeof(EbpfInst));
30,918✔
323
}
30,918✔
324

325
std::vector<ELFIO::section*> global_sections(const ELFIO::elfio& reader) {
1,684✔
326
    std::vector<ELFIO::section*> result;
1,684✔
327
    for (auto& section : reader.sections) {
89,394✔
328
        if (!section || !is_global_section(section->get_name())) {
131,565✔
329
            continue;
86,210✔
330
        }
331

332
        const auto type = section->get_type();
1,500✔
333
        if ((type == ELFIO::SHT_NOBITS || type == ELFIO::SHT_PROGBITS) && section->get_size() != 0) {
1,500✔
334
            result.push_back(section.get());
1,500✔
335
        }
336
    }
337
    return result;
1,684✔
338
}
×
339

340
RawProgram* find_subprogram(std::vector<RawProgram>& programs, const ELFIO::section& subprogram_section,
770✔
341
                            const std::string& symbol_name) {
342
    for (auto& subprog : programs) {
2,804✔
343
        if (subprog.section_name == subprogram_section.get_name() && subprog.function_name == symbol_name) {
4,206✔
344
            return &subprog;
770✔
345
        }
346
    }
347
    return nullptr;
348
}
349

350
static std::uintmax_t get_data_size(std::istream& input_stream, const std::string& path) {
694✔
351
    std::error_code ec;
694✔
352
    const auto size = std::filesystem::file_size(path, ec);
694✔
353
    if (!ec) {
694✔
354
        return size;
344✔
355
    }
356
    // Fallback: compute size from the input stream (handles in-memory ELF data
357
    // where path is not a real filesystem path, e.g. path="memory").
358
    input_stream.seekg(0, std::ios::end);
6✔
359
    const auto end_pos = input_stream.tellg();
6✔
360
    if (end_pos < 0) {
6✔
361
        throw UnmarshalError("Cannot determine data size for " + path);
×
362
    }
363
    input_stream.seekg(0);
6✔
364
    return end_pos;
6✔
365
}
366

367
template <std::unsigned_integral T>
368
static T read_uint(const unsigned char* bytes, const bool little_endian) {
2,084✔
369
    T value = 0;
2,084✔
370
    for (size_t i = 0; i < sizeof(T); ++i) {
10,428✔
371
        const size_t byte_index = little_endian ? i : sizeof(T) - 1U - i;
8,344✔
372
        value |= static_cast<T>(bytes[byte_index]) << (8U * i);
8,344✔
373
    }
374
    return value;
2,084✔
375
}
376

377
static std::streamsize read_stream_bytes_at(std::istream& input_stream, const std::uintmax_t offset,
696✔
378
                                            unsigned char* output, const size_t size) {
379
    input_stream.clear();
696✔
380
    input_stream.seekg(gsl::narrow<std::streamoff>(offset));
696✔
381
    input_stream.read(reinterpret_cast<char*>(output), gsl::narrow<std::streamsize>(size));
696✔
382
    const auto bytes_read = input_stream.gcount();
696✔
383
    input_stream.clear();
696✔
384
    input_stream.seekg(0);
696✔
385
    return bytes_read;
696✔
386
}
387

388
struct SectionHeaderTableLayout {
389
    uint64_t offset{};
390
    uint16_t entry_size{};
391
    uint16_t count{};
392
    size_t min_entry_size{};
393
    size_t section_size_offset{};
394
};
395

396
static std::optional<SectionHeaderTableLayout>
397
read_section_header_table_layout(const std::array<unsigned char, sizeof(ELFIO::Elf64_Ehdr)>& header,
694✔
398
                                 const std::streamsize bytes_read, const unsigned char elf_class,
399
                                 const bool little_endian) {
400
    constexpr size_t elf64_header_size = sizeof(ELFIO::Elf64_Ehdr);
694✔
401
    constexpr size_t elf32_section_header_size = sizeof(ELFIO::Elf32_Shdr);
694✔
402
    constexpr size_t elf64_section_header_size = sizeof(ELFIO::Elf64_Shdr);
694✔
403
    constexpr size_t elf32_section_size_offset = offsetof(ELFIO::Elf32_Shdr, sh_size);
694✔
404
    constexpr size_t elf64_section_size_offset = offsetof(ELFIO::Elf64_Shdr, sh_size);
694✔
405

406
    if (elf_class == ELFIO::ELFCLASS32) {
694✔
407
        return SectionHeaderTableLayout{
×
408
            .offset = read_uint<ELFIO::Elf32_Off>(header.data() + offsetof(ELFIO::Elf32_Ehdr, e_shoff), little_endian),
×
409
            .entry_size =
410
                read_uint<ELFIO::Elf_Half>(header.data() + offsetof(ELFIO::Elf32_Ehdr, e_shentsize), little_endian),
×
411
            .count = read_uint<ELFIO::Elf_Half>(header.data() + offsetof(ELFIO::Elf32_Ehdr, e_shnum), little_endian),
×
412
            .min_entry_size = elf32_section_header_size,
413
            .section_size_offset = elf32_section_size_offset,
414
        };
×
415
    }
416

417
    if (elf_class == ELFIO::ELFCLASS64 && bytes_read >= gsl::narrow<std::streamsize>(elf64_header_size)) {
694✔
418
        return SectionHeaderTableLayout{
694✔
419
            .offset = read_uint<ELFIO::Elf64_Off>(header.data() + offsetof(ELFIO::Elf64_Ehdr, e_shoff), little_endian),
694✔
420
            .entry_size =
421
                read_uint<ELFIO::Elf_Half>(header.data() + offsetof(ELFIO::Elf64_Ehdr, e_shentsize), little_endian),
694✔
422
            .count = read_uint<ELFIO::Elf_Half>(header.data() + offsetof(ELFIO::Elf64_Ehdr, e_shnum), little_endian),
694✔
423
            .min_entry_size = elf64_section_header_size,
424
            .section_size_offset = elf64_section_size_offset,
425
        };
694✔
426
    }
427

428
    return std::nullopt;
×
429
}
430

431
static void validate_section_header_table_bounds(std::istream& input_stream, const std::string& path,
694✔
432
                                                 const std::uintmax_t data_size) {
433
    // This pre-load check validates only the section header table extent.
434
    // Individual section payload ranges are checked after ELFIO loads metadata.
435
    constexpr size_t min_elf_header_size = sizeof(ELFIO::Elf32_Ehdr);
694✔
436
    constexpr size_t elf64_header_size = sizeof(ELFIO::Elf64_Ehdr);
694✔
437
    constexpr unsigned char elf_magic[] = {0x7f, 'E', 'L', 'F'};
694✔
438

439
    std::array<unsigned char, elf64_header_size> header{};
694✔
440
    const auto bytes_read = read_stream_bytes_at(input_stream, 0, header.data(), header.size());
1,041✔
441

442
    if (bytes_read < gsl::narrow<std::streamsize>(min_elf_header_size) ||
1,041✔
443
        !std::equal(std::begin(elf_magic), std::end(elf_magic), header.begin())) {
1,388✔
444
        return;
×
445
    }
446

447
    const unsigned char elf_class = header[ELFIO::EI_CLASS];
694✔
448
    const unsigned char elf_data = header[ELFIO::EI_DATA];
694✔
449
    const bool little_endian = elf_data == ELFIO::ELFDATA2LSB;
694✔
450
    if (elf_data != ELFIO::ELFDATA2LSB && elf_data != ELFIO::ELFDATA2MSB) {
694✔
451
        return;
452
    }
453

454
    const std::optional<SectionHeaderTableLayout> layout =
347✔
455
        read_section_header_table_layout(header, bytes_read, elf_class, little_endian);
694✔
456
    if (!layout) {
694✔
457
        return;
458
    }
459
    const SectionHeaderTableLayout table = *layout;
694✔
460

461
    if (table.offset == 0 && table.count == 0) {
694✔
462
        return;
463
    }
464
    if (table.entry_size < table.min_entry_size) {
694✔
465
        throw UnmarshalError("ELF section header table in " + path + " has invalid entry size");
6✔
466
    }
467
    if (table.offset > data_size) {
690✔
468
        throw UnmarshalError("ELF section header table in " + path + " is out of bounds");
3✔
469
    }
470

471
    const std::uintmax_t available = data_size - table.offset;
688✔
472
    std::uintmax_t required_entries = table.count;
688✔
473
    if (table.count == 0) {
688✔
474
        if (available < table.min_entry_size) {
2✔
475
            throw UnmarshalError("ELF section header table in " + path + " is out of bounds");
×
476
        }
477

478
        std::array<unsigned char, sizeof(ELFIO::Elf64_Shdr)> null_section_header{};
2✔
479
        const auto null_section_bytes_read =
1✔
480
            read_stream_bytes_at(input_stream, table.offset, null_section_header.data(), table.min_entry_size);
3✔
481
        if (null_section_bytes_read < gsl::narrow<std::streamsize>(table.min_entry_size)) {
2✔
482
            throw UnmarshalError("ELF section header table in " + path + " is out of bounds");
×
483
        }
484

485
        required_entries =
2✔
486
            elf_class == ELFIO::ELFCLASS32
487
                ? read_uint<ELFIO::Elf_Word>(null_section_header.data() + table.section_size_offset, little_endian)
2✔
488
                : read_uint<ELFIO::Elf_Xword>(null_section_header.data() + table.section_size_offset, little_endian);
3✔
489
    }
490
    if (required_entries > available / table.entry_size) {
688✔
491
        throw UnmarshalError("ELF section header table in " + path + " is out of bounds");
3✔
492
    }
493
}
494

495
ELFIO::elfio load_elf(std::istream& input_stream, const std::string& path) {
694✔
496
    const std::uintmax_t data_size = get_data_size(input_stream, path);
694✔
497
    validate_section_header_table_bounds(input_stream, path, data_size);
694✔
498

499
    ELFIO::elfio reader;
686✔
500
    if (!reader.load(input_stream)) {
686✔
501
        throw UnmarshalError("Can't process ELF file " + path);
×
502
    }
503

504
    if (reader.get_machine() != ELFIO::EM_BPF && reader.get_machine() != ELFIO::EM_NONE) {
686✔
505
        throw UnmarshalError("Unsupported ELF machine in file " + path + ": expected EM_BPF");
3✔
506
    }
507

508
    for (const auto& section : reader.sections) {
15,924✔
509
        if (!section || section->get_type() == ELFIO::SHT_NOBITS) {
15,242✔
510
            continue;
86✔
511
        }
512

513
        const std::uintmax_t offset = section->get_offset();
15,156✔
514
        const std::uintmax_t size = section->get_size();
15,156✔
515
        if (offset > data_size || size > data_size - offset) {
15,156✔
516
            throw UnmarshalError("ELF section '" + section->get_name() + "' has out-of-bounds file range");
3✔
517
        }
518
    }
519

520
    return reader;
682✔
521
}
4✔
522

523
ELFIO::const_symbol_section_accessor read_and_validate_symbol_section(const ELFIO::elfio& reader,
682✔
524
                                                                      const std::string& path) {
525
    const ELFIO::section* symbol_section = reader.sections[".symtab"];
682✔
526
    if (!symbol_section) {
682✔
527
        throw UnmarshalError("No symbol section found in ELF file " + path);
×
528
    }
529
    const auto expected_entry_size =
341✔
530
        reader.get_class() == ELFIO::ELFCLASS32 ? sizeof(ELFIO::Elf32_Sym) : sizeof(ELFIO::Elf64_Sym);
682✔
531
    if (symbol_section->get_entry_size() != expected_entry_size || symbol_section->get_entry_size() == 0 ||
1,702✔
532
        symbol_section->get_data() == nullptr || symbol_section->get_size() % symbol_section->get_entry_size() != 0) {
1,702✔
533
        throw UnmarshalError("Invalid symbol section in ELF file " + path);
3✔
534
    }
535

536
    const auto linked_strtab_index = symbol_section->get_link();
680✔
537
    if (linked_strtab_index >= reader.sections.size() || reader.sections[linked_strtab_index] == nullptr ||
1,360✔
538
        reader.sections[linked_strtab_index]->get_data() == nullptr) {
680✔
539
        throw UnmarshalError("Invalid symbol string table link in ELF file " + path);
×
540
    }
541
    return ELFIO::const_symbol_section_accessor{reader, symbol_section};
680✔
542
}
543

544
// ---------------------------------------------------------------------------
545
// ProgramReader methods
546
// ---------------------------------------------------------------------------
547

548
void ProgramReader::record_function_relocation(FunctionRelocation reloc) {
848✔
549
    function_relocation_index_.emplace(reloc.prog_index, reloc.source_offset);
848✔
550
    function_relocations.push_back(std::move(reloc));
848✔
551
}
848✔
552

553
bool ProgramReader::has_function_relocation(const size_t prog_index, const size_t source_offset) const {
2,716✔
554
    return function_relocation_index_.contains({prog_index, source_offset});
2,716✔
555
}
556

557
void ProgramReader::enqueue_synthetic_local_calls(const std::vector<EbpfInst>& instructions,
30,912✔
558
                                                  const ELFIO::Elf_Half section_index,
559
                                                  const ELFIO::Elf_Xword program_offset) {
560
    if (section_index >= reader.sections.size()) {
30,912✔
561
        throw UnmarshalError("Invalid section index");
×
562
    }
563
    const auto* section = reader.sections[section_index];
30,912✔
564
    if (!section) {
30,912✔
565
        throw UnmarshalError("Invalid section index");
×
566
    }
567

568
    const int64_t section_insn_count = gsl::narrow<int64_t>(section->get_size() / sizeof(EbpfInst));
30,912✔
569
    const int64_t program_start = gsl::narrow<int64_t>(program_offset / sizeof(EbpfInst));
30,912✔
570
    const int64_t program_end = program_start + gsl::narrow<int64_t>(instructions.size());
30,912✔
571

572
    for (size_t loc = 0; loc < instructions.size(); ++loc) {
31,609,208✔
573
        const EbpfInst& inst = instructions[loc];
31,578,296✔
574
        if (!(inst.opcode == INST_OP_CALL && inst.src == INST_CALL_LOCAL)) {
31,578,296✔
575
            continue;
31,578,284✔
576
        }
577
        if (has_function_relocation(raw_programs.size(), loc)) {
1,880✔
578
            continue;
836✔
579
        }
580

581
        const int64_t target = program_start + gsl::narrow<int64_t>(loc) + 1 + inst.imm;
1,044✔
582
        if (target >= program_start && target < program_end) {
1,044✔
583
            continue;
1,032✔
584
        }
585
        if (target < 0 || target >= section_insn_count) {
12✔
586
            throw UnmarshalError("Local call target out of section bounds");
×
587
        }
588

589
        const auto target_offset = gsl::narrow<ELFIO::Elf_Xword>(target * sizeof(EbpfInst));
12✔
590
        const auto target_name = find_function_symbol_at_offset(symbols, section_index, target_offset);
12✔
591
        if (!target_name) {
12✔
592
            throw UnmarshalError("Subprogram not found at section offset " + std::to_string(target_offset));
×
593
        }
594

595
        record_function_relocation(FunctionRelocation{
18✔
596
            .prog_index = raw_programs.size(),
12✔
597
            .source_offset = loc,
598
            .relocation_entry_index = std::nullopt,
599
            .target_section_index = section_index,
600
            .target_function_name = *target_name,
601
        });
602
    }
12✔
603
}
30,912✔
604

605
std::string ProgramReader::append_subprograms(RawProgram& prog) {
31,678✔
606
    if (resolved_subprograms[&prog]) {
31,678✔
607
        return {};
770✔
608
    }
609

610
    if (currently_visiting.contains(&prog)) {
30,908✔
611
        throw UnmarshalError("Mutual recursion in subprogram calls");
×
612
    }
613
    currently_visiting.insert(&prog);
30,908✔
614

615
    std::map<std::string, ELFIO::Elf_Xword> subprogram_offsets;
30,908✔
616
    for (const auto& reloc : function_relocations) {
39,696✔
617
        if (reloc.prog_index >= raw_programs.size() ||
13,182✔
618
            raw_programs[reloc.prog_index].function_name != prog.function_name) {
8,788✔
619
            continue;
7,940✔
620
        }
621
        const auto& target_function_name = reloc.target_function_name;
848✔
622
        if (!subprogram_offsets.contains(target_function_name)) {
848✔
623
            subprogram_offsets[target_function_name] = prog.prog.size();
770✔
624
            if (reloc.target_section_index >= reader.sections.size()) {
770✔
625
                throw UnmarshalError("Invalid section index");
×
626
            }
627
            const auto& sub_sec = *reader.sections[reloc.target_section_index];
770✔
628
            if (const auto sub = find_subprogram(raw_programs, sub_sec, target_function_name)) {
770✔
629
                if (sub == &prog) {
770✔
630
                    throw UnmarshalError("Recursive subprogram call");
×
631
                }
632
                const std::string err = append_subprograms(*sub);
770✔
633
                if (!err.empty()) {
770✔
634
                    return err;
×
635
                }
636
                const size_t base = subprogram_offsets[target_function_name];
770✔
637

638
                prog.prog.insert(prog.prog.end(), sub->prog.begin(), sub->prog.end());
770✔
639
                if (parse_params.options.verbosity_opts.print_line_info) {
770✔
640
                    for (const auto& [k, info] : sub->info.line_info) {
×
641
                        prog.info.line_info[base + k] = info;
×
642
                    }
643
                }
644
                for (const size_t builtin_offset : sub->info.builtin_call_offsets) {
770✔
645
                    prog.info.builtin_call_offsets.insert(base + builtin_offset);
×
646
                }
647
            } else {
770✔
648
                return "Subprogram not found: " + target_function_name;
×
649
            }
650
        }
651
        const auto target_offset = gsl::narrow<int64_t>(subprogram_offsets[target_function_name]);
848✔
652
        const auto source_offset = gsl::narrow<int64_t>(reloc.source_offset);
848✔
653
        prog.prog[reloc.source_offset].imm = gsl::narrow<int32_t>(target_offset - source_offset - 1);
848✔
654
    }
655
    currently_visiting.erase(&prog);
30,908✔
656
    resolved_subprograms[&prog] = true;
30,908✔
657
    return {};
30,908✔
658
}
30,908✔
659

660
int ProgramReader::relocate_map(const std::string& name, const ELFIO::Elf_Word index) const {
477,074✔
661
    size_t val{};
477,074✔
662
    if (const auto* record_size = std::get_if<size_t>(&global.map_record_size_or_map_offsets)) {
477,074✔
663
        const auto symbol_value = get_symbol_details(symbols, index).value;
×
664
        if (symbol_value % *record_size != 0) {
×
665
            throw UnmarshalError("Map symbol offset " + std::to_string(symbol_value) +
×
666
                                 " is not aligned to record size " + std::to_string(*record_size));
×
667
        }
668

669
        val = symbol_value / *record_size;
×
670
    } else {
671
        const auto& offsets = std::get<MapOffsets>(global.map_record_size_or_map_offsets);
477,074✔
672
        const auto it = offsets.find(name);
477,074✔
673
        if (it == offsets.end()) {
477,074✔
674
            throw UnmarshalError("Map descriptor not found: " + name);
×
675
        }
676
        val = it->second;
477,074✔
677
    }
678
    if (val >= global.map_descriptors.size()) {
477,074✔
679
        throw UnmarshalError(bad_reloc_value(val));
×
680
    }
681
    return global.map_descriptors.at(val).original_fd;
477,074✔
682
}
683

684
int ProgramReader::relocate_global_variable(const std::string& name) const {
12,208✔
685
    const auto* offsets = std::get_if<MapOffsets>(&global.map_record_size_or_map_offsets);
12,208✔
686
    if (!offsets) {
12,208✔
687
        throw UnmarshalError("Invalid map offsets");
×
688
    }
689
    const auto it = offsets->find(name);
12,208✔
690
    if (it == offsets->end()) {
12,208✔
691
        throw UnmarshalError("Map descriptor not found: " + name);
×
692
    }
693
    const size_t val = it->second;
12,208✔
694
    if (val >= global.map_descriptors.size()) {
12,208✔
695
        throw UnmarshalError(bad_reloc_value(val));
×
696
    }
697
    return global.map_descriptors.at(val).original_fd;
18,312✔
698
}
699

700
int32_t ProgramReader::compute_lddw_reloc_offset_imm(const ELFIO::Elf_Sxword addend, const ELFIO::Elf_Word index,
12,208✔
701
                                                     const std::reference_wrapper<EbpfInst> lo_inst) const {
702
    const auto& sym = get_symbol_details(symbols, index);
12,208✔
703
    if (sym.type == ELFIO::STT_SECTION) {
12,208✔
704
        return addend != 0 ? gsl::narrow<int32_t>(addend) : lo_inst.get().imm;
446✔
705
    }
706
    return gsl::narrow<int32_t>(sym.value + addend);
11,762✔
707
}
12,208✔
708

709
void ProgramReader::build_ksym_function_resolution_cache() {
1,684✔
710
    ksym_function_resolution_cache.clear();
1,684✔
711
    const auto* btf_sec = reader.sections[".BTF"];
1,684✔
712
    if (!btf_sec || !btf_sec->get_data()) {
1,684✔
713
        return;
1,620✔
714
    }
715

716
    const libbtf::btf_type_data btf_data{vector_of<std::byte>(*btf_sec)};
1,114✔
717

718
    const auto ksyms_id = btf_data.get_id(".ksyms");
1,082✔
719
    if (ksyms_id == 0) {
1,082✔
720
        return;
1,034✔
721
    }
722

723
    libbtf::btf_kind_data_section ksyms;
48✔
724
    try {
24✔
725
        ksyms = btf_data.get_kind_type<libbtf::btf_kind_data_section>(ksyms_id);
48✔
726
    } catch (const std::exception& e) {
×
727
        throw UnmarshalError(std::string("Unsupported or invalid BTF .ksyms section (id ") + std::to_string(ksyms_id) +
×
728
                             "): " + e.what());
×
729
    }
×
730

731
    for (const auto& member : ksyms.members) {
176✔
732
        if (btf_data.get_kind_index(member.type) != libbtf::BTF_KIND_FUNCTION) {
128✔
733
            continue;
18✔
734
        }
735
        libbtf::btf_kind_function function;
110✔
736
        try {
55✔
737
            function = btf_data.get_kind_type<libbtf::btf_kind_function>(member.type);
165✔
738
        } catch (const std::exception& e) {
×
739
            throw UnmarshalError(std::string("Unsupported or invalid BTF .ksyms function (type ") +
×
740
                                 std::to_string(member.type) + "): " + e.what());
×
741
        }
×
742
        if (function.name.empty() || ksym_function_resolution_cache.contains(function.name)) {
110✔
743
            continue;
×
744
        }
745

746
        std::optional<KsymBtfId> resolved;
110✔
747
        if (parse_params.platform && parse_params.platform->resolve_ksym_btf_id) {
110✔
748
            resolved = parse_params.platform->resolve_ksym_btf_id(function.name);
110✔
749
        }
750
        ksym_function_resolution_cache.emplace(function.name, resolved);
110✔
751
    }
110✔
752
}
1,082✔
753

754
bool ProgramReader::try_reloc(const std::string& symbol_name, const ELFIO::Elf_Half symbol_section_index,
723,032✔
755
                              const unsigned char symbol_type, const unsigned char symbol_bind,
756
                              std::vector<EbpfInst>& instructions, const size_t location, const ELFIO::Elf_Word index,
757
                              const ELFIO::Elf_Sxword addend) {
758
    EbpfInst& instruction_to_relocate = instructions[location];
723,032✔
759

760
    if (symbol_section_index == ELFIO::SHN_UNDEF) {
723,032✔
761
        if (const auto value = resolve_known_linux_extern_symbol(symbol_name)) {
232,890✔
762
            if (rewrite_extern_constant_load(instructions, location, *value)) {
52✔
763
                return true;
52✔
764
            }
765
        }
766
        if (rewrite_extern_address_load_to_zero(instructions, location)) {
232,838✔
767
            return true;
70✔
768
        }
769
    }
770

771
    // Handle local function calls - queue for post-processing.
772
    if (instruction_to_relocate.opcode == INST_OP_CALL && instruction_to_relocate.src == INST_CALL_LOCAL) {
722,840✔
773
        if (symbol_section_index == ELFIO::SHN_UNDEF) {
233,534✔
774
            if (const auto it = ksym_function_resolution_cache.find(symbol_name);
232,698✔
775
                it != ksym_function_resolution_cache.end()) {
232,698✔
776
                if (it->second.has_value()) {
116✔
777
                    if (!rewrite_extern_kfunc_call(instruction_to_relocate, *it->second)) {
98✔
778
                        throw UnmarshalError("Invalid kfunc call rewrite for symbol " + symbol_name +
×
779
                                             ": instruction encoding or resolver output is invalid");
×
780
                    }
781
                    return true;
50✔
782
                }
783
                if (symbol_bind != ELFIO::STB_WEAK) {
18✔
784
                    return false;
1✔
785
                }
786
            }
787
        }
788
        if (symbol_section_index == ELFIO::SHN_UNDEF && parse_params.platform->resolve_builtin_call) {
233,434✔
789
            if (const auto builtin_id = parse_params.platform->resolve_builtin_call(symbol_name)) {
232,598✔
790
                instruction_to_relocate.src = INST_CALL_STATIC_HELPER;
232,598✔
791
                instruction_to_relocate.imm = *builtin_id;
232,598✔
792
                if (*builtin_id < 0) {
232,598✔
793
                    builtin_offsets_for_current_program.insert(location);
232,592✔
794
                }
795
                return true;
232,598✔
796
            }
797
        }
798
        if (symbol_section_index == ELFIO::SHN_UNDEF) {
836✔
799
            return false;
800
        }
801

802
        std::string target_function_name = symbol_name;
836✔
803
        if (target_function_name.empty() && symbol_type == ELFIO::STT_SECTION) {
836✔
804
            const int64_t target_byte_offset =
190✔
805
                addend != 0 ? addend : (gsl::narrow<int64_t>(instruction_to_relocate.imm) + 1) * sizeof(EbpfInst);
380✔
806
            if (target_byte_offset < 0 || target_byte_offset % sizeof(EbpfInst) != 0) {
380✔
807
                throw UnmarshalError("Invalid section-local call target offset");
×
808
            }
809
            if (const auto target = find_function_symbol_at_offset(symbols, symbol_section_index,
570✔
810
                                                                   gsl::narrow<ELFIO::Elf_Xword>(target_byte_offset))) {
380✔
811
                target_function_name = *target;
570✔
812
            }
380✔
813
        }
814

815
        if (!target_function_name.empty() && !has_function_relocation(raw_programs.size(), location)) {
836✔
816
            record_function_relocation(FunctionRelocation{
836✔
817
                .prog_index = raw_programs.size(),
836✔
818
                .source_offset = location,
819
                .relocation_entry_index = index,
820
                .target_section_index = symbol_section_index,
821
                .target_function_name = target_function_name,
822
            });
823
            return true;
836✔
824
        }
825
        return false;
×
826
    }
836✔
827

828
    // Handle empty symbol names for global variable sections
829
    if (symbol_name.empty()) {
489,306✔
830
        if (global.variable_section_indices.contains(symbol_section_index)) {
470✔
831
            if (!std::holds_alternative<MapOffsets>(global.map_record_size_or_map_offsets)) {
446✔
832
                return false;
833
            }
834

835
            auto [lo_inst, hi_inst] = validate_and_get_lddw_pair(instructions, location, "global variable");
669✔
836

837
            hi_inst.get().imm = compute_lddw_reloc_offset_imm(addend, index, lo_inst);
446✔
838
            lo_inst.get().src = INST_LD_MODE_MAP_VALUE;
446✔
839

840
            const std::string section_name = reader.sections[symbol_section_index]->get_name();
669✔
841
            lo_inst.get().imm = relocate_global_variable(section_name);
446✔
842
            return true;
446✔
843
        }
446✔
844
        return true;
12✔
845
    }
846

847
    if ((instruction_to_relocate.opcode & INST_CLS_MASK) != INST_CLS_LD) {
488,836✔
848
        return false;
849
    }
850

851
    if (global.map_section_indices.contains(symbol_section_index)) {
488,836✔
852
        instruction_to_relocate.src = INST_LD_MODE_MAP_FD;
477,074✔
853
        instruction_to_relocate.imm = relocate_map(symbol_name, index);
477,074✔
854
        return true;
477,074✔
855
    }
856

857
    if (global.variable_section_indices.contains(symbol_section_index)) {
11,762✔
858
        auto [lo_inst, hi_inst] =
17,643✔
859
            validate_and_get_lddw_pair(instructions, location, "global variable '" + symbol_name + "'");
17,643✔
860

861
        hi_inst.get().imm = compute_lddw_reloc_offset_imm(addend, index, lo_inst);
11,762✔
862
        lo_inst.get().src = INST_LD_MODE_MAP_VALUE;
11,762✔
863
        lo_inst.get().imm = relocate_global_variable(reader.sections[symbol_section_index]->get_name());
17,643✔
864
        return true;
11,762✔
865
    }
866

867
    if (symbol_name.rfind("__config_", 0) == 0) {
×
868
        instruction_to_relocate.imm = 0;
×
869
        return true;
×
870
    }
871

872
    return false;
873
}
874

875
void ProgramReader::process_relocations(std::vector<EbpfInst>& instructions, const ELFIO::section& reloc,
29,928✔
876
                                        const std::string& section_name, const ELFIO::Elf_Xword program_offset,
877
                                        const size_t program_size) {
878
    const auto layout = relocation_section_layout(reader, reloc, section_name);
29,928✔
879
    for (ELFIO::Elf_Xword i = 0; i < layout.entries_num; i++) {
1,328,534✔
880
        const auto entry = read_relocation_entry(reloc, i, layout);
1,298,612✔
881
        ELFIO::Elf64_Addr o = entry.offset;
1,298,612✔
882
        const ELFIO::Elf_Word idx = entry.symbol_index;
1,298,612✔
883
        const unsigned type = entry.type;
1,298,612✔
884
        const ELFIO::Elf_Sxword addend = entry.addend;
1,298,612✔
885
        if (!is_supported_bpf_relocation_type(type)) {
1,298,612✔
886
            throw UnmarshalError("Unsupported relocation type " + std::to_string(type) + " in section " + section_name);
4✔
887
        }
888
        if (type == R_BPF_NONE_TYPE && idx == 0) {
1,298,610✔
889
            continue;
575,576✔
890
        }
891
        if (idx >= symbols.get_symbols_num()) {
1,298,610✔
892
            throw UnmarshalError("Invalid relocation symbol index " + std::to_string(idx) + " in section " +
5✔
893
                                 section_name);
6✔
894
        }
895
        if (o < program_offset || o >= program_offset + program_size) {
1,298,608✔
896
            continue;
575,576✔
897
        }
898
        o -= program_offset;
723,032✔
899

900
        if (o % sizeof(EbpfInst) != 0) {
723,032✔
901
            throw UnmarshalError("Unaligned relocation offset");
×
902
        }
903
        const auto loc = o / sizeof(EbpfInst);
723,032✔
904
        if (loc >= instructions.size()) {
723,032✔
905
            throw UnmarshalError("Invalid relocation");
×
906
        }
907
        auto sym = get_symbol_details(symbols, idx);
723,032✔
908

909
        if (!try_reloc(sym.name, sym.section_index, sym.type, sym.bind, instructions, loc, idx, addend)) {
723,032✔
910
            unresolved_symbol_errors.push_back(unresolved_symbol_error_t{
5✔
911
                .section = section_name,
912
                .message = "Unresolved external symbol " + (sym.name.empty() ? "<anonymous>" : sym.name) +
4✔
913
                           " in section " + section_name + " at location " + std::to_string(loc),
9✔
914
            });
915
        }
916
    }
723,032✔
917
}
29,922✔
918

919
const ELFIO::section* ProgramReader::get_relocation_section(const std::string& name) const {
30,918✔
920
    if (name == ".BTF") {
30,918✔
921
        return nullptr;
922
    }
923
    const auto* relocs = reader.sections[".rel" + name];
30,918✔
924
    if (!relocs) {
30,918✔
925
        relocs = reader.sections[".rela" + name];
1,620✔
926
    }
927
    if (!relocs || !relocs->get_data()) {
30,918✔
928
        return nullptr;
990✔
929
    }
930
    return relocs;
14,964✔
931
}
932

933
void ProgramReader::read_programs() {
1,684✔
934
    resolved_subprograms.clear();
1,684✔
935
    build_ksym_function_resolution_cache();
1,684✔
936

937
    for (const auto& sec : reader.sections) {
88,754✔
938
        if (!(sec->get_flags() & ELFIO::SHF_EXECINSTR) || !sec->get_size() || !sec->get_data()) {
87,094✔
939
            continue;
57,372✔
940
        }
941
        auto section_instructions = vector_of<EbpfInst>(*sec);
29,722✔
942
        const auto& sec_name = sec->get_name();
29,722✔
943
        const auto prog_type = parse_params.platform->get_program_type(sec_name, parse_params.path);
29,722✔
944
        for (ELFIO::Elf_Xword offset = 0; offset < sec->get_size();) {
60,634✔
945
            builtin_offsets_for_current_program.clear();
30,920✔
946
            auto [name, symbol_size] = get_program_name_and_size(*sec, offset, symbols);
30,920✔
947
            const auto extracted_size = compute_reachable_program_span(section_instructions, offset, symbol_size);
30,918✔
948
            if (offset + extracted_size > sec->get_size()) {
30,918✔
949
                throw UnmarshalError("Program span exceeds section bounds in section " + sec->get_name());
×
950
            }
951
            auto instructions = vector_of<EbpfInst>(sec->get_data() + offset, extracted_size);
30,918✔
952
            if (const auto reloc_sec = get_relocation_section(sec_name)) {
30,918✔
953
                process_relocations(instructions, *reloc_sec, sec_name, offset, extracted_size);
29,928✔
954
            }
955
            enqueue_synthetic_local_calls(instructions, sec->get_index(), offset);
30,912✔
956
            ProgramInfo program_info{
15,456✔
957
                .platform = parse_params.platform,
30,912✔
958
                .map_descriptors = global.map_descriptors,
30,912✔
959
                .type = prog_type,
960
                .builtin_call_offsets = std::move(builtin_offsets_for_current_program),
30,912✔
961
            };
30,912✔
962
            raw_programs.emplace_back(RawProgram{
77,280✔
963
                parse_params.path,
30,912✔
964
                sec_name,
965
                gsl::narrow<uint32_t>(offset),
30,912✔
966
                name,
967
                std::move(instructions),
15,456✔
968
                std::move(program_info),
15,456✔
969
            });
970
            offset += symbol_size;
30,912✔
971
        }
30,924✔
972
    }
29,734✔
973

974
    if (const auto btf_sec = reader.sections[".BTF"]) {
1,660✔
975
        try {
538✔
976
            process_core_relocations({vector_of<std::byte>(*btf_sec)});
1,079✔
977
        } catch (const std::exception& e) {
2✔
978
            throw UnmarshalError(std::string("Unsupported or invalid CO-RE/BTF relocation data: ") + e.what());
5✔
979
        }
2✔
980
    }
981

982
    bool has_relevant_unresolved_symbols = false;
1,658✔
983
    for (const auto& err : unresolved_symbol_errors) {
1,660✔
984
        if (!parse_params.desired_section.empty() && err.section != parse_params.desired_section) {
2✔
985
            continue;
×
986
        }
987
        has_relevant_unresolved_symbols = true;
2✔
988
        std::cerr << err.message << std::endl;
2✔
989
    }
990
    if (has_relevant_unresolved_symbols) {
1,658✔
991
        throw UnmarshalError("Unresolved symbols found.");
5✔
992
    }
993

994
    if (parse_params.options.verbosity_opts.print_line_info) {
1,656✔
995
        if (const auto btf_sec = reader.sections[".BTF"]) {
×
996
            if (const auto btf_ext = reader.sections[".BTF.ext"]) {
×
997
                try {
998
                    update_line_info(raw_programs, btf_sec, btf_ext);
×
999
                } catch (const std::exception& e) {
×
1000
                    throw UnmarshalError(std::string("Unsupported or invalid BTF line info: ") + e.what());
×
1001
                }
×
1002
            }
1003
        }
1004
    }
1005

1006
    for (auto& prog : raw_programs) {
32,564✔
1007
        const auto err = append_subprograms(prog);
30,908✔
1008
        if (!err.empty() && prog.section_name == parse_params.desired_section) {
30,908✔
1009
            throw UnmarshalError(err);
×
1010
        }
1011
    }
30,908✔
1012

1013
    if (!parse_params.desired_section.empty()) {
1,656✔
1014
        std::erase_if(raw_programs, [&](const auto& p) { return p.section_name != parse_params.desired_section; });
17,110✔
1015
    }
1016

1017
    if (raw_programs.empty()) {
1,656✔
1018
        throw UnmarshalError(parse_params.desired_section.empty() ? "No executable sections" : "Section not found");
×
1019
    }
1020
}
1,656✔
1021

1022
// ---------------------------------------------------------------------------
1023
// Top-level read_elf functions
1024
// ---------------------------------------------------------------------------
1025

1026
std::vector<RawProgram> read_elf(std::istream& input_stream, const std::string& path,
6✔
1027
                                 const std::string& desired_section, const std::string& desired_program,
1028
                                 const VerifierOptions& options, const ebpf_platform_t* platform) {
1029
    try {
3✔
1030
        std::vector<RawProgram> res;
6✔
1031
        parse_params_t params{path, options, platform, desired_section};
6✔
1032
        auto reader = load_elf(input_stream, path);
6✔
1033
        auto symbols = read_and_validate_symbol_section(reader, path);
6✔
1034
        auto global = extract_global_data(params, reader, symbols);
6✔
1035
        ProgramReader program_reader{params, reader, symbols, global};
6✔
1036
        program_reader.read_programs();
6✔
1037

1038
        if (desired_program.empty()) {
4✔
1039
            return std::move(program_reader.raw_programs);
4✔
1040
        }
1041
        for (RawProgram& cur : program_reader.raw_programs) {
×
1042
            if (cur.function_name == desired_program) {
×
1043
                res.emplace_back(std::move(cur));
×
1044
                return res;
×
1045
            }
1046
        }
1047
        return std::move(program_reader.raw_programs);
×
1048
    } catch (const UnmarshalError&) {
15✔
1049
        throw;
2✔
1050
    } catch (const std::exception& e) {
2✔
1051
        throw UnmarshalError(std::string("Unsupported or invalid ELF/BTF data: ") + e.what());
×
1052
    }
×
1053
}
1054

1055
std::vector<RawProgram> read_elf(const std::string& path, const std::string& desired_section,
×
1056
                                 const std::string& desired_program, const VerifierOptions& options,
1057
                                 const ebpf_platform_t* platform) {
1058
    if (std::ifstream stream{path, std::ios::in | std::ios::binary}) {
×
1059
        return read_elf(stream, path, desired_section, desired_program, options, platform);
×
1060
    }
×
1061
    struct stat st; // NOLINT(*-pro-type-member-init)
1062
    if (stat(path.c_str(), &st)) {
×
1063
        throw UnmarshalError(std::string(strerror(errno)) + " opening " + path);
×
1064
    }
1065
    throw UnmarshalError("Can't process ELF file " + path);
×
1066
}
1067

1068
} // namespace prevail
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc