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

vbpf / ebpf-verifier / 13955273819

19 Mar 2025 07:38PM UTC coverage: 88.66% (+0.5%) from 88.134%
13955273819

push

github

web-flow
Fix sign extension (#850)

* fix sign extension
* add exhaustive tests for sign extension of <=3 bits
* fix interval::size()
* streamline bit/width operations

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

203 of 214 new or added lines in 9 files covered. (94.86%)

9 existing lines in 5 files now uncovered.

8639 of 9744 relevant lines covered (88.66%)

8977818.22 hits per line

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

78.38
/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,164✔
23
    reg_pack_t r = reg_pack(reg);
31,164✔
24
    switch (type) {
31,164✔
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,938✔
32
    }
33
}
34

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

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

43
string_invariant ebpf_domain_t::to_set() const { return this->m_inv.to_set() + this->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,011,310✔
52
    ebpf_domain_t abs;
1,011,310✔
53
    abs.set_to_bottom();
1,516,965✔
54
    return abs;
1,011,310✔
55
}
56

57
ebpf_domain_t::ebpf_domain_t() : m_inv(NumAbsDomain::top()) {}
1,014,680✔
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,011,310✔
68

69
bool ebpf_domain_t::is_bottom() const { return m_inv.is_bottom(); }
1,354,892✔
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) {
352,858✔
82
    if (is_bottom()) {
352,858✔
83
        *this = std::move(other);
329,452✔
84
        return;
329,452✔
85
    }
86
    if (other.is_bottom()) {
23,406✔
87
        return;
339✔
88
    }
89

90
    type_inv.selectively_join_based_on_type(m_inv, std::move(other.m_inv));
22,728✔
91

92
    stack |= std::move(other.stack);
22,728✔
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 += r.svalue <= std::numeric_limits<int32_t>::max();
6,600✔
122
        inv += r.svalue >= std::numeric_limits<int32_t>::min();
6,600✔
123
        inv += r.uvalue <= std::numeric_limits<uint32_t>::max();
6,600✔
124
        inv += r.uvalue >= 0;
6,600✔
125
        inv += r.stack_offset <= EBPF_TOTAL_STACK_SIZE;
6,600✔
126
        inv += r.stack_offset >= 0;
6,600✔
127
        inv += r.shared_offset <= r.shared_region_size;
6,600✔
128
        inv += r.shared_offset >= 0;
6,600✔
129
        inv += r.packet_offset <= variable_t::packet_size();
6,600✔
130
        inv += 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()) {
×
133
                inv += counter <= std::numeric_limits<int32_t>::max();
×
134
                inv += counter >= 0;
×
135
                inv += counter <= r.svalue;
×
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::operator+=(const linear_constraint_t& cst) { m_inv += cst; }
51,144✔
157

158
void ebpf_domain_t::operator-=(const variable_t var) { m_inv -= 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,054✔
164
    const interval_t& map_fd_interval = m_inv[reg_pack(map_fd_reg).map_fd];
13,054✔
165
    const auto lb = map_fd_interval.lb().number();
26,108✔
166
    const auto ub = map_fd_interval.ub().number();
26,108✔
167
    if (!lb || !lb->fits<int32_t>() || !ub || !ub->fits<int32_t>()) {
13,054✔
168
        return false;
1✔
169
    }
170
    *start_fd = lb->narrow<int32_t>();
13,052✔
171
    *end_fd = ub->narrow<int32_t>();
13,052✔
172

173
    // Cap the maximum range we'll check.
174
    constexpr int max_range = 32;
13,052✔
175
    return *map_fd_interval.finite_size() < max_range;
13,052✔
176
}
19,581✔
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,528✔
180
    int32_t start_fd, end_fd;
3,264✔
181
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
6,528✔
182
        return std::optional<uint32_t>();
2✔
183
    }
184

185
    std::optional<uint32_t> type;
6,526✔
186
    for (int32_t map_fd = start_fd; map_fd <= end_fd; map_fd++) {
13,054✔
187
        EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd);
6,532✔
188
        if (map == nullptr) {
6,530✔
189
            return std::optional<uint32_t>();
×
190
        }
191
        if (!type.has_value()) {
6,530✔
192
            type = map->type;
6,524✔
193
        } else if (map->type != *type) {
6✔
194
            return std::optional<uint32_t>();
2✔
195
        }
196
    }
197
    return type;
6,522✔
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,990✔
224
    int start_fd, end_fd;
995✔
225
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
1,990✔
226
        return interval_t::top();
×
227
    }
228

229
    interval_t result = interval_t::bottom();
1,990✔
230
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
3,982✔
231
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
1,992✔
232
            result = result | interval_t{map->key_size};
3,984✔
233
        } else {
234
            return interval_t::top();
×
235
        }
236
    }
237
    return result;
1,990✔
238
}
1,990✔
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,498✔
242
    int start_fd, end_fd;
2,249✔
243
    if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) {
4,498✔
244
        return interval_t::top();
×
245
    }
246

247
    interval_t result = interval_t::bottom();
4,498✔
248
    for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) {
8,998✔
249
        if (const EbpfMapDescriptor* map = &thread_local_program_info->platform->get_map_descriptor(map_fd)) {
4,500✔
250
            result = result | interval_t(map->value_size);
9,000✔
251
        } else {
252
            return interval_t::top();
×
253
        }
254
    }
255
    return result;
4,498✔
256
}
4,498✔
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()) {
×
279
        ub = std::max(ub, m_inv[counter].ub());
×
280
    }
×
281
    return ub;
×
282
}
×
283

284
interval_t ebpf_domain_t::get_r0() const { return m_inv[reg_pack(R0_RETURN_VALUE).svalue]; }
657✔
285

286
std::ostream& operator<<(std::ostream& o, const ebpf_domain_t& dom) {
16✔
287
    if (dom.is_bottom()) {
16✔
UNCOV
288
        o << "_|_";
×
289
    } else {
290
        o << dom.m_inv << "\nStack: " << dom.stack;
16✔
291
    }
292
    return o;
16✔
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 -= variable_t::packet_size();
1,148✔
299
    inv -= variable_t::meta_offset();
1,148✔
300

301
    inv += 0 <= variable_t::packet_size();
2,296✔
302
    inv += 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 += variable_t::meta_offset() <= 0;
738✔
306
        inv += 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,044✔
319
        inv += cst;
8,220✔
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 += EBPF_TOTAL_STACK_SIZE <= r10.svalue;
2,212✔
337
    inv.m_inv += 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 += 1 <= r1.svalue;
1,333✔
347
        inv.m_inv += 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

© 2025 Coveralls, Inc