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

vbpf / ebpf-verifier / 14231336081

02 Apr 2025 11:08PM UTC coverage: 87.272% (-0.9%) from 88.177%
14231336081

push

github

web-flow
Propogate ebpf_verifier_options_t to thread_local_options (#856)

Signed-off-by: Alan Jowett <alanjo@microsoft.com>

5 of 5 new or added lines in 2 files covered. (100.0%)

58 existing lines in 19 files now uncovered.

8324 of 9538 relevant lines covered (87.27%)

4881701.3 hits per line

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

95.5
/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)
169,566✔
34
        : dom(dom), m_inv(dom.m_inv), stack(dom.stack), type_inv(dom.type_inv) {}
169,566✔
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) {
170,812✔
92
    if (inv.is_bottom()) {
170,812✔
93
        return;
1,262✔
94
    }
95
    const auto pre = inv;
169,550✔
96
    std::visit(ebpf_transformer{inv}, ins);
169,550✔
97
    if (inv.is_bottom() && !std::holds_alternative<Assume>(ins)) {
169,549✔
98
        // Fail. raise an exception to stop the analysis.
99
        std::stringstream msg;
×
100
        msg << "Bug! pre-invariant:\n"
×
101
            << pre << "\n followed by instruction: " << ins << "\n"
×
102
            << "leads to bottom";
×
103
        throw std::logic_error(msg.str());
×
104
    }
×
105
}
169,549✔
106

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

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

125
void ebpf_transformer::scratch_caller_saved_registers() {
10,013✔
126
    for (int i = R1_ARG; i <= R5_ARG; i++) {
60,078✔
127
        Reg r{gsl::narrow<uint8_t>(i)};
50,065✔
128
        havoc_register(m_inv, r);
50,065✔
129
        type_inv.havoc_type(m_inv, r);
50,065✔
130
    }
131
}
10,013✔
132

133
void ebpf_transformer::save_callee_saved_registers(const std::string& prefix) {
29✔
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++) {
145✔
137
        for (const data_kind_t kind : iterate_kinds()) {
1,276✔
138
            const variable_t src_var = variable_t::reg(kind, r);
1,160✔
139
            if (!m_inv.eval_interval(src_var).is_top()) {
3,480✔
140
                m_inv.assign(variable_t::stack_frame_var(kind, r, prefix), src_var);
100✔
141
            }
142
        }
116✔
143
    }
144
}
29✔
145

146
void ebpf_transformer::restore_callee_saved_registers(const std::string& prefix) {
29✔
147
    for (int r = R6; r <= R9; r++) {
145✔
148
        for (const data_kind_t kind : iterate_kinds()) {
1,276✔
149
            const variable_t src_var = variable_t::stack_frame_var(kind, r, prefix);
1,160✔
150
            if (!m_inv.eval_interval(src_var).is_top()) {
3,480✔
151
                m_inv.assign(variable_t::reg(kind, r), src_var);
100✔
152
            } else {
153
                m_inv.havoc(variable_t::reg(kind, r));
1,110✔
154
            }
155
            m_inv.havoc(src_var);
1,160✔
156
        }
116✔
157
    }
158
}
29✔
159

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

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

175
    for (const variable_t type_variable : variable_t::get_type_variables()) {
761✔
176
        if (type_inv.has_type(m_inv, type_variable, T_PACKET)) {
740✔
177
            m_inv.havoc(variable_t::kind_var(data_kind_t::types, type_variable));
478✔
178
            m_inv.havoc(variable_t::kind_var(data_kind_t::packet_offsets, type_variable));
478✔
179
            m_inv.havoc(variable_t::kind_var(data_kind_t::svalues, type_variable));
478✔
180
            m_inv.havoc(variable_t::kind_var(data_kind_t::uvalues, type_variable));
478✔
181
        }
182
    }
21✔
183

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

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

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

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

200
static linear_constraint_t type_is_not_stack(const reg_pack_t& r) {
199✔
201
    using namespace crab::dsl_syntax;
199✔
202
    return r.type != T_STACK;
199✔
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,
956✔
208
                                                  const variable_t src_offset) {
209
    using namespace crab::dsl_syntax;
956✔
210
    using Op = Condition::Op;
956✔
211
    switch (op) {
956✔
212
    case Op::EQ: return eq(dst_offset, src_offset);
3✔
213
    case Op::NE: return neq(dst_offset, src_offset);
6✔
214
    case Op::GE: return dst_offset >= src_offset;
4✔
215
    case Op::SGE: return dst_offset >= src_offset; // pointer comparison is unsigned
×
216
    case Op::LE: return dst_offset <= src_offset;
471✔
217
    case Op::SLE: return dst_offset <= src_offset; // pointer comparison is unsigned
×
218
    case Op::GT: return dst_offset > src_offset;
942✔
219
    case Op::SGT: return dst_offset > src_offset; // pointer comparison is unsigned
×
220
    case Op::SLT:
×
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
6✔
224
    default: return dst_offset - dst_offset == 0;
×
225
    }
226
}
227

228
void ebpf_transformer::operator()(const Assume& s) {
25,244✔
229
    if (m_inv.is_bottom()) {
25,244✔
230
        return;
×
231
    }
232
    const Condition cond = s.cond;
25,244✔
233
    const auto dst = reg_pack(cond.left);
25,244✔
234
    if (const auto psrc_reg = std::get_if<Reg>(&cond.right)) {
25,244✔
235
        const auto src_reg = *psrc_reg;
5,028✔
236
        const auto src = reg_pack(src_reg);
5,028✔
237
        if (type_inv.same_type(m_inv, cond.left, std::get<Reg>(cond.right))) {
5,028✔
238
            m_inv = type_inv.join_over_types(m_inv, cond.left, [&](NumAbsDomain& inv, const type_encoding_t type) {
9,272✔
239
                if (type == T_NUM) {
4,638✔
240
                    for (const linear_constraint_t& cst :
12,771✔
241
                         inv->assume_cst_reg(cond.op, cond.is64, dst.svalue, dst.uvalue, src.svalue, src.uvalue)) {
9,089✔
242
                        inv.add_constraint(cst);
5,407✔
243
                    }
3,682✔
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)) {
956✔
248
                        if (const auto src_offset = dom.get_type_offset_variable(src_reg, type)) {
956✔
249
                            inv.add_constraint(assume_cst_offsets_reg(cond.op, dst_offset.value(), src_offset.value()));
1,912✔
250
                        }
251
                    }
252
                }
253
            });
9,274✔
254
        } else {
255
            // We should only reach here if `--assume-assert` is off
256
            assert(!thread_local_options.assume_assertions || dom.is_bottom());
392✔
257
            // be sound in any case, it happens to flush out bugs:
258
            m_inv.set_to_top();
392✔
259
        }
260
    } else {
261
        const int64_t imm = gsl::narrow_cast<int64_t>(std::get<Imm>(cond.right).v);
20,216✔
262
        for (const linear_constraint_t& cst : m_inv->assume_cst_imm(cond.op, cond.is64, dst.svalue, dst.uvalue, imm)) {
62,439✔
263
            m_inv.add_constraint(cst);
42,223✔
264
        }
20,216✔
265
    }
266
}
267

UNCOV
268
void ebpf_transformer::operator()(const Undefined& a) {}
×
269

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

276
void ebpf_transformer::operator()(const Un& stmt) {
1,478✔
277
    if (m_inv.is_bottom()) {
1,478✔
278
        return;
×
279
    }
280
    const auto dst = reg_pack(stmt.dst);
1,478✔
281
    auto swap_endianness = [&](const variable_t v, auto be_or_le) {
4,280✔
282
        if (m_inv.entail(type_is_number(stmt.dst))) {
5,604✔
283
            if (const auto n = m_inv.eval_interval(v).singleton()) {
8,304✔
284
                if (n->fits_cast_to<int64_t>()) {
122✔
285
                    m_inv.set(v, interval_t{be_or_le(n->cast_to<int64_t>())});
122✔
286
                    return;
122✔
287
                }
288
            }
289
        }
290
        m_inv.havoc(v);
2,680✔
291
        havoc_offsets(stmt.dst);
2,680✔
292
    };
1,478✔
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) {
1,478✔
297
    case Un::Op::BE16:
1,316✔
298
        if (!thread_local_options.big_endian) {
1,316✔
299
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
1,314✔
300
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
1,314✔
301
        } else {
302
            swap_endianness(dst.svalue, truncate<uint16_t>);
2✔
303
            swap_endianness(dst.uvalue, truncate<uint16_t>);
2✔
304
        }
305
        break;
306
    case Un::Op::BE32:
59✔
307
        if (!thread_local_options.big_endian) {
59✔
308
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
58✔
309
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
58✔
310
        } else {
311
            swap_endianness(dst.svalue, truncate<uint32_t>);
1✔
312
            swap_endianness(dst.uvalue, truncate<uint32_t>);
1✔
313
        }
314
        break;
315
    case Un::Op::BE64:
5✔
316
        if (!thread_local_options.big_endian) {
5✔
317
            swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
4✔
318
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
4✔
319
        }
320
        break;
321
    case Un::Op::LE16:
6✔
322
        if (thread_local_options.big_endian) {
6✔
323
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
1✔
324
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
1✔
325
        } else {
326
            swap_endianness(dst.svalue, truncate<uint16_t>);
5✔
327
            swap_endianness(dst.uvalue, truncate<uint16_t>);
5✔
328
        }
329
        break;
330
    case Un::Op::LE32:
4✔
331
        if (thread_local_options.big_endian) {
4✔
332
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
1✔
333
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
1✔
334
        } else {
335
            swap_endianness(dst.svalue, truncate<uint32_t>);
3✔
336
            swap_endianness(dst.uvalue, truncate<uint32_t>);
3✔
337
        }
338
        break;
339
    case Un::Op::LE64:
4✔
340
        if (thread_local_options.big_endian) {
4✔
341
            swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
1✔
342
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
1✔
343
        }
344
        break;
345
    case Un::Op::SWAP16:
5✔
346
        swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
5✔
347
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
5✔
348
        break;
349
    case Un::Op::SWAP32:
3✔
350
        swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
3✔
351
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
3✔
352
        break;
353
    case Un::Op::SWAP64:
3✔
354
        swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
3✔
355
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
3✔
356
        break;
357
    case Un::Op::NEG:
73✔
358
        m_inv->neg(dst.svalue, dst.uvalue, stmt.is64 ? 64 : 32);
93✔
359
        havoc_offsets(stmt.dst);
1,478✔
360
        break;
361
    }
362
}
363

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

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

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

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

398
void ebpf_transformer::do_load_stack(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr,
6,259✔
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));
6,259✔
401
    using namespace crab::dsl_syntax;
6,259✔
402
    if (inv.entail(width <= reg_pack(src_reg).stack_numeric_size)) {
18,777✔
403
        type_inv.assign_type(inv, target_reg, T_NUM);
482✔
404
    }
405

406
    const reg_pack_t& target = reg_pack(target_reg);
6,259✔
407
    if (width == 1 || width == 2 || width == 4 || width == 8) {
6,259✔
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);
6,259✔
411
        const std::optional<linear_expression_t> uresult = stack.load(inv, data_kind_t::uvalues, addr, width);
6,259✔
412
        havoc_register(inv, target_reg);
6,259✔
413
        inv.assign(target.svalue, sresult);
6,259✔
414
        inv.assign(target.uvalue, uresult);
6,259✔
415

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

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

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

447
    const reg_pack_t& target = reg_pack(target_reg);
1,767✔
448

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

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

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

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

471
    const number_t addr = *maybe_addr;
1,621✔
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;
1,621✔
478
    if (addr == desc->data) {
1,621✔
479
        if (width == offset_width) {
500✔
480
            inv.assign(target.packet_offset, 0);
1,000✔
481
        }
482
    } else if (addr == desc->end) {
1,121✔
483
        if (width == offset_width) {
495✔
484
            inv.assign(target.packet_offset, variable_t::packet_size());
986✔
485
        }
486
    } else if (addr == desc->meta) {
626✔
487
        if (width == offset_width) {
9✔
488
            inv.assign(target.packet_offset, variable_t::meta_offset());
18✔
489
        }
490
    } else {
491
        if (may_touch_ptr) {
617✔
492
            type_inv.havoc_type(inv, target_reg);
×
493
        } else {
494
            type_inv.assign_type(inv, target_reg, T_NUM);
1,234✔
495
        }
496
        return;
617✔
497
    }
498
    if (width == offset_width) {
1,004✔
499
        type_inv.assign_type(inv, target_reg, T_PACKET);
1,002✔
500
        inv.add_constraint(4098 <= target.svalue);
3,006✔
501
        inv.add_constraint(target.svalue <= PTR_MAX);
2,004✔
502
    }
503
}
3,861✔
504

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

512
    type_inv.assign_type(inv, target_reg, T_NUM);
5,236✔
513
    havoc_register(inv, target_reg);
5,236✔
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) {
5,236✔
517
        const interval_t full = interval_t::unsigned_int(width * 8);
3,227✔
518
        inv.set(target.svalue, full);
3,227✔
519
        inv.set(target.uvalue, full);
3,227✔
520
    }
3,227✔
521
}
522

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

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

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

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

566
void ebpf_transformer::do_store_stack(NumAbsDomain& inv, const linear_expression_t& addr, const int width,
15,835✔
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);
15,835✔
572
        type_inv.assign_type(inv, var, val_type);
15,835✔
573
    }
574
    if (width == 8) {
15,835✔
575
        inv.assign(stack.store(inv, data_kind_t::svalues, addr, width, val_svalue), val_svalue);
6,417✔
576
        inv.assign(stack.store(inv, data_kind_t::uvalues, addr, width, val_uvalue), val_uvalue);
6,417✔
577

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

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

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

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

609
        if (opt_val_reg && type_inv.has_type(m_inv, val_type, T_STACK)) {
6,417✔
610
            inv.assign(stack.store(inv, data_kind_t::stack_offsets, addr, width, opt_val_reg->stack_offset),
30✔
611
                       opt_val_reg->stack_offset);
15✔
612
            inv.assign(stack.store(inv, data_kind_t::stack_numeric_sizes, addr, width, opt_val_reg->stack_numeric_size),
30✔
613
                       opt_val_reg->stack_numeric_size);
15✔
614
        } else {
615
            stack.havoc(inv, data_kind_t::stack_offsets, addr, width);
6,402✔
616
            stack.havoc(inv, data_kind_t::stack_numeric_sizes, addr, width);
6,402✔
617
        }
618
    } else {
619
        if ((width == 1 || width == 2 || width == 4) && type_inv.get_type(m_inv, val_type) == T_NUM) {
9,418✔
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)) {
9,319✔
622
                inv.assign(stack_svalue, val_svalue);
9,318✔
623
                inv->overflow_bounds(*stack_svalue, width * 8, true);
9,318✔
624
            }
625
            if (const auto stack_uvalue = stack.store(inv, data_kind_t::uvalues, addr, width, val_uvalue)) {
9,319✔
626
                inv.assign(stack_uvalue, val_uvalue);
9,318✔
627
                inv->overflow_bounds(*stack_uvalue, width * 8, false);
9,318✔
628
            }
629
        } else {
630
            stack.havoc(inv, data_kind_t::svalues, addr, width);
99✔
631
            stack.havoc(inv, data_kind_t::uvalues, addr, width);
99✔
632
        }
633
        stack.havoc(inv, data_kind_t::ctx_offsets, addr, width);
9,418✔
634
        stack.havoc(inv, data_kind_t::map_fds, addr, width);
9,418✔
635
        stack.havoc(inv, data_kind_t::packet_offsets, addr, width);
9,418✔
636
        stack.havoc(inv, data_kind_t::shared_offsets, addr, width);
9,418✔
637
        stack.havoc(inv, data_kind_t::stack_offsets, addr, width);
9,418✔
638
        stack.havoc(inv, data_kind_t::shared_region_sizes, addr, width);
9,418✔
639
        stack.havoc(inv, data_kind_t::stack_numeric_sizes, addr, width);
9,418✔
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();
31,670✔
645
    auto updated_ub = m_inv.eval_interval(addr).ub() + width;
63,340✔
646
    for (const variable_t type_variable : variable_t::get_type_variables()) {
916,448✔
647
        if (!type_inv.has_type(inv, type_variable, T_STACK)) {
900,613✔
648
            continue;
468,228✔
649
        }
650
        const variable_t stack_offset_variable = variable_t::kind_var(data_kind_t::stack_offsets, type_variable);
432,385✔
651
        const variable_t stack_numeric_size_variable =
432,385✔
652
            variable_t::kind_var(data_kind_t::stack_numeric_sizes, type_variable);
432,385✔
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)) &&
1,297,155✔
657
            m_inv.intersect(operator>=(addr + width, stack_offset_variable))) {
861,921✔
658
            m_inv.havoc(stack_numeric_size_variable);
413,518✔
659
            recompute_stack_numeric_size(m_inv, type_variable);
413,518✔
660
        }
661
    }
15,835✔
662
}
15,835✔
663

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

681
void ebpf_transformer::do_mem_store(const Mem& b, const linear_expression_t& val_type,
18,746✔
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()) {
18,746✔
685
        return;
15,904✔
686
    }
687
    const int width = b.access.width;
18,746✔
688
    const number_t offset{b.access.offset};
18,746✔
689
    if (b.access.basereg.v == R10_STACK_POINTER) {
18,746✔
690
        const auto r10_stack_offset = reg_pack(b.access.basereg).stack_offset;
15,904✔
691
        const auto r10_interval = m_inv.eval_interval(r10_stack_offset);
15,904✔
692
        if (r10_interval.is_singleton()) {
15,904✔
693
            const int32_t stack_offset = r10_interval.singleton()->cast_to<int32_t>();
15,762✔
694
            const number_t base_addr{stack_offset};
15,762✔
695
            do_store_stack(m_inv, base_addr + offset, width, val_type, val_svalue, val_uvalue, opt_val_reg);
31,524✔
696
        }
15,762✔
697
        return;
15,904✔
698
    }
15,904✔
699
    m_inv = type_inv.join_over_types(m_inv, b.access.basereg, [&](NumAbsDomain& inv, const type_encoding_t type) {
5,684✔
700
        if (type == T_STACK) {
2,842✔
701
            const auto base_addr = linear_expression_t(dom.get_type_offset_variable(b.access.basereg, type).value());
146✔
702
            do_store_stack(inv, dsl_syntax::operator+(base_addr, offset), width, val_type, val_svalue, val_uvalue,
146✔
703
                           opt_val_reg);
704
        }
73✔
705
        // do nothing for any other type
706
    });
5,684✔
707
}
18,746✔
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) {
42✔
711
    Bin bin{.dst = Reg{R11_ATOMIC_SCRATCH}, .v = a.valreg, .is64 = a.access.width == sizeof(uint64_t), .lddw = false};
42✔
712
    switch (a.op) {
42✔
713
    case Atomic::Op::ADD: bin.op = Bin::Op::ADD; break;
8✔
714
    case Atomic::Op::OR: bin.op = Bin::Op::OR; break;
8✔
715
    case Atomic::Op::AND: bin.op = Bin::Op::AND; break;
8✔
716
    case Atomic::Op::XOR: bin.op = Bin::Op::XOR; break;
8✔
717
    case Atomic::Op::XCHG:
718
    case Atomic::Op::CMPXCHG: bin.op = Bin::Op::MOV; break;
719
    default: throw std::exception();
×
720
    }
721
    return bin;
42✔
722
}
723

724
void ebpf_transformer::operator()(const Atomic& a) {
207✔
725
    if (m_inv.is_bottom()) {
207✔
726
        return;
165✔
727
    }
728
    if (!m_inv.entail(type_is_pointer(reg_pack(a.access.basereg))) ||
827✔
729
        !m_inv.entail(type_is_number(reg_pack(a.valreg)))) {
612✔
730
        return;
731
    }
732
    if (m_inv.entail(type_is_not_stack(reg_pack(a.access.basereg)))) {
398✔
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) {
157✔
736
            havoc_register(m_inv, Reg{R0_RETURN_VALUE});
2✔
737
        } else if (a.fetch) {
155✔
738
            havoc_register(m_inv, a.valreg);
10✔
739
        }
740
        return;
157✔
741
    }
742

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

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

750
    if (a.op == Atomic::Op::CMPXCHG) {
42✔
751
        // For CMPXCHG, store the original value in r0.
752
        (*this)(Mem{.access = a.access, .value = Reg{R0_RETURN_VALUE}, .is_load = true});
6✔
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);
6✔
760
    } else if (a.fetch) {
36✔
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});
20✔
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});
42✔
769

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

775
void ebpf_transformer::operator()(const Call& call) {
9,896✔
776
    using namespace crab::dsl_syntax;
9,896✔
777
    if (m_inv.is_bottom()) {
9,896✔
778
        return;
×
779
    }
780
    std::optional<Reg> maybe_fd_reg{};
9,896✔
781
    for (ArgSingle param : call.singles) {
33,244✔
782
        switch (param.kind) {
23,348✔
783
        case ArgSingle::Kind::MAP_FD: maybe_fd_reg = param.reg; break;
29,405✔
784
        case ArgSingle::Kind::ANYTHING:
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;
791
        }
792
    }
793
    for (ArgPair param : call.pairs) {
12,056✔
794
        switch (param.kind) {
2,160✔
795
        case ArgPair::Kind::PTR_TO_READABLE_MEM_OR_NULL:
796
        case ArgPair::Kind::PTR_TO_READABLE_MEM:
797
            // Do nothing. No side effect allowed.
798
            break;
799

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

810
            m_inv = type_inv.join_over_types(m_inv, param.mem, [&](NumAbsDomain& inv, const type_encoding_t type) {
862✔
811
                if (type == T_STACK) {
431✔
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);
425✔
815
                    stack.havoc(inv, data_kind_t::svalues, addr, width);
425✔
816
                    stack.havoc(inv, data_kind_t::uvalues, addr, width);
425✔
817
                    stack.havoc(inv, data_kind_t::ctx_offsets, addr, width);
425✔
818
                    stack.havoc(inv, data_kind_t::map_fds, addr, width);
425✔
819
                    stack.havoc(inv, data_kind_t::packet_offsets, addr, width);
425✔
820
                    stack.havoc(inv, data_kind_t::shared_offsets, addr, width);
425✔
821
                    stack.havoc(inv, data_kind_t::stack_offsets, addr, width);
425✔
822
                    stack.havoc(inv, data_kind_t::shared_region_sizes, addr, width);
425✔
823
                } else {
824
                    store_numbers = false;
6✔
825
                }
826
            });
862✔
827
            if (store_numbers) {
431✔
828
                // Functions are not allowed to write sensitive data,
829
                // and initialization is guaranteed
830
                stack.store_numbers(m_inv, addr, width);
425✔
831
            }
832
        }
833
        }
834
    }
835

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

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

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

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

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

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

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

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

931
    if (type.value_type == EbpfMapValueType::PROGRAM) {
5✔
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);
5✔
937
    const reg_pack_t& dst = reg_pack(dst_reg);
5✔
938
    m_inv.assign(dst.shared_offset, offset);
5✔
939
    m_inv.assign(dst.shared_region_size, desc.value_size);
5✔
940
    assign_valid_ptr(dst_reg, false);
5✔
941
}
5✔
942

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

950
void ebpf_transformer::assign_valid_ptr(const Reg& dst_reg, const bool maybe_null) {
10,454✔
951
    using namespace crab::dsl_syntax;
10,454✔
952
    const reg_pack_t& reg = reg_pack(dst_reg);
10,454✔
953
    m_inv.havoc(reg.svalue);
10,454✔
954
    m_inv.havoc(reg.uvalue);
10,454✔
955
    if (maybe_null) {
10,454✔
956
        m_inv.add_constraint(0 <= reg.svalue);
12,621✔
957
    } else {
958
        m_inv.add_constraint(0 < reg.svalue);
18,741✔
959
    }
960
    m_inv.add_constraint(reg.svalue <= PTR_MAX);
20,908✔
961
    m_inv.assign(reg.uvalue, reg.svalue);
10,454✔
962
}
10,454✔
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 {
423,602✔
967
    const variable_t stack_numeric_size_variable =
423,602✔
968
        variable_t::kind_var(data_kind_t::stack_numeric_sizes, type_variable);
423,602✔
969

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

974
    if (type_inv.has_type(inv, type_variable, T_STACK)) {
423,602✔
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));
423,078✔
977
        if (numeric_size > 0) {
423,078✔
978
            inv.assign(stack_numeric_size_variable, numeric_size);
19,076✔
979
        }
980
    }
981
}
982

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

987
void ebpf_transformer::add(const Reg& dst_reg, const int imm, const int finite_width) {
12,533✔
988
    const auto dst = reg_pack(dst_reg);
12,533✔
989
    m_inv->add_overflow(dst.svalue, dst.uvalue, imm, finite_width);
12,533✔
990
    if (const auto offset = dom.get_type_offset_variable(dst_reg)) {
12,533✔
991
        m_inv->add(*offset, imm);
10,081✔
992
        if (imm > 0) {
10,081✔
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,038✔
996
        } else if (imm < 0) {
9,562✔
997
            m_inv.havoc(dst.stack_numeric_size);
9,562✔
998
        }
999
        recompute_stack_numeric_size(dom.m_inv, dst_reg);
10,081✔
1000
    }
1001
}
12,533✔
1002

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

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

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

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

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

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

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

1047
    type_inv.assign_type(dom.m_inv, dst_reg, T_NUM);
1,117✔
1048

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

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

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

1068
    auto dst = reg_pack(bin.dst);
76,282✔
1069
    int finite_width = bin.is64 ? 64 : 32;
76,282✔
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) &&
101,712✔
1074
        !std::set{Bin::Op::MOV, Bin::Op::MOVSX8, Bin::Op::MOVSX16, Bin::Op::MOVSX32}.contains(bin.op)) {
126,234✔
1075
        havoc_register(m_inv, bin.dst);
908✔
1076
        type_inv.havoc_type(m_inv, bin.dst);
908✔
1077
        return;
908✔
1078
    }
1079
    if (auto pimm = std::get_if<Imm>(&bin.v)) {
75,374✔
1080
        // dst += K
1081
        int64_t imm;
43,543✔
1082
        if (bin.is64) {
43,543✔
1083
            // Use the full signed value.
1084
            imm = to_signed(pimm->v);
31,911✔
1085
        } else {
1086
            // Use only the low 32 bits of the value.
1087
            imm = gsl::narrow_cast<int32_t>(pimm->v);
11,632✔
1088
            m_inv->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
11,632✔
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)) {
11,632✔
1091
                havoc_register(m_inv, bin.dst);
371✔
1092
                havoc_offsets(bin.dst);
371✔
1093
                m_inv.havoc(dst.type);
371✔
1094
            }
1095
        }
1096
        switch (bin.op) {
43,543✔
1097
        case Bin::Op::MOV:
19,671✔
1098
            m_inv.assign(dst.svalue, imm);
19,671✔
1099
            m_inv.assign(dst.uvalue, imm);
19,671✔
1100
            m_inv->overflow_bounds(dst.uvalue, bin.is64 ? 64 : 32, false);
25,003✔
1101
            type_inv.assign_type(m_inv, bin.dst, T_NUM);
19,671✔
1102
            havoc_offsets(bin.dst);
19,671✔
1103
            break;
1104
        case Bin::Op::MOVSX8:
×
UNCOV
1105
        case Bin::Op::MOVSX16:
×
1106
        case Bin::Op::MOVSX32: CRAB_ERROR("Unsupported operation");
×
1107
        case Bin::Op::ADD:
12,464✔
1108
            if (imm == 0) {
12,464✔
1109
                return;
1,480✔
1110
            }
1111
            add(bin.dst, gsl::narrow<int>(imm), finite_width);
12,462✔
1112
            break;
1113
        case Bin::Op::SUB:
14✔
1114
            if (imm == 0) {
14✔
1115
                return;
1116
            }
1117
            add(bin.dst, gsl::narrow<int>(-imm), finite_width);
13✔
1118
            break;
1119
        case Bin::Op::MUL:
51✔
1120
            m_inv->mul(dst.svalue, dst.uvalue, imm, finite_width);
51✔
1121
            havoc_offsets(bin.dst);
51✔
1122
            break;
1123
        case Bin::Op::UDIV:
1,091✔
1124
            m_inv->udiv(dst.svalue, dst.uvalue, imm, finite_width);
1,091✔
1125
            havoc_offsets(bin.dst);
1,091✔
1126
            break;
1127
        case Bin::Op::UMOD:
12✔
1128
            m_inv->urem(dst.svalue, dst.uvalue, imm, finite_width);
12✔
1129
            havoc_offsets(bin.dst);
12✔
1130
            break;
1131
        case Bin::Op::SDIV:
9✔
1132
            m_inv->sdiv(dst.svalue, dst.uvalue, imm, finite_width);
9✔
1133
            havoc_offsets(bin.dst);
9✔
1134
            break;
1135
        case Bin::Op::SMOD:
17✔
1136
            m_inv->srem(dst.svalue, dst.uvalue, imm, finite_width);
17✔
1137
            havoc_offsets(bin.dst);
17✔
1138
            break;
1139
        case Bin::Op::OR:
1,325✔
1140
            m_inv->bitwise_or(dst.svalue, dst.uvalue, imm);
1,325✔
1141
            havoc_offsets(bin.dst);
1,325✔
1142
            break;
1143
        case Bin::Op::AND:
3,391✔
1144
            // FIX: what to do with ptr&-8 as in counter/simple_loop_unrolled?
1145
            m_inv->bitwise_and(dst.svalue, dst.uvalue, imm);
3,391✔
1146
            if (gsl::narrow<int32_t>(imm) > 0) {
3,391✔
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);
6,246✔
1149
                m_inv.add_constraint(dst.uvalue <= imm);
6,246✔
1150
                m_inv.add_constraint(0 <= dst.svalue);
9,369✔
1151
                m_inv.add_constraint(0 <= dst.uvalue);
9,369✔
1152
            }
1153
            havoc_offsets(bin.dst);
3,391✔
1154
            break;
1155
        case Bin::Op::LSH: shl(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
2,566✔
1156
        case Bin::Op::RSH: lshr(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
2,418✔
1157
        case Bin::Op::ARSH: ashr(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
330✔
1158
        case Bin::Op::XOR:
184✔
1159
            m_inv->bitwise_xor(dst.svalue, dst.uvalue, imm);
184✔
1160
            havoc_offsets(bin.dst);
184✔
1161
            break;
1162
        }
1163
    } else {
1164
        // dst op= src
1165
        auto src_reg = std::get<Reg>(bin.v);
31,831✔
1166
        auto src = reg_pack(src_reg);
31,831✔
1167
        if (type_inv.has_type(m_inv, src_reg, T_UNINIT)) {
31,831✔
1168
            havoc_register(m_inv, bin.dst);
489✔
1169
            type_inv.havoc_type(m_inv, bin.dst);
489✔
1170
            return;
1,058✔
1171
        }
1172
        switch (bin.op) {
31,342✔
1173
        case Bin::Op::ADD: {
2,147✔
1174
            if (type_inv.same_type(m_inv, bin.dst, std::get<Reg>(bin.v))) {
2,147✔
1175
                // both must have been checked to be numbers
1176
                m_inv->add_overflow(dst.svalue, dst.uvalue, src.svalue, finite_width);
1,811✔
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 =
336✔
1181
                    type_inv.join_over_types(m_inv, bin.dst, [&](NumAbsDomain& inv, const type_encoding_t dst_type) {
672✔
1182
                        inv = type_inv.join_over_types(
676✔
1183
                            inv, src_reg, [&](NumAbsDomain& inv, const type_encoding_t src_type) {
338✔
1184
                                if (dst_type == T_NUM && src_type != T_NUM) {
341✔
1185
                                    // num += ptr
1186
                                    type_inv.assign_type(inv, bin.dst, src_type);
5✔
1187
                                    if (const auto dst_offset = dom.get_type_offset_variable(bin.dst, src_type)) {
5✔
1188
                                        inv->apply(arith_binop_t::ADD, dst_offset.value(), dst.svalue,
5✔
1189
                                                   dom.get_type_offset_variable(src_reg, src_type).value());
10✔
1190
                                    }
1191
                                    if (src_type == T_SHARED) {
5✔
1192
                                        inv.assign(dst.shared_region_size, src.shared_region_size);
×
1193
                                    }
1194
                                } else if (dst_type != T_NUM && src_type == T_NUM) {
336✔
1195
                                    // ptr += num
1196
                                    type_inv.assign_type(inv, bin.dst, dst_type);
334✔
1197
                                    if (const auto dst_offset = dom.get_type_offset_variable(bin.dst, dst_type)) {
334✔
1198
                                        inv->apply(arith_binop_t::ADD, dst_offset.value(), dst_offset.value(),
334✔
1199
                                                   src.svalue);
334✔
1200
                                        if (dst_type == T_STACK) {
334✔
1201
                                            // Reduce the numeric size.
1202
                                            using namespace crab::dsl_syntax;
8✔
1203
                                            if (inv.intersect(src.svalue < 0)) {
16✔
1204
                                                inv.havoc(dst.stack_numeric_size);
1✔
1205
                                                recompute_stack_numeric_size(inv, dst.type);
1✔
1206
                                            } else {
1207
                                                inv->apply_signed(arith_binop_t::SUB, dst.stack_numeric_size,
14✔
1208
                                                                  dst.stack_numeric_size, dst.stack_numeric_size,
7✔
1209
                                                                  src.svalue, 0);
7✔
1210
                                            }
1211
                                        }
1212
                                    }
1213
                                } else if (dst_type == T_NUM && src_type == T_NUM) {
336✔
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,
2✔
1217
                                                      src.svalue, finite_width);
1✔
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();
1✔
1223
                                }
1224
                            });
679✔
1225
                    });
674✔
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);
336✔
1228
            }
1229
            break;
1230
        }
1231
        case Bin::Op::SUB: {
389✔
1232
            if (type_inv.same_type(m_inv, bin.dst, std::get<Reg>(bin.v))) {
389✔
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) {
770✔
1235
                    switch (type) {
387✔
1236
                    case T_NUM:
252✔
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,
252✔
1239
                                          finite_width);
1240
                        type_inv.assign_type(inv, bin.dst, T_NUM);
252✔
1241
                        crab::havoc_offsets(inv, bin.dst);
252✔
1242
                        break;
252✔
1243
                    default:
135✔
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)) {
135✔
1247
                            inv->apply_signed(arith_binop_t::SUB, dst.svalue, dst.uvalue, dst_offset.value(),
270✔
1248
                                              dom.get_type_offset_variable(src_reg, type).value(), finite_width);
270✔
1249
                            inv.havoc(dst_offset.value());
135✔
1250
                        }
1251
                        crab::havoc_offsets(inv, bin.dst);
135✔
1252
                        type_inv.assign_type(inv, bin.dst, T_NUM);
135✔
1253
                        break;
135✔
1254
                    }
1255
                });
772✔
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) {
4✔
1260
                    type_inv.havoc_type(m_inv, bin.dst);
2✔
1261
                    m_inv.havoc(dst.svalue);
2✔
1262
                    m_inv.havoc(dst.uvalue);
2✔
1263
                    havoc_offsets(bin.dst);
2✔
1264
                } else {
1265
                    m_inv->sub_overflow(dst.svalue, dst.uvalue, src.svalue, finite_width);
2✔
1266
                    if (auto dst_offset = dom.get_type_offset_variable(bin.dst)) {
2✔
1267
                        m_inv->sub(dst_offset.value(), src.svalue);
2✔
1268
                        if (type_inv.has_type(m_inv, dst.type, T_STACK)) {
2✔
1269
                            // Reduce the numeric size.
1270
                            using namespace crab::dsl_syntax;
2✔
1271
                            if (m_inv.intersect(src.svalue > 0)) {
6✔
1272
                                m_inv.havoc(dst.stack_numeric_size);
2✔
1273
                                recompute_stack_numeric_size(m_inv, dst.type);
2✔
1274
                            } else {
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;
1283
        }
1284
        case Bin::Op::MUL:
36✔
1285
            m_inv->mul(dst.svalue, dst.uvalue, src.svalue, finite_width);
36✔
1286
            havoc_offsets(bin.dst);
36✔
1287
            break;
1288
        case Bin::Op::UDIV:
52✔
1289
            m_inv->udiv(dst.svalue, dst.uvalue, src.uvalue, finite_width);
52✔
1290
            havoc_offsets(bin.dst);
52✔
1291
            break;
1292
        case Bin::Op::UMOD:
17✔
1293
            m_inv->urem(dst.svalue, dst.uvalue, src.uvalue, finite_width);
17✔
1294
            havoc_offsets(bin.dst);
17✔
1295
            break;
1296
        case Bin::Op::SDIV:
16✔
1297
            m_inv->sdiv(dst.svalue, dst.uvalue, src.svalue, finite_width);
16✔
1298
            havoc_offsets(bin.dst);
16✔
1299
            break;
1300
        case Bin::Op::SMOD:
23✔
1301
            m_inv->srem(dst.svalue, dst.uvalue, src.svalue, finite_width);
23✔
1302
            havoc_offsets(bin.dst);
23✔
1303
            break;
1304
        case Bin::Op::OR:
2,847✔
1305
            m_inv->bitwise_or(dst.svalue, dst.uvalue, src.uvalue, finite_width);
2,847✔
1306
            havoc_offsets(bin.dst);
2,847✔
1307
            break;
1308
        case Bin::Op::AND:
238✔
1309
            m_inv->bitwise_and(dst.svalue, dst.uvalue, src.uvalue, finite_width);
238✔
1310
            havoc_offsets(bin.dst);
238✔
1311
            break;
1312
        case Bin::Op::LSH:
294✔
1313
            if (m_inv.entail(type_is_number(src_reg))) {
588✔
1314
                auto src_interval = m_inv.eval_interval(src.uvalue);
294✔
1315
                if (std::optional<number_t> sn = src_interval.singleton()) {
294✔
1316
                    // truncate to uint64?
1317
                    uint64_t imm = sn->cast_to<uint64_t>() & (bin.is64 ? 63 : 31);
23✔
1318
                    if (imm <= std::numeric_limits<int32_t>::max()) {
23✔
1319
                        if (!bin.is64) {
23✔
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());
26✔
1322
                        }
1323
                        shl(bin.dst, gsl::narrow_cast<int32_t>(imm), finite_width);
23✔
1324
                        break;
23✔
1325
                    }
1326
                }
294✔
1327
            }
294✔
1328
            m_inv->shl_overflow(dst.svalue, dst.uvalue, src.uvalue);
271✔
1329
            havoc_offsets(bin.dst);
271✔
1330
            break;
1331
        case Bin::Op::RSH:
39✔
1332
            if (m_inv.entail(type_is_number(src_reg))) {
78✔
1333
                auto src_interval = m_inv.eval_interval(src.uvalue);
39✔
1334
                if (std::optional<number_t> sn = src_interval.singleton()) {
39✔
1335
                    uint64_t imm = sn->cast_to<uint64_t>() & (bin.is64 ? 63 : 31);
17✔
1336
                    if (imm <= std::numeric_limits<int32_t>::max()) {
17✔
1337
                        if (!bin.is64) {
17✔
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());
16✔
1340
                        }
1341
                        lshr(bin.dst, gsl::narrow_cast<int32_t>(imm), finite_width);
17✔
1342
                        break;
17✔
1343
                    }
1344
                }
39✔
1345
            }
39✔
1346
            m_inv.havoc(dst.svalue);
22✔
1347
            m_inv.havoc(dst.uvalue);
22✔
1348
            havoc_offsets(bin.dst);
22✔
1349
            break;
1350
        case Bin::Op::ARSH:
17✔
1351
            if (m_inv.entail(type_is_number(src_reg))) {
34✔
1352
                ashr(bin.dst, src.svalue, finite_width);
17✔
1353
                break;
17✔
1354
            }
1355
            m_inv.havoc(dst.svalue);
×
1356
            m_inv.havoc(dst.uvalue);
×
1357
            havoc_offsets(bin.dst);
×
1358
            break;
1359
        case Bin::Op::XOR:
237✔
1360
            m_inv->bitwise_xor(dst.svalue, dst.uvalue, src.uvalue, finite_width);
237✔
1361
            havoc_offsets(bin.dst);
237✔
1362
            break;
1363
        case Bin::Op::MOVSX8:
1,119✔
1364
        case Bin::Op::MOVSX16:
1,119✔
1365
        case Bin::Op::MOVSX32: {
1,119✔
1366
            const int source_width = _movsx_bits(bin.op);
1,119✔
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)) {
3,317✔
1369
                return;
1370
            }
1371
            if (m_inv.entail(type_is_number(src_reg))) {
2,234✔
1372
                sign_extend(bin.dst, src.svalue, finite_width, source_width);
1,117✔
1373
                break;
1,117✔
1374
            }
1375
            m_inv.havoc(dst.svalue);
×
1376
            m_inv.havoc(dst.uvalue);
×
1377
            havoc_offsets(bin.dst);
31,262✔
1378
            break;
1379
        }
1380
        case Bin::Op::MOV:
23,871✔
1381
            // Keep relational information if operation is a no-op.
1382
            if (dst.svalue == src.svalue &&
23,871✔
1383
                m_inv.eval_interval(dst.uvalue) <= interval_t::unsigned_int(bin.is64 ? 64 : 32)) {
26,104✔
1384
                return;
1385
            }
1386
            m_inv.assign(dst.svalue, src.svalue);
23,793✔
1387
            m_inv.assign(dst.uvalue, src.uvalue);
23,793✔
1388
            havoc_offsets(bin.dst);
23,793✔
1389
            m_inv = type_inv.join_over_types(m_inv, src_reg, [&](NumAbsDomain& inv, const type_encoding_t type) {
47,586✔
1390
                switch (type) {
23,804✔
1391
                case T_CTX:
2,533✔
1392
                    if (bin.is64) {
2,533✔
1393
                        inv.assign(dst.type, type);
2,531✔
1394
                        inv.assign(dst.ctx_offset, src.ctx_offset);
5,062✔
1395
                    }
1396
                    break;
1397
                case T_MAP:
151✔
1398
                case T_MAP_PROGRAMS:
151✔
1399
                    if (bin.is64) {
151✔
1400
                        inv.assign(dst.type, type);
149✔
1401
                        inv.assign(dst.map_fd, src.map_fd);
298✔
1402
                    }
1403
                    break;
1404
                case T_PACKET:
513✔
1405
                    if (bin.is64) {
513✔
1406
                        inv.assign(dst.type, type);
512✔
1407
                        inv.assign(dst.packet_offset, src.packet_offset);
1,024✔
1408
                    }
1409
                    break;
1410
                case T_SHARED:
454✔
1411
                    if (bin.is64) {
454✔
1412
                        inv.assign(dst.type, type);
453✔
1413
                        inv.assign(dst.shared_region_size, src.shared_region_size);
453✔
1414
                        inv.assign(dst.shared_offset, src.shared_offset);
906✔
1415
                    }
1416
                    break;
1417
                case T_STACK:
9,938✔
1418
                    if (bin.is64) {
9,938✔
1419
                        inv.assign(dst.type, type);
9,937✔
1420
                        inv.assign(dst.stack_offset, src.stack_offset);
9,937✔
1421
                        inv.assign(dst.stack_numeric_size, src.stack_numeric_size);
19,874✔
1422
                    }
1423
                    break;
1424
                default: inv.assign(dst.type, type); break;
20,430✔
1425
                }
1426
            });
23,793✔
1427
            if (bin.is64) {
23,793✔
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) {
18,261✔
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);
18,259✔
1433
                }
1434
                type_inv.assign_type(m_inv, bin.dst, std::get<Reg>(bin.v));
18,260✔
1435
            }
1436
            break;
1437
        }
1438
    }
1439
    if (!bin.is64) {
74,802✔
1440
        m_inv->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
39,124✔
1441
    }
1442
}
1443

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

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

1456
void ebpf_domain_initialize_loop_counter(ebpf_domain_t& dom, const label_t& label) {
16✔
1457
    ebpf_transformer{dom}.initialize_loop_counter(label);
16✔
1458
}
16✔
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