• 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

74.3
/src/io/elf_map_parser.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <algorithm>
4
#include <cassert>
5
#include <iostream>
6
#include <map>
7
#include <ranges>
8
#include <set>
9
#include <sstream>
10
#include <string>
11
#include <variant>
12
#include <vector>
13

14
#include <elfio/elfio.hpp>
15
#include <libbtf/btf_json.h>
16
#include <libbtf/btf_map.h>
17
#include <libbtf/btf_parse.h>
18

19
#include "crab_utils/num_safety.hpp"
20
#include "io/elf_reader.hpp"
21

22
namespace prevail {
23

24
namespace {
25

26
constexpr int DEFAULT_MAP_FD = -1;
27

28
void dump_btf_types(const libbtf::btf_type_data& btf_data, const std::string& path) {
×
29
    std::stringstream output;
×
30
    std::cout << "Dumping BTF data for " << path << std::endl;
×
31
    btf_data.to_json(output);
×
32
    std::cout << libbtf::pretty_print_json(output.str()) << std::endl;
×
33
}
×
34

35
std::map<int, int> map_typeid_to_fd(const std::vector<EbpfMapDescriptor>& map_descriptors) {
704✔
36
    std::map<int, int> type_id_to_fd_map;
704✔
37
    int pseudo_fd = 1;
704✔
38
    for (const auto& map_descriptor : map_descriptors) {
5,518✔
39
        if (!type_id_to_fd_map.contains(map_descriptor.original_fd)) {
4,814✔
40
            type_id_to_fd_map[map_descriptor.original_fd] = pseudo_fd++;
4,752✔
41
        }
42
    }
43
    return type_id_to_fd_map;
704✔
44
}
×
45

46
/// @brief Add implicit map descriptors for global variable sections.
47
void add_global_variable_maps(const ELFIO::elfio& reader, ElfGlobalData& global, MapOffsets& map_offsets) {
704✔
48
    for (const auto section : global_sections(reader)) {
1,518✔
49
        map_offsets[section->get_name()] = global.map_descriptors.size();
814✔
50

51
        global.map_descriptors.push_back(EbpfMapDescriptor{
814✔
52
            .original_fd = gsl::narrow<int>(global.map_descriptors.size() + 1),
814✔
53
            .type = 0,
54
            .key_size = sizeof(uint32_t),
55
            .value_size = gsl::narrow<uint32_t>(section->get_size()),
814✔
56
            .max_entries = 1,
57
            .inner_map_fd = DEFAULT_MAP_FD,
58
            .name = section->get_name(),
814✔
59
        });
60

61
        global.variable_section_indices.insert(section->get_index());
814✔
62
    }
704✔
63
}
704✔
64

65
ElfGlobalData parse_btf_section(const parse_params_t& parse_params, const ELFIO::elfio& reader) {
708✔
66
    const auto btf_section = reader.sections[".BTF"];
708✔
67
    if (!btf_section) {
708✔
68
        return {};
×
69
    }
70

71
    std::optional<libbtf::btf_type_data> btf_data;
708✔
72
    try {
354✔
73
        btf_data.emplace(vector_of<std::byte>(*btf_section));
708✔
74
    } catch (const std::exception& e) {
×
75
        throw UnmarshalError(std::string("Unsupported or invalid BTF data: ") + e.what());
×
76
    }
×
77
    if (parse_params.options.verbosity_opts.dump_btf_types_json) {
708✔
78
        dump_btf_types(*btf_data, parse_params.path);
×
79
    }
80

81
    ElfGlobalData global;
708✔
82
    MapOffsets map_offsets;
708✔
83

84
    // Parse BTF-defined maps from the .maps DATASEC
85
    try {
354✔
86
        for (const auto& map : parse_btf_map_section(*btf_data)) {
5,522✔
87
            map_offsets.emplace(map.name, global.map_descriptors.size());
4,814✔
88
            global.map_descriptors.push_back(EbpfMapDescriptor{
4,814✔
89
                .original_fd = gsl::narrow<int>(map.type_id), // Temporary: stores BTF type ID
4,814✔
90
                .type = map.map_type,
4,814✔
91
                .key_size = map.key_size,
4,814✔
92
                .value_size = map.value_size,
4,814✔
93
                .max_entries = map.max_entries,
4,814✔
94
                .inner_map_fd = map.inner_map_type_id == 0 ? DEFAULT_MAP_FD : gsl::narrow<int>(map.inner_map_type_id),
4,814✔
95
                .name = map.name,
2,407✔
96
            });
97
        }
704✔
98
    } catch (const std::exception& e) {
4✔
99
        throw UnmarshalError(std::string("Unsupported or invalid BTF map metadata: ") + e.what());
10✔
100
    }
4✔
101

102
    // Remap BTF type IDs to pseudo file descriptors
103
    const auto type_id_to_fd_map = map_typeid_to_fd(global.map_descriptors);
704✔
104
    for (auto& desc : global.map_descriptors) {
5,518✔
105
        if (auto it = type_id_to_fd_map.find(desc.original_fd); it != type_id_to_fd_map.end()) {
4,814✔
106
            desc.original_fd = it->second;
4,814✔
107
        } else {
108
            throw UnmarshalError("Unknown map type ID in BTF: " + std::to_string(desc.original_fd));
×
109
        }
110

111
        if (desc.inner_map_fd != DEFAULT_MAP_FD) {
4,814✔
112
            auto inner_it = type_id_to_fd_map.find(desc.inner_map_fd);
496✔
113
            if (inner_it == type_id_to_fd_map.end()) {
496✔
114
                throw UnmarshalError("Unknown inner map type ID in BTF: " + std::to_string(desc.inner_map_fd));
×
115
            }
116
            desc.inner_map_fd = inner_it->second;
496✔
117
        }
118
    }
119

120
    if (const auto maps_section = reader.sections[".maps"]) {
704✔
121
        global.map_section_indices.insert(maps_section->get_index());
522✔
122
    }
123

124
    add_global_variable_maps(reader, global, map_offsets);
704✔
125

126
    global.map_record_size_or_map_offsets = std::move(map_offsets);
704✔
127
    return global;
704✔
128
}
712✔
129

130
ElfGlobalData create_global_variable_maps(const ELFIO::elfio& reader) {
76✔
131
    ElfGlobalData global;
76✔
132
    MapOffsets offsets;
76✔
133

134
    for (const auto section : global_sections(reader)) {
108✔
135
        offsets[section->get_name()] = global.map_descriptors.size();
32✔
136

137
        global.map_descriptors.push_back(EbpfMapDescriptor{
32✔
138
            .original_fd = gsl::narrow<int>(global.map_descriptors.size() + 1),
32✔
139
            .type = 0,
140
            .key_size = sizeof(uint32_t),
141
            .value_size = gsl::narrow<uint32_t>(section->get_size()),
32✔
142
            .max_entries = 1,
143
            .inner_map_fd = DEFAULT_MAP_FD,
144
            .name = section->get_name(),
32✔
145
        });
146

147
        global.variable_section_indices.insert(section->get_index());
32✔
148
    }
38✔
149

150
    global.map_record_size_or_map_offsets = std::move(offsets);
76✔
151
    return global;
114✔
152
}
76✔
153

154
ElfGlobalData parse_map_sections(const parse_params_t& parse_params, const ELFIO::elfio& reader,
906✔
155
                                 const ELFIO::const_symbol_section_accessor& symbols) {
156
    ElfGlobalData global;
906✔
157
    std::map<ELFIO::Elf_Half, size_t> section_record_sizes;
906✔
158
    std::map<ELFIO::Elf_Half, size_t> section_base_index;
906✔
159

160
    for (ELFIO::Elf_Half i = 0; i < reader.sections.size(); ++i) {
64,874✔
161
        const auto s = reader.sections[i];
63,970✔
162
        assert(s);
63,970✔
163
        if (!is_map_section(s->get_name())) {
63,971✔
164
            continue;
63,062✔
165
        }
166

167
        std::vector<symbol_details_t> map_symbols;
908✔
168
        for (ELFIO::Elf_Xword index = 0; index < symbols.get_symbols_num(); ++index) {
2,611,284✔
169
            const auto symbol_details = get_symbol_details(symbols, index);
2,610,376✔
170
            if (symbol_details.section_index == i && !symbol_details.name.empty()) {
2,610,376✔
171
                map_symbols.push_back(symbol_details);
9,782✔
172
            }
173
        }
2,610,376✔
174

175
        global.map_section_indices.insert(s->get_index());
908✔
176

177
        if (map_symbols.empty()) {
908✔
178
            continue;
×
179
        }
180

181
        const size_t base_index = global.map_descriptors.size();
908✔
182
        if (s->get_data() == nullptr) {
908✔
183
            throw UnmarshalError("Malformed legacy maps section: " + s->get_name());
×
184
        }
185

186
        size_t map_record_size = 0;
908✔
187
        for (const auto& symbol : map_symbols) {
10,690✔
188
            if (symbol.size == 0) {
9,782✔
189
                continue;
4,008✔
190
            }
191
            const auto symbol_size = gsl::narrow<size_t>(symbol.size);
5,774✔
192
            map_record_size = map_record_size == 0 ? symbol_size : std::min(map_record_size, symbol_size);
8,452✔
193
        }
194
        if (map_record_size == 0) {
908✔
195
            map_record_size = parse_params.platform->map_record_size;
490✔
196
        }
197

198
        if (map_record_size < 4 * sizeof(uint32_t) || map_record_size % sizeof(uint32_t) != 0) {
908✔
199
            throw UnmarshalError("Malformed legacy maps section: " + s->get_name());
×
200
        }
201
        if (s->get_size() < map_record_size) {
908✔
202
            throw UnmarshalError("Malformed legacy maps section: " + s->get_name());
3✔
203
        }
204

205
        size_t map_count = s->get_size() / map_record_size;
906✔
206
        if (map_count == 0) {
906✔
207
            throw UnmarshalError("Malformed legacy maps section: " + s->get_name());
×
208
        }
209
        if (s->get_size() % map_record_size != 0) {
906✔
210
            size_t max_record_end = 0;
×
211
            for (const auto& symbol : map_symbols) {
×
212
                const size_t symbol_offset = gsl::narrow<size_t>(symbol.value);
×
213
                if (symbol_offset >= s->get_size()) {
×
214
                    throw UnmarshalError("Malformed legacy maps section: " + s->get_name());
×
215
                }
216
                max_record_end = std::max(max_record_end, symbol_offset + map_record_size);
×
217
            }
218
            if (max_record_end > s->get_size()) {
×
219
                throw UnmarshalError("Malformed legacy maps section: " + s->get_name());
×
220
            }
221
            // Use floor division to ensure map_count * map_record_size <= section size.
222
            // Ceiling division can produce a count whose last record extends past the buffer,
223
            // causing a heap-buffer-overflow in the platform's parse_maps_section callback.
224
            map_count = max_record_end / map_record_size;
×
225
        }
226

227
        section_record_sizes[i] = map_record_size;
906✔
228
        section_base_index[i] = base_index;
906✔
229

230
        // Safety invariant: all records must fit within the section data.
231
        if (map_count * map_record_size > s->get_size()) {
906✔
232
            throw UnmarshalError("Malformed legacy maps section: " + s->get_name());
×
233
        }
234

235
        parse_params.platform->parse_maps_section(global.map_descriptors, s->get_data(), map_record_size,
906✔
236
                                                  gsl::narrow<int>(map_count), parse_params.platform,
453✔
237
                                                  parse_params.options);
238
    }
908✔
239

240
    parse_params.platform->resolve_inner_map_references(global.map_descriptors);
904✔
241

242
    MapOffsets map_offsets;
904✔
243
    for (ELFIO::Elf_Xword index = 0; index < symbols.get_symbols_num(); ++index) {
2,611,138✔
244
        const auto sym_details = get_symbol_details(symbols, index);
2,610,234✔
245

246
        if (!global.map_section_indices.contains(sym_details.section_index) || sym_details.name.empty()) {
2,610,234✔
247
            continue;
2,600,480✔
248
        }
249

250
        const auto record_size_it = section_record_sizes.find(sym_details.section_index);
9,754✔
251
        const auto base_index_it = section_base_index.find(sym_details.section_index);
9,754✔
252
        if (record_size_it == section_record_sizes.end() || base_index_it == section_base_index.end()) {
9,754✔
253
            continue;
×
254
        }
255

256
        const auto* section = reader.sections[sym_details.section_index];
9,754✔
257
        assert(section);
9,754✔
258
        const size_t record_size = record_size_it->second;
9,754✔
259

260
        if (sym_details.value % record_size != 0 || sym_details.value >= section->get_size()) {
9,754✔
261
            throw UnmarshalError("Legacy map symbol '" + sym_details.name + "' has invalid offset: not aligned to " +
×
262
                                 std::to_string(record_size) + "-byte boundary or out of section bounds");
×
263
        }
264

265
        const size_t local_index = sym_details.value / record_size;
9,754✔
266
        const size_t descriptor_index = base_index_it->second + local_index;
9,754✔
267

268
        if (descriptor_index >= global.map_descriptors.size()) {
9,754✔
269
            throw UnmarshalError("Legacy map symbol index out of range for: " + sym_details.name);
×
270
        }
271

272
        map_offsets[sym_details.name] = descriptor_index;
9,754✔
273
        global.map_descriptors[descriptor_index].name = sym_details.name;
14,631✔
274
    }
2,610,234✔
275

276
    for (const auto section : global_sections(reader)) {
1,558✔
277
        map_offsets[section->get_name()] = global.map_descriptors.size();
654✔
278
        global.map_descriptors.push_back(EbpfMapDescriptor{
654✔
279
            .original_fd = gsl::narrow<int>(global.map_descriptors.size() + 1),
654✔
280
            .type = 0,
281
            .key_size = sizeof(uint32_t),
282
            .value_size = gsl::narrow<uint32_t>(section->get_size()),
654✔
283
            .max_entries = 1,
284
            .inner_map_fd = DEFAULT_MAP_FD,
285
            .name = section->get_name(),
654✔
286
        });
287
        global.variable_section_indices.insert(section->get_index());
654✔
288
    }
452✔
289

290
    global.map_record_size_or_map_offsets = std::move(map_offsets);
904✔
291
    return global;
1,356✔
292
}
909✔
293

294
} // namespace
295

296
ElfGlobalData extract_global_data(const parse_params_t& params, const ELFIO::elfio& reader,
1,686✔
297
                                  const ELFIO::const_symbol_section_accessor& symbols) {
298
    ElfGlobalData global = [&] {
5,057✔
299
        if (reader.sections[".BTF"] && reader.sections[".maps"]) {
1,686✔
300
            try {
263✔
301
                return parse_btf_section(params, reader);
526✔
302
            } catch (const UnmarshalError& e) {
4✔
303
                // BTF-defined maps can't be decoded; fall back to section-based map descriptors.
304
                std::cerr << "BTF map parsing failed, falling back to section-based maps: " << e.what() << std::endl;
4✔
305
                return parse_map_sections(params, reader, symbols);
4✔
306
            }
4✔
307
        }
308
        if (std::ranges::any_of(reader.sections, [](const auto& s) { return is_map_section(s->get_name()); })) {
59,646✔
309
            return parse_map_sections(params, reader, symbols);
902✔
310
        }
311
        if (reader.sections[".BTF"]) {
258✔
312
            return parse_btf_section(params, reader);
182✔
313
        }
314
        return create_global_variable_maps(reader);
76✔
315
    }();
1,686✔
316

317
    /// Mark descriptors that serve only as inner map templates.
318
    /// At runtime the actual inner map can be any map with matching structure,
319
    /// not necessarily the template defined in the ELF.
320
    for (const auto& desc : global.map_descriptors) {
17,752✔
321
        if (desc.inner_map_fd != DEFAULT_MAP_FD) {
16,068✔
322
            for (auto& inner_desc : global.map_descriptors) {
234,602✔
323
                if (inner_desc.original_fd == desc.inner_map_fd) {
224,352✔
324
                    inner_desc.is_inner_map_template = true;
11,772✔
325
                }
326
            }
327
        }
328
    }
329
    return global;
1,684✔
330
}
331

332
void update_line_info(std::vector<RawProgram>& raw_programs, const ELFIO::section* btf_section,
×
333
                      const ELFIO::section* btf_ext) {
334
    auto visitor = [&raw_programs](const std::string& section, const uint32_t instruction_offset,
×
335
                                   const std::string& file_name, const std::string& source, const uint32_t line_number,
336
                                   const uint32_t column_number) {
337
        for (auto& program : raw_programs) {
×
338
            if (program.section_name == section && instruction_offset >= program.insn_off &&
×
339
                instruction_offset < program.insn_off + program.prog.size() * sizeof(EbpfInst)) {
×
340
                const size_t inst_index = (instruction_offset - program.insn_off) / sizeof(EbpfInst);
×
341
                if (inst_index >= program.prog.size()) {
×
342
                    throw UnmarshalError("Invalid BTF data");
×
343
                }
344
                program.info.line_info.insert_or_assign(inst_index,
×
345
                                                        btf_line_info_t{file_name, source, line_number, column_number});
×
346
            }
347
        }
348
    };
×
349
    libbtf::btf_parse_line_information(vector_of<std::byte>(*btf_section), vector_of<std::byte>(*btf_ext), visitor);
×
350
    for (auto& program : raw_programs) {
×
351
        std::optional<btf_line_info_t> last;
×
352
        for (size_t i = 0; i < program.prog.size(); ++i) {
×
353
            auto it = program.info.line_info.find(i);
×
354
            if (it != program.info.line_info.end()) {
×
355
                if (it->second.line_number != 0) {
×
356
                    last = it->second;
×
357
                }
358
            } else if (last) {
×
359
                program.info.line_info[i] = *last;
×
360
            }
361
        }
362
    }
×
363
}
×
364

365
} // 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