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

Alan-Jowett / ebpf-verifier / 21968098219

12 Feb 2026 11:19PM UTC coverage: 87.328% (+0.5%) from 86.783%
21968098219

Pull #161

github

web-flow
Merge fcfbad30c into f1f1d42d7
Pull Request #161: Failure slice

527 of 677 new or added lines in 8 files covered. (77.84%)

20 existing lines in 2 files now uncovered.

10020 of 11474 relevant lines covered (87.33%)

2947086.45 hits per line

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

83.59
/src/crab/ebpf_domain.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3

4
// This file is eBPF-specific, not derived from CRAB.
5

6
#include <optional>
7
#include <utility>
8
#include <vector>
9

10
#include "boost/endian/conversion.hpp"
11

12
#include "arith/dsl_syntax.hpp"
13
#include "config.hpp"
14
#include "crab/array_domain.hpp"
15
#include "crab/ebpf_domain.hpp"
16
#include "crab/var_registry.hpp"
17
#include "crab_utils/lazy_allocator.hpp"
18
#include "ir/unmarshal.hpp"
19

20
namespace prevail {
21

22
StringInvariant EbpfDomain::to_set() const { return rcp.to_set() + stack.to_set(); }
2,100✔
23

24
std::optional<int64_t> EbpfDomain::get_stack_offset(const Reg& reg) const {
10✔
25
    // Only return an offset when the register is *definitely* a stack pointer,
26
    // not just possibly one. This ensures we don't misclassify memory deps.
27
    if (rcp.types.get_type(reg) != T_STACK) {
10✔
28
        return std::nullopt;
8✔
29
    }
30
    const auto offset = rcp.values.eval_interval(reg_pack(reg).stack_offset);
2✔
31
    if (const auto singleton = offset.singleton()) {
2✔
NEW
32
        return singleton->cast_to<int64_t>();
×
33
    }
1✔
34
    return std::nullopt;
2✔
35
}
2✔
36

37
EbpfDomain EbpfDomain::top() {
8✔
38
    EbpfDomain abs;
12✔
39
    abs.set_to_top();
8✔
40
    return abs;
8✔
41
}
×
42

43
EbpfDomain EbpfDomain::bottom() {
1,169,164✔
44
    EbpfDomain abs;
1,753,746✔
45
    abs.set_to_bottom();
1,753,746✔
46
    return abs;
584,582✔
47
}
48

49
EbpfDomain::EbpfDomain() = default;
1,172,958✔
50

51
EbpfDomain::EbpfDomain(TypeToNumDomain rcp, ArrayDomain stack) : rcp(std::move(rcp)), stack(std::move(stack)) {}
128✔
52

53
void EbpfDomain::set_to_top() {
8✔
54
    rcp.set_to_top();
8✔
55
    stack.set_to_top();
8✔
56
}
8✔
57

58
void EbpfDomain::set_to_bottom() { rcp.set_to_bottom(); }
1,169,164✔
59

60
bool EbpfDomain::is_bottom() const { return rcp.is_bottom(); }
1,907,840✔
61

62
bool EbpfDomain::is_top() const { return rcp.is_top() && stack.is_top(); }
×
63

64
bool EbpfDomain::operator<=(const EbpfDomain& other) const {
506✔
65
    if (!(stack <= other.stack)) {
506✔
66
        return false;
67
    }
68
    return rcp <= other.rcp;
506✔
69
}
70

71
bool EbpfDomain::operator<=(EbpfDomain&& other) const {
×
72
    if (!(stack <= other.stack)) {
×
73
        return false;
74
    }
75
    return rcp <= std::move(other.rcp);
×
76
}
77

78
void EbpfDomain::operator|=(EbpfDomain&& other) {
418,450✔
79
    if (other.is_bottom()) {
418,450✔
80
        return;
80,806✔
81
    }
82
    if (is_bottom()) {
256,838✔
83
        *this = std::move(other);
239,804✔
84
        return;
239,804✔
85
    }
86
    stack |= std::move(other.stack);
17,034✔
87
    rcp |= std::move(other.rcp);
17,034✔
88
}
89

90
void EbpfDomain::operator|=(const EbpfDomain& other) {
120✔
91
    if (other.is_bottom()) {
120✔
92
        return;
93
    }
94
    if (is_bottom()) {
120✔
95
        *this = other;
×
96
        return;
×
97
    }
98
    stack |= other.stack;
120✔
99
    rcp |= other.rcp;
120✔
100
}
101

102
EbpfDomain EbpfDomain::operator|(EbpfDomain&& other) const {
×
103
    if (other.is_bottom()) {
×
104
        return *this;
×
105
    }
106
    if (is_bottom()) {
×
107
        return std::move(other);
×
108
    }
109
    other |= *this;
×
110
    return other;
×
111
}
112

113
EbpfDomain EbpfDomain::operator|(const EbpfDomain& other) const& {
122✔
114
    if (other.is_bottom()) {
122✔
115
        return *this;
×
116
    }
117
    if (is_bottom()) {
122✔
118
        return other;
2✔
119
    }
120
    EbpfDomain res{other};
120✔
121
    res |= *this;
120✔
122
    return res;
120✔
123
}
120✔
124

125
EbpfDomain EbpfDomain::operator|(const EbpfDomain& other) && {
×
126
    if (other.is_bottom()) {
×
127
        return std::move(*this);
×
128
    }
129
    if (is_bottom()) {
×
130
        return other;
×
131
    }
132
    *this |= other;
×
133
    return std::move(*this);
×
134
}
135

136
EbpfDomain EbpfDomain::operator&(const EbpfDomain& other) const {
44✔
137
    auto res = rcp & other.rcp;
44✔
138
    if (!res.is_bottom()) {
44✔
139
        return {std::move(res), stack & other.stack};
88✔
140
    }
141
    return bottom();
×
142
}
44✔
143

144
EbpfDomain EbpfDomain::calculate_constant_limits() {
38✔
145
    EbpfDomain inv;
38✔
146
    using namespace dsl_syntax;
19✔
147
    for (const int i : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) {
418✔
148
        const auto r = reg_pack(i);
380✔
149
        inv.add_value_constraint(r.svalue <= std::numeric_limits<int32_t>::max());
570✔
150
        inv.add_value_constraint(r.svalue >= std::numeric_limits<int32_t>::min());
570✔
151
        inv.add_value_constraint(r.uvalue <= std::numeric_limits<uint32_t>::max());
570✔
152
        inv.add_value_constraint(r.uvalue >= 0);
570✔
153
        inv.add_value_constraint(r.stack_offset <= EBPF_TOTAL_STACK_SIZE);
570✔
154
        inv.add_value_constraint(r.stack_offset >= 0);
570✔
155
        inv.add_value_constraint(r.shared_offset <= r.shared_region_size);
570✔
156
        inv.add_value_constraint(r.shared_offset >= 0);
570✔
157
        inv.add_value_constraint(r.packet_offset <= variable_registry->packet_size());
570✔
158
        inv.add_value_constraint(r.packet_offset >= 0);
570✔
159
        if (thread_local_options.cfg_opts.check_for_termination) {
380✔
160
            for (const Variable counter : variable_registry->get_loop_counters()) {
640✔
161
                inv.add_value_constraint(counter <= std::numeric_limits<int32_t>::max());
480✔
162
                inv.add_value_constraint(counter >= 0);
480✔
163
                inv.add_value_constraint(counter <= r.svalue);
480✔
164
            }
320✔
165
        }
166
    }
167
    return inv;
38✔
168
}
×
169

170
// Lazy init via LazyAllocator<EbpfDomain, EbpfDomain::calculate_constant_limits> ensures
171
// thread_local_options and variable_registry are populated before EbpfDomain::calculate_constant_limits
172
// captures their state. LazyAllocator::get() has no reentrancy guard, but calculate_constant_limits
173
// does not call get_constant_limits() or widen(to_constants=true), so recursion cannot occur.
174
// Clamping via intersection (operator&) is sound because it only shrinks the domain by tightening
175
// constraints element-wise, preserving the over-approximation required for soundness.
176
static thread_local LazyAllocator<EbpfDomain, EbpfDomain::calculate_constant_limits> constant_limits;
723✔
177

178
static const EbpfDomain& get_constant_limits() { return constant_limits.get(); }
38✔
179

180
void EbpfDomain::clear_thread_local_state() { constant_limits.clear(); }
1,408✔
181

182
EbpfDomain EbpfDomain::widen(const EbpfDomain& other, const bool to_constants) const {
84✔
183
    EbpfDomain res{this->rcp.widen(other.rcp), stack.widen(other.stack)};
126✔
184

185
    if (to_constants) {
84✔
186
        return res & get_constant_limits();
57✔
187
    }
188
    return res;
46✔
189
}
84✔
190

191
EbpfDomain EbpfDomain::narrow(const EbpfDomain& other) const { return {rcp.narrow(other.rcp), stack & other.stack}; }
×
192

193
void EbpfDomain::add_value_constraint(const LinearConstraint& cst) { rcp.values.add_constraint(cst); }
11,683✔
194

195
void EbpfDomain::add_type_constraint(const LinearConstraint& cst) { rcp.types.add_constraint(cst); }
2,790✔
196

197
void EbpfDomain::havoc(const Variable var) {
2,800✔
198
    // TODO: type inv?
199
    rcp.values.havoc(var);
2,800✔
200
}
1,400✔
201

202
// Get the start and end of the range of possible map fd values.
203
// In the future, it would be cleaner to use a set rather than an interval
204
// for map fds.
205
bool EbpfDomain::get_map_fd_range(const Reg& map_fd_reg, int32_t* start_fd, int32_t* end_fd) const {
13,698✔
206
    const Interval& map_fd_interval = rcp.values.eval_interval(reg_pack(map_fd_reg).map_fd);
13,698✔
207
    const auto lb = map_fd_interval.lb().number();
27,396✔
208
    const auto ub = map_fd_interval.ub().number();
27,396✔
209
    if (!lb || !lb->fits<int32_t>() || !ub || !ub->fits<int32_t>()) {
13,698✔
210
        return false;
211
    }
212
    *start_fd = lb->narrow<int32_t>();
13,698✔
213
    *end_fd = ub->narrow<int32_t>();
13,698✔
214

215
    // Cap the maximum range we'll check.
216
    constexpr int max_range = 32;
13,698✔
217
    return *map_fd_interval.finite_size() < max_range;
13,698✔
218
}
20,547✔
219

220
// All maps in the range must have the same type for us to use it.
221
std::optional<uint32_t> EbpfDomain::get_map_type(const Reg& map_fd_reg) const {
6,848✔
222
    int32_t start_fd, end_fd;
3,424✔
223
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
6,848✔
224
        return {};
×
225
    }
226

227
    std::optional<uint32_t> type;
6,848✔
228
    for (int32_t map_fd = start_fd; map_fd <= end_fd; map_fd++) {
13,732✔
229
        EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd);
6,888✔
230
        if (map == nullptr) {
6,888✔
231
            return {};
×
232
        }
233
        if (!type.has_value()) {
6,888✔
234
            type = map->type;
6,848✔
235
        } else if (map->type != *type) {
40✔
236
            return {};
4✔
237
        }
238
    }
239
    return type;
6,844✔
240
}
241

242
// All maps in the range must have the same inner map fd for us to use it.
243
std::optional<uint32_t> EbpfDomain::get_map_inner_map_fd(const Reg& map_fd_reg) const {
32✔
244
    int start_fd, end_fd;
16✔
245
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
32✔
246
        return {};
×
247
    }
248

249
    std::optional<uint32_t> inner_map_fd;
32✔
250
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
64✔
251
        EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd);
32✔
252
        if (map == nullptr) {
32✔
253
            return {};
×
254
        }
255
        if (!inner_map_fd.has_value()) {
32✔
256
            inner_map_fd = map->inner_map_fd;
32✔
257
        } else if (map->type != *inner_map_fd) {
×
258
            return {};
×
259
        }
260
    }
261
    return inner_map_fd;
32✔
262
}
263

264
// We can deal with a range of key sizes.
265
Interval EbpfDomain::get_map_key_size(const Reg& map_fd_reg) const {
3,498✔
266
    int start_fd, end_fd;
1,749✔
267
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
3,498✔
268
        return Interval::top();
×
269
    }
270

271
    Interval result = Interval::bottom();
3,498✔
272
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
7,016✔
273
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
3,518✔
274
            result = result | Interval{map->key_size};
7,036✔
275
        } else {
276
            return Interval::top();
×
277
        }
278
    }
279
    return result;
3,498✔
280
}
3,498✔
281

282
// We can deal with a range of value sizes.
283
Interval EbpfDomain::get_map_value_size(const Reg& map_fd_reg) const {
3,316✔
284
    int start_fd, end_fd;
1,658✔
285
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
3,316✔
286
        return Interval::top();
×
287
    }
288

289
    Interval result = Interval::bottom();
3,316✔
290
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
6,650✔
291
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
3,334✔
292
            result = result | Interval(map->value_size);
6,668✔
293
        } else {
294
            return Interval::top();
×
295
        }
296
    }
297
    return result;
3,316✔
298
}
3,316✔
299

300
// We can deal with a range of max_entries values.
301
Interval EbpfDomain::get_map_max_entries(const Reg& map_fd_reg) const {
4✔
302
    int start_fd, end_fd;
2✔
303
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
4✔
304
        return Interval::top();
×
305
    }
306

307
    Interval result = Interval::bottom();
4✔
308
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
8✔
309
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
4✔
310
            result = result | Interval(map->max_entries);
8✔
311
        } else {
312
            return Interval::top();
×
313
        }
314
    }
315
    return result;
4✔
316
}
4✔
317

318
ExtendedNumber EbpfDomain::get_loop_count_upper_bound() const {
160✔
319
    ExtendedNumber ub{0};
160✔
320
    for (const Variable counter : variable_registry->get_loop_counters()) {
304✔
321
        ub = std::max(ub, rcp.values.eval_interval(counter).ub());
414✔
322
    }
80✔
323
    return ub;
160✔
324
}
×
325

326
Interval EbpfDomain::get_r0() const { return rcp.values.eval_interval(reg_pack(R0_RETURN_VALUE).svalue); }
2,760✔
327

328
std::ostream& operator<<(std::ostream& o, const TypeDomain& dom) { return o << dom.inv; }
3✔
329

330
std::ostream& operator<<(std::ostream& o, const TypeToNumDomain& dom) {
6✔
331
    if (dom.is_bottom()) {
6✔
332
        o << "_|_";
×
333
    } else {
334
        o << dom.types << dom.values;
6✔
335
    }
336
    return o;
6✔
337
}
338

339
std::ostream& operator<<(std::ostream& o, const EbpfDomain& dom) {
6✔
340
    if (dom.is_bottom()) {
6✔
341
        o << "_|_";
×
342
    } else {
343
        o << dom.rcp << "\nStack: " << dom.stack;
6✔
344
    }
345
    return o;
6✔
346
}
347

348
void EbpfDomain::initialize_packet() {
1,400✔
349
    using namespace dsl_syntax;
700✔
350
    EbpfDomain& inv = *this;
1,400✔
351
    inv.havoc(variable_registry->packet_size());
1,400✔
352
    inv.havoc(variable_registry->meta_offset());
1,400✔
353

354
    inv.add_value_constraint(0 <= variable_registry->packet_size());
2,800✔
355
    inv.add_value_constraint(variable_registry->packet_size() < MAX_PACKET_SIZE);
2,100✔
356
    const auto info = *thread_local_program_info;
1,400✔
357
    if (info.type.context_descriptor->meta >= 0) {
1,400✔
358
        inv.add_value_constraint(variable_registry->meta_offset() <= 0);
831✔
359
        inv.add_value_constraint(variable_registry->meta_offset() >= -4098);
831✔
360
    } else {
361
        inv.rcp.values.assign(variable_registry->meta_offset(), 0);
1,269✔
362
    }
363
}
1,400✔
364

365
EbpfDomain EbpfDomain::from_constraints(const std::vector<LinearConstraint>& type_constraints,
356✔
366
                                        const std::vector<LinearConstraint>& value_constraints) {
367
    EbpfDomain inv;
356✔
368

369
    for (const auto& cst : type_constraints) {
798✔
370
        inv.add_type_constraint(cst);
663✔
371
    }
372
    for (const auto& cst : value_constraints) {
880✔
373
        inv.add_value_constraint(cst);
524✔
374
    }
375
    return inv;
356✔
376
}
×
377

378
EbpfDomain EbpfDomain::from_constraints(const std::set<std::string>& constraints, const bool setup_constraints) {
2,032✔
379
    EbpfDomain inv;
2,032✔
380
    if (setup_constraints) {
2,032✔
381
        inv = setup_entry(false);
930✔
382
    }
383
    auto numeric_ranges = std::vector<Interval>();
2,032✔
384
    auto [type_constraints, value_constraints] = parse_linear_constraints(constraints, numeric_ranges);
2,032✔
385
    for (const auto& cst : type_constraints) {
4,380✔
386
        inv.add_type_constraint(cst);
3,522✔
387
    }
388
    for (const auto& cst : value_constraints) {
8,092✔
389
        inv.add_value_constraint(cst);
6,060✔
390
    }
391
    for (const Interval& range : numeric_ranges) {
2,304✔
392
        const auto [start, ub] = range.pair<int64_t>();
272✔
393
        const int width = 1 + (ub - start);
272✔
394
        inv.stack.initialize_numbers(start, width);
272✔
395
    }
396
    // TODO: handle other stack type constraints
397
    return inv;
4,064✔
398
}
2,032✔
399

400
EbpfDomain EbpfDomain::setup_entry(const bool init_r1) {
1,360✔
401
    using namespace dsl_syntax;
680✔
402

403
    EbpfDomain inv;
1,360✔
404

405
    const auto r10 = reg_pack(R10_STACK_POINTER);
1,360✔
406
    constexpr Reg r10_reg{R10_STACK_POINTER};
1,360✔
407
    inv.rcp.values.add_constraint(EBPF_TOTAL_STACK_SIZE <= r10.svalue);
2,720✔
408
    inv.rcp.values.add_constraint(r10.svalue <= PTR_MAX);
2,040✔
409
    inv.rcp.values.assign(r10.stack_offset, EBPF_TOTAL_STACK_SIZE);
1,360✔
410
    // stack_numeric_size would be 0, but TOP has the same result
411
    // so no need to assign it.
412
    inv.rcp.types.assign_type(r10_reg, T_STACK);
1,360✔
413

414
    if (init_r1) {
1,360✔
415
        const auto r1 = reg_pack(R1_ARG);
740✔
416
        constexpr Reg r1_reg{R1_ARG};
740✔
417
        inv.rcp.values.add_constraint(1 <= r1.svalue);
1,480✔
418
        inv.rcp.values.add_constraint(r1.svalue <= PTR_MAX);
1,110✔
419
        inv.rcp.values.assign(r1.ctx_offset, 0);
740✔
420
        inv.rcp.types.assign_type(r1_reg, T_CTX);
1,110✔
421
    }
422

423
    inv.initialize_packet();
1,360✔
424
    return inv;
2,040✔
425
}
×
426

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