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

vbpf / ebpf-verifier / 13958948275

19 Mar 2025 11:53PM UTC coverage: 88.194% (-0.5%) from 88.66%
13958948275

push

github

web-flow
Finite domain (#849)

* Move arithmetic and bit operations functions to finite_domain.cpp
* Remove operator-= (now havoc) and operator+= (now add_constraint)
* Abort early when registers are not usable; clear type variable instead of explicitly assigning T_UNINIT. Update YAML tests accordingly
---------
Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

847 of 898 new or added lines in 11 files covered. (94.32%)

57 existing lines in 8 files now uncovered.

8628 of 9783 relevant lines covered (88.19%)

9034663.84 hits per line

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

76.58
/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 "asm_unmarshal.hpp"
13
#include "config.hpp"
14
#include "crab/array_domain.hpp"
15
#include "crab/ebpf_domain.hpp"
16
#include "dsl_syntax.hpp"
17
#include "string_constraints.hpp"
18

19
using crab::domains::NumAbsDomain;
20
namespace crab {
21

22
std::optional<variable_t> ebpf_domain_t::get_type_offset_variable(const Reg& reg, const int type) {
31,130✔
23
    reg_pack_t r = reg_pack(reg);
31,130✔
24
    switch (type) {
31,130✔
25
    case T_CTX: return r.ctx_offset;
22✔
26
    case T_MAP:
22✔
27
    case T_MAP_PROGRAMS: return r.map_fd;
22✔
28
    case T_PACKET: return r.packet_offset;
5,828✔
29
    case T_SHARED: return r.shared_offset;
172✔
30
    case T_STACK: return r.stack_offset;
20,182✔
31
    default: return {};
4,904✔
32
    }
33
}
34

35
std::optional<variable_t> ebpf_domain_t::get_type_offset_variable(const Reg& reg, const NumAbsDomain& inv) const {
25,932✔
36
    return get_type_offset_variable(reg, type_inv.get_type(inv, reg_pack(reg).type));
25,932✔
37
}
38

39
std::optional<variable_t> ebpf_domain_t::get_type_offset_variable(const Reg& reg) const {
25,932✔
40
    return get_type_offset_variable(reg, m_inv);
25,932✔
41
}
42

43
string_invariant ebpf_domain_t::to_set() const { return m_inv.to_set() + stack.to_set(); }
2,079✔
44

45
ebpf_domain_t ebpf_domain_t::top() {
×
46
    ebpf_domain_t abs;
×
47
    abs.set_to_top();
×
48
    return abs;
×
49
}
×
50

51
ebpf_domain_t ebpf_domain_t::bottom() {
1,021,534✔
52
    ebpf_domain_t abs;
1,021,534✔
53
    abs.set_to_bottom();
1,532,301✔
54
    return abs;
1,021,534✔
55
}
56

57
ebpf_domain_t::ebpf_domain_t() : m_inv(NumAbsDomain::top()) {}
1,024,904✔
58

59
ebpf_domain_t::ebpf_domain_t(NumAbsDomain inv, domains::array_domain_t stack)
160✔
60
    : m_inv(std::move(inv)), stack(std::move(stack)) {}
160✔
61

62
void ebpf_domain_t::set_to_top() {
×
63
    m_inv.set_to_top();
×
64
    stack.set_to_top();
×
65
}
×
66

67
void ebpf_domain_t::set_to_bottom() { m_inv.set_to_bottom(); }
1,021,534✔
68

69
bool ebpf_domain_t::is_bottom() const { return m_inv.is_bottom(); }
1,392,972✔
70

71
bool ebpf_domain_t::is_top() const { return m_inv.is_top() && stack.is_top(); }
×
72

73
bool ebpf_domain_t::operator<=(const ebpf_domain_t& other) const {
198✔
74
    return m_inv <= other.m_inv && stack <= other.stack;
198✔
75
}
76

77
bool ebpf_domain_t::operator==(const ebpf_domain_t& other) const {
×
78
    return stack == other.stack && m_inv <= other.m_inv && other.m_inv <= m_inv;
×
79
}
80

81
void ebpf_domain_t::operator|=(ebpf_domain_t&& other) {
364,602✔
82
    if (is_bottom()) {
364,602✔
83
        *this = std::move(other);
339,680✔
84
        return;
339,680✔
85
    }
86
    if (other.is_bottom()) {
24,922✔
87
        return;
345✔
88
    }
89

90
    type_inv.selectively_join_based_on_type(m_inv, std::move(other.m_inv));
24,232✔
91

92
    stack |= std::move(other.stack);
24,232✔
93
}
94

95
void ebpf_domain_t::operator|=(const ebpf_domain_t& other) {
×
96
    ebpf_domain_t tmp{other};
×
97
    operator|=(std::move(tmp));
×
98
}
×
99

100
ebpf_domain_t ebpf_domain_t::operator|(ebpf_domain_t&& other) const {
×
101
    return ebpf_domain_t(m_inv | std::move(other.m_inv), stack | other.stack);
×
102
}
103

104
ebpf_domain_t ebpf_domain_t::operator|(const ebpf_domain_t& other) const& {
38✔
105
    return ebpf_domain_t(m_inv | other.m_inv, stack | other.stack);
57✔
106
}
107

108
ebpf_domain_t ebpf_domain_t::operator|(const ebpf_domain_t& other) && {
×
109
    return ebpf_domain_t(other.m_inv | std::move(m_inv), other.stack | stack);
×
110
}
111

112
ebpf_domain_t ebpf_domain_t::operator&(const ebpf_domain_t& other) const {
38✔
113
    return ebpf_domain_t(m_inv & other.m_inv, stack & other.stack);
57✔
114
}
115

116
ebpf_domain_t ebpf_domain_t::calculate_constant_limits() {
440✔
117
    ebpf_domain_t inv;
440✔
118
    using namespace crab::dsl_syntax;
220✔
119
    for (const int i : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) {
4,840✔
120
        const auto r = reg_pack(i);
4,400✔
121
        inv.add_constraint(r.svalue <= std::numeric_limits<int32_t>::max());
6,600✔
122
        inv.add_constraint(r.svalue >= std::numeric_limits<int32_t>::min());
6,600✔
123
        inv.add_constraint(r.uvalue <= std::numeric_limits<uint32_t>::max());
6,600✔
124
        inv.add_constraint(r.uvalue >= 0);
6,600✔
125
        inv.add_constraint(r.stack_offset <= EBPF_TOTAL_STACK_SIZE);
6,600✔
126
        inv.add_constraint(r.stack_offset >= 0);
6,600✔
127
        inv.add_constraint(r.shared_offset <= r.shared_region_size);
6,600✔
128
        inv.add_constraint(r.shared_offset >= 0);
6,600✔
129
        inv.add_constraint(r.packet_offset <= variable_t::packet_size());
6,600✔
130
        inv.add_constraint(r.packet_offset >= 0);
6,600✔
131
        if (thread_local_options.cfg_opts.check_for_termination) {
4,400✔
132
            for (const variable_t counter : variable_t::get_loop_counters()) {
×
NEW
133
                inv.add_constraint(counter <= std::numeric_limits<int32_t>::max());
×
NEW
134
                inv.add_constraint(counter >= 0);
×
NEW
135
                inv.add_constraint(counter <= r.svalue);
×
UNCOV
136
            }
×
137
        }
138
    }
139
    return inv;
440✔
140
}
×
141

142
static const ebpf_domain_t constant_limits = ebpf_domain_t::calculate_constant_limits();
143

144
ebpf_domain_t ebpf_domain_t::widen(const ebpf_domain_t& other, const bool to_constants) const {
84✔
145
    ebpf_domain_t res{m_inv.widen(other.m_inv), stack | other.stack};
126✔
146
    if (to_constants) {
84✔
147
        return res & constant_limits;
38✔
148
    }
149
    return res;
46✔
150
}
84✔
151

152
ebpf_domain_t ebpf_domain_t::narrow(const ebpf_domain_t& other) const {
×
153
    return ebpf_domain_t(m_inv.narrow(other.m_inv), stack & other.stack);
×
154
}
155

156
void ebpf_domain_t::add_constraint(const linear_constraint_t& cst) { m_inv.add_constraint(cst); }
51,149✔
157

158
void ebpf_domain_t::havoc(const variable_t var) { m_inv.havoc(var); }
2,296✔
159

160
// Get the start and end of the range of possible map fd values.
161
// In the future, it would be cleaner to use a set rather than an interval
162
// for map fds.
163
bool ebpf_domain_t::get_map_fd_range(const Reg& map_fd_reg, int32_t* start_fd, int32_t* end_fd) const {
13,198✔
164
    const interval_t& map_fd_interval = m_inv.eval_interval(reg_pack(map_fd_reg).map_fd);
13,198✔
165
    const auto lb = map_fd_interval.lb().number();
26,396✔
166
    const auto ub = map_fd_interval.ub().number();
26,396✔
167
    if (!lb || !lb->fits<int32_t>() || !ub || !ub->fits<int32_t>()) {
13,198✔
168
        return false;
1✔
169
    }
170
    *start_fd = lb->narrow<int32_t>();
13,196✔
171
    *end_fd = ub->narrow<int32_t>();
13,196✔
172

173
    // Cap the maximum range we'll check.
174
    constexpr int max_range = 32;
13,196✔
175
    return *map_fd_interval.finite_size() < max_range;
13,196✔
176
}
19,797✔
177

178
// All maps in the range must have the same type for us to use it.
179
std::optional<uint32_t> ebpf_domain_t::get_map_type(const Reg& map_fd_reg) const {
6,600✔
180
    int32_t start_fd, end_fd;
3,300✔
181
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
6,600✔
182
        return std::optional<uint32_t>();
2✔
183
    }
184

185
    std::optional<uint32_t> type;
6,598✔
186
    for (int32_t map_fd = start_fd; map_fd <= end_fd; map_fd++) {
13,198✔
187
        EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd);
6,604✔
188
        if (map == nullptr) {
6,602✔
189
            return std::optional<uint32_t>();
×
190
        }
191
        if (!type.has_value()) {
6,602✔
192
            type = map->type;
6,596✔
193
        } else if (map->type != *type) {
6✔
194
            return std::optional<uint32_t>();
2✔
195
        }
196
    }
197
    return type;
6,594✔
198
}
199

200
// All maps in the range must have the same inner map fd for us to use it.
201
std::optional<uint32_t> ebpf_domain_t::get_map_inner_map_fd(const Reg& map_fd_reg) const {
34✔
202
    int start_fd, end_fd;
17✔
203
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
34✔
204
        return {};
×
205
    }
206

207
    std::optional<uint32_t> inner_map_fd;
34✔
208
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
68✔
209
        EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd);
34✔
210
        if (map == nullptr) {
34✔
211
            return {};
×
212
        }
213
        if (!inner_map_fd.has_value()) {
34✔
214
            inner_map_fd = map->inner_map_fd;
34✔
215
        } else if (map->type != *inner_map_fd) {
×
216
            return {};
×
217
        }
218
    }
219
    return inner_map_fd;
34✔
220
}
221

222
// We can deal with a range of key sizes.
223
interval_t ebpf_domain_t::get_map_key_size(const Reg& map_fd_reg) const {
1,998✔
224
    int start_fd, end_fd;
999✔
225
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
1,998✔
226
        return interval_t::top();
×
227
    }
228

229
    interval_t result = interval_t::bottom();
1,998✔
230
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
3,998✔
231
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
2,000✔
232
            result = result | interval_t{map->key_size};
4,000✔
233
        } else {
234
            return interval_t::top();
×
235
        }
236
    }
237
    return result;
1,998✔
238
}
1,998✔
239

240
// We can deal with a range of value sizes.
241
interval_t ebpf_domain_t::get_map_value_size(const Reg& map_fd_reg) const {
4,562✔
242
    int start_fd, end_fd;
2,281✔
243
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
4,562✔
244
        return interval_t::top();
×
245
    }
246

247
    interval_t result = interval_t::bottom();
4,562✔
248
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
9,126✔
249
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
4,564✔
250
            result = result | interval_t(map->value_size);
9,128✔
251
        } else {
252
            return interval_t::top();
×
253
        }
254
    }
255
    return result;
4,562✔
256
}
4,562✔
257

258
// We can deal with a range of max_entries values.
259
interval_t ebpf_domain_t::get_map_max_entries(const Reg& map_fd_reg) const {
4✔
260
    int start_fd, end_fd;
2✔
261
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
4✔
262
        return interval_t::top();
×
263
    }
264

265
    interval_t result = interval_t::bottom();
4✔
266
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
8✔
267
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
4✔
268
            result = result | interval_t(map->max_entries);
8✔
269
        } else {
270
            return interval_t::top();
×
271
        }
272
    }
273
    return result;
4✔
274
}
4✔
275

276
extended_number ebpf_domain_t::get_loop_count_upper_bound() const {
×
277
    extended_number ub{0};
×
278
    for (const variable_t counter : variable_t::get_loop_counters()) {
×
NEW
279
        ub = std::max(ub, m_inv.eval_interval(counter).ub());
×
280
    }
×
281
    return ub;
×
282
}
×
283

284
interval_t ebpf_domain_t::get_r0() const { return m_inv.eval_interval(reg_pack(R0_RETURN_VALUE).svalue); }
438✔
285

UNCOV
286
std::ostream& operator<<(std::ostream& o, const ebpf_domain_t& dom) {
×
UNCOV
287
    if (dom.is_bottom()) {
×
288
        o << "_|_";
×
289
    } else {
UNCOV
290
        o << dom.m_inv << "\nStack: " << dom.stack;
×
291
    }
UNCOV
292
    return o;
×
293
}
294

295
void ebpf_domain_t::initialize_packet() {
1,148✔
296
    using namespace crab::dsl_syntax;
574✔
297
    ebpf_domain_t& inv = *this;
1,148✔
298
    inv.havoc(variable_t::packet_size());
1,148✔
299
    inv.havoc(variable_t::meta_offset());
1,148✔
300

301
    inv.add_constraint(0 <= variable_t::packet_size());
2,296✔
302
    inv.add_constraint(variable_t::packet_size() < MAX_PACKET_SIZE);
1,722✔
303
    const auto info = *thread_local_program_info;
1,148✔
304
    if (info.type.context_descriptor->meta >= 0) {
1,148✔
305
        inv.add_constraint(variable_t::meta_offset() <= 0);
738✔
306
        inv.add_constraint(variable_t::meta_offset() >= -4098);
738✔
307
    } else {
308
        inv.m_inv.assign(variable_t::meta_offset(), 0);
984✔
309
    }
310
}
1,148✔
311

312
ebpf_domain_t ebpf_domain_t::from_constraints(const std::set<std::string>& constraints, const bool setup_constraints) {
1,824✔
313
    ebpf_domain_t inv;
1,824✔
314
    if (setup_constraints) {
1,824✔
315
        inv = setup_entry(false);
438✔
316
    }
317
    auto numeric_ranges = std::vector<interval_t>();
1,824✔
318
    for (const auto& cst : parse_linear_constraints(constraints, numeric_ranges)) {
10,054✔
319
        inv.add_constraint(cst);
8,230✔
320
    }
1,824✔
321
    for (const interval_t& range : numeric_ranges) {
2,080✔
322
        const int start = range.lb().narrow<int>();
384✔
323
        const int width = 1 + range.finite_size()->narrow<int>();
256✔
324
        inv.stack.initialize_numbers(start, width);
256✔
325
    }
326
    // TODO: handle other stack type constraints
327
    return inv;
2,736✔
328
}
1,824✔
329

330
ebpf_domain_t ebpf_domain_t::setup_entry(const bool init_r1) {
1,106✔
331
    using namespace crab::dsl_syntax;
553✔
332

333
    ebpf_domain_t inv;
1,106✔
334
    const auto r10 = reg_pack(R10_STACK_POINTER);
1,106✔
335
    constexpr Reg r10_reg{R10_STACK_POINTER};
1,106✔
336
    inv.m_inv.add_constraint(EBPF_TOTAL_STACK_SIZE <= r10.svalue);
2,212✔
337
    inv.m_inv.add_constraint(r10.svalue <= PTR_MAX);
1,659✔
338
    inv.m_inv.assign(r10.stack_offset, EBPF_TOTAL_STACK_SIZE);
1,106✔
339
    // stack_numeric_size would be 0, but TOP has the same result
340
    // so no need to assign it.
341
    inv.type_inv.assign_type(inv.m_inv, r10_reg, T_STACK);
1,106✔
342

343
    if (init_r1) {
1,106✔
344
        const auto r1 = reg_pack(R1_ARG);
667✔
345
        constexpr Reg r1_reg{R1_ARG};
667✔
346
        inv.m_inv.add_constraint(1 <= r1.svalue);
1,333✔
347
        inv.m_inv.add_constraint(r1.svalue <= PTR_MAX);
1,000✔
348
        inv.m_inv.assign(r1.ctx_offset, 0);
667✔
349
        inv.type_inv.assign_type(inv.m_inv, r1_reg, T_CTX);
1,000✔
350
    }
351

352
    inv.initialize_packet();
1,106✔
353
    return inv;
1,659✔
354
}
×
355

356
} // namespace crab
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