• 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

61.96
/src/asm_cfg.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <algorithm>
4
#include <cassert>
5
#include <map>
6
#include <optional>
7
#include <string>
8
#include <vector>
9

10
#include "config.hpp"
11
#include "crab/cfg.hpp"
12
#include "crab/wto.hpp"
13

14
#include "asm_syntax.hpp"
15
#include "program.hpp"
16

17
using std::optional;
18
using std::set;
19
using std::string;
20
using std::to_string;
21
using std::vector;
22

23
struct cfg_builder_t final {
1,246✔
24
    Program prog;
25

26
    // TODO: ins should be inserted elsewhere
27
    void insert_after(const label_t& prev_label, const label_t& new_label, const Instruction& ins) {
16✔
28
        if (prev_label == new_label) {
16✔
29
            CRAB_ERROR("Cannot insert after the same label ", to_string(new_label));
×
30
        }
31
        std::set<label_t> prev_children;
16✔
32
        std::swap(prev_children, prog.m_cfg.get_node(prev_label).children);
16✔
33

34
        for (const label_t& next_label : prev_children) {
36✔
35
            prog.m_cfg.get_node(next_label).parents.erase(prev_label);
20✔
36
        }
37

38
        insert(new_label, ins);
16✔
39
        for (const label_t& next_label : prev_children) {
36✔
40
            add_child(prev_label, new_label);
20✔
41
            add_child(new_label, next_label);
20✔
42
        }
43
    }
16✔
44

45
    // TODO: ins should be inserted elsewhere
46
    void insert(const label_t& _label, const Instruction& ins) {
168,180✔
47
        if (const auto it = prog.m_cfg.neighbours.find(_label); it != prog.m_cfg.neighbours.end()) {
168,180✔
48
            CRAB_ERROR("Label ", to_string(_label), " already exists");
×
49
        }
50
        prog.m_cfg.neighbours.emplace(_label, crab::cfg_t::adjacent_t{});
168,180✔
51
        prog.m_instructions.emplace(_label, ins);
168,180✔
52
    }
168,180✔
53

54
    // TODO: ins should be inserted elsewhere
55
    label_t insert_jump(const label_t& from, const label_t& to, const Instruction& ins) {
25,224✔
56
        const label_t jump_label = label_t::make_jump(from, to);
25,224✔
57
        if (prog.m_cfg.contains(jump_label)) {
25,224✔
58
            CRAB_ERROR("Jump label ", to_string(jump_label), " already exists");
×
59
        }
60
        insert(jump_label, ins);
25,224✔
61
        add_child(from, jump_label);
25,224✔
62
        add_child(jump_label, to);
25,224✔
63
        return jump_label;
25,224✔
64
    }
×
65

66
    void add_child(const label_t& a, const label_t& b) {
182,147✔
67
        assert(b != label_t::entry);
182,147✔
68
        assert(a != label_t::exit);
182,147✔
69
        prog.m_cfg.neighbours.at(a).children.insert(b);
182,147✔
70
        prog.m_cfg.neighbours.at(b).parents.insert(a);
182,147✔
71
    }
182,147✔
72

73
    void remove_child(const label_t& a, const label_t& b) {
68✔
74
        prog.m_cfg.get_node(a).children.erase(b);
68✔
75
        prog.m_cfg.get_node(b).parents.erase(a);
68✔
76
    }
68✔
77

78
    void set_assertions(const label_t& label, const std::vector<Assertion>& assertions) {
170,591✔
79
        if (!prog.m_cfg.contains(label)) {
170,591✔
80
            CRAB_ERROR("Label ", to_string(label), " not found in the CFG: ");
×
81
        }
82
        prog.m_assertions.insert_or_assign(label, assertions);
170,591✔
83
    }
170,591✔
84
};
85

86
using crab::basic_block_t;
87

88
/// Get the inverse of a given comparison operation.
89
static Condition::Op reverse(const Condition::Op op) {
12,612✔
90
    switch (op) {
12,612✔
91
    case Condition::Op::EQ: return Condition::Op::NE;
92
    case Condition::Op::NE: return Condition::Op::EQ;
93

94
    case Condition::Op::GE: return Condition::Op::LT;
95
    case Condition::Op::LT: return Condition::Op::GE;
96

97
    case Condition::Op::SGE: return Condition::Op::SLT;
98
    case Condition::Op::SLT: return Condition::Op::SGE;
99

100
    case Condition::Op::LE: return Condition::Op::GT;
101
    case Condition::Op::GT: return Condition::Op::LE;
102

103
    case Condition::Op::SLE: return Condition::Op::SGT;
104
    case Condition::Op::SGT: return Condition::Op::SLE;
105

106
    case Condition::Op::SET: return Condition::Op::NSET;
107
    case Condition::Op::NSET: return Condition::Op::SET;
108
    }
109
    assert(false);
110
    return {};
111
}
112

113
/// Get the inverse of a given comparison condition.
114
static Condition reverse(const Condition& cond) {
12,612✔
115
    return {.op = reverse(cond.op), .left = cond.left, .right = cond.right, .is64 = cond.is64};
12,612✔
116
}
117

118
static bool has_fall(const Instruction& ins) {
129,045✔
119
    if (std::holds_alternative<Exit>(ins)) {
129,045✔
120
        return false;
121
    }
122

123
    if (const auto pins = std::get_if<Jmp>(&ins)) {
127,768✔
124
        if (!pins->cond) {
57✔
125
            return false;
126
        }
127
    }
128

129
    return true;
130
}
131

132
/// Update a control-flow graph to inline function macros.
133
static void add_cfg_nodes(cfg_builder_t& builder, const label_t& caller_label, const label_t& entry_label) {
70✔
134
    bool first = true;
70✔
135

136
    // Get the label of the node to go to on returning from the macro.
137
    label_t exit_to_label = builder.prog.cfg().get_child(caller_label);
70✔
138

139
    // Construct the variable prefix to use for the new stack frame,
140
    // and store a copy in the CallLocal instruction since the instruction-specific
141
    // labels may only exist until the CFG is simplified.
142
    const std::string stack_frame_prefix = to_string(caller_label);
70✔
143
    if (const auto pcall = std::get_if<CallLocal>(&builder.prog.instruction_at(caller_label))) {
140✔
144
        pcall->stack_frame_prefix = stack_frame_prefix;
70✔
145
    }
146

147
    // Walk the transitive closure of CFG nodes starting at entry_label and ending at
148
    // any exit instruction.
149
    std::set macro_labels{entry_label};
157✔
150
    std::set seen_labels{entry_label};
157✔
151
    while (!macro_labels.empty()) {
331✔
152
        label_t macro_label = *macro_labels.begin();
263✔
153
        macro_labels.erase(macro_label);
263✔
154

155
        if (stack_frame_prefix == macro_label.stack_frame_prefix) {
263✔
156
            throw InvalidControlFlow{stack_frame_prefix + ": illegal recursion"};
4✔
157
        }
158

159
        // Clone the macro block into a new block with the new stack frame prefix.
160
        const label_t label{macro_label.from, macro_label.to, stack_frame_prefix};
524✔
161
        auto inst = builder.prog.instruction_at(macro_label);
261✔
162
        if (const auto pexit = std::get_if<Exit>(&inst)) {
261✔
163
            pexit->stack_frame_prefix = label.stack_frame_prefix;
71✔
164
        } else if (const auto pcall = std::get_if<Call>(&inst)) {
262✔
165
            pcall->stack_frame_prefix = label.stack_frame_prefix;
1✔
166
        }
167
        builder.insert(label, inst);
261✔
168

169
        if (first) {
261✔
170
            // Add an edge from the caller to the new block.
171
            first = false;
70✔
172
            builder.add_child(caller_label, label);
70✔
173
        }
174

175
        // Add an edge from any other predecessors.
176
        for (const auto& prev_macro_nodes = builder.prog.cfg().parents_of(macro_label);
261✔
177
             const auto& prev_macro_label : prev_macro_nodes) {
456✔
178
            const label_t prev_label(prev_macro_label.from, prev_macro_label.to, to_string(caller_label));
195✔
179
            if (const auto& labels = builder.prog.cfg().labels();
195✔
180
                std::ranges::find(labels, prev_label) != labels.end()) {
195✔
181
                builder.add_child(prev_label, label);
192✔
182
            }
183
        }
195✔
184

185
        // Walk all successor nodes.
186
        for (const auto& next_macro_nodes = builder.prog.cfg().children_of(macro_label);
525✔
187
             const auto& next_macro_label : next_macro_nodes) {
525✔
188
            if (next_macro_label == builder.prog.cfg().exit_label()) {
528✔
189
                // This is an exit transition, so add edge to the block to execute
190
                // upon returning from the macro.
191
                builder.add_child(label, exit_to_label);
70✔
192
            } else if (!seen_labels.contains(next_macro_label)) {
194✔
193
                // Push any other unprocessed successor label onto the list to be processed.
194
                if (!macro_labels.contains(next_macro_label)) {
194✔
195
                    macro_labels.insert(next_macro_label);
193✔
196
                }
197
                seen_labels.insert(macro_label);
194✔
198
            }
199
        }
200
    }
785✔
201

202
    // Remove the original edge from the caller node to its successor,
203
    // since processing now goes through the function macro instead.
204
    builder.remove_child(caller_label, exit_to_label);
68✔
205

206
    // Finally, recurse to replace any nested function macros.
207
    string caller_label_str = to_string(caller_label);
68✔
208
    const long stack_frame_depth = std::ranges::count(caller_label_str, STACK_FRAME_DELIMITER) + 2;
68✔
209
    for (const auto& macro_label : seen_labels) {
240✔
210
        const label_t label{macro_label.from, macro_label.to, caller_label_str};
389✔
211
        if (const auto pins = std::get_if<CallLocal>(&builder.prog.instruction_at(label))) {
359✔
212
            if (stack_frame_depth >= MAX_CALL_STACK_FRAMES) {
41✔
213
                throw InvalidControlFlow{"too many call stack frames"};
4✔
214
            }
215
            add_cfg_nodes(builder, label, pins->target);
39✔
216
        }
217
    }
187✔
218
}
312✔
219

220
/// Convert an instruction sequence to a control-flow graph (CFG).
221
static cfg_builder_t instruction_seq_to_cfg(const InstructionSeq& insts, const bool must_have_exit) {
1,250✔
222
    cfg_builder_t builder;
1,250✔
223

224
    // First add all instructions to the CFG without connecting
225
    for (const auto& [label, inst, _] : insts) {
143,912✔
226
        if (std::holds_alternative<Undefined>(inst)) {
142,662✔
227
            continue;
×
228
        }
229
        builder.insert(label, inst);
142,662✔
230
    }
231

232
    if (insts.size() == 0) {
1,250✔
233
        throw InvalidControlFlow{"empty instruction sequence"};
×
234
    } else {
235
        const auto& [label, inst, _0] = insts[0];
1,250✔
236
        builder.add_child(builder.prog.cfg().entry_label(), label);
1,254✔
237
    }
238

239
    // Do a first pass ignoring all function macro calls.
240
    for (size_t i = 0; i < insts.size(); i++) {
143,912✔
241
        const auto& [label, inst, _0] = insts[i];
142,662✔
242

243
        if (std::holds_alternative<Undefined>(inst)) {
142,662✔
244
            continue;
3✔
245
        }
246

247
        label_t fallthrough{builder.prog.cfg().exit_label()};
142,662✔
248
        if (i + 1 < insts.size()) {
142,662✔
249
            fallthrough = std::get<0>(insts[i + 1]);
141,412✔
250
        } else {
251
            if (has_fall(inst) && must_have_exit) {
1,930✔
252
                throw InvalidControlFlow{"fallthrough in last instruction"};
×
253
            }
254
        }
255
        if (const auto jmp = std::get_if<Jmp>(&inst)) {
142,662✔
256
            if (const auto cond = jmp->cond) {
14,867✔
257
                label_t target_label = jmp->target;
12,615✔
258
                if (target_label == fallthrough) {
12,615✔
259
                    builder.add_child(label, fallthrough);
3✔
260
                    continue;
3✔
261
                }
262
                if (!builder.prog.cfg().contains(target_label)) {
12,612✔
263
                    throw InvalidControlFlow{"jump to undefined label " + to_string(target_label)};
×
264
                }
265
                builder.insert_jump(label, target_label, Assume{.cond = *cond, .is_implicit = true});
25,224✔
266
                builder.insert_jump(label, fallthrough, Assume{.cond = reverse(*cond), .is_implicit = true});
37,836✔
267
            } else {
12,615✔
268
                builder.add_child(label, jmp->target);
2,252✔
269
            }
270
        } else {
271
            if (has_fall(inst)) {
127,795✔
272
                builder.add_child(label, fallthrough);
127,088✔
273
            }
274
        }
275
        if (std::holds_alternative<Exit>(inst)) {
142,659✔
276
            builder.add_child(label, builder.prog.cfg().exit_label());
1,414✔
277
        }
278
    }
142,662✔
279

280
    // Now replace macros. We have to do this as a second pass so that
281
    // we only add new nodes that are actually reachable, based on the
282
    // results of the first pass.
283
    for (const auto& [label, inst, _] : insts) {
143,884✔
284
        if (const auto pins = std::get_if<CallLocal>(&inst)) {
142,665✔
285
            add_cfg_nodes(builder, label, pins->target);
31✔
286
        }
287
    }
288

289
    return builder;
1,246✔
290
}
4✔
291

292
Program Program::from_sequence(const InstructionSeq& inst_seq, const program_info& info,
1,250✔
293
                               const ebpf_verifier_options_t& options) {
294
    thread_local_program_info.set(info);
2,500✔
295
    thread_local_options = options;
1,250✔
296

297
    // Convert the instruction sequence to a deterministic control-flow graph.
298
    cfg_builder_t builder = instruction_seq_to_cfg(inst_seq, options.cfg_opts.must_have_exit);
1,250✔
299

300
    // Detect loops using Weak Topological Ordering (WTO) and insert counters at loop entry points. WTO provides a
301
    // hierarchical decomposition of the CFG that identifies all strongly connected components (cycles) and their entry
302
    // points. These entry points serve as natural locations for loop counters that help verify program termination.
303
    if (options.cfg_opts.check_for_termination) {
1,246✔
304
        const crab::wto_t wto{builder.prog.cfg()};
17✔
305
        wto.for_each_loop_head([&](const label_t& label) -> void {
17✔
306
            builder.insert_after(label, label_t::make_increment_counter(label), IncrementLoopCounter{label});
48✔
307
        });
16✔
308
    }
17✔
309

310
    // Annotate the CFG by explicitly adding in assertions before every memory instruction.
311
    for (const auto& label : builder.prog.labels()) {
171,837✔
312
        builder.set_assertions(label, get_assertions(builder.prog.instruction_at(label), info, label));
511,773✔
313
    }
314
    return builder.prog;
1,246✔
315
}
1,246✔
316

317
std::set<basic_block_t> basic_block_t::collect_basic_blocks(const cfg_t& cfg, const bool simplify) {
×
318
    if (!simplify) {
×
319
        std::set<basic_block_t> res;
×
320
        for (const label_t& label : cfg.labels()) {
×
321
            if (label != cfg.entry_label() && label != cfg.exit_label()) {
×
322
                res.insert(basic_block_t{label});
×
323
            }
324
        }
325
        return res;
×
326
    }
×
327

328
    std::set<basic_block_t> res;
×
329
    std::set<label_t> worklist;
×
330
    for (const label_t& label : cfg.labels()) {
×
331
        worklist.insert(label);
×
332
    }
333
    std::set<label_t> seen;
×
334
    while (!worklist.empty()) {
×
335
        label_t label = *worklist.begin();
×
336
        worklist.erase(label);
×
337
        if (seen.contains(label)) {
×
338
            continue;
×
339
        }
340
        seen.insert(label);
×
341

342
        if (cfg.in_degree(label) == 1 && cfg.num_siblings(label) == 1) {
×
343
            continue;
×
344
        }
345
        basic_block_t bb{label};
×
346
        while (cfg.out_degree(label) == 1) {
×
347
            const label_t& next_label = cfg.get_child(bb.last_label());
×
348

349
            if (seen.contains(next_label) || next_label == cfg.exit_label() || cfg.in_degree(next_label) != 1) {
×
350
                break;
351
            }
352

353
            if (bb.first_label() == cfg.entry_label()) {
×
354
                // Entry instruction is Undefined. We want to start with 0
355
                bb.m_ts.clear();
×
356
            }
357
            bb.m_ts.push_back(next_label);
×
358

359
            worklist.erase(next_label);
×
360
            seen.insert(next_label);
×
361

362
            label = next_label;
×
363
        }
×
364
        res.emplace(std::move(bb));
×
365
    }
×
366
    return res;
×
367
}
×
368

369
/// Get the type of given Instruction.
370
/// Most of these type names are also statistics header labels.
371
static std::string instype(Instruction ins) {
×
372
    if (const auto pcall = std::get_if<Call>(&ins)) {
×
373
        if (pcall->is_map_lookup) {
×
374
            return "call_1";
×
375
        }
376
        if (pcall->pairs.empty()) {
×
377
            if (std::ranges::all_of(pcall->singles,
×
378
                                    [](const ArgSingle kr) { return kr.kind == ArgSingle::Kind::ANYTHING; })) {
379
                return "call_nomem";
×
380
            }
381
        }
382
        return "call_mem";
×
383
    } else if (std::holds_alternative<Callx>(ins)) {
384
        return "callx";
×
385
    } else if (const auto pimm = std::get_if<Mem>(&ins)) {
×
386
        return pimm->is_load ? "load" : "store";
×
387
    } else if (std::holds_alternative<Atomic>(ins)) {
388
        return "load_store";
×
389
    } else if (std::holds_alternative<Packet>(ins)) {
390
        return "packet_access";
×
391
    } else if (const auto pins = std::get_if<Bin>(&ins)) {
×
392
        switch (pins->op) {
×
393
        case Bin::Op::MOV:
×
UNCOV
394
        case Bin::Op::MOVSX8:
×
UNCOV
395
        case Bin::Op::MOVSX16:
×
396
        case Bin::Op::MOVSX32: return "assign";
×
397
        default: return "arith";
×
398
        }
399
    } else if (std::holds_alternative<Un>(ins)) {
400
        return "arith";
×
401
    } else if (std::holds_alternative<LoadMapFd>(ins)) {
402
        return "assign";
×
403
    } else if (std::holds_alternative<LoadMapAddress>(ins)) {
404
        return "assign";
×
405
    } else if (std::holds_alternative<Assume>(ins)) {
406
        return "assume";
×
407
    } else {
408
        return "other";
×
409
    }
410
}
411

412
std::vector<std::string> stats_headers() {
×
UNCOV
413
    return {
×
414
        "instructions", "joins",      "other",      "jumps",         "assign",  "arith",
415
        "load",         "store",      "load_store", "packet_access", "call_1",  "call_mem",
416
        "call_nomem",   "reallocate", "map_in_map", "arith64",       "arith32",
417
    };
×
418
}
419

420
std::map<std::string, int> collect_stats(const Program& prog) {
×
421
    std::map<std::string, int> res;
×
422
    for (const auto& h : stats_headers()) {
×
423
        res[h] = 0;
×
424
    }
×
425
    for (const auto& label : prog.labels()) {
×
426
        res["instructions"]++;
×
427
        const auto cmd = prog.instruction_at(label);
×
428
        if (const auto pins = std::get_if<LoadMapFd>(&cmd)) {
×
429
            if (pins->mapfd == -1) {
×
430
                res["map_in_map"] = 1;
×
431
            }
432
        }
433
        if (const auto pins = std::get_if<Call>(&cmd)) {
×
434
            if (pins->reallocate_packet) {
×
435
                res["reallocate"] = 1;
×
436
            }
437
        }
438
        if (const auto pins = std::get_if<Bin>(&cmd)) {
×
439
            res[pins->is64 ? "arith64" : "arith32"]++;
×
440
        }
441
        res[instype(cmd)]++;
×
442
        if (prog.cfg().in_degree(label) > 1) {
×
443
            res["joins"]++;
×
444
        }
445
        if (prog.cfg().out_degree(label) > 1) {
×
446
            res["jumps"]++;
×
447
        }
448
    }
×
449
    return res;
×
450
}
×
451

452
crab::cfg_t crab::cfg_from_adjacency_list(const std::map<label_t, std::vector<label_t>>& adj_list) {
3✔
453
    cfg_builder_t builder;
3✔
454
    for (const auto& label : std::views::keys(adj_list)) {
23✔
455
        if (label == label_t::entry || label == label_t::exit) {
20✔
456
            continue;
3✔
457
        }
458
        builder.insert(label, Undefined{});
34✔
459
    }
460
    for (const auto& [label, children] : adj_list) {
23✔
461
        for (const auto& child : children) {
47✔
462
            builder.add_child(label, child);
27✔
463
        }
464
    }
465
    return builder.prog.cfg();
6✔
466
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc