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

Alan-Jowett / ebpf-verifier / 19340586454

08 Nov 2025 03:47PM UTC coverage: 86.605% (-0.3%) from 86.936%
19340586454

push

github

web-flow
Implement checks for the --line-info flag and move program selection into read_elf (#946)

* Check when --line-info is used, and avoid computing btf_line_info in read_elf and unmarshall
* Modify main and read_elf to avoid returning multiple RawProgram when desired_program is specified
* Use thread_local_options instead of {} in test_verify
* Avoid using brace initialization when returning the selected RawProgram in read_elf

Signed-off-by: Maxime Derri <maxime.derri@orange.com>

33 of 42 new or added lines in 6 files covered. (78.57%)

27 existing lines in 4 files now uncovered.

9019 of 10414 relevant lines covered (86.6%)

3908138.07 hits per line

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

94.57
/src/ir/assertions.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <cassert>
4
#include <cinttypes>
5
#include <utility>
6
#include <vector>
7

8
#include "cfg/cfg.hpp"
9
#include "ir/syntax.hpp"
10
#include "platform.hpp"
11

12
using std::string;
13
using std::to_string;
14
using std::vector;
15

16
namespace prevail {
17
class AssertExtractor {
18
    const ProgramInfo& info;
19
    const std::optional<Label>& current_label; ///< Pre-simplification label this assert is part of.
20

21
    static Imm imm(const Value& v) { return std::get<Imm>(v); }
22

23
    static vector<Assertion> zero_offset_ctx(const Reg reg, const bool or_null) {
6,036✔
24
        vector<Assertion> res;
6,036✔
25
        res.emplace_back(TypeConstraint{reg, or_null ? TypeGroup::ctx_or_num : TypeGroup::ctx});
9,030✔
26
        res.emplace_back(ZeroCtxOffset{reg, or_null});
6,036✔
27
        return res;
6,036✔
28
    }
×
29

30
    [[nodiscard]]
31
    ValidAccess make_valid_access(const Reg reg, const int32_t offset = {}, const Value& width = Imm{0},
58,534✔
32
                                  const bool or_null = {}, const AccessType access_type = {}) const {
33
        const int depth = current_label.has_value() ? current_label.value().call_stack_depth() : 1;
58,534✔
34
        return ValidAccess{depth, reg, offset, width, or_null, access_type};
58,534✔
35
    }
36

37
  public:
38
    explicit AssertExtractor(const ProgramInfo& info, const std::optional<Label>& label)
387,188✔
39
        : info{info}, current_label{label} {}
387,188✔
40

41
    vector<Assertion> operator()(const Undefined&) const {
5,112✔
42
        // assert(false);
43
        return {};
5,112✔
44
    }
45

46
    vector<Assertion> operator()(const IncrementLoopCounter& ipc) const { return {{BoundedLoopCount{ipc.name}}}; }
128✔
47

48
    vector<Assertion> operator()(const LoadMapFd&) const { return {}; }
13,632✔
49
    vector<Assertion> operator()(const LoadMapAddress&) const { return {}; }
424✔
50

51
    /// Packet access implicitly uses R6, so verify that R6 still has a pointer to the context.
52
    vector<Assertion> operator()(const Packet&) const { return zero_offset_ctx({6}, false); }
236✔
53

54
    vector<Assertion> operator()(const Exit&) const {
1,552✔
55
        vector<Assertion> res;
1,552✔
56
        if (current_label->stack_frame_prefix.empty()) {
1,552✔
57
            // Verify that Exit returns a number.
58
            res.emplace_back(TypeConstraint{Reg{R0_RETURN_VALUE}, TypeGroup::number});
1,448✔
59
        }
60
        return res;
1,552✔
61
    }
×
62

63
    vector<Assertion> operator()(const Call& call) const {
21,534✔
64
        vector<Assertion> res;
21,534✔
65
        std::optional<Reg> map_fd_reg;
21,534✔
66
        res.emplace_back(ValidCall{call.func, call.stack_frame_prefix});
21,534✔
67
        for (ArgSingle arg : call.singles) {
72,018✔
68
            switch (arg.kind) {
50,484✔
69
            case ArgSingle::Kind::ANYTHING:
14,142✔
70
                // avoid pointer leakage:
71
                if (!info.type.is_privileged) {
14,142✔
72
                    res.emplace_back(TypeConstraint{arg.reg, TypeGroup::number});
13,954✔
73
                }
74
                break;
7,071✔
75
            case ArgSingle::Kind::MAP_FD_PROGRAMS:
500✔
76
                res.emplace_back(TypeConstraint{arg.reg, TypeGroup::map_fd_programs});
500✔
77
                // Do not update map_fd_reg
78
                break;
500✔
79
            case ArgSingle::Kind::MAP_FD:
13,092✔
80
                res.emplace_back(TypeConstraint{arg.reg, TypeGroup::map_fd});
13,092✔
81
                map_fd_reg = arg.reg;
38,334✔
82
                break;
6,546✔
83
            case ArgSingle::Kind::PTR_TO_MAP_KEY:
16,950✔
84
            case ArgSingle::Kind::PTR_TO_MAP_VALUE:
8,475✔
85
                assert(map_fd_reg);
16,950✔
86
                res.emplace_back(TypeConstraint{arg.reg, TypeGroup::mem});
16,950✔
87
                res.emplace_back(ValidMapKeyValue{arg.reg, *map_fd_reg, arg.kind == ArgSingle::Kind::PTR_TO_MAP_KEY});
16,950✔
88
                break;
16,950✔
89
            case ArgSingle::Kind::PTR_TO_STACK:
×
90
                res.emplace_back(TypeConstraint{arg.reg, arg.or_null ? TypeGroup::stack_or_num : TypeGroup::stack});
×
91
                // TODO: check 0 when null
92
                break;
×
93
            case ArgSingle::Kind::PTR_TO_CTX:
5,800✔
94
                for (const Assertion& a : zero_offset_ctx(arg.reg, arg.or_null)) {
17,400✔
95
                    res.emplace_back(a);
11,600✔
96
                }
5,800✔
97
                break;
5,800✔
98
            }
99
        }
100
        for (ArgPair arg : call.pairs) {
26,252✔
101
            const auto group = arg.or_null ? TypeGroup::mem_or_num : TypeGroup::mem;
4,718✔
102
            const auto access_type =
7,077✔
103
                arg.kind == ArgPair::Kind::PTR_TO_READABLE_MEM ? AccessType::read : AccessType::write;
4,718✔
104
            res.emplace_back(TypeConstraint{arg.size, TypeGroup::number});
4,718✔
105
            res.emplace_back(ValidSize{arg.size, arg.can_be_zero});
4,718✔
106
            res.emplace_back(TypeConstraint{arg.mem, group});
4,718✔
107
            res.emplace_back(make_valid_access(arg.mem, 0, arg.size, arg.or_null, access_type));
7,071✔
108
            // TODO: reg is constant (or maybe it's not important)
109
        }
110
        return res;
32,301✔
111
    }
×
112

113
    vector<Assertion> operator()(const CallLocal&) const { return {}; }
104✔
114

115
    vector<Assertion> operator()(const Callx& callx) const {
48✔
116
        vector<Assertion> res;
48✔
117
        res.emplace_back(TypeConstraint{callx.func, TypeGroup::number});
48✔
118
        res.emplace_back(FuncConstraint{callx.func});
48✔
119
        return res;
48✔
120
    }
×
121

122
    [[nodiscard]]
123
    vector<Assertion> explicate(const Condition& cond) const {
30,034✔
124
        vector<Assertion> res;
30,034✔
125
        if (info.type.is_privileged) {
30,034✔
126
            return res;
85✔
127
        }
128
        if (const auto pimm = std::get_if<Imm>(&cond.right)) {
29,864✔
129
            if (pimm->v != 0) {
24,194✔
130
                // no need to check for valid access, it must be a number
131
                res.emplace_back(TypeConstraint{cond.left, TypeGroup::number});
7,964✔
132
            } else {
133
                bool allow_pointers = false;
16,230✔
134

135
                switch (cond.op) {
16,230✔
136
                case Condition::Op::EQ: allow_pointers = true; break;
11,106✔
137
                case Condition::Op::NE: allow_pointers = true; break;
10,744✔
138
                case Condition::Op::SET: allow_pointers = true; break;
7,298✔
139
                case Condition::Op::NSET: allow_pointers = true; break;
7,298✔
140
                case Condition::Op::LT: allow_pointers = true; break;
7,311✔
141
                case Condition::Op::LE: allow_pointers = true; break;
7,298✔
142
                case Condition::Op::GT: allow_pointers = true; break;
7,310✔
143
                case Condition::Op::GE: allow_pointers = true; break;
7,299✔
144
                case Condition::Op::SLT: allow_pointers = false; break;
795✔
145
                case Condition::Op::SLE: allow_pointers = false; break;
3✔
146
                case Condition::Op::SGT: allow_pointers = false; break;
17✔
147
                case Condition::Op::SGE: allow_pointers = false; break;
4✔
148
                default: assert(!"unexpected condition");
8,115✔
149
                }
150

151
                // Only permit pointer comparisons if the operation is 64-bit.
152
                allow_pointers &= cond.is64;
16,230✔
153

154
                if (!allow_pointers) {
16,230✔
155
                    res.emplace_back(TypeConstraint{cond.left, TypeGroup::number});
8,718✔
156
                }
157

158
                res.emplace_back(make_valid_access(cond.left));
24,345✔
159
                // OK - map_fd is just another pointer
160
                // Anything can be compared to 0
161
            }
162
        } else {
163
            const auto reg_right = get<Reg>(cond.right);
5,670✔
164
            res.emplace_back(make_valid_access(cond.left));
8,505✔
165
            res.emplace_back(make_valid_access(reg_right));
8,505✔
166
            if (cond.op != Condition::Op::EQ && cond.op != Condition::Op::NE) {
5,670✔
167
                res.emplace_back(TypeConstraint{cond.left, TypeGroup::ptr_or_num});
4,056✔
168
            }
169
            res.emplace_back(Comparable{.r1 = cond.left, .r2 = reg_right, .or_r2_is_number = false});
5,670✔
170
        }
171
        return res;
14,932✔
UNCOV
172
    }
×
173

174
    vector<Assertion> operator()(const Assume& ins) const {
59,692✔
175
        if (!ins.is_implicit) {
59,692✔
176
            return explicate(ins.cond);
340✔
177
        }
178
        return {};
59,352✔
179
    }
180

181
    vector<Assertion> operator()(const Jmp& ins) const {
35,170✔
182
        if (!ins.cond) {
35,170✔
183
            return {};
5,476✔
184
        }
185
        return explicate(*ins.cond);
29,694✔
186
    }
187

188
    vector<Assertion> operator()(const Mem& ins) const {
76,638✔
189
        vector<Assertion> res;
76,638✔
190
        const Reg basereg = ins.access.basereg;
76,638✔
191
        Imm width{gsl::narrow<uint32_t>(ins.access.width)};
76,638✔
192
        const int offset = ins.access.offset;
76,638✔
193
        if (basereg.v == R10_STACK_POINTER) {
76,638✔
194
            // We know we are accessing the stack.
195
            if (offset < -EBPF_SUBPROGRAM_STACK_SIZE || offset + static_cast<int>(width.v) > 0) {
50,888✔
196
                // This assertion will fail
197
                res.emplace_back(make_valid_access(basereg, offset, width, false,
3✔
198
                                                   ins.is_load ? AccessType::read : AccessType::write));
2✔
199
            } else if (ins.is_load) {
200
                // This assertion is not for bound checks but for reading initialized memory.
201
                // TODO: avoid load assertions: use post-assertion to check that the loaded value is initialized
202
                // res.emplace_back(make_valid_access(basereg, offset, width, false, AccessType::read));
203
            }
204
        } else {
205
            res.emplace_back(TypeConstraint{basereg, TypeGroup::pointer});
25,750✔
206
            res.emplace_back(
38,625✔
207
                make_valid_access(basereg, offset, width, false, ins.is_load ? AccessType::read : AccessType::write));
42,119✔
208
            if (!info.type.is_privileged && !ins.is_load) {
25,750✔
209
                if (const auto preg = std::get_if<Reg>(&ins.value)) {
6,974✔
210
                    if (width.v != 8) {
6,964✔
211
                        res.emplace_back(TypeConstraint{*preg, TypeGroup::number});
5,866✔
212
                    } else {
213
                        res.emplace_back(ValidStore{ins.access.basereg, *preg});
1,098✔
214
                    }
215
                }
216
            }
217
        }
218
        return res;
114,957✔
219
    }
×
220

221
    vector<Assertion> operator()(const Atomic& ins) const {
494✔
222

223
        return {
247✔
224
            Assertion{TypeConstraint{ins.valreg, TypeGroup::number}},
247✔
225
            Assertion{TypeConstraint{ins.access.basereg, TypeGroup::pointer}},
247✔
226
            Assertion{make_valid_access(ins.access.basereg, ins.access.offset,
741✔
227
                                        Imm{static_cast<uint32_t>(ins.access.width)}, false)},
247✔
228
        };
2,223✔
229
    }
988✔
230

231
    vector<Assertion> operator()(const Un& ins) const {
3,118✔
232
        return {Assertion{TypeConstraint{ins.dst, TypeGroup::number}}};
7,795✔
233
    }
3,118✔
234

235
    vector<Assertion> operator()(const Bin& ins) const {
169,402✔
236
        switch (ins.op) {
169,402✔
237
        case Bin::Op::MOV:
98,220✔
238
            if (const auto src = std::get_if<Reg>(&ins.v)) {
98,220✔
239
                if (!ins.is64) {
53,110✔
240
                    return {Assertion{TypeConstraint{*src, TypeGroup::number}}};
33,050✔
241
                }
242
            }
243
            return {};
85,000✔
244
        case Bin::Op::MOVSX8:
2,264✔
245
        case Bin::Op::MOVSX16:
1,132✔
246
        case Bin::Op::MOVSX32:
1,132✔
247
            if (const auto src = std::get_if<Reg>(&ins.v)) {
2,264✔
248
                return {Assertion{TypeConstraint{*src, TypeGroup::number}}};
5,660✔
249
            }
250
            return {};
×
251
        case Bin::Op::ADD: {
32,790✔
252
            if (const auto src = std::get_if<Reg>(&ins.v)) {
32,790✔
253
                return {Assertion{TypeConstraint{ins.dst, TypeGroup::ptr_or_num}},
2,641✔
254
                        Assertion{TypeConstraint{*src, TypeGroup::ptr_or_num}}, Assertion{Addable{*src, ins.dst}},
2,641✔
255
                        Assertion{Addable{ins.dst, *src}}};
29,051✔
256
            }
257
            return {Assertion{TypeConstraint{ins.dst, TypeGroup::ptr_or_num}}};
68,770✔
258
        }
259
        case Bin::Op::SUB: {
850✔
260
            if (const auto reg = std::get_if<Reg>(&ins.v)) {
850✔
261
                // disallow map-map since same type does not mean same offset
262
                // TODO: map identities
263
                return {TypeConstraint{ins.dst, TypeGroup::ptr_or_num},
411✔
264
                        Comparable{.r1 = ins.dst, .r2 = *reg, .or_r2_is_number = true}};
2,877✔
265
            }
266
            return {Assertion{TypeConstraint{ins.dst, TypeGroup::ptr_or_num}}};
70✔
267
        }
268
        case Bin::Op::UDIV:
2,592✔
269
        case Bin::Op::UMOD:
1,296✔
270
        case Bin::Op::SDIV:
1,296✔
271
        case Bin::Op::SMOD: {
1,296✔
272
            if (const auto src = std::get_if<Reg>(&ins.v)) {
2,592✔
273
                const bool is_signed = (ins.op == Bin::Op::SDIV || ins.op == Bin::Op::SMOD);
234✔
274
                return {Assertion{TypeConstraint{ins.dst, TypeGroup::number}},
117✔
275
                        Assertion{ValidDivisor{*src, is_signed}}};
819✔
276
            }
277
            return {Assertion{TypeConstraint{ins.dst, TypeGroup::number}}};
5,895✔
278
        }
279
            // For all other binary operations, the destination register must be a number and the source must either be
280
            // an immediate or a number.
281
        default:
32,686✔
282
            if (const auto src = std::get_if<Reg>(&ins.v)) {
32,686✔
283
                return {Assertion{TypeConstraint{ins.dst, TypeGroup::number}},
4,349✔
284
                        Assertion{TypeConstraint{*src, TypeGroup::number}}};
30,443✔
285
            } else {
286
                return {Assertion{TypeConstraint{ins.dst, TypeGroup::number}}};
59,970✔
287
            }
288
        }
289
        assert(false);
290
    }
84,402✔
291
};
292

293
/// Annotate the CFG by adding explicit assertions for all the preconditions
294
/// of any instruction. For example, jump instructions are asserted not to
295
/// compare numbers and pointers, or pointers to potentially distinct memory
296
/// regions. The verifier will use these assertions to treat the program as
297
/// unsafe unless it can prove that the assertions can never fail.
298
vector<Assertion> get_assertions(const Instruction& ins, const ProgramInfo& info, const std::optional<Label>& label) {
387,188✔
299
    return std::visit(AssertExtractor{info, label}, ins);
387,188✔
300
}
301
} // namespace prevail
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc