• 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.6
/src/ir/unmarshal.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <cassert>
4
#include <iostream>
5
#include <string>
6
#include <vector>
7

8
#include "crab_utils/debug.hpp"
9
#include "crab_utils/num_safety.hpp"
10
#include "ir/unmarshal.hpp"
11
#include "spec/vm_isa.hpp"
12

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

16
namespace prevail {
17
int opcode_to_width(const uint8_t opcode) {
51,532✔
18
    switch (opcode & INST_SIZE_MASK) {
51,532✔
19
    case INST_SIZE_B: return 1;
2,678✔
20
    case INST_SIZE_H: return 2;
8,298✔
21
    case INST_SIZE_W: return 4;
15,390✔
22
    case INST_SIZE_DW: return 8;
22,488✔
23
    default: CRAB_ERROR("unexpected opcode", opcode);
×
24
    }
25
}
26

27
uint8_t width_to_opcode(const int width) {
122✔
28
    switch (width) {
122✔
29
    case 1: return INST_SIZE_B;
14✔
30
    case 2: return INST_SIZE_H;
14✔
31
    case 4: return INST_SIZE_W;
38✔
32
    case 8: return INST_SIZE_DW;
42✔
33
    default: CRAB_ERROR("unexpected width", width);
×
34
    }
35
}
36

37
template <typename T>
38
void compare(const string& field, T actual, T expected) {
39
    if (actual != expected) {
40
        std::cerr << field << ": (actual) " << std::hex << static_cast<int>(actual)
41
                  << " != " << static_cast<int>(expected) << " (expected)\n";
42
    }
43
}
44

45
static std::string make_opcode_message(const char* msg, const uint8_t opcode) {
1,108✔
46
    std::ostringstream oss;
1,108✔
47
    oss << msg << " op 0x" << std::hex << static_cast<int>(opcode);
1,108✔
48
    return oss.str();
2,216✔
49
}
1,108✔
50

51
struct InvalidInstruction final : std::invalid_argument {
52
    size_t pc;
53
    explicit InvalidInstruction(const size_t pc, const char* what) : std::invalid_argument{what}, pc{pc} {}
1,074✔
54
    InvalidInstruction(const size_t pc, const std::string& what) : std::invalid_argument{what}, pc{pc} {}
229✔
55
    InvalidInstruction(const size_t pc, const uint8_t opcode)
784✔
56
        : std::invalid_argument{make_opcode_message("bad instruction", opcode)}, pc{pc} {}
1,176✔
57
};
58

59
static auto getMemIsLoad(const uint8_t opcode) -> bool {
77,848✔
60
    switch (opcode & INST_CLS_MASK) {
77,848✔
61
    case INST_CLS_LD:
16,869✔
62
    case INST_CLS_LDX: return true;
16,869✔
63
    case INST_CLS_ST:
44,110✔
64
    case INST_CLS_STX: return false;
44,110✔
65
    default: CRAB_ERROR("unexpected opcode", opcode);
×
66
    }
67
}
68

69
static auto getMemWidth(const uint8_t opcode) -> int {
78,986✔
70
    switch (opcode & INST_SIZE_MASK) {
78,986✔
71
    case INST_SIZE_B: return 1;
6,939✔
72
    case INST_SIZE_H: return 2;
12,438✔
73
    case INST_SIZE_W: return 4;
27,168✔
74
    case INST_SIZE_DW: return 8;
25,502✔
75
    default: CRAB_ERROR("unexpected opcode", opcode);
×
76
    }
77
}
78

79
static Instruction shift32(const Reg dst, const Bin::Op op) {
175,546✔
80
    return Bin{.op = op, .dst = dst, .v = Imm{32}, .is64 = true, .lddw = false};
175,546✔
81
}
82

83
struct Unmarshaller {
84
    vector<vector<string>>& notes;
85
    const ProgramInfo& info;
86
    // ReSharper disable once CppMemberFunctionMayBeConst
87
    void note(const string& what) { notes.back().emplace_back(what); }
848✔
88
    // ReSharper disable once CppMemberFunctionMayBeConst
89
    void note_next_pc() { notes.emplace_back(); }
354,962✔
90
    explicit Unmarshaller(vector<vector<string>>& notes, const ProgramInfo& info) : notes{notes}, info{info} {
4,744✔
91
        note_next_pc();
7,116✔
92
    }
4,744✔
93

94
    auto getAluOp(const size_t pc, const EbpfInst inst) -> std::variant<Bin::Op, Un::Op> {
172,930✔
95
        // First handle instructions that support a non-zero offset.
96
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
172,930✔
97
        switch (inst.opcode & INST_ALU_OP_MASK) {
172,930✔
98
        case INST_ALU_OP_DIV:
2,458✔
99
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
3,535✔
100
                                                    : bpf_conformance_groups_t::divmul32)) {
101
                throw InvalidInstruction(pc, inst.opcode);
16✔
102
            }
103
            switch (inst.offset) {
2,442✔
104
            case 0: return Bin::Op::UDIV;
2,380✔
105
            case 1: return Bin::Op::SDIV;
54✔
106
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
12✔
107
            }
108
        case INST_ALU_OP_MOD:
146✔
109
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
180✔
110
                                                    : bpf_conformance_groups_t::divmul32)) {
111
                throw InvalidInstruction(pc, inst.opcode);
16✔
112
            }
113
            switch (inst.offset) {
130✔
114
            case 0: return Bin::Op::UMOD;
54✔
115
            case 1: return Bin::Op::SMOD;
68✔
116
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
12✔
117
            }
118
        case INST_ALU_OP_MOV:
91,470✔
119
            if (inst.offset > 0 && !(inst.opcode & INST_SRC_REG)) {
91,470✔
120
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
121
            }
122
            switch (inst.offset) {
91,466✔
123
            case 0: return Bin::Op::MOV;
91,410✔
124
            case 8: return Bin::Op::MOVSX8;
16✔
125
            case 16: return Bin::Op::MOVSX16;
16✔
126
            case 32: return Bin::Op::MOVSX32;
10✔
127
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
21✔
128
            }
129
        default: break;
78,856✔
130
        }
131

132
        // All the rest require a zero offset.
133
        if (inst.offset != 0) {
78,856✔
134
            throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
141✔
135
        }
136

137
        switch (inst.opcode & INST_ALU_OP_MASK) {
78,762✔
138
        case INST_ALU_OP_ADD: return Bin::Op::ADD;
32,990✔
139
        case INST_ALU_OP_SUB: return Bin::Op::SUB;
838✔
140
        case INST_ALU_OP_MUL:
204✔
141
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
232✔
142
                                                    : bpf_conformance_groups_t::divmul32)) {
143
                throw InvalidInstruction(pc, inst.opcode);
8✔
144
            }
145
            return Bin::Op::MUL;
196✔
146
        case INST_ALU_OP_OR: return Bin::Op::OR;
10,082✔
147
        case INST_ALU_OP_AND: return Bin::Op::AND;
8,920✔
148
        case INST_ALU_OP_LSH: return Bin::Op::LSH;
10,950✔
149
        case INST_ALU_OP_RSH: return Bin::Op::RSH;
6,922✔
150
        case INST_ALU_OP_NEG:
168✔
151
            // Negation is a unary operation. The SRC bit, src, and imm must be all 0.
152
            if (inst.opcode & INST_SRC_REG) {
168✔
153
                throw InvalidInstruction{pc, inst.opcode};
4✔
154
            }
155
            if (inst.src != 0) {
164✔
156
                throw InvalidInstruction{pc, inst.opcode};
4✔
157
            }
158
            if (inst.imm != 0) {
160✔
159
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
6✔
160
            }
161
            return Un::Op::NEG;
156✔
162
        case INST_ALU_OP_XOR: return Bin::Op::XOR;
1,086✔
163
        case INST_ALU_OP_ARSH:
3,572✔
164
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU) {
3,572✔
165
                note("arsh32 is not allowed");
356✔
166
            }
167
            return Bin::Op::ARSH;
3,572✔
168
        case INST_ALU_OP_END:
3,014✔
169
            if (inst.src != 0) {
3,014✔
170
                throw InvalidInstruction{pc, inst.opcode};
18✔
171
            }
172
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) {
2,996✔
173
                if (inst.opcode & INST_END_BE) {
32✔
174
                    throw InvalidInstruction(pc, inst.opcode);
2✔
175
                }
176
                switch (inst.imm) {
30✔
177
                case 16:
8✔
178
                    if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
8✔
179
                        throw InvalidInstruction(pc, inst.opcode);
2✔
180
                    }
181
                    return Un::Op::SWAP16;
6✔
182
                case 32:
8✔
183
                    if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
8✔
184
                        throw InvalidInstruction(pc, inst.opcode);
2✔
185
                    }
186
                    return Un::Op::SWAP32;
6✔
187
                case 64:
6✔
188
                    if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) {
6✔
189
                        throw InvalidInstruction(pc, inst.opcode);
×
190
                    }
191
                    return Un::Op::SWAP64;
6✔
192
                default: throw InvalidInstruction(pc, "unsupported immediate");
8✔
193
                }
194
            }
195
            switch (inst.imm) {
2,964✔
196
            case 16:
2,746✔
197
                if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
2,746✔
198
                    throw InvalidInstruction(pc, inst.opcode);
×
199
                }
200
                return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16;
2,752✔
201
            case 32:
156✔
202
                if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
156✔
203
                    throw InvalidInstruction(pc, inst.opcode);
×
204
                }
205
                return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32;
162✔
206
            case 64:
46✔
207
                if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) {
46✔
208
                    throw InvalidInstruction(pc, inst.opcode);
4✔
209
                }
210
                return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64;
47✔
211
            default: throw InvalidInstruction(pc, "unsupported immediate");
16✔
212
            }
213
        case 0xe0: throw InvalidInstruction{pc, inst.opcode};
8✔
214
        case 0xf0: throw InvalidInstruction{pc, inst.opcode};
8✔
215
        default: return {};
×
216
        }
217
    }
218

219
    static auto getAtomicOp(const size_t pc, const EbpfInst inst) -> Atomic::Op {
508✔
220
        switch (const auto op = gsl::narrow<Atomic::Op>(inst.imm & ~INST_FETCH)) {
508✔
221
        case Atomic::Op::XCHG:
36✔
222
        case Atomic::Op::CMPXCHG:
18✔
223
            if ((inst.imm & INST_FETCH) == 0) {
36✔
224
                throw InvalidInstruction(pc, "unsupported immediate");
8✔
225
            }
226
        case Atomic::Op::ADD:
238✔
227
        case Atomic::Op::OR:
238✔
228
        case Atomic::Op::AND:
238✔
229
        case Atomic::Op::XOR: return op;
476✔
230
        }
231
        throw InvalidInstruction(pc, "unsupported immediate");
24✔
232
    }
233

234
    static uint64_t sign_extend(const int32_t imm) { return to_unsigned(int64_t{imm}); }
126,346✔
235

236
    static uint64_t zero_extend(const int32_t imm) { return uint64_t{to_unsigned(imm)}; }
30✔
237

238
    static auto getBinValue(const Pc pc, const EbpfInst inst) -> Value {
169,564✔
239
        if (inst.opcode & INST_SRC_REG) {
169,564✔
240
            if (inst.imm != 0) {
67,588✔
241
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
66✔
242
            }
243
            return Reg{inst.src};
67,522✔
244
        }
245
        if (inst.src != 0) {
101,976✔
246
            throw InvalidInstruction{pc, inst.opcode};
56✔
247
        }
248
        // Imm is a signed 32-bit number.  Sign extend it to 64-bits for storage.
249
        return Imm{sign_extend(inst.imm)};
101,920✔
250
    }
251

252
    static auto getJmpOp(const size_t pc, const uint8_t opcode) -> Condition::Op {
36,018✔
253
        using Op = Condition::Op;
18,009✔
254
        switch ((opcode >> 4) & 0xF) {
36,018✔
255
        case 0x0: return {}; // goto
2,758✔
256
        case 0x1: return Op::EQ;
6,409✔
257
        case 0x2: return Op::GT;
2,458✔
258
        case 0x3: return Op::GE;
378✔
259
        case 0x4: return Op::SET;
66✔
260
        case 0x5: return Op::NE;
9,946✔
261
        case 0x6: return Op::SGT;
2,086✔
262
        case 0x7: return Op::SGE;
72✔
263
        case 0x8: return {}; // call
264
        case 0x9: return {}; // exit
265
        case 0xa: return Op::LT;
786✔
266
        case 0xb: return Op::LE;
120✔
267
        case 0xc: return Op::SLT;
1,684✔
268
        case 0xd: return Op::SLE;
72✔
269
        case 0xe: throw InvalidInstruction(pc, opcode);
8✔
270
        case 0xf: throw InvalidInstruction(pc, opcode);
8✔
271
        default: return {};
272
        }
273
    }
274

275
    auto makeMemOp(const Pc pc, const EbpfInst inst) -> Instruction {
79,112✔
276
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
79,112✔
277
            throw InvalidInstruction(pc, "bad register");
126✔
278
        }
279

280
        const int width = getMemWidth(inst.opcode);
78,986✔
281
        if (!info.platform->supports_group((width == sizeof(uint64_t)) ? bpf_conformance_groups_t::base64
105,728✔
282
                                                                       : bpf_conformance_groups_t::base32)) {
283
            throw InvalidInstruction(pc, inst.opcode);
24✔
284
        }
285
        const bool isLD = (inst.opcode & INST_CLS_MASK) == INST_CLS_LD;
78,962✔
286
        switch (inst.opcode & INST_MODE_MASK) {
78,962✔
287
        case INST_MODE_IMM: throw InvalidInstruction(pc, inst.opcode);
30✔
288

289
        case INST_MODE_ABS:
162✔
290
            if (!info.platform->supports_group(bpf_conformance_groups_t::packet) || !isLD || (width == 8)) {
162✔
291
                throw InvalidInstruction(pc, inst.opcode);
62✔
292
            }
293
            if (inst.dst != 0) {
100✔
294
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
6✔
295
            }
296
            if (inst.src > 0) {
94✔
297
                throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode));
6✔
298
            }
299
            if (inst.offset != 0) {
88✔
300
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
301
            }
302
            return Packet{.width = width, .offset = inst.imm, .regoffset = {}};
82✔
303

304
        case INST_MODE_IND:
242✔
305
            if (!info.platform->supports_group(bpf_conformance_groups_t::packet) || !isLD || (width == 8)) {
242✔
306
                throw InvalidInstruction(pc, inst.opcode);
52✔
307
            }
308
            if (inst.dst != 0) {
190✔
309
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
6✔
310
            }
311
            if (inst.src > R10_STACK_POINTER) {
184✔
312
                throw InvalidInstruction(pc, "bad register");
313
            }
314
            if (inst.offset != 0) {
184✔
315
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
316
            }
317
            return Packet{.width = width, .offset = inst.imm, .regoffset = Reg{inst.src}};
178✔
318

319
        case INST_MODE_MEM: {
77,856✔
320
            if (isLD) {
77,856✔
321
                throw InvalidInstruction(pc, inst.opcode);
8✔
322
            }
323
            const bool isLoad = getMemIsLoad(inst.opcode);
77,848✔
324
            if (isLoad && inst.dst == R10_STACK_POINTER) {
77,848✔
325
                throw InvalidInstruction(pc, "cannot modify r10");
2✔
326
            }
327
            const bool isImm = !(inst.opcode & 1);
77,846✔
328
            if (isImm && inst.src != 0) {
77,846✔
329
                throw InvalidInstruction(pc, inst.opcode);
8✔
330
            }
331
            if (!isImm && inst.imm != 0) {
77,823✔
332
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
16✔
333
            }
334

335
            assert(!(isLoad && isImm));
77,822✔
336
            const uint8_t basereg = isLoad ? inst.src : inst.dst;
77,822✔
337

338
            if (basereg == R10_STACK_POINTER &&
103,588✔
339
                (inst.offset + opcode_to_width(inst.opcode) > 0 || inst.offset < -EBPF_TOTAL_STACK_SIZE)) {
51,532✔
340
                note("Stack access out of bounds");
16✔
341
            }
342
            auto res = Mem{
38,911✔
343
                .access =
344
                    Deref{
345
                        .width = width,
346
                        .basereg = Reg{basereg},
347
                        .offset = inst.offset,
77,822✔
348
                    },
349
                .value = isLoad  ? Value{Reg{inst.dst}}
16,864✔
350
                         : isImm ? Value{Imm{zero_extend(inst.imm)}}
44,094✔
351
                                 : Value{Reg{inst.src}},
22,032✔
352
                .is_load = isLoad,
353
            };
121,916✔
354
            return res;
77,822✔
355
        }
356

357
        case INST_MODE_ATOMIC:
576✔
358
            if (((inst.opcode & INST_CLS_MASK) != INST_CLS_STX) ||
576✔
359
                ((inst.opcode & INST_SIZE_MASK) != INST_SIZE_W && (inst.opcode & INST_SIZE_MASK) != INST_SIZE_DW)) {
552✔
360
                throw InvalidInstruction(pc, inst.opcode);
28✔
361
            }
362
            if (!info.platform->supports_group(((inst.opcode & INST_SIZE_MASK) == INST_SIZE_DW)
600✔
363
                                                   ? bpf_conformance_groups_t::atomic64
364
                                                   : bpf_conformance_groups_t::atomic32)) {
365
                throw InvalidInstruction(pc, inst.opcode);
40✔
366
            }
367
            return Atomic{
746✔
368
                .op = getAtomicOp(pc, inst),
508✔
369
                .fetch = (inst.imm & INST_FETCH) == INST_FETCH,
476✔
370
                .access =
371
                    Deref{
372
                        .width = width,
373
                        .basereg = Reg{inst.dst},
476✔
374
                        .offset = inst.offset,
476✔
375
                    },
376
                .valreg = Reg{inst.src},
476✔
377
            };
476✔
378
        default: throw InvalidInstruction(pc, inst.opcode);
96✔
379
        }
380
    }
381

382
    auto makeAluOp(const size_t pc, const EbpfInst inst) -> Instruction {
173,238✔
383
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
173,238✔
384
        if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64
199,139✔
385
                                                : bpf_conformance_groups_t::base32)) {
386
            throw InvalidInstruction(pc, inst.opcode);
96✔
387
        }
388
        if (inst.dst == R10_STACK_POINTER) {
173,142✔
389
            throw InvalidInstruction(pc, "invalid target r10");
2✔
390
        }
391
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
173,140✔
392
            throw InvalidInstruction(pc, "bad register");
210✔
393
        }
394
        return std::visit(
86,465✔
395
            Overloaded{[&](const Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; },
89,520✔
396
                       [&](const Bin::Op op) -> Instruction {
169,564✔
397
                           Bin res{
169,564✔
398
                               .op = op,
399
                               .dst = Reg{inst.dst},
169,564✔
400
                               .v = getBinValue(pc, inst),
169,564✔
401
                               .is64 = is64,
169,442✔
402
                           };
169,564✔
403
                           if (!thread_local_options.allow_division_by_zero &&
169,442✔
404
                               (op == Bin::Op::UDIV || op == Bin::Op::UMOD)) {
×
405
                               if (const auto pimm = std::get_if<Imm>(&res.v)) {
×
406
                                   if (pimm->v == 0) {
×
407
                                       note("division by zero");
×
408
                                   }
409
                               }
410
                           }
411
                           return res;
254,163✔
412
                       }},
413
            getAluOp(pc, inst));
259,271✔
414
    }
415

416
    [[nodiscard]]
417
    auto makeLddw(const EbpfInst inst, const int32_t next_imm, const vector<EbpfInst>& insts,
20,378✔
418
                  const Pc pc) const -> Instruction {
419
        if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) {
20,378✔
420
            throw InvalidInstruction{pc, inst.opcode};
6✔
421
        }
422
        if (pc >= insts.size() - 1) {
20,372✔
423
            throw InvalidInstruction(pc, "incomplete lddw");
18✔
424
        }
425
        const EbpfInst next = insts[pc + 1];
20,354✔
426
        if (next.opcode != 0 || next.dst != 0 || next.src != 0 || next.offset != 0) {
20,354✔
427
            throw InvalidInstruction(pc, "invalid lddw");
16✔
428
        }
429
        if (inst.src > INST_LD_MODE_MAP_VALUE) {
20,338✔
430
            throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode));
2✔
431
        }
432
        if (inst.offset != 0) {
20,336✔
433
            throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
434
        }
435
        if (inst.dst > R10_STACK_POINTER) {
20,330✔
436
            throw InvalidInstruction(pc, "bad register");
6✔
437
        }
438

439
        switch (inst.src) {
20,324✔
440
        case INST_LD_MODE_IMM:
6,182✔
441
            return Bin{
15,455✔
442
                .op = Bin::Op::MOV,
443
                .dst = Reg{inst.dst},
6,182✔
444
                .v = Imm{merge(inst.imm, next_imm)},
9,273✔
445
                .is64 = true,
446
                .lddw = true,
447
            };
6,182✔
448
        case INST_LD_MODE_MAP_FD: {
13,716✔
449
            // magic number, meaning we're a per-process file descriptor defining the map.
450
            // (for details, look for BPF_PSEUDO_MAP_FD in the kernel)
451
            if (next.imm != 0) {
13,716✔
452
                throw InvalidInstruction(pc, "lddw uses reserved fields");
2✔
453
            }
454
            return LoadMapFd{.dst = Reg{inst.dst}, .mapfd = inst.imm};
13,714✔
455
        }
456
        case INST_LD_MODE_MAP_VALUE: return LoadMapAddress{.dst = Reg{inst.dst}, .mapfd = inst.imm, .offset = next_imm};
426✔
457
        default: throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode));
458
        }
459
    }
460

461
    static ArgSingle::Kind toArgSingleKind(const ebpf_argument_type_t t) {
53,302✔
462
        switch (t) {
53,302✔
463
        case EBPF_ARGUMENT_TYPE_ANYTHING: return ArgSingle::Kind::ANYTHING;
7,428✔
464
        case EBPF_ARGUMENT_TYPE_PTR_TO_STACK: return ArgSingle::Kind::PTR_TO_STACK;
465
        case EBPF_ARGUMENT_TYPE_PTR_TO_STACK_OR_NULL: return ArgSingle::Kind::PTR_TO_STACK;
466
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP: return ArgSingle::Kind::MAP_FD;
6,948✔
467
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS: return ArgSingle::Kind::MAP_FD_PROGRAMS;
270✔
468
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY: return ArgSingle::Kind::PTR_TO_MAP_KEY;
6,347✔
469
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE: return ArgSingle::Kind::PTR_TO_MAP_VALUE;
2,630✔
470
        case EBPF_ARGUMENT_TYPE_PTR_TO_CTX: return ArgSingle::Kind::PTR_TO_CTX;
3,004✔
471
        case EBPF_ARGUMENT_TYPE_PTR_TO_CTX_OR_NULL: return ArgSingle::Kind::PTR_TO_CTX;
24✔
472
        default: break;
473
        }
474
        return {};
475
    }
476

477
    static ArgPair::Kind toArgPairKind(const ebpf_argument_type_t t) {
5,092✔
478
        switch (t) {
5,092✔
479
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL:
1,870✔
480
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM: return ArgPair::Kind::PTR_TO_READABLE_MEM;
1,870✔
481
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL:
1,352✔
482
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM: return ArgPair::Kind::PTR_TO_WRITABLE_MEM;
1,352✔
483
        default: break;
484
        }
485
        return {};
486
    }
487

488
    [[nodiscard]]
489
    auto makeCall(const int32_t imm) const -> Call {
22,688✔
490
        const EbpfHelperPrototype proto = info.platform->get_helper_prototype(imm);
22,688✔
491
        if (proto.return_type == EBPF_RETURN_TYPE_UNSUPPORTED) {
22,688✔
492
            throw std::runtime_error(std::string("unsupported function: ") + proto.name);
×
493
        }
494
        Call res;
22,688✔
495
        res.func = imm;
22,688✔
496
        res.name = proto.name;
22,688✔
497
        res.reallocate_packet = proto.reallocate_packet;
22,688✔
498
        res.is_map_lookup = proto.return_type == EBPF_RETURN_TYPE_PTR_TO_MAP_VALUE_OR_NULL;
22,688✔
499
        const std::array<ebpf_argument_type_t, 7> args = {
22,688✔
500
            {EBPF_ARGUMENT_TYPE_DONTCARE, proto.argument_type[0], proto.argument_type[1], proto.argument_type[2],
22,688✔
501
             proto.argument_type[3], proto.argument_type[4], EBPF_ARGUMENT_TYPE_DONTCARE}};
22,688✔
502
        for (size_t i = 1; i < args.size() - 1; i++) {
121,623✔
503
            switch (args[i]) {
77,626✔
504
            case EBPF_ARGUMENT_TYPE_UNSUPPORTED: {
×
505
                throw std::runtime_error(std::string("unsupported function: ") + proto.name);
×
506
            }
507
            case EBPF_ARGUMENT_TYPE_DONTCARE: return res;
9,616✔
508
            case EBPF_ARGUMENT_TYPE_ANYTHING:
53,254✔
509
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP:
26,627✔
510
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS:
26,627✔
511
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY:
26,627✔
512
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE:
26,627✔
513
            case EBPF_ARGUMENT_TYPE_PTR_TO_STACK:
26,627✔
514
            case EBPF_ARGUMENT_TYPE_PTR_TO_CTX:
26,627✔
515
                res.singles.push_back({toArgSingleKind(args[i]), false, Reg{gsl::narrow<uint8_t>(i)}});
53,254✔
516
                break;
53,254✔
517
            case EBPF_ARGUMENT_TYPE_PTR_TO_STACK_OR_NULL:
48✔
518
            case EBPF_ARGUMENT_TYPE_PTR_TO_CTX_OR_NULL:
24✔
519
                res.singles.push_back({toArgSingleKind(args[i]), true, Reg{gsl::narrow<uint8_t>(i)}});
48✔
520
                break;
48✔
521
            case EBPF_ARGUMENT_TYPE_CONST_SIZE: {
×
522
                // Sanity check: This argument should never be seen in isolation.
523
                throw std::runtime_error(
524
                    std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE: ") +
×
525
                    proto.name);
×
526
            }
527
            case EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: {
×
528
                // Sanity check: This argument should never be seen in isolation.
529
                throw std::runtime_error(
530
                    std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
×
531
                    proto.name);
×
532
            }
533
            case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL:
5,092✔
534
            case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM:
2,546✔
535
            case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL:
2,546✔
536
            case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM:
2,546✔
537
                // Sanity check: This argument must be followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or
538
                // EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO.
539
                if (args.size() - i < 2) {
5,092✔
540
                    throw std::runtime_error(
541
                        std::string(
542
                            "missing EBPF_ARGUMENT_TYPE_CONST_SIZE or EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
543
                        proto.name);
544
                }
545
                if (args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE &&
6,246✔
546
                    args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO) {
2,308✔
547
                    throw std::runtime_error(
548
                        std::string("Pointer argument not followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or "
×
549
                                    "EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
×
550
                        proto.name);
×
551
                }
552
                const bool can_be_zero = (args[i + 1] == EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO);
5,092✔
553
                const bool or_null = args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL ||
7,130✔
554
                                     args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL;
4,076✔
555
                res.pairs.push_back({toArgPairKind(args[i]), or_null, Reg{gsl::narrow<uint8_t>(i)},
8,314✔
556
                                     Reg{gsl::narrow<uint8_t>(i + 1)}, can_be_zero});
5,092✔
557
                i++;
5,092✔
558
                break;
5,092✔
559
            }
560
        }
561
        return res;
1,728✔
562
    }
×
563

564
    /// Given a program counter and an offset, get the label of the target instruction.
565
    static Label getJumpTarget(const int32_t offset, const vector<EbpfInst>& insts, const Pc pc) {
35,928✔
566
        const Pc new_pc = pc + 1 + offset;
35,928✔
567
        if (new_pc >= insts.size()) {
35,928✔
568
            throw InvalidInstruction(pc, "jump out of bounds");
138✔
569
        }
570
        if (insts[new_pc].opcode == 0) {
35,790✔
571
            throw InvalidInstruction(pc, "jump to middle of lddw");
×
572
        }
573
        return Label{gsl::narrow<int>(new_pc)};
35,790✔
574
    }
575

576
    static auto makeCallLocal(const EbpfInst inst, const vector<EbpfInst>& insts, const Pc pc) -> CallLocal {
22✔
577
        if (inst.opcode & INST_SRC_REG) {
22✔
578
            throw InvalidInstruction(pc, inst.opcode);
2✔
579
        }
580
        if (inst.dst != 0) {
20✔
581
            throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
582
        }
583
        return CallLocal{.target = getJumpTarget(inst.imm, insts, pc)};
18✔
584
    }
585

586
    static auto makeCallx(const EbpfInst inst, const Pc pc) -> Callx {
22✔
587
        // callx puts the register number in the 'dst' field rather than the 'src' field.
588
        if (inst.dst > R10_STACK_POINTER) {
22✔
589
            throw InvalidInstruction(pc, "bad register");
4✔
590
        }
591
        if (inst.imm != 0) {
18✔
592
            // Clang prior to v19 put the register number into the 'imm' field.
593
            if (inst.dst > 0) {
10✔
594
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
4✔
595
            }
596
            if (inst.imm < 0 || inst.imm > R10_STACK_POINTER) {
6✔
597
                throw InvalidInstruction(pc, "bad register");
4✔
598
            }
599
            return Callx{gsl::narrow<uint8_t>(inst.imm)};
2✔
600
        }
601
        return Callx{inst.dst};
8✔
602
    }
603

604
    [[nodiscard]]
605
    auto makeJmp(const EbpfInst inst, const vector<EbpfInst>& insts, const Pc pc) const -> Instruction {
59,914✔
606
        switch ((inst.opcode >> 4) & 0xF) {
59,914✔
607
        case INST_CALL:
21,738✔
608
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) {
21,738✔
609
                throw InvalidInstruction(pc, inst.opcode);
4✔
610
            }
611
            if (!info.platform->supports_group(bpf_conformance_groups_t::callx) && (inst.opcode & INST_SRC_REG)) {
21,734✔
612
                throw InvalidInstruction(pc, inst.opcode);
4✔
613
            }
614
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
21,730✔
615
                throw InvalidInstruction(pc, inst.opcode);
4✔
616
            }
617
            if (inst.src >= INST_CALL_BTF_HELPER) {
21,726✔
618
                throw InvalidInstruction(pc, inst.opcode);
2✔
619
            }
620
            if (inst.offset != 0) {
21,724✔
621
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
622
            }
623
            if (inst.src == INST_CALL_LOCAL) {
21,718✔
624
                return makeCallLocal(inst, insts, pc);
30✔
625
            }
626
            if (inst.opcode & INST_SRC_REG) {
21,696✔
627
                return makeCallx(inst, pc);
22✔
628
            }
629
            if (inst.dst != 0) {
21,674✔
630
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
631
            }
632
            if (!info.platform->is_helper_usable(inst.imm)) {
21,672✔
633
                throw InvalidInstruction(pc, "invalid helper function id " + std::to_string(inst.imm));
2✔
634
            }
635
            return makeCall(inst.imm);
32,505✔
636
        case INST_EXIT:
2,054✔
637
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
2,054✔
638
                throw InvalidInstruction(pc, inst.opcode);
2✔
639
            }
640
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) {
2,052✔
641
                throw InvalidInstruction(pc, inst.opcode);
6✔
642
            }
643
            if (inst.src != 0) {
2,046✔
644
                throw InvalidInstruction(pc, inst.opcode);
2✔
645
            }
646
            if (inst.dst != 0) {
2,044✔
647
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
648
            }
649
            if (inst.imm != 0) {
2,042✔
650
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
2✔
651
            }
652
            if (inst.offset != 0) {
2,040✔
653
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
2✔
654
            }
655
            return Exit{};
2,038✔
656
        case INST_JA:
5,532✔
657
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) {
5,532✔
658
                throw InvalidInstruction(pc, inst.opcode);
×
659
            }
660
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
5,532✔
661
                throw InvalidInstruction(pc, inst.opcode);
4✔
662
            }
663
            if (inst.opcode & INST_SRC_REG) {
5,528✔
664
                throw InvalidInstruction(pc, inst.opcode);
4✔
665
            }
666
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP && (inst.imm != 0)) {
5,524✔
667
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
2✔
668
            }
669
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP32 && (inst.offset != 0)) {
5,522✔
670
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
2✔
671
            }
672
            if (inst.dst != 0) {
5,520✔
673
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
4✔
674
            }
675
        default: {
18,053✔
676
            // First validate the opcode, src, and imm.
677
            const auto is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP;
36,106✔
678
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64
42,524✔
679
                                                    : bpf_conformance_groups_t::base32)) {
680
                throw InvalidInstruction(pc, inst.opcode);
88✔
681
            }
682
            const auto op = getJmpOp(pc, inst.opcode);
36,018✔
683
            if (!(inst.opcode & INST_SRC_REG) && (inst.src != 0)) {
36,002✔
684
                throw InvalidInstruction(pc, inst.opcode);
48✔
685
            }
686
            if ((inst.opcode & INST_SRC_REG) && (inst.imm != 0)) {
35,954✔
687
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
44✔
688
            }
689

690
            const int32_t offset = (inst.opcode == INST_OP_JA32) ? inst.imm : inst.offset;
35,910✔
691
            const Label target = getJumpTarget(offset, insts, pc);
35,910✔
692
            if (inst.opcode != INST_OP_JA16 && inst.opcode != INST_OP_JA32) {
35,774✔
693
                if (inst.dst > R10_STACK_POINTER) {
30,266✔
694
                    throw InvalidInstruction(pc, "bad register");
88✔
695
                }
696
                if ((inst.opcode & INST_SRC_REG) && inst.src > R10_STACK_POINTER) {
30,178✔
697
                    throw InvalidInstruction(pc, "bad register");
44✔
698
                }
699
            }
700

701
            const auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32)
32,892✔
702
                                  ? std::optional<Condition>{}
35,642✔
703
                                  : Condition{.op = op,
15,067✔
704
                                              .left = Reg{inst.dst},
30,134✔
705
                                              .right = (inst.opcode & INST_SRC_REG) ? Value{Reg{inst.src}}
30,134✔
706
                                                                                    : Value{Imm{sign_extend(inst.imm)}},
24,426✔
707
                                              .is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP};
35,642✔
708
            return Jmp{.cond = cond, .target = target};
53,529✔
709
        }
35,708✔
710
        }
711
    }
712

713
    vector<LabeledInstruction> unmarshal(vector<EbpfInst> const& insts,
3,726✔
714
                                         const prevail::ebpf_verifier_options_t& options) {
715
        vector<LabeledInstruction> prog;
3,726✔
716
        int exit_count = 0;
3,726✔
717
        if (insts.empty()) {
3,726✔
718
            throw std::invalid_argument("Zero length programs are not allowed");
×
719
        }
720
        for (size_t pc = 0; pc < insts.size();) {
329,776✔
721
            const EbpfInst inst = insts[pc];
327,876✔
722
            Instruction new_ins;
327,876✔
723
            bool skip_instruction = false;
327,876✔
724
            bool fallthrough = true;
327,876✔
725
            switch (inst.opcode & INST_CLS_MASK) {
327,876✔
726
            case INST_CLS_LD:
20,786✔
727
                if (inst.opcode == INST_OP_LDDW_IMM) {
20,786✔
728
                    const int32_t next_imm = pc < insts.size() - 1 ? insts[pc + 1].imm : 0;
20,378✔
729
                    new_ins = makeLddw(inst, next_imm, insts, pc);
30,539✔
730
                    skip_instruction = true;
20,322✔
731
                    break;
20,322✔
732
                }
733
                // fallthrough
734
            case INST_CLS_LDX:
39,556✔
735
            case INST_CLS_ST:
39,556✔
736
            case INST_CLS_STX: new_ins = makeMemOp(pc, inst); break;
118,391✔
737

738
            case INST_CLS_ALU:
168,472✔
739
            case INST_CLS_ALU64: {
84,236✔
740
                new_ins = makeAluOp(pc, inst);
252,369✔
741

742
                // Merge (rX <<= 32; rX >>>= 32) into wX = rX
743
                //       (rX <<= 32; rX >>= 32)  into rX s32= rX
744
                if (pc >= insts.size() - 1) {
167,794✔
745
                    break;
101✔
746
                }
747
                const EbpfInst next = insts[pc + 1];
167,592✔
748
                auto dst = Reg{inst.dst};
167,592✔
749

750
                if (new_ins != shift32(dst, Bin::Op::LSH)) {
167,592✔
751
                    break;
80,879✔
752
                }
753

754
                if ((next.opcode & INST_CLS_MASK) != INST_CLS_ALU64) {
5,834✔
755
                    break;
534✔
756
                }
757
                auto next_ins = makeAluOp(pc + 1, next);
4,766✔
758
                if (next_ins == shift32(dst, Bin::Op::RSH)) {
4,766✔
759
                    new_ins = Bin{.op = Bin::Op::MOV, .dst = dst, .v = dst, .is64 = false};
1,578✔
760
                    skip_instruction = true;
1,578✔
761
                } else if (next_ins == shift32(dst, Bin::Op::ARSH)) {
3,188✔
762
                    new_ins = Bin{.op = Bin::Op::MOVSX32, .dst = dst, .v = dst, .is64 = true};
2,268✔
763
                    skip_instruction = true;
2,268✔
764
                }
765

766
                break;
4,766✔
767
            }
4,766✔
768

769
            case INST_CLS_JMP32:
59,914✔
770
            case INST_CLS_JMP: {
29,957✔
771
                new_ins = makeJmp(inst, insts, pc);
89,602✔
772
                if (std::holds_alternative<Exit>(new_ins)) {
59,376✔
773
                    fallthrough = false;
2,038✔
774
                    exit_count++;
2,038✔
775
                }
776
                if (const auto pjmp = std::get_if<Jmp>(&new_ins)) {
59,376✔
777
                    if (!pjmp->cond) {
35,642✔
778
                        fallthrough = false;
165,779✔
779
                    }
780
                }
781
                break;
29,688✔
782
            }
783
            default: CRAB_ERROR("invalid class: ", inst.opcode & INST_CLS_MASK);
163,025✔
784
            }
785
            if (pc == insts.size() - 1 && fallthrough) {
326,050✔
786
                note("fallthrough in last instruction");
1,569✔
787
            }
788

789
            std::optional<btf_line_info_t> current_line_info = {};
326,050✔
790

791
            if (options.verbosity_opts.print_line_info && pc < info.line_info.size()) {
326,050✔
UNCOV
792
                current_line_info = info.line_info.at(pc);
×
793
            }
794

795
            prog.emplace_back(Label(gsl::narrow<int>(pc)), new_ins, current_line_info);
489,075✔
796

797
            pc++;
326,050✔
798
            note_next_pc();
326,050✔
799
            if (skip_instruction) {
326,050✔
800
                pc++;
24,168✔
801
                note_next_pc();
175,109✔
802
            }
803
        }
327,876✔
804
        if (exit_count == 0) {
1,900✔
805
            note("no exit instruction");
668✔
806
        }
807
        return prog;
1,900✔
808
    }
1,826✔
809
};
810

811
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog, vector<vector<string>>& notes,
3,726✔
812
                                                    const prevail::ebpf_verifier_options_t& options) {
813
    thread_local_program_info = raw_prog.info;
3,726✔
814
    try {
1,863✔
815
        return Unmarshaller{notes, raw_prog.info}.unmarshal(raw_prog.prog, options);
5,589✔
816
    } catch (InvalidInstruction& arg) {
1,826✔
817
        std::ostringstream ss;
1,826✔
818
        ss << arg.pc << ": " << arg.what() << "\n";
1,826✔
819
        return ss.str();
1,826✔
820
    }
1,826✔
821
}
822

823
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog,
3,726✔
824
                                                    const prevail::ebpf_verifier_options_t& options) {
825
    vector<vector<string>> notes;
3,726✔
826
    return unmarshal(raw_prog, notes, options);
7,452✔
827
}
3,726✔
828

829
Call make_call(const int imm, const ebpf_platform_t& platform) {
1,018✔
830
    vector<vector<string>> notes;
1,018✔
831
    const ProgramInfo info{.platform = &platform};
1,018✔
832
    return Unmarshaller{notes, info}.makeCall(imm);
2,545✔
833
}
1,018✔
834
} // 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