• 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

96.28
/src/crab/ebpf_transformer.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 <bitset>
7
#include <optional>
8
#include <utility>
9
#include <vector>
10

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

13
#include "asm_unmarshal.hpp"
14
#include "config.hpp"
15
#include "crab/array_domain.hpp"
16
#include "crab/ebpf_domain.hpp"
17
#include "crab_utils/num_safety.hpp"
18
#include "dsl_syntax.hpp"
19
#include "platform.hpp"
20
#include "string_constraints.hpp"
21

22
using crab::domains::NumAbsDomain;
23
namespace crab {
24

25
class ebpf_transformer final {
26
    ebpf_domain_t& dom;
27
    // shorthands:
28
    NumAbsDomain& m_inv;
29
    domains::array_domain_t& stack;
30
    TypeDomain& type_inv;
31

32
  public:
33
    explicit ebpf_transformer(ebpf_domain_t& dom)
339,132✔
34
        : dom(dom), m_inv(dom.m_inv), stack(dom.stack), type_inv(dom.type_inv) {}
339,132✔
35

36
    // abstract transformers
37
    void operator()(const Assume&);
38
    void operator()(const Atomic&);
39
    void operator()(const Bin&);
40
    void operator()(const Call&);
41
    void operator()(const CallLocal&);
42
    void operator()(const Callx&);
43
    void operator()(const Exit&);
44
    void operator()(const IncrementLoopCounter&);
45
    void operator()(const Jmp&) const;
46
    void operator()(const LoadMapFd&);
47
    void operator()(const LoadMapAddress&);
48
    void operator()(const Mem&);
49
    void operator()(const Packet&);
50
    void operator()(const Un&);
51
    void operator()(const Undefined&);
52

53
    void initialize_loop_counter(const label_t& label);
54

55
  private:
56
    /// Forget everything about all offset variables for a given register.
57
    void havoc_offsets(const Reg& reg);
58

59
    void scratch_caller_saved_registers();
60
    void save_callee_saved_registers(const std::string& prefix);
61
    void restore_callee_saved_registers(const std::string& prefix);
62
    void havoc_subprogram_stack(const std::string& prefix);
63
    void forget_packet_pointers();
64
    void do_load_mapfd(const Reg& dst_reg, int mapfd, bool maybe_null);
65
    void do_load_map_address(const Reg& dst_reg, const int mapfd, int32_t offset);
66

67
    void assign_valid_ptr(const Reg& dst_reg, bool maybe_null);
68

69
    void recompute_stack_numeric_size(NumAbsDomain& inv, const Reg& reg) const;
70
    void recompute_stack_numeric_size(NumAbsDomain& inv, variable_t type_variable) const;
71
    void do_load_stack(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr, int width,
72
                       const Reg& src_reg);
73
    void do_load_ctx(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr_vague, int width);
74
    void do_load_packet_or_shared(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr, int width);
75
    void do_load(const Mem& b, const Reg& target_reg);
76

77
    void do_store_stack(NumAbsDomain& inv, const linear_expression_t& addr, int width,
78
                        const linear_expression_t& val_type, const linear_expression_t& val_svalue,
79
                        const linear_expression_t& val_uvalue, const std::optional<reg_pack_t>& opt_val_reg);
80

81
    void do_mem_store(const Mem& b, const linear_expression_t& val_type, const linear_expression_t& val_svalue,
82
                      const linear_expression_t& val_uvalue, const std::optional<reg_pack_t>& opt_val_reg);
83

84
    void add(const Reg& dst_reg, int imm, int finite_width);
85
    void shl(const Reg& dst_reg, int imm, int finite_width);
86
    void lshr(const Reg& dst_reg, int imm, int finite_width);
87
    void ashr(const Reg& dst_reg, const linear_expression_t& right_svalue, int finite_width);
88
    void sign_extend(const Reg& dst_reg, const linear_expression_t& right_svalue, int target_width, int source_width);
89
}; // end ebpf_domain_t
90

91
void ebpf_domain_transform(ebpf_domain_t& inv, const Instruction& ins) {
341,624✔
92
    if (inv.is_bottom()) {
341,624✔
93
        return;
2,524✔
94
    }
95
    const auto pre = inv;
339,100✔
96
    std::visit(ebpf_transformer{inv}, ins);
339,100✔
97
    if (inv.is_bottom() && !std::holds_alternative<Assume>(ins)) {
339,098✔
98
        // Fail. raise an exception to stop the analysis.
UNCOV
99
        std::stringstream msg;
×
UNCOV
100
        msg << "Bug! pre-invariant:\n"
×
UNCOV
101
            << pre << "\n followed by instruction: " << ins << "\n"
×
UNCOV
102
            << "leads to bottom";
×
UNCOV
103
        throw std::logic_error(msg.str());
×
UNCOV
104
    }
×
105
}
339,099✔
106

107
static void havoc_offsets(NumAbsDomain& inv, const Reg& reg) {
272,718✔
108
    const reg_pack_t r = reg_pack(reg);
272,718✔
109
    inv.havoc(r.ctx_offset);
272,718✔
110
    inv.havoc(r.map_fd);
272,718✔
111
    inv.havoc(r.packet_offset);
272,718✔
112
    inv.havoc(r.shared_offset);
272,718✔
113
    inv.havoc(r.shared_region_size);
272,718✔
114
    inv.havoc(r.stack_offset);
272,718✔
115
    inv.havoc(r.stack_numeric_size);
272,718✔
116
}
272,718✔
117

118
static void havoc_register(NumAbsDomain& inv, const Reg& reg) {
130,310✔
119
    const reg_pack_t r = reg_pack(reg);
130,310✔
120
    havoc_offsets(inv, reg);
130,310✔
121
    inv.havoc(r.svalue);
130,310✔
122
    inv.havoc(r.uvalue);
130,310✔
123
}
130,310✔
124

125
void ebpf_transformer::scratch_caller_saved_registers() {
20,026✔
126
    for (int i = R1_ARG; i <= R5_ARG; i++) {
120,156✔
127
        Reg r{gsl::narrow<uint8_t>(i)};
100,130✔
128
        havoc_register(m_inv, r);
100,130✔
129
        type_inv.havoc_type(m_inv, r);
100,130✔
130
    }
131
}
20,026✔
132

133
void ebpf_transformer::save_callee_saved_registers(const std::string& prefix) {
58✔
134
    // Create variables specific to the new call stack frame that store
135
    // copies of the states of r6 through r9.
136
    for (int r = R6; r <= R9; r++) {
290✔
137
        for (const data_kind_t kind : iterate_kinds()) {
2,552✔
138
            const variable_t src_var = variable_t::reg(kind, r);
2,320✔
139
            if (!m_inv.eval_interval(src_var).is_top()) {
4,640✔
140
                m_inv.assign(variable_t::stack_frame_var(kind, r, prefix), src_var);
150✔
141
            }
142
        }
232✔
143
    }
144
}
58✔
145

146
void ebpf_transformer::restore_callee_saved_registers(const std::string& prefix) {
58✔
147
    for (int r = R6; r <= R9; r++) {
290✔
148
        for (const data_kind_t kind : iterate_kinds()) {
2,552✔
149
            const variable_t src_var = variable_t::stack_frame_var(kind, r, prefix);
2,320✔
150
            if (!m_inv.eval_interval(src_var).is_top()) {
4,640✔
151
                m_inv.assign(variable_t::reg(kind, r), src_var);
150✔
152
            } else {
153
                m_inv.havoc(variable_t::reg(kind, r));
2,220✔
154
            }
155
            m_inv.havoc(src_var);
2,320✔
156
        }
232✔
157
    }
158
}
58✔
159

160
void ebpf_transformer::havoc_subprogram_stack(const std::string& prefix) {
58✔
161
    const variable_t r10_stack_offset = reg_pack(R10_STACK_POINTER).stack_offset;
58✔
162
    const auto intv = m_inv.eval_interval(r10_stack_offset);
58✔
163
    if (!intv.is_singleton()) {
58✔
164
        return;
36✔
165
    }
166
    const int64_t stack_start = intv.singleton()->cast_to<int64_t>() - EBPF_SUBPROGRAM_STACK_SIZE;
22✔
167
    for (const data_kind_t kind : iterate_kinds()) {
242✔
168
        stack.havoc(m_inv, kind, stack_start, EBPF_SUBPROGRAM_STACK_SIZE);
220✔
169
    }
22✔
170
}
58✔
171

172
void ebpf_transformer::forget_packet_pointers() {
42✔
173
    using namespace crab::dsl_syntax;
21✔
174

175
    for (const variable_t type_variable : variable_t::get_type_variables()) {
1,522✔
176
        if (type_inv.has_type(m_inv, type_variable, T_PACKET)) {
1,480✔
177
            m_inv.havoc(variable_t::kind_var(data_kind_t::types, type_variable));
956✔
178
            m_inv.havoc(variable_t::kind_var(data_kind_t::packet_offsets, type_variable));
956✔
179
            m_inv.havoc(variable_t::kind_var(data_kind_t::svalues, type_variable));
956✔
180
            m_inv.havoc(variable_t::kind_var(data_kind_t::uvalues, type_variable));
956✔
181
        }
182
    }
42✔
183

184
    dom.initialize_packet();
42✔
185
}
42✔
186

187
void ebpf_transformer::havoc_offsets(const Reg& reg) { crab::havoc_offsets(m_inv, reg); }
70,817✔
188
static linear_constraint_t type_is_pointer(const reg_pack_t& r) {
414✔
189
    using namespace crab::dsl_syntax;
207✔
190
    return r.type >= T_CTX;
621✔
191
}
192

193
static linear_constraint_t type_is_number(const reg_pack_t& r) {
19,692✔
194
    using namespace crab::dsl_syntax;
9,846✔
195
    return r.type == T_NUM;
19,692✔
196
}
197

198
static linear_constraint_t type_is_number(const Reg& r) { return type_is_number(reg_pack(r)); }
8,538✔
199

200
static linear_constraint_t type_is_not_stack(const reg_pack_t& r) {
398✔
201
    using namespace crab::dsl_syntax;
199✔
202
    return r.type != T_STACK;
398✔
203
}
204

205
/** Linear constraint for a pointer comparison.
206
 */
207
static linear_constraint_t assume_cst_offsets_reg(const Condition::Op op, const variable_t dst_offset,
1,912✔
208
                                                  const variable_t src_offset) {
209
    using namespace crab::dsl_syntax;
956✔
210
    using Op = Condition::Op;
956✔
211
    switch (op) {
1,912✔
212
    case Op::EQ: return eq(dst_offset, src_offset);
6✔
213
    case Op::NE: return neq(dst_offset, src_offset);
12✔
214
    case Op::GE: return dst_offset >= src_offset;
6✔
NEW
215
    case Op::SGE: return dst_offset >= src_offset; // pointer comparison is unsigned
×
216
    case Op::LE: return dst_offset <= src_offset;
942✔
NEW
217
    case Op::SLE: return dst_offset <= src_offset; // pointer comparison is unsigned
×
218
    case Op::GT: return dst_offset > src_offset;
1,413✔
NEW
219
    case Op::SGT: return dst_offset > src_offset; // pointer comparison is unsigned
×
NEW
220
    case Op::SLT:
×
NEW
221
        return src_offset > dst_offset;
×
222
        // Note: reverse the test as a workaround strange lookup:
223
    case Op::LT: return src_offset > dst_offset; // FIX unsigned
9✔
NEW
224
    default: return dst_offset - dst_offset == 0;
×
225
    }
226
}
227

228
void ebpf_transformer::operator()(const Assume& s) {
50,488✔
229
    if (m_inv.is_bottom()) {
50,488✔
NEW
230
        return;
×
231
    }
232
    const Condition cond = s.cond;
50,488✔
233
    const auto dst = reg_pack(cond.left);
50,488✔
234
    if (const auto psrc_reg = std::get_if<Reg>(&cond.right)) {
50,488✔
235
        const auto src_reg = *psrc_reg;
10,056✔
236
        const auto src = reg_pack(src_reg);
10,056✔
237
        if (type_inv.same_type(m_inv, cond.left, std::get<Reg>(cond.right))) {
10,056✔
238
            m_inv = type_inv.join_over_types(m_inv, cond.left, [&](NumAbsDomain& inv, const type_encoding_t type) {
18,544✔
239
                if (type == T_NUM) {
9,276✔
240
                    for (const linear_constraint_t& cst :
16,453✔
241
                         inv->assume_cst_reg(cond.op, cond.is64, dst.svalue, dst.uvalue, src.svalue, src.uvalue)) {
21,860✔
242
                        inv.add_constraint(cst);
10,814✔
243
                    }
7,364✔
244
                } else {
245
                    // Either pointers to a singleton region,
246
                    // or an equality comparison on map descriptors/pointers to non-singleton locations
247
                    if (const auto dst_offset = dom.get_type_offset_variable(cond.left, type)) {
1,912✔
248
                        if (const auto src_offset = dom.get_type_offset_variable(src_reg, type)) {
1,912✔
249
                            inv.add_constraint(assume_cst_offsets_reg(cond.op, dst_offset.value(), src_offset.value()));
2,868✔
250
                        }
251
                    }
252
                }
253
            });
18,548✔
254
        } else {
255
            // We should only reach here if `--assume-assert` is off
256
            assert(!thread_local_options.assume_assertions || dom.is_bottom());
784✔
257
            // be sound in any case, it happens to flush out bugs:
258
            m_inv.set_to_top();
784✔
259
        }
260
    } else {
261
        const int64_t imm = gsl::narrow_cast<int64_t>(std::get<Imm>(cond.right).v);
40,432✔
262
        for (const linear_constraint_t& cst : m_inv->assume_cst_imm(cond.op, cond.is64, dst.svalue, dst.uvalue, imm)) {
124,878✔
263
            m_inv.add_constraint(cst);
84,446✔
264
        }
40,432✔
265
    }
266
}
267

268
void ebpf_transformer::operator()(const Undefined& a) {}
2,392✔
269

270
// Simple truncation function usable with swap_endianness().
271
template <class T>
272
constexpr T truncate(T x) noexcept {
24✔
273
    return x;
24✔
274
}
275

276
void ebpf_transformer::operator()(const Un& stmt) {
2,956✔
277
    if (m_inv.is_bottom()) {
2,956✔
NEW
278
        return;
×
279
    }
280
    const auto dst = reg_pack(stmt.dst);
2,956✔
281
    auto swap_endianness = [&](const variable_t v, auto be_or_le) {
7,082✔
282
        if (m_inv.entail(type_is_number(stmt.dst))) {
8,406✔
283
            if (const auto n = m_inv.eval_interval(v).singleton()) {
11,194✔
284
                if (n->fits_cast_to<int64_t>()) {
244✔
285
                    m_inv.set(v, interval_t{be_or_le(n->cast_to<int64_t>())});
244✔
286
                    return;
244✔
287
                }
288
            }
289
        }
290
        m_inv.havoc(v);
5,360✔
291
        havoc_offsets(stmt.dst);
5,360✔
292
    };
2,956✔
293
    // Swap bytes if needed.  For 64-bit types we need the weights to fit in a
294
    // signed int64, but for smaller types we don't want sign extension,
295
    // so we use unsigned which still fits in a signed int64.
296
    switch (stmt.op) {
2,956✔
297
    case Un::Op::BE16:
2,632✔
298
        if (!thread_local_options.big_endian) {
2,632✔
299
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
2,628✔
300
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
2,628✔
301
        } else {
302
            swap_endianness(dst.svalue, truncate<uint16_t>);
4✔
303
            swap_endianness(dst.uvalue, truncate<uint16_t>);
4✔
304
        }
305
        break;
1,316✔
306
    case Un::Op::BE32:
118✔
307
        if (!thread_local_options.big_endian) {
118✔
308
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
116✔
309
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
116✔
310
        } else {
311
            swap_endianness(dst.svalue, truncate<uint32_t>);
2✔
312
            swap_endianness(dst.uvalue, truncate<uint32_t>);
2✔
313
        }
314
        break;
59✔
315
    case Un::Op::BE64:
10✔
316
        if (!thread_local_options.big_endian) {
10✔
317
            swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
8✔
318
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
8✔
319
        }
320
        break;
5✔
321
    case Un::Op::LE16:
12✔
322
        if (thread_local_options.big_endian) {
12✔
323
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
2✔
324
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
2✔
325
        } else {
326
            swap_endianness(dst.svalue, truncate<uint16_t>);
10✔
327
            swap_endianness(dst.uvalue, truncate<uint16_t>);
10✔
328
        }
329
        break;
6✔
330
    case Un::Op::LE32:
8✔
331
        if (thread_local_options.big_endian) {
8✔
332
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
2✔
333
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
2✔
334
        } else {
335
            swap_endianness(dst.svalue, truncate<uint32_t>);
6✔
336
            swap_endianness(dst.uvalue, truncate<uint32_t>);
6✔
337
        }
338
        break;
4✔
339
    case Un::Op::LE64:
8✔
340
        if (thread_local_options.big_endian) {
8✔
341
            swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
2✔
342
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
2✔
343
        }
344
        break;
4✔
345
    case Un::Op::SWAP16:
10✔
346
        swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
10✔
347
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
10✔
348
        break;
5✔
349
    case Un::Op::SWAP32:
6✔
350
        swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
6✔
351
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
6✔
352
        break;
3✔
353
    case Un::Op::SWAP64:
6✔
354
        swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
6✔
355
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
6✔
356
        break;
3✔
357
    case Un::Op::NEG:
146✔
358
        m_inv->neg(dst.svalue, dst.uvalue, stmt.is64 ? 64 : 32);
166✔
359
        havoc_offsets(stmt.dst);
1,551✔
360
        break;
73✔
361
    }
362
}
363

364
void ebpf_transformer::operator()(const Exit& a) {
1,256✔
365
    if (m_inv.is_bottom()) {
1,256✔
366
        return;
1,198✔
367
    }
368
    // Clean up any state for the current stack frame.
369
    const std::string prefix = a.stack_frame_prefix;
1,256✔
370
    if (prefix.empty()) {
1,256✔
371
        return;
1,198✔
372
    }
373
    havoc_subprogram_stack(prefix);
58✔
374
    restore_callee_saved_registers(prefix);
58✔
375

376
    // Restore r10.
377
    constexpr Reg r10_reg{R10_STACK_POINTER};
58✔
378
    add(r10_reg, EBPF_SUBPROGRAM_STACK_SIZE, 64);
58✔
379
}
1,256✔
380

381
void ebpf_transformer::operator()(const Jmp&) const {
14,796✔
382
    // This is a NOP. It only exists to hold the jump preconditions.
383
}
14,796✔
384

385
void ebpf_transformer::operator()(const Packet& a) {
236✔
386
    if (m_inv.is_bottom()) {
236✔
NEW
387
        return;
×
388
    }
389
    const auto reg = reg_pack(R0_RETURN_VALUE);
236✔
390
    constexpr Reg r0_reg{R0_RETURN_VALUE};
236✔
391
    type_inv.assign_type(m_inv, r0_reg, T_NUM);
236✔
392
    havoc_offsets(r0_reg);
236✔
393
    m_inv.havoc(reg.svalue);
236✔
394
    m_inv.havoc(reg.uvalue);
236✔
395
    scratch_caller_saved_registers();
236✔
396
}
397

398
void ebpf_transformer::do_load_stack(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr,
12,518✔
399
                                     const int width, const Reg& src_reg) {
400
    type_inv.assign_type(inv, target_reg, stack.load(inv, data_kind_t::types, addr, width));
12,518✔
401
    using namespace crab::dsl_syntax;
6,259✔
402
    if (inv.entail(width <= reg_pack(src_reg).stack_numeric_size)) {
25,036✔
403
        type_inv.assign_type(inv, target_reg, T_NUM);
723✔
404
    }
405

406
    const reg_pack_t& target = reg_pack(target_reg);
12,518✔
407
    if (width == 1 || width == 2 || width == 4 || width == 8) {
12,518✔
408
        // Use the addr before we havoc the destination register since we might be getting the
409
        // addr from that same register.
410
        const std::optional<linear_expression_t> sresult = stack.load(inv, data_kind_t::svalues, addr, width);
12,518✔
411
        const std::optional<linear_expression_t> uresult = stack.load(inv, data_kind_t::uvalues, addr, width);
12,518✔
412
        havoc_register(inv, target_reg);
12,518✔
413
        inv.assign(target.svalue, sresult);
12,518✔
414
        inv.assign(target.uvalue, uresult);
12,518✔
415

416
        if (type_inv.has_type(inv, target.type, T_CTX)) {
12,518✔
417
            inv.assign(target.ctx_offset, stack.load(inv, data_kind_t::ctx_offsets, addr, width));
3,318✔
418
        }
419
        if (type_inv.has_type(inv, target.type, T_MAP) || type_inv.has_type(inv, target.type, T_MAP_PROGRAMS)) {
12,518✔
420
            inv.assign(target.map_fd, stack.load(inv, data_kind_t::map_fds, addr, width));
1,248✔
421
        }
422
        if (type_inv.has_type(inv, target.type, T_PACKET)) {
12,518✔
423
            inv.assign(target.packet_offset, stack.load(inv, data_kind_t::packet_offsets, addr, width));
1,089✔
424
        }
425
        if (type_inv.has_type(inv, target.type, T_SHARED)) {
12,518✔
426
            inv.assign(target.shared_offset, stack.load(inv, data_kind_t::shared_offsets, addr, width));
2,388✔
427
            inv.assign(target.shared_region_size, stack.load(inv, data_kind_t::shared_region_sizes, addr, width));
2,388✔
428
        }
429
        if (type_inv.has_type(inv, target.type, T_STACK)) {
12,518✔
430
            inv.assign(target.stack_offset, stack.load(inv, data_kind_t::stack_offsets, addr, width));
1,131✔
431
            inv.assign(target.stack_numeric_size, stack.load(inv, data_kind_t::stack_numeric_sizes, addr, width));
1,131✔
432
        }
433
    } else {
12,518✔
434
        havoc_register(inv, target_reg);
×
435
    }
436
}
12,518✔
437

438
void ebpf_transformer::do_load_ctx(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr_vague,
3,534✔
439
                                   const int width) {
440
    using namespace crab::dsl_syntax;
1,767✔
441
    if (inv.is_bottom()) {
3,534✔
442
        return;
1,526✔
443
    }
444

445
    const ebpf_context_descriptor_t* desc = thread_local_program_info->type.context_descriptor;
3,534✔
446

447
    const reg_pack_t& target = reg_pack(target_reg);
3,534✔
448

449
    if (desc->end < 0) {
3,534✔
450
        havoc_register(inv, target_reg);
290✔
451
        type_inv.assign_type(inv, target_reg, T_NUM);
290✔
452
        return;
290✔
453
    }
454

455
    const interval_t interval = inv.eval_interval(addr_vague);
3,244✔
456
    const std::optional<number_t> maybe_addr = interval.singleton();
3,244✔
457
    havoc_register(inv, target_reg);
3,244✔
458

459
    const bool may_touch_ptr =
1,622✔
460
        interval.contains(desc->data) || interval.contains(desc->meta) || interval.contains(desc->end);
5,477✔
461

462
    if (!maybe_addr) {
3,244✔
463
        if (may_touch_ptr) {
2✔
464
            type_inv.havoc_type(inv, target_reg);
2✔
465
        } else {
466
            type_inv.assign_type(inv, target_reg, T_NUM);
×
467
        }
468
        return;
2✔
469
    }
470

471
    const number_t addr = *maybe_addr;
3,242✔
472

473
    // We use offsets for packet data, data_end, and meta during verification,
474
    // but at runtime they will be 64-bit pointers.  We can use the offset values
475
    // for verification like we use map_fd's as a proxy for maps which
476
    // at runtime are actually 64-bit memory pointers.
477
    const int offset_width = desc->end - desc->data;
3,242✔
478
    if (addr == desc->data) {
3,242✔
479
        if (width == offset_width) {
1,000✔
480
            inv.assign(target.packet_offset, 0);
1,500✔
481
        }
482
    } else if (addr == desc->end) {
2,242✔
483
        if (width == offset_width) {
990✔
484
            inv.assign(target.packet_offset, variable_t::packet_size());
1,479✔
485
        }
486
    } else if (addr == desc->meta) {
1,252✔
487
        if (width == offset_width) {
18✔
488
            inv.assign(target.packet_offset, variable_t::meta_offset());
27✔
489
        }
490
    } else {
491
        if (may_touch_ptr) {
1,234✔
492
            type_inv.havoc_type(inv, target_reg);
×
493
        } else {
494
            type_inv.assign_type(inv, target_reg, T_NUM);
1,851✔
495
        }
496
        return;
1,234✔
497
    }
498
    if (width == offset_width) {
2,008✔
499
        type_inv.assign_type(inv, target_reg, T_PACKET);
2,004✔
500
        inv.add_constraint(4098 <= target.svalue);
4,008✔
501
        inv.add_constraint(target.svalue <= PTR_MAX);
3,006✔
502
    }
503
}
6,718✔
504

505
void ebpf_transformer::do_load_packet_or_shared(NumAbsDomain& inv, const Reg& target_reg,
10,472✔
506
                                                const linear_expression_t& addr, const int width) {
507
    if (inv.is_bottom()) {
10,472✔
508
        return;
×
509
    }
510
    const reg_pack_t& target = reg_pack(target_reg);
10,472✔
511

512
    type_inv.assign_type(inv, target_reg, T_NUM);
10,472✔
513
    havoc_register(inv, target_reg);
10,472✔
514

515
    // A 1 or 2 byte copy results in a limited range of values that may be used as array indices.
516
    if (width == 1 || width == 2) {
10,472✔
517
        const interval_t full = interval_t::unsigned_int(width * 8);
6,454✔
518
        inv.set(target.svalue, full);
6,454✔
519
        inv.set(target.uvalue, full);
6,454✔
520
    }
6,454✔
521
}
522

523
void ebpf_transformer::do_load(const Mem& b, const Reg& target_reg) {
27,108✔
524
    using namespace crab::dsl_syntax;
13,554✔
525

526
    const auto mem_reg = reg_pack(b.access.basereg);
27,108✔
527
    const int width = b.access.width;
27,108✔
528
    const int offset = b.access.offset;
27,108✔
529

530
    if (b.access.basereg.v == R10_STACK_POINTER) {
27,108✔
531
        const linear_expression_t addr = mem_reg.stack_offset + offset;
17,955✔
532
        do_load_stack(m_inv, target_reg, addr, width, b.access.basereg);
11,970✔
533
        return;
11,970✔
534
    }
11,970✔
535

536
    m_inv = type_inv.join_over_types(m_inv, b.access.basereg, [&](NumAbsDomain& inv, type_encoding_t type) {
30,276✔
537
        switch (type) {
15,368✔
538
        case T_UNINIT: return;
407✔
539
        case T_MAP: return;
540
        case T_MAP_PROGRAMS: return;
541
        case T_NUM: return;
542
        case T_CTX: {
3,534✔
543
            linear_expression_t addr = mem_reg.ctx_offset + offset;
5,301✔
544
            do_load_ctx(inv, target_reg, addr, width);
3,534✔
545
            break;
3,534✔
546
        }
3,534✔
547
        case T_STACK: {
548✔
548
            linear_expression_t addr = mem_reg.stack_offset + offset;
822✔
549
            do_load_stack(inv, target_reg, addr, width, b.access.basereg);
548✔
550
            break;
548✔
551
        }
548✔
552
        case T_PACKET: {
3,260✔
553
            linear_expression_t addr = mem_reg.packet_offset + offset;
4,890✔
554
            do_load_packet_or_shared(inv, target_reg, addr, width);
3,260✔
555
            break;
3,260✔
556
        }
3,260✔
557
        default: {
7,212✔
558
            linear_expression_t addr = mem_reg.shared_offset + offset;
10,818✔
559
            do_load_packet_or_shared(inv, target_reg, addr, width);
7,212✔
560
            break;
7,212✔
561
        }
7,212✔
562
        }
563
    });
15,138✔
564
}
565

566
void ebpf_transformer::do_store_stack(NumAbsDomain& inv, const linear_expression_t& addr, const int width,
31,670✔
567
                                      const linear_expression_t& val_type, const linear_expression_t& val_svalue,
568
                                      const linear_expression_t& val_uvalue,
569
                                      const std::optional<reg_pack_t>& opt_val_reg) {
570
    {
15,835✔
571
        const std::optional<variable_t> var = stack.store_type(inv, addr, width, val_type);
31,670✔
572
        type_inv.assign_type(inv, var, val_type);
31,670✔
573
    }
574
    if (width == 8) {
31,670✔
575
        inv.assign(stack.store(inv, data_kind_t::svalues, addr, width, val_svalue), val_svalue);
12,834✔
576
        inv.assign(stack.store(inv, data_kind_t::uvalues, addr, width, val_uvalue), val_uvalue);
12,834✔
577

578
        if (opt_val_reg && type_inv.has_type(m_inv, val_type, T_CTX)) {
12,834✔
579
            inv.assign(stack.store(inv, data_kind_t::ctx_offsets, addr, width, opt_val_reg->ctx_offset),
399✔
580
                       opt_val_reg->ctx_offset);
266✔
581
        } else {
582
            stack.havoc(inv, data_kind_t::ctx_offsets, addr, width);
12,568✔
583
        }
584

585
        if (opt_val_reg &&
25,658✔
586
            (type_inv.has_type(m_inv, val_type, T_MAP) || type_inv.has_type(m_inv, val_type, T_MAP_PROGRAMS))) {
25,611✔
587
            inv.assign(stack.store(inv, data_kind_t::map_fds, addr, width, opt_val_reg->map_fd), opt_val_reg->map_fd);
141✔
588
        } else {
589
            stack.havoc(inv, data_kind_t::map_fds, addr, width);
12,740✔
590
        }
591

592
        if (opt_val_reg && type_inv.has_type(m_inv, val_type, T_PACKET)) {
12,834✔
593
            inv.assign(stack.store(inv, data_kind_t::packet_offsets, addr, width, opt_val_reg->packet_offset),
39✔
594
                       opt_val_reg->packet_offset);
26✔
595
        } else {
596
            stack.havoc(inv, data_kind_t::packet_offsets, addr, width);
12,808✔
597
        }
598

599
        if (opt_val_reg && type_inv.has_type(m_inv, val_type, T_SHARED)) {
12,834✔
600
            inv.assign(stack.store(inv, data_kind_t::shared_offsets, addr, width, opt_val_reg->shared_offset),
225✔
601
                       opt_val_reg->shared_offset);
150✔
602
            inv.assign(stack.store(inv, data_kind_t::shared_region_sizes, addr, width, opt_val_reg->shared_region_size),
225✔
603
                       opt_val_reg->shared_region_size);
150✔
604
        } else {
605
            stack.havoc(inv, data_kind_t::shared_region_sizes, addr, width);
12,684✔
606
            stack.havoc(inv, data_kind_t::shared_offsets, addr, width);
12,684✔
607
        }
608

609
        if (opt_val_reg && type_inv.has_type(m_inv, val_type, T_STACK)) {
12,834✔
610
            inv.assign(stack.store(inv, data_kind_t::stack_offsets, addr, width, opt_val_reg->stack_offset),
45✔
611
                       opt_val_reg->stack_offset);
30✔
612
            inv.assign(stack.store(inv, data_kind_t::stack_numeric_sizes, addr, width, opt_val_reg->stack_numeric_size),
45✔
613
                       opt_val_reg->stack_numeric_size);
30✔
614
        } else {
615
            stack.havoc(inv, data_kind_t::stack_offsets, addr, width);
12,804✔
616
            stack.havoc(inv, data_kind_t::stack_numeric_sizes, addr, width);
12,804✔
617
        }
618
    } else {
619
        if ((width == 1 || width == 2 || width == 4) && type_inv.get_type(m_inv, val_type) == T_NUM) {
18,836✔
620
            // Keep track of numbers on the stack that might be used as array indices.
621
            if (const auto stack_svalue = stack.store(inv, data_kind_t::svalues, addr, width, val_svalue)) {
18,638✔
622
                inv.assign(stack_svalue, val_svalue);
18,636✔
623
                inv->overflow_bounds(*stack_svalue, width * 8, true);
18,636✔
624
            }
625
            if (const auto stack_uvalue = stack.store(inv, data_kind_t::uvalues, addr, width, val_uvalue)) {
18,638✔
626
                inv.assign(stack_uvalue, val_uvalue);
18,636✔
627
                inv->overflow_bounds(*stack_uvalue, width * 8, false);
18,636✔
628
            }
629
        } else {
630
            stack.havoc(inv, data_kind_t::svalues, addr, width);
198✔
631
            stack.havoc(inv, data_kind_t::uvalues, addr, width);
198✔
632
        }
633
        stack.havoc(inv, data_kind_t::ctx_offsets, addr, width);
18,836✔
634
        stack.havoc(inv, data_kind_t::map_fds, addr, width);
18,836✔
635
        stack.havoc(inv, data_kind_t::packet_offsets, addr, width);
18,836✔
636
        stack.havoc(inv, data_kind_t::shared_offsets, addr, width);
18,836✔
637
        stack.havoc(inv, data_kind_t::stack_offsets, addr, width);
18,836✔
638
        stack.havoc(inv, data_kind_t::shared_region_sizes, addr, width);
18,836✔
639
        stack.havoc(inv, data_kind_t::stack_numeric_sizes, addr, width);
18,836✔
640
    }
641

642
    // Update stack_numeric_size for any stack type variables.
643
    // stack_numeric_size holds the number of continuous bytes starting from stack_offset that are known to be numeric.
644
    auto updated_lb = m_inv.eval_interval(addr).lb();
47,505✔
645
    auto updated_ub = m_inv.eval_interval(addr).ub() + width;
79,175✔
646
    for (const variable_t type_variable : variable_t::get_type_variables()) {
1,832,896✔
647
        if (!type_inv.has_type(inv, type_variable, T_STACK)) {
1,801,226✔
648
            continue;
936,457✔
649
        }
650
        const variable_t stack_offset_variable = variable_t::kind_var(data_kind_t::stack_offsets, type_variable);
864,769✔
651
        const variable_t stack_numeric_size_variable =
432,385✔
652
            variable_t::kind_var(data_kind_t::stack_numeric_sizes, type_variable);
864,769✔
653

654
        using namespace crab::dsl_syntax;
432,385✔
655
        // See if the variable's numeric interval overlaps with changed bytes.
656
        if (m_inv.intersect(dsl_syntax::operator<=(addr, stack_offset_variable + stack_numeric_size_variable)) &&
2,591,458✔
657
            m_inv.intersect(operator>=(addr + width, stack_offset_variable))) {
1,723,840✔
658
            m_inv.havoc(stack_numeric_size_variable);
827,035✔
659
            recompute_stack_numeric_size(m_inv, type_variable);
827,035✔
660
        }
661
    }
31,670✔
662
}
31,670✔
663

664
void ebpf_transformer::operator()(const Mem& b) {
64,600✔
665
    if (m_inv.is_bottom()) {
64,600✔
666
        return;
667
    }
668
    if (const auto preg = std::get_if<Reg>(&b.value)) {
64,600✔
669
        if (b.is_load) {
64,564✔
670
            do_load(b, *preg);
27,108✔
671
        } else {
672
            const auto data_reg = reg_pack(*preg);
37,456✔
673
            do_mem_store(b, data_reg.type, data_reg.svalue, data_reg.uvalue, data_reg);
37,456✔
674
        }
675
    } else {
676
        const uint64_t imm = std::get<Imm>(b.value).v;
36✔
677
        do_mem_store(b, T_NUM, to_signed(imm), imm, {});
36✔
678
    }
679
}
680

681
void ebpf_transformer::do_mem_store(const Mem& b, const linear_expression_t& val_type,
37,492✔
682
                                    const linear_expression_t& val_svalue, const linear_expression_t& val_uvalue,
683
                                    const std::optional<reg_pack_t>& opt_val_reg) {
684
    if (m_inv.is_bottom()) {
37,492✔
685
        return;
31,808✔
686
    }
687
    const int width = b.access.width;
37,492✔
688
    const number_t offset{b.access.offset};
37,492✔
689
    if (b.access.basereg.v == R10_STACK_POINTER) {
37,492✔
690
        const auto r10_stack_offset = reg_pack(b.access.basereg).stack_offset;
31,808✔
691
        const auto r10_interval = m_inv.eval_interval(r10_stack_offset);
31,808✔
692
        if (r10_interval.is_singleton()) {
31,808✔
693
            const int32_t stack_offset = r10_interval.singleton()->cast_to<int32_t>();
31,524✔
694
            const number_t base_addr{stack_offset};
31,524✔
695
            do_store_stack(m_inv, base_addr + offset, width, val_type, val_svalue, val_uvalue, opt_val_reg);
47,286✔
696
        }
31,524✔
697
        return;
31,808✔
698
    }
31,808✔
699
    m_inv = type_inv.join_over_types(m_inv, b.access.basereg, [&](NumAbsDomain& inv, const type_encoding_t type) {
11,368✔
700
        if (type == T_STACK) {
5,684✔
701
            const auto base_addr = linear_expression_t(dom.get_type_offset_variable(b.access.basereg, type).value());
219✔
702
            do_store_stack(inv, dsl_syntax::operator+(base_addr, offset), width, val_type, val_svalue, val_uvalue,
219✔
703
                           opt_val_reg);
704
        }
146✔
705
        // do nothing for any other type
706
    });
11,368✔
707
}
37,492✔
708

709
// Construct a Bin operation that does the main operation that a given Atomic operation does atomically.
710
static Bin atomic_to_bin(const Atomic& a) {
84✔
711
    Bin bin{.dst = Reg{R11_ATOMIC_SCRATCH}, .v = a.valreg, .is64 = a.access.width == sizeof(uint64_t), .lddw = false};
84✔
712
    switch (a.op) {
84✔
713
    case Atomic::Op::ADD: bin.op = Bin::Op::ADD; break;
16✔
714
    case Atomic::Op::OR: bin.op = Bin::Op::OR; break;
16✔
715
    case Atomic::Op::AND: bin.op = Bin::Op::AND; break;
16✔
716
    case Atomic::Op::XOR: bin.op = Bin::Op::XOR; break;
16✔
717
    case Atomic::Op::XCHG:
10✔
718
    case Atomic::Op::CMPXCHG: bin.op = Bin::Op::MOV; break;
10✔
719
    default: throw std::exception();
×
720
    }
721
    return bin;
84✔
722
}
723

724
void ebpf_transformer::operator()(const Atomic& a) {
414✔
725
    if (m_inv.is_bottom()) {
414✔
726
        return;
330✔
727
    }
728
    if (!m_inv.entail(type_is_pointer(reg_pack(a.access.basereg))) ||
1,447✔
729
        !m_inv.entail(type_is_number(reg_pack(a.valreg)))) {
1,025✔
730
        return;
8✔
731
    }
732
    if (m_inv.entail(type_is_not_stack(reg_pack(a.access.basereg)))) {
597✔
733
        // Shared memory regions are volatile so we can just havoc
734
        // any register that will be updated.
735
        if (a.op == Atomic::Op::CMPXCHG) {
314✔
736
            havoc_register(m_inv, Reg{R0_RETURN_VALUE});
4✔
737
        } else if (a.fetch) {
310✔
738
            havoc_register(m_inv, a.valreg);
20✔
739
        }
740
        return;
314✔
741
    }
742

743
    // Fetch the current value into the R11 pseudo-register.
744
    constexpr Reg r11{R11_ATOMIC_SCRATCH};
84✔
745
    (*this)(Mem{.access = a.access, .value = r11, .is_load = true});
84✔
746

747
    // Compute the new value in R11.
748
    (*this)(atomic_to_bin(a));
84✔
749

750
    if (a.op == Atomic::Op::CMPXCHG) {
84✔
751
        // For CMPXCHG, store the original value in r0.
752
        (*this)(Mem{.access = a.access, .value = Reg{R0_RETURN_VALUE}, .is_load = true});
12✔
753

754
        // For the destination, there are 3 possibilities:
755
        // 1) dst.value == r0.value : set R11 to valreg
756
        // 2) dst.value != r0.value : don't modify R11
757
        // 3) dst.value may or may not == r0.value : set R11 to the union of R11 and valreg
758
        // For now we just havoc the value of R11.
759
        havoc_register(m_inv, r11);
12✔
760
    } else if (a.fetch) {
72✔
761
        // For other FETCH operations, store the original value in the src register.
762
        (*this)(Mem{.access = a.access, .value = a.valreg, .is_load = true});
40✔
763
    }
764

765
    // Store the new value back in the original shared memory location.
766
    // Note that do_mem_store() currently doesn't track shared memory values,
767
    // but stack memory values are tracked and are legal here.
768
    (*this)(Mem{.access = a.access, .value = r11, .is_load = false});
84✔
769

770
    // Clear the R11 pseudo-register.
771
    havoc_register(m_inv, r11);
84✔
772
    type_inv.havoc_type(m_inv, r11);
84✔
773
}
774

775
void ebpf_transformer::operator()(const Call& call) {
19,792✔
776
    using namespace crab::dsl_syntax;
9,896✔
777
    if (m_inv.is_bottom()) {
19,792✔
778
        return;
×
779
    }
780
    std::optional<Reg> maybe_fd_reg{};
19,792✔
781
    for (ArgSingle param : call.singles) {
66,488✔
782
        switch (param.kind) {
46,696✔
783
        case ArgSingle::Kind::MAP_FD: maybe_fd_reg = param.reg; break;
35,462✔
784
        case ArgSingle::Kind::ANYTHING:
17,291✔
785
        case ArgSingle::Kind::MAP_FD_PROGRAMS:
786
        case ArgSingle::Kind::PTR_TO_MAP_KEY:
787
        case ArgSingle::Kind::PTR_TO_MAP_VALUE:
788
        case ArgSingle::Kind::PTR_TO_CTX:
789
            // Do nothing. We don't track the content of relevant memory regions
790
            break;
17,291✔
791
        }
792
    }
793
    for (ArgPair param : call.pairs) {
24,112✔
794
        switch (param.kind) {
4,320✔
795
        case ArgPair::Kind::PTR_TO_READABLE_MEM_OR_NULL:
1,729✔
796
        case ArgPair::Kind::PTR_TO_READABLE_MEM:
797
            // Do nothing. No side effect allowed.
798
            break;
1,729✔
799

800
        case ArgPair::Kind::PTR_TO_WRITABLE_MEM: {
862✔
801
            bool store_numbers = true;
862✔
802
            auto variable = dom.get_type_offset_variable(param.mem);
862✔
803
            if (!variable.has_value()) {
862✔
804
                // checked by the checker
805
                break;
806
            }
807
            variable_t addr = variable.value();
862✔
808
            variable_t width = reg_pack(param.size).svalue;
862✔
809

810
            m_inv = type_inv.join_over_types(m_inv, param.mem, [&](NumAbsDomain& inv, const type_encoding_t type) {
1,724✔
811
                if (type == T_STACK) {
862✔
812
                    // Pointer to a memory region that the called function may change,
813
                    // so we must havoc.
814
                    stack.havoc(inv, data_kind_t::types, addr, width);
850✔
815
                    stack.havoc(inv, data_kind_t::svalues, addr, width);
850✔
816
                    stack.havoc(inv, data_kind_t::uvalues, addr, width);
850✔
817
                    stack.havoc(inv, data_kind_t::ctx_offsets, addr, width);
850✔
818
                    stack.havoc(inv, data_kind_t::map_fds, addr, width);
850✔
819
                    stack.havoc(inv, data_kind_t::packet_offsets, addr, width);
850✔
820
                    stack.havoc(inv, data_kind_t::shared_offsets, addr, width);
850✔
821
                    stack.havoc(inv, data_kind_t::stack_offsets, addr, width);
850✔
822
                    stack.havoc(inv, data_kind_t::shared_region_sizes, addr, width);
850✔
823
                } else {
824
                    store_numbers = false;
12✔
825
                }
826
            });
1,724✔
827
            if (store_numbers) {
862✔
828
                // Functions are not allowed to write sensitive data,
829
                // and initialization is guaranteed
830
                stack.store_numbers(m_inv, addr, width);
850✔
831
            }
832
        }
833
        }
834
    }
835

836
    constexpr Reg r0_reg{R0_RETURN_VALUE};
19,792✔
837
    const auto r0_pack = reg_pack(r0_reg);
19,792✔
838
    m_inv.havoc(r0_pack.stack_numeric_size);
19,792✔
839
    if (call.is_map_lookup) {
19,792✔
840
        // This is the only way to get a null pointer
841
        if (maybe_fd_reg) {
4,228✔
842
            if (const auto map_type = dom.get_map_type(*maybe_fd_reg)) {
4,228✔
843
                if (thread_local_program_info->platform->get_map_type(*map_type).value_type == EbpfMapValueType::MAP) {
4,222✔
844
                    if (const auto inner_map_fd = dom.get_map_inner_map_fd(*maybe_fd_reg)) {
34✔
845
                        do_load_mapfd(r0_reg, to_signed(*inner_map_fd), true);
34✔
846
                        goto out;
34✔
847
                    }
848
                } else {
849
                    assign_valid_ptr(r0_reg, true);
4,188✔
850
                    m_inv.assign(r0_pack.shared_offset, 0);
4,188✔
851
                    m_inv.set(r0_pack.shared_region_size, dom.get_map_value_size(*maybe_fd_reg));
4,188✔
852
                    type_inv.assign_type(m_inv, r0_reg, T_SHARED);
6,282✔
853
                }
854
            }
855
        }
856
        assign_valid_ptr(r0_reg, true);
4,192✔
857
        m_inv.assign(r0_pack.shared_offset, 0);
4,192✔
858
        type_inv.assign_type(m_inv, r0_reg, T_SHARED);
6,288✔
859
    } else {
860
        m_inv.havoc(r0_pack.svalue);
15,564✔
861
        m_inv.havoc(r0_pack.uvalue);
15,564✔
862
        havoc_offsets(r0_reg);
15,564✔
863
        type_inv.assign_type(m_inv, r0_reg, T_NUM);
23,346✔
864
        // m_inv.add_constraint(r0_pack.value < 0); for INTEGER_OR_NO_RETURN_IF_SUCCEED.
865
    }
866
out:
19,790✔
867
    scratch_caller_saved_registers();
19,790✔
868
    if (call.reallocate_packet) {
19,790✔
869
        forget_packet_pointers();
42✔
870
    }
871
}
872

873
void ebpf_transformer::operator()(const CallLocal& call) {
58✔
874
    using namespace crab::dsl_syntax;
29✔
875
    if (m_inv.is_bottom()) {
58✔
876
        return;
×
877
    }
878
    save_callee_saved_registers(call.stack_frame_prefix);
58✔
879

880
    // Update r10.
881
    constexpr Reg r10_reg{R10_STACK_POINTER};
58✔
882
    add(r10_reg, -EBPF_SUBPROGRAM_STACK_SIZE, 64);
58✔
883
}
884

885
void ebpf_transformer::operator()(const Callx& callx) {
50✔
886
    using namespace crab::dsl_syntax;
25✔
887
    if (m_inv.is_bottom()) {
50✔
888
        return;
4✔
889
    }
890

891
    // Look up the helper function id.
892
    const reg_pack_t& reg = reg_pack(callx.func);
50✔
893
    const auto src_interval = m_inv.eval_interval(reg.svalue);
50✔
894
    if (const auto sn = src_interval.singleton()) {
50✔
895
        if (sn->fits<int32_t>()) {
48✔
896
            // We can now process it as if the id was immediate.
897
            const int32_t imm = sn->cast_to<int32_t>();
48✔
898
            if (!thread_local_program_info->platform->is_helper_usable(imm)) {
48✔
899
                return;
4✔
900
            }
901
            const Call call = make_call(imm, *thread_local_program_info->platform);
44✔
902
            (*this)(call);
44✔
903
        }
44✔
904
    }
50✔
905
}
50✔
906

907
void ebpf_transformer::do_load_mapfd(const Reg& dst_reg, const int mapfd, const bool maybe_null) {
12,518✔
908
    const EbpfMapDescriptor& desc = thread_local_program_info->platform->get_map_descriptor(mapfd);
12,518✔
909
    const EbpfMapType& type = thread_local_program_info->platform->get_map_type(desc.type);
12,518✔
910
    if (type.value_type == EbpfMapValueType::PROGRAM) {
12,518✔
911
        type_inv.assign_type(m_inv, dst_reg, T_MAP_PROGRAMS);
621✔
912
    } else {
913
        type_inv.assign_type(m_inv, dst_reg, T_MAP);
18,156✔
914
    }
915
    const reg_pack_t& dst = reg_pack(dst_reg);
12,518✔
916
    m_inv.assign(dst.map_fd, mapfd);
12,518✔
917
    assign_valid_ptr(dst_reg, maybe_null);
12,518✔
918
}
12,518✔
919

920
void ebpf_transformer::operator()(const LoadMapFd& ins) {
12,484✔
921
    if (m_inv.is_bottom()) {
12,484✔
922
        return;
923
    }
924
    do_load_mapfd(ins.dst, ins.mapfd, false);
12,484✔
925
}
926

927
void ebpf_transformer::do_load_map_address(const Reg& dst_reg, const int mapfd, const int32_t offset) {
10✔
928
    const EbpfMapDescriptor& desc = thread_local_program_info->platform->get_map_descriptor(mapfd);
10✔
929
    const EbpfMapType& type = thread_local_program_info->platform->get_map_type(desc.type);
10✔
930

931
    if (type.value_type == EbpfMapValueType::PROGRAM) {
10✔
932
        throw std::invalid_argument("Cannot load address of program map type - only data maps are supported");
×
933
    }
934

935
    // Set the shared region size and offset for the map.
936
    type_inv.assign_type(m_inv, dst_reg, T_SHARED);
10✔
937
    const reg_pack_t& dst = reg_pack(dst_reg);
10✔
938
    m_inv.assign(dst.shared_offset, offset);
10✔
939
    m_inv.assign(dst.shared_region_size, desc.value_size);
10✔
940
    assign_valid_ptr(dst_reg, false);
10✔
941
}
10✔
942

943
void ebpf_transformer::operator()(const LoadMapAddress& ins) {
10✔
944
    if (m_inv.is_bottom()) {
10✔
945
        return;
946
    }
947
    do_load_map_address(ins.dst, ins.mapfd, ins.offset);
10✔
948
}
949

950
void ebpf_transformer::assign_valid_ptr(const Reg& dst_reg, const bool maybe_null) {
20,908✔
951
    using namespace crab::dsl_syntax;
10,454✔
952
    const reg_pack_t& reg = reg_pack(dst_reg);
20,908✔
953
    m_inv.havoc(reg.svalue);
20,908✔
954
    m_inv.havoc(reg.uvalue);
20,908✔
955
    if (maybe_null) {
20,908✔
956
        m_inv.add_constraint(0 <= reg.svalue);
16,828✔
957
    } else {
958
        m_inv.add_constraint(0 < reg.svalue);
24,988✔
959
    }
960
    m_inv.add_constraint(reg.svalue <= PTR_MAX);
31,362✔
961
    m_inv.assign(reg.uvalue, reg.svalue);
20,908✔
962
}
20,908✔
963

964
// If nothing is known of the stack_numeric_size,
965
// try to recompute the stack_numeric_size.
966
void ebpf_transformer::recompute_stack_numeric_size(NumAbsDomain& inv, const variable_t type_variable) const {
847,203✔
967
    const variable_t stack_numeric_size_variable =
423,602✔
968
        variable_t::kind_var(data_kind_t::stack_numeric_sizes, type_variable);
847,203✔
969

970
    if (!inv.eval_interval(stack_numeric_size_variable).is_top()) {
1,694,407✔
971
        return;
×
972
    }
973

974
    if (type_inv.has_type(inv, type_variable, T_STACK)) {
847,203✔
975
        const int numeric_size =
423,078✔
976
            stack.min_all_num_size(inv, variable_t::kind_var(data_kind_t::stack_offsets, type_variable));
846,155✔
977
        if (numeric_size > 0) {
846,155✔
978
            inv.assign(stack_numeric_size_variable, numeric_size);
28,614✔
979
        }
980
    }
981
}
982

983
void ebpf_transformer::recompute_stack_numeric_size(NumAbsDomain& inv, const Reg& reg) const {
20,162✔
984
    recompute_stack_numeric_size(inv, reg_pack(reg).type);
20,162✔
985
}
20,162✔
986

987
void ebpf_transformer::add(const Reg& dst_reg, const int imm, const int finite_width) {
25,066✔
988
    const auto dst = reg_pack(dst_reg);
25,066✔
989
    m_inv->add_overflow(dst.svalue, dst.uvalue, imm, finite_width);
25,066✔
990
    if (const auto offset = dom.get_type_offset_variable(dst_reg)) {
25,066✔
991
        m_inv->add(*offset, imm);
20,162✔
992
        if (imm > 0) {
20,162✔
993
            // Since the start offset is increasing but
994
            // the end offset is not, the numeric size decreases.
995
            m_inv->sub(dst.stack_numeric_size, imm);
1,557✔
996
        } else if (imm < 0) {
19,124✔
997
            m_inv.havoc(dst.stack_numeric_size);
19,124✔
998
        }
999
        recompute_stack_numeric_size(dom.m_inv, dst_reg);
20,162✔
1000
    }
1001
}
25,066✔
1002

1003
void ebpf_transformer::shl(const Reg& dst_reg, int imm, const int finite_width) {
5,178✔
1004
    havoc_offsets(dst_reg);
5,178✔
1005

1006
    // The BPF ISA requires masking the imm.
1007
    imm &= finite_width - 1;
5,178✔
1008
    const reg_pack_t dst = reg_pack(dst_reg);
5,178✔
1009
    if (m_inv.entail(type_is_number(dst))) {
7,767✔
1010
        m_inv->shl(dst.svalue, dst.uvalue, imm, finite_width);
5,176✔
1011
    } else {
1012
        m_inv.havoc(dst.svalue);
2✔
1013
        m_inv.havoc(dst.uvalue);
2✔
1014
    }
1015
}
5,178✔
1016

1017
void ebpf_transformer::lshr(const Reg& dst_reg, int imm, int finite_width) {
4,870✔
1018
    havoc_offsets(dst_reg);
4,870✔
1019

1020
    // The BPF ISA requires masking the imm.
1021
    imm &= finite_width - 1;
4,870✔
1022
    const reg_pack_t dst = reg_pack(dst_reg);
4,870✔
1023
    if (m_inv.entail(type_is_number(dst))) {
7,305✔
1024
        m_inv->lshr(dst.svalue, dst.uvalue, imm, finite_width);
4,868✔
1025
    } else {
1026
        m_inv.havoc(dst.svalue);
2✔
1027
        m_inv.havoc(dst.uvalue);
2✔
1028
    }
1029
}
4,870✔
1030

1031
void ebpf_transformer::ashr(const Reg& dst_reg, const linear_expression_t& right_svalue, const int finite_width) {
694✔
1032
    havoc_offsets(dst_reg);
694✔
1033

1034
    const reg_pack_t dst = reg_pack(dst_reg);
694✔
1035
    if (m_inv.entail(type_is_number(dst))) {
1,041✔
1036
        m_inv->ashr(dst.svalue, dst.uvalue, right_svalue, finite_width);
692✔
1037
    } else {
1038
        m_inv.havoc(dst.svalue);
2✔
1039
        m_inv.havoc(dst.uvalue);
2✔
1040
    }
1041
}
694✔
1042

1043
void ebpf_transformer::sign_extend(const Reg& dst_reg, const linear_expression_t& right_svalue, const int target_width,
2,234✔
1044
                                   const int source_width) {
1045
    havoc_offsets(dst_reg);
2,234✔
1046

1047
    type_inv.assign_type(dom.m_inv, dst_reg, T_NUM);
2,234✔
1048

1049
    const reg_pack_t dst = reg_pack(dst_reg);
2,234✔
1050
    m_inv->sign_extend(dst.svalue, dst.uvalue, right_svalue, target_width, source_width);
2,234✔
1051
}
2,234✔
1052

1053
static int _movsx_bits(const Bin::Op op) {
2,238✔
1054
    switch (op) {
2,238✔
1055
    case Bin::Op::MOVSX8: return 8;
8✔
1056
    case Bin::Op::MOVSX16: return 16;
8✔
1057
    case Bin::Op::MOVSX32: return 32;
1,103✔
NEW
1058
    default: throw std::exception();
×
1059
    }
1060
}
1061

1062
void ebpf_transformer::operator()(const Bin& bin) {
152,564✔
1063
    if (m_inv.is_bottom()) {
152,564✔
1064
        return;
2,960✔
1065
    }
1066
    using namespace crab::dsl_syntax;
76,282✔
1067

1068
    auto dst = reg_pack(bin.dst);
152,564✔
1069
    int finite_width = bin.is64 ? 64 : 32;
152,564✔
1070

1071
    // TODO: Unusable states and values should be better handled.
1072
    //       Probably by propagating an error state.
1073
    if (type_inv.has_type(m_inv, bin.dst, T_UNINIT) &&
203,424✔
1074
        !std::set{Bin::Op::MOV, Bin::Op::MOVSX8, Bin::Op::MOVSX16, Bin::Op::MOVSX32}.contains(bin.op)) {
278,806✔
1075
        havoc_register(m_inv, bin.dst);
1,816✔
1076
        type_inv.havoc_type(m_inv, bin.dst);
1,816✔
1077
        return;
1,816✔
1078
    }
1079
    if (auto pimm = std::get_if<Imm>(&bin.v)) {
150,748✔
1080
        // dst += K
1081
        int64_t imm;
43,543✔
1082
        if (bin.is64) {
87,086✔
1083
            // Use the full signed value.
1084
            imm = to_signed(pimm->v);
63,822✔
1085
        } else {
1086
            // Use only the low 32 bits of the value.
1087
            imm = gsl::narrow_cast<int32_t>(pimm->v);
23,264✔
1088
            m_inv->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
23,264✔
1089
            // If this is a 32-bit operation and the destination is not a number, forget everything about the register.
1090
            if (!type_inv.has_type(m_inv, bin.dst, T_NUM)) {
23,264✔
1091
                havoc_register(m_inv, bin.dst);
742✔
1092
                havoc_offsets(bin.dst);
742✔
1093
                m_inv.havoc(dst.type);
742✔
1094
            }
1095
        }
1096
        switch (bin.op) {
87,086✔
1097
        case Bin::Op::MOV:
39,342✔
1098
            m_inv.assign(dst.svalue, imm);
39,342✔
1099
            m_inv.assign(dst.uvalue, imm);
39,342✔
1100
            m_inv->overflow_bounds(dst.uvalue, bin.is64 ? 64 : 32, false);
44,674✔
1101
            type_inv.assign_type(m_inv, bin.dst, T_NUM);
39,342✔
1102
            havoc_offsets(bin.dst);
39,342✔
1103
            break;
19,671✔
1104
        case Bin::Op::MOVSX8:
×
1105
        case Bin::Op::MOVSX16:
1106
        case Bin::Op::MOVSX32: CRAB_ERROR("Unsupported operation");
×
1107
        case Bin::Op::ADD:
24,928✔
1108
            if (imm == 0) {
24,928✔
1109
                return;
1,483✔
1110
            }
1111
            add(bin.dst, gsl::narrow<int>(imm), finite_width);
24,924✔
1112
            break;
12,462✔
1113
        case Bin::Op::SUB:
28✔
1114
            if (imm == 0) {
28✔
1115
                return;
1✔
1116
            }
1117
            add(bin.dst, gsl::narrow<int>(-imm), finite_width);
26✔
1118
            break;
13✔
1119
        case Bin::Op::MUL:
102✔
1120
            m_inv->mul(dst.svalue, dst.uvalue, imm, finite_width);
102✔
1121
            havoc_offsets(bin.dst);
102✔
1122
            break;
51✔
1123
        case Bin::Op::UDIV:
2,182✔
1124
            m_inv->udiv(dst.svalue, dst.uvalue, imm, finite_width);
2,182✔
1125
            havoc_offsets(bin.dst);
2,182✔
1126
            break;
1,091✔
1127
        case Bin::Op::UMOD:
24✔
1128
            m_inv->urem(dst.svalue, dst.uvalue, imm, finite_width);
24✔
1129
            havoc_offsets(bin.dst);
24✔
1130
            break;
12✔
1131
        case Bin::Op::SDIV:
18✔
1132
            m_inv->sdiv(dst.svalue, dst.uvalue, imm, finite_width);
18✔
1133
            havoc_offsets(bin.dst);
18✔
1134
            break;
9✔
1135
        case Bin::Op::SMOD:
34✔
1136
            m_inv->srem(dst.svalue, dst.uvalue, imm, finite_width);
34✔
1137
            havoc_offsets(bin.dst);
34✔
1138
            break;
17✔
1139
        case Bin::Op::OR:
2,650✔
1140
            m_inv->bitwise_or(dst.svalue, dst.uvalue, imm);
2,650✔
1141
            havoc_offsets(bin.dst);
2,650✔
1142
            break;
1,325✔
1143
        case Bin::Op::AND:
6,782✔
1144
            // FIX: what to do with ptr&-8 as in counter/simple_loop_unrolled?
1145
            m_inv->bitwise_and(dst.svalue, dst.uvalue, imm);
6,782✔
1146
            if (gsl::narrow<int32_t>(imm) > 0) {
6,782✔
1147
                // AND with immediate is only a 32-bit operation so svalue and uvalue are the same.
1148
                m_inv.add_constraint(dst.svalue <= imm);
9,369✔
1149
                m_inv.add_constraint(dst.uvalue <= imm);
9,369✔
1150
                m_inv.add_constraint(0 <= dst.svalue);
12,492✔
1151
                m_inv.add_constraint(0 <= dst.uvalue);
12,492✔
1152
            }
1153
            havoc_offsets(bin.dst);
6,782✔
1154
            break;
3,391✔
1155
        case Bin::Op::LSH: shl(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
5,132✔
1156
        case Bin::Op::RSH: lshr(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
4,836✔
1157
        case Bin::Op::ARSH: ashr(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
660✔
1158
        case Bin::Op::XOR:
368✔
1159
            m_inv->bitwise_xor(dst.svalue, dst.uvalue, imm);
368✔
1160
            havoc_offsets(bin.dst);
368✔
1161
            break;
184✔
1162
        }
1163
    } else {
1164
        // dst op= src
1165
        auto src_reg = std::get<Reg>(bin.v);
63,662✔
1166
        auto src = reg_pack(src_reg);
63,662✔
1167
        if (type_inv.has_type(m_inv, src_reg, T_UNINIT)) {
63,662✔
1168
            havoc_register(m_inv, bin.dst);
978✔
1169
            type_inv.havoc_type(m_inv, bin.dst);
978✔
1170
            return;
1,627✔
1171
        }
1172
        switch (bin.op) {
62,684✔
1173
        case Bin::Op::ADD: {
4,294✔
1174
            if (type_inv.same_type(m_inv, bin.dst, std::get<Reg>(bin.v))) {
4,294✔
1175
                // both must have been checked to be numbers
1176
                m_inv->add_overflow(dst.svalue, dst.uvalue, src.svalue, finite_width);
3,622✔
1177
            } else {
1178
                // Here we're not sure that lhs and rhs are the same type; they might be.
1179
                // But previous assertions should fail unless we know that exactly one of lhs or rhs is a pointer.
1180
                m_inv =
672✔
1181
                    type_inv.join_over_types(m_inv, bin.dst, [&](NumAbsDomain& inv, const type_encoding_t dst_type) {
1,344✔
1182
                        inv = type_inv.join_over_types(
1,352✔
1183
                            inv, src_reg, [&](NumAbsDomain& inv, const type_encoding_t src_type) {
676✔
1184
                                if (dst_type == T_NUM && src_type != T_NUM) {
682✔
1185
                                    // num += ptr
1186
                                    type_inv.assign_type(inv, bin.dst, src_type);
10✔
1187
                                    if (const auto dst_offset = dom.get_type_offset_variable(bin.dst, src_type)) {
10✔
1188
                                        inv->apply(arith_binop_t::ADD, dst_offset.value(), dst.svalue,
15✔
1189
                                                   dom.get_type_offset_variable(src_reg, src_type).value());
20✔
1190
                                    }
1191
                                    if (src_type == T_SHARED) {
10✔
1192
                                        inv.assign(dst.shared_region_size, src.shared_region_size);
×
1193
                                    }
1194
                                } else if (dst_type != T_NUM && src_type == T_NUM) {
677✔
1195
                                    // ptr += num
1196
                                    type_inv.assign_type(inv, bin.dst, dst_type);
668✔
1197
                                    if (const auto dst_offset = dom.get_type_offset_variable(bin.dst, dst_type)) {
668✔
1198
                                        inv->apply(arith_binop_t::ADD, dst_offset.value(), dst_offset.value(),
1,002✔
1199
                                                   src.svalue);
668✔
1200
                                        if (dst_type == T_STACK) {
668✔
1201
                                            // Reduce the numeric size.
1202
                                            using namespace crab::dsl_syntax;
8✔
1203
                                            if (inv.intersect(src.svalue < 0)) {
24✔
1204
                                                inv.havoc(dst.stack_numeric_size);
2✔
1205
                                                recompute_stack_numeric_size(inv, dst.type);
2✔
1206
                                            } else {
1207
                                                inv->apply_signed(arith_binop_t::SUB, dst.stack_numeric_size,
28✔
1208
                                                                  dst.stack_numeric_size, dst.stack_numeric_size,
14✔
1209
                                                                  src.svalue, 0);
14✔
1210
                                            }
1211
                                        }
1212
                                    }
1213
                                } else if (dst_type == T_NUM && src_type == T_NUM) {
672✔
1214
                                    // dst and src don't necessarily have the same type, but among the possibilities
1215
                                    // enumerated is the case where they are both numbers.
1216
                                    inv->apply_signed(arith_binop_t::ADD, dst.svalue, dst.uvalue, dst.svalue,
4✔
1217
                                                      src.svalue, finite_width);
2✔
1218
                                } else {
1219
                                    // We ignore the cases here that do not match the assumption described
1220
                                    // above.  Joining bottom with another results will leave the other
1221
                                    // results unchanged.
1222
                                    inv.set_to_bottom();
2✔
1223
                                }
1224
                            });
1,358✔
1225
                    });
1,348✔
1226
                // careful: change dst.value only after dealing with offset
1227
                m_inv->apply_signed(arith_binop_t::ADD, dst.svalue, dst.uvalue, dst.svalue, src.svalue, finite_width);
672✔
1228
            }
1229
            break;
2,147✔
1230
        }
1231
        case Bin::Op::SUB: {
778✔
1232
            if (type_inv.same_type(m_inv, bin.dst, std::get<Reg>(bin.v))) {
778✔
1233
                // src and dest have the same type.
1234
                m_inv = type_inv.join_over_types(m_inv, bin.dst, [&](NumAbsDomain& inv, const type_encoding_t type) {
1,540✔
1235
                    switch (type) {
774✔
1236
                    case T_NUM:
504✔
1237
                        // This is: sub_overflow(inv, dst.value, src.value, finite_width);
1238
                        inv->apply_signed(arith_binop_t::SUB, dst.svalue, dst.uvalue, dst.svalue, src.svalue,
504✔
1239
                                          finite_width);
1240
                        type_inv.assign_type(inv, bin.dst, T_NUM);
504✔
1241
                        crab::havoc_offsets(inv, bin.dst);
504✔
1242
                        break;
504✔
1243
                    default:
270✔
1244
                        // ptr -= ptr
1245
                        // Assertions should make sure we only perform this on non-shared pointers.
1246
                        if (const auto dst_offset = dom.get_type_offset_variable(bin.dst, type)) {
270✔
1247
                            inv->apply_signed(arith_binop_t::SUB, dst.svalue, dst.uvalue, dst_offset.value(),
540✔
1248
                                              dom.get_type_offset_variable(src_reg, type).value(), finite_width);
405✔
1249
                            inv.havoc(dst_offset.value());
270✔
1250
                        }
1251
                        crab::havoc_offsets(inv, bin.dst);
270✔
1252
                        type_inv.assign_type(inv, bin.dst, T_NUM);
270✔
1253
                        break;
270✔
1254
                    }
1255
                });
1,544✔
1256
            } else {
1257
                // We're not sure that lhs and rhs are the same type.
1258
                // Either they're different, or at least one is not a singleton.
1259
                if (type_inv.get_type(m_inv, std::get<Reg>(bin.v)) != T_NUM) {
8✔
1260
                    type_inv.havoc_type(m_inv, bin.dst);
4✔
1261
                    m_inv.havoc(dst.svalue);
4✔
1262
                    m_inv.havoc(dst.uvalue);
4✔
1263
                    havoc_offsets(bin.dst);
4✔
1264
                } else {
1265
                    m_inv->sub_overflow(dst.svalue, dst.uvalue, src.svalue, finite_width);
4✔
1266
                    if (auto dst_offset = dom.get_type_offset_variable(bin.dst)) {
4✔
1267
                        m_inv->sub(dst_offset.value(), src.svalue);
4✔
1268
                        if (type_inv.has_type(m_inv, dst.type, T_STACK)) {
4✔
1269
                            // Reduce the numeric size.
1270
                            using namespace crab::dsl_syntax;
2✔
1271
                            if (m_inv.intersect(src.svalue > 0)) {
8✔
1272
                                m_inv.havoc(dst.stack_numeric_size);
4✔
1273
                                recompute_stack_numeric_size(m_inv, dst.type);
4✔
1274
                            } else {
NEW
1275
                                m_inv->apply(arith_binop_t::ADD, dst.stack_numeric_size, dst.stack_numeric_size,
×
1276
                                             src.svalue);
1277
                            }
1278
                        }
1279
                    }
1280
                }
1281
            }
1282
            break;
389✔
1283
        }
1284
        case Bin::Op::MUL:
72✔
1285
            m_inv->mul(dst.svalue, dst.uvalue, src.svalue, finite_width);
72✔
1286
            havoc_offsets(bin.dst);
72✔
1287
            break;
36✔
1288
        case Bin::Op::UDIV:
104✔
1289
            m_inv->udiv(dst.svalue, dst.uvalue, src.uvalue, finite_width);
104✔
1290
            havoc_offsets(bin.dst);
104✔
1291
            break;
52✔
1292
        case Bin::Op::UMOD:
34✔
1293
            m_inv->urem(dst.svalue, dst.uvalue, src.uvalue, finite_width);
34✔
1294
            havoc_offsets(bin.dst);
34✔
1295
            break;
17✔
1296
        case Bin::Op::SDIV:
32✔
1297
            m_inv->sdiv(dst.svalue, dst.uvalue, src.svalue, finite_width);
32✔
1298
            havoc_offsets(bin.dst);
32✔
1299
            break;
16✔
1300
        case Bin::Op::SMOD:
46✔
1301
            m_inv->srem(dst.svalue, dst.uvalue, src.svalue, finite_width);
46✔
1302
            havoc_offsets(bin.dst);
46✔
1303
            break;
23✔
1304
        case Bin::Op::OR:
5,694✔
1305
            m_inv->bitwise_or(dst.svalue, dst.uvalue, src.uvalue, finite_width);
5,694✔
1306
            havoc_offsets(bin.dst);
5,694✔
1307
            break;
2,847✔
1308
        case Bin::Op::AND:
476✔
1309
            m_inv->bitwise_and(dst.svalue, dst.uvalue, src.uvalue, finite_width);
476✔
1310
            havoc_offsets(bin.dst);
476✔
1311
            break;
238✔
1312
        case Bin::Op::LSH:
588✔
1313
            if (m_inv.entail(type_is_number(src_reg))) {
882✔
1314
                auto src_interval = m_inv.eval_interval(src.uvalue);
588✔
1315
                if (std::optional<number_t> sn = src_interval.singleton()) {
588✔
1316
                    // truncate to uint64?
1317
                    uint64_t imm = sn->cast_to<uint64_t>() & (bin.is64 ? 63 : 31);
46✔
1318
                    if (imm <= std::numeric_limits<int32_t>::max()) {
46✔
1319
                        if (!bin.is64) {
46✔
1320
                            // Use only the low 32 bits of the value.
1321
                            m_inv->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
39✔
1322
                        }
1323
                        shl(bin.dst, gsl::narrow_cast<int32_t>(imm), finite_width);
46✔
1324
                        break;
46✔
1325
                    }
1326
                }
588✔
1327
            }
588✔
1328
            m_inv->shl_overflow(dst.svalue, dst.uvalue, src.uvalue);
542✔
1329
            havoc_offsets(bin.dst);
542✔
1330
            break;
271✔
1331
        case Bin::Op::RSH:
78✔
1332
            if (m_inv.entail(type_is_number(src_reg))) {
117✔
1333
                auto src_interval = m_inv.eval_interval(src.uvalue);
78✔
1334
                if (std::optional<number_t> sn = src_interval.singleton()) {
78✔
1335
                    uint64_t imm = sn->cast_to<uint64_t>() & (bin.is64 ? 63 : 31);
34✔
1336
                    if (imm <= std::numeric_limits<int32_t>::max()) {
34✔
1337
                        if (!bin.is64) {
34✔
1338
                            // Use only the low 32 bits of the value.
1339
                            m_inv->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
24✔
1340
                        }
1341
                        lshr(bin.dst, gsl::narrow_cast<int32_t>(imm), finite_width);
34✔
1342
                        break;
34✔
1343
                    }
1344
                }
78✔
1345
            }
78✔
1346
            m_inv.havoc(dst.svalue);
44✔
1347
            m_inv.havoc(dst.uvalue);
44✔
1348
            havoc_offsets(bin.dst);
44✔
1349
            break;
22✔
1350
        case Bin::Op::ARSH:
34✔
1351
            if (m_inv.entail(type_is_number(src_reg))) {
51✔
1352
                ashr(bin.dst, src.svalue, finite_width);
34✔
1353
                break;
34✔
1354
            }
NEW
1355
            m_inv.havoc(dst.svalue);
×
NEW
1356
            m_inv.havoc(dst.uvalue);
×
UNCOV
1357
            havoc_offsets(bin.dst);
×
1358
            break;
1359
        case Bin::Op::XOR:
474✔
1360
            m_inv->bitwise_xor(dst.svalue, dst.uvalue, src.uvalue, finite_width);
474✔
1361
            havoc_offsets(bin.dst);
474✔
1362
            break;
237✔
1363
        case Bin::Op::MOVSX8:
2,238✔
1364
        case Bin::Op::MOVSX16:
1,119✔
1365
        case Bin::Op::MOVSX32: {
1,119✔
1366
            const int source_width = _movsx_bits(bin.op);
2,238✔
1367
            // Keep relational information if operation is a no-op.
1368
            if (dst.svalue == src.svalue && m_inv.eval_interval(dst.svalue) <= interval_t::signed_int(source_width)) {
4,436✔
1369
                return;
2✔
1370
            }
1371
            if (m_inv.entail(type_is_number(src_reg))) {
3,351✔
1372
                sign_extend(bin.dst, src.svalue, finite_width, source_width);
2,234✔
1373
                break;
2,234✔
1374
            }
NEW
1375
            m_inv.havoc(dst.svalue);
×
NEW
1376
            m_inv.havoc(dst.uvalue);
×
1377
            havoc_offsets(bin.dst);
31,262✔
1378
            break;
1379
        }
1380
        case Bin::Op::MOV:
47,742✔
1381
            // Keep relational information if operation is a no-op.
1382
            if (dst.svalue == src.svalue &&
48,488✔
1383
                m_inv.eval_interval(dst.uvalue) <= interval_t::unsigned_int(bin.is64 ? 64 : 32)) {
50,721✔
1384
                return;
78✔
1385
            }
1386
            m_inv.assign(dst.svalue, src.svalue);
47,586✔
1387
            m_inv.assign(dst.uvalue, src.uvalue);
47,586✔
1388
            havoc_offsets(bin.dst);
47,586✔
1389
            m_inv = type_inv.join_over_types(m_inv, src_reg, [&](NumAbsDomain& inv, const type_encoding_t type) {
95,172✔
1390
                switch (type) {
47,608✔
1391
                case T_CTX:
5,066✔
1392
                    if (bin.is64) {
5,066✔
1393
                        inv.assign(dst.type, type);
5,062✔
1394
                        inv.assign(dst.ctx_offset, src.ctx_offset);
7,593✔
1395
                    }
1396
                    break;
2,533✔
1397
                case T_MAP:
302✔
1398
                case T_MAP_PROGRAMS:
151✔
1399
                    if (bin.is64) {
302✔
1400
                        inv.assign(dst.type, type);
298✔
1401
                        inv.assign(dst.map_fd, src.map_fd);
447✔
1402
                    }
1403
                    break;
151✔
1404
                case T_PACKET:
1,026✔
1405
                    if (bin.is64) {
1,026✔
1406
                        inv.assign(dst.type, type);
1,024✔
1407
                        inv.assign(dst.packet_offset, src.packet_offset);
1,536✔
1408
                    }
1409
                    break;
513✔
1410
                case T_SHARED:
908✔
1411
                    if (bin.is64) {
908✔
1412
                        inv.assign(dst.type, type);
906✔
1413
                        inv.assign(dst.shared_region_size, src.shared_region_size);
906✔
1414
                        inv.assign(dst.shared_offset, src.shared_offset);
1,359✔
1415
                    }
1416
                    break;
454✔
1417
                case T_STACK:
19,876✔
1418
                    if (bin.is64) {
19,876✔
1419
                        inv.assign(dst.type, type);
19,874✔
1420
                        inv.assign(dst.stack_offset, src.stack_offset);
19,874✔
1421
                        inv.assign(dst.stack_numeric_size, src.stack_numeric_size);
29,811✔
1422
                    }
1423
                    break;
9,938✔
1424
                default: inv.assign(dst.type, type); break;
30,645✔
1425
                }
1426
            });
47,586✔
1427
            if (bin.is64) {
47,586✔
1428
                // Add dst.type=src.type invariant.
1429
                if (bin.dst.v != std::get<Reg>(bin.v).v || type_inv.get_type(m_inv, dst.type) == T_UNINIT) {
36,521✔
1430
                    // Only forget the destination type if we're copying from a different register,
1431
                    // or from the same uninitialized register.
1432
                    m_inv.havoc(dst.type);
36,518✔
1433
                }
1434
                type_inv.assign_type(m_inv, bin.dst, std::get<Reg>(bin.v));
36,520✔
1435
            }
1436
            break;
23,793✔
1437
        }
1438
    }
1439
    if (!bin.is64) {
149,604✔
1440
        m_inv->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
58,686✔
1441
    }
1442
}
1443

1444
void ebpf_transformer::initialize_loop_counter(const label_t& label) {
32✔
1445
    m_inv.assign(variable_t::loop_counter(to_string(label)), 0);
48✔
1446
}
32✔
1447

1448
void ebpf_transformer::operator()(const IncrementLoopCounter& ins) {
164✔
1449
    if (m_inv.is_bottom()) {
164✔
NEW
1450
        return;
×
1451
    }
1452
    const auto counter = variable_t::loop_counter(to_string(ins.name));
164✔
1453
    m_inv->add(counter, 1);
246✔
1454
}
1455

1456
void ebpf_domain_initialize_loop_counter(ebpf_domain_t& dom, const label_t& label) {
32✔
1457
    ebpf_transformer{dom}.initialize_loop_counter(label);
32✔
1458
}
32✔
1459

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