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

Alan-Jowett / ebpf-verifier / 21966441414

12 Feb 2026 10:18PM UTC coverage: 87.366% (+0.6%) from 86.783%
21966441414

Pull #161

github

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

519 of 664 new or added lines in 8 files covered. (78.16%)

21 existing lines in 2 files now uncovered.

10013 of 11461 relevant lines covered (87.37%)

2951035.16 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 {
34✔
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) {
34✔
28
        return std::nullopt;
32✔
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,478✔
44
    EbpfDomain abs;
1,754,217✔
45
    abs.set_to_bottom();
1,754,217✔
46
    return abs;
584,739✔
47
}
48

49
EbpfDomain::EbpfDomain() = default;
1,173,276✔
50

51
EbpfDomain::EbpfDomain(TypeToNumDomain rcp, ArrayDomain stack) : rcp(std::move(rcp)), stack(std::move(stack)) {}
136✔
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,478✔
59

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

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

64
bool EbpfDomain::operator<=(const EbpfDomain& other) const {
518✔
65
    if (!(stack <= other.stack)) {
518✔
66
        return false;
67
    }
68
    return rcp <= other.rcp;
518✔
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,692✔
79
    if (other.is_bottom()) {
418,692✔
80
        return;
80,806✔
81
    }
82
    if (is_bottom()) {
257,080✔
83
        *this = std::move(other);
240,022✔
84
        return;
240,022✔
85
    }
86
    stack |= std::move(other.stack);
17,058✔
87
    rcp |= std::move(other.rcp);
17,058✔
88
}
89

90
void EbpfDomain::operator|=(const EbpfDomain& other) {
122✔
91
    if (other.is_bottom()) {
122✔
92
        return;
93
    }
94
    if (is_bottom()) {
122✔
95
        *this = other;
×
96
        return;
×
97
    }
98
    stack |= other.stack;
122✔
99
    rcp |= other.rcp;
122✔
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& {
124✔
114
    if (other.is_bottom()) {
124✔
115
        return *this;
×
116
    }
117
    if (is_bottom()) {
124✔
118
        return other;
2✔
119
    }
120
    EbpfDomain res{other};
122✔
121
    res |= *this;
122✔
122
    return res;
122✔
123
}
122✔
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 {
46✔
137
    auto res = rcp & other.rcp;
46✔
138
    if (!res.is_bottom()) {
46✔
139
        return {std::move(res), stack & other.stack};
92✔
140
    }
141
    return bottom();
×
142
}
46✔
143

144
EbpfDomain EbpfDomain::calculate_constant_limits() {
40✔
145
    EbpfDomain inv;
40✔
146
    using namespace dsl_syntax;
20✔
147
    for (const int i : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) {
440✔
148
        const auto r = reg_pack(i);
400✔
149
        inv.add_value_constraint(r.svalue <= std::numeric_limits<int32_t>::max());
600✔
150
        inv.add_value_constraint(r.svalue >= std::numeric_limits<int32_t>::min());
600✔
151
        inv.add_value_constraint(r.uvalue <= std::numeric_limits<uint32_t>::max());
600✔
152
        inv.add_value_constraint(r.uvalue >= 0);
600✔
153
        inv.add_value_constraint(r.stack_offset <= EBPF_TOTAL_STACK_SIZE);
600✔
154
        inv.add_value_constraint(r.stack_offset >= 0);
600✔
155
        inv.add_value_constraint(r.shared_offset <= r.shared_region_size);
600✔
156
        inv.add_value_constraint(r.shared_offset >= 0);
600✔
157
        inv.add_value_constraint(r.packet_offset <= variable_registry->packet_size());
600✔
158
        inv.add_value_constraint(r.packet_offset >= 0);
600✔
159
        if (thread_local_options.cfg_opts.check_for_termination) {
400✔
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;
40✔
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;
724✔
177

178
static const EbpfDomain& get_constant_limits() { return constant_limits.get(); }
40✔
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 {
90✔
183
    EbpfDomain res{this->rcp.widen(other.rcp), stack.widen(other.stack)};
135✔
184

185
    if (to_constants) {
90✔
186
        return res & get_constant_limits();
60✔
187
    }
188
    return res;
50✔
189
}
90✔
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,890✔
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,804✔
198
    // TODO: type inv?
199
    rcp.values.havoc(var);
2,804✔
200
}
1,402✔
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,738✔
206
    const Interval& map_fd_interval = rcp.values.eval_interval(reg_pack(map_fd_reg).map_fd);
13,738✔
207
    const auto lb = map_fd_interval.lb().number();
27,476✔
208
    const auto ub = map_fd_interval.ub().number();
27,476✔
209
    if (!lb || !lb->fits<int32_t>() || !ub || !ub->fits<int32_t>()) {
13,738✔
210
        return false;
211
    }
212
    *start_fd = lb->narrow<int32_t>();
13,738✔
213
    *end_fd = ub->narrow<int32_t>();
13,738✔
214

215
    // Cap the maximum range we'll check.
216
    constexpr int max_range = 32;
13,738✔
217
    return *map_fd_interval.finite_size() < max_range;
13,738✔
218
}
20,607✔
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,868✔
222
    int32_t start_fd, end_fd;
3,434✔
223
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
6,868✔
224
        return {};
×
225
    }
226

227
    std::optional<uint32_t> type;
6,868✔
228
    for (int32_t map_fd = start_fd; map_fd <= end_fd; map_fd++) {
13,772✔
229
        EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd);
6,908✔
230
        if (map == nullptr) {
6,908✔
231
            return {};
×
232
        }
233
        if (!type.has_value()) {
6,908✔
234
            type = map->type;
6,868✔
235
        } else if (map->type != *type) {
40✔
236
            return {};
4✔
237
        }
238
    }
239
    return type;
6,864✔
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,508✔
266
    int start_fd, end_fd;
1,754✔
267
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
3,508✔
268
        return Interval::top();
×
269
    }
270

271
    Interval result = Interval::bottom();
3,508✔
272
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
7,036✔
273
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
3,528✔
274
            result = result | Interval{map->key_size};
7,056✔
275
        } else {
276
            return Interval::top();
×
277
        }
278
    }
279
    return result;
3,508✔
280
}
3,508✔
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,326✔
284
    int start_fd, end_fd;
1,663✔
285
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
3,326✔
286
        return Interval::top();
×
287
    }
288

289
    Interval result = Interval::bottom();
3,326✔
290
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
6,670✔
291
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
3,344✔
292
            result = result | Interval(map->value_size);
6,688✔
293
        } else {
294
            return Interval::top();
×
295
        }
296
    }
297
    return result;
3,326✔
298
}
3,326✔
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,762✔
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,402✔
349
    using namespace dsl_syntax;
701✔
350
    EbpfDomain& inv = *this;
1,402✔
351
    inv.havoc(variable_registry->packet_size());
1,402✔
352
    inv.havoc(variable_registry->meta_offset());
1,402✔
353

354
    inv.add_value_constraint(0 <= variable_registry->packet_size());
2,804✔
355
    inv.add_value_constraint(variable_registry->packet_size() < MAX_PACKET_SIZE);
2,103✔
356
    const auto info = *thread_local_program_info;
1,402✔
357
    if (info.type.context_descriptor->meta >= 0) {
1,402✔
358
        inv.add_value_constraint(variable_registry->meta_offset() <= 0);
834✔
359
        inv.add_value_constraint(variable_registry->meta_offset() >= -4098);
834✔
360
    } else {
361
        inv.rcp.values.assign(variable_registry->meta_offset(), 0);
1,269✔
362
    }
363
}
1,402✔
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,362✔
401
    using namespace dsl_syntax;
681✔
402

403
    EbpfDomain inv;
1,362✔
404

405
    const auto r10 = reg_pack(R10_STACK_POINTER);
1,362✔
406
    constexpr Reg r10_reg{R10_STACK_POINTER};
1,362✔
407
    inv.rcp.values.add_constraint(EBPF_TOTAL_STACK_SIZE <= r10.svalue);
2,724✔
408
    inv.rcp.values.add_constraint(r10.svalue <= PTR_MAX);
2,043✔
409
    inv.rcp.values.assign(r10.stack_offset, EBPF_TOTAL_STACK_SIZE);
1,362✔
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,362✔
413

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

423
    inv.initialize_packet();
1,362✔
424
    return inv;
2,043✔
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