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

Alan-Jowett / ebpf-verifier / 18658728485

18 Oct 2025 05:56PM UTC coverage: 88.47% (+0.4%) from 88.11%
18658728485

push

github

elazarg
Bump external/bpf_conformance from `8f3c2fe` to `6fa6a20`

Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `8f3c2fe` to `6fa6a20`.
- [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases)
- [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/8f3c2fe88...<a class=hub.com/Alan-Jowett/ebpf-verifier/commit/6fa6a20ac6fd3612ea9338312a67408687b9f06b">6fa6a20ac)

---
updated-dependencies:
- dependency-name: external/bpf_conformance
  dependency-version: 6fa6a20ac6fd3612ea9338312a67408687b9f06b
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

8954 of 10121 relevant lines covered (88.47%)

18293099.16 hits per line

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

94.73
/src/asm_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 "asm_unmarshal.hpp"
9
#include "crab_utils/debug.hpp"
10
#include "crab_utils/num_safety.hpp"
11
#include "ebpf_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) {
44,466✔
18
    switch (opcode & INST_SIZE_MASK) {
44,466✔
19
    case INST_SIZE_B: return 1;
2,217✔
20
    case INST_SIZE_H: return 2;
7,624✔
21
    case INST_SIZE_W: return 4;
11,970✔
22
    case INST_SIZE_DW: return 8;
20,438✔
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 {
65,974✔
60
    switch (opcode & INST_CLS_MASK) {
65,974✔
61
    case INST_CLS_LD:
13,834✔
62
    case INST_CLS_LDX: return true;
13,834✔
63
    case INST_CLS_ST:
38,306✔
64
    case INST_CLS_STX: return false;
38,306✔
65
    default: CRAB_ERROR("unexpected opcode", opcode);
×
66
    }
67
}
68

69
static auto getMemWidth(const uint8_t opcode) -> int {
67,032✔
70
    switch (opcode & INST_SIZE_MASK) {
67,032✔
71
    case INST_SIZE_B: return 1;
5,623✔
72
    case INST_SIZE_H: return 2;
11,282✔
73
    case INST_SIZE_W: return 4;
21,768✔
74
    case INST_SIZE_DW: return 8;
22,736✔
75
    default: CRAB_ERROR("unexpected opcode", opcode);
×
76
    }
77
}
78

79
static Instruction shift32(const Reg dst, const Bin::Op op) {
159,600✔
80
    return Bin{.op = op, .dst = dst, .v = Imm{32}, .is64 = true, .lddw = false};
159,600✔
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); }
748✔
88
    // ReSharper disable once CppMemberFunctionMayBeConst
89
    void note_next_pc() { notes.emplace_back(); }
316,050✔
90
    explicit Unmarshaller(vector<vector<string>>& notes, const ProgramInfo& info) : notes{notes}, info{info} {
4,704✔
91
        note_next_pc();
7,056✔
92
    }
4,704✔
93

94
    auto getAluOp(const size_t pc, const EbpfInst inst) -> std::variant<Bin::Op, Un::Op> {
157,016✔
95
        // First handle instructions that support a non-zero offset.
96
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
157,016✔
97
        switch (inst.opcode & INST_ALU_OP_MASK) {
157,016✔
98
        case INST_ALU_OP_DIV:
2,376✔
99
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
3,453✔
100
                                                    : bpf_conformance_groups_t::divmul32)) {
101
                throw InvalidInstruction(pc, inst.opcode);
16✔
102
            }
103
            switch (inst.offset) {
2,360✔
104
            case 0: return Bin::Op::UDIV;
2,298✔
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:
134✔
109
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
168✔
110
                                                    : bpf_conformance_groups_t::divmul32)) {
111
                throw InvalidInstruction(pc, inst.opcode);
16✔
112
            }
113
            switch (inst.offset) {
118✔
114
            case 0: return Bin::Op::UMOD;
42✔
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:
82,274✔
119
            if (inst.offset > 0 && !(inst.opcode & INST_SRC_REG)) {
82,274✔
120
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
121
            }
122
            switch (inst.offset) {
82,270✔
123
            case 0: return Bin::Op::MOV;
82,214✔
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;
72,232✔
130
        }
131

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

137
        switch (inst.opcode & INST_ALU_OP_MASK) {
72,138✔
138
        case INST_ALU_OP_ADD: return Bin::Op::ADD;
30,388✔
139
        case INST_ALU_OP_SUB: return Bin::Op::SUB;
798✔
140
        case INST_ALU_OP_MUL:
202✔
141
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
230✔
142
                                                    : bpf_conformance_groups_t::divmul32)) {
143
                throw InvalidInstruction(pc, inst.opcode);
8✔
144
            }
145
            return Bin::Op::MUL;
194✔
146
        case INST_ALU_OP_OR: return Bin::Op::OR;
9,020✔
147
        case INST_ALU_OP_AND: return Bin::Op::AND;
7,718✔
148
        case INST_ALU_OP_LSH: return Bin::Op::LSH;
9,832✔
149
        case INST_ALU_OP_RSH: return Bin::Op::RSH;
6,640✔
150
        case INST_ALU_OP_NEG:
156✔
151
            // Negation is a unary operation. The SRC bit, src, and imm must be all 0.
152
            if (inst.opcode & INST_SRC_REG) {
156✔
153
                throw InvalidInstruction{pc, inst.opcode};
4✔
154
            }
155
            if (inst.src != 0) {
152✔
156
                throw InvalidInstruction{pc, inst.opcode};
4✔
157
            }
158
            if (inst.imm != 0) {
148✔
159
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
6✔
160
            }
161
            return Un::Op::NEG;
144✔
162
        case INST_ALU_OP_XOR: return Bin::Op::XOR;
1,030✔
163
        case INST_ALU_OP_ARSH:
3,470✔
164
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU) {
3,470✔
165
                note("arsh32 is not allowed");
156✔
166
            }
167
            return Bin::Op::ARSH;
3,470✔
168
        case INST_ALU_OP_END:
2,868✔
169
            if (inst.src != 0) {
2,868✔
170
                throw InvalidInstruction{pc, inst.opcode};
18✔
171
            }
172
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) {
2,850✔
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,818✔
196
            case 16:
2,642✔
197
                if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
2,642✔
198
                    throw InvalidInstruction(pc, inst.opcode);
×
199
                }
200
                return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16;
2,648✔
201
            case 32:
134✔
202
                if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
134✔
203
                    throw InvalidInstruction(pc, inst.opcode);
×
204
                }
205
                return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32;
140✔
206
            case 64:
26✔
207
                if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) {
26✔
208
                    throw InvalidInstruction(pc, inst.opcode);
4✔
209
                }
210
                return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64;
27✔
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 {
428✔
220
        switch (const auto op = gsl::narrow<Atomic::Op>(inst.imm & ~INST_FETCH)) {
428✔
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:
198✔
227
        case Atomic::Op::OR:
198✔
228
        case Atomic::Op::AND:
198✔
229
        case Atomic::Op::XOR: return op;
396✔
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}); }
112,566✔
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 {
153,808✔
239
        if (inst.opcode & INST_SRC_REG) {
153,808✔
240
            if (inst.imm != 0) {
61,710✔
241
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
66✔
242
            }
243
            return Reg{inst.src};
61,644✔
244
        }
245
        if (inst.src != 0) {
92,098✔
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)};
92,042✔
250
    }
251

252
    static auto getJmpOp(const size_t pc, const uint8_t opcode) -> Condition::Op {
30,588✔
253
        using Op = Condition::Op;
15,294✔
254
        switch ((opcode >> 4) & 0xF) {
30,588✔
255
        case 0x0: return {}; // goto
2,274✔
256
        case 0x1: return Op::EQ;
5,261✔
257
        case 0x2: return Op::GT;
2,210✔
258
        case 0x3: return Op::GE;
336✔
259
        case 0x4: return Op::SET;
66✔
260
        case 0x5: return Op::NE;
8,932✔
261
        case 0x6: return Op::SGT;
1,958✔
262
        case 0x7: return Op::SGE;
72✔
263
        case 0x8: return {}; // call
264
        case 0x9: return {}; // exit
265
        case 0xa: return Op::LT;
514✔
266
        case 0xb: return Op::LE;
68✔
267
        case 0xc: return Op::SLT;
1,274✔
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 {
67,158✔
276
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
67,158✔
277
            throw InvalidInstruction(pc, "bad register");
126✔
278
        }
279

280
        const int width = getMemWidth(inst.opcode);
67,032✔
281
        if (!info.platform->supports_group((width == sizeof(uint64_t)) ? bpf_conformance_groups_t::base64
89,180✔
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;
67,008✔
286
        switch (inst.opcode & INST_MODE_MASK) {
67,008✔
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: {
65,982✔
320
            if (isLD) {
65,982✔
321
                throw InvalidInstruction(pc, inst.opcode);
8✔
322
            }
323
            const bool isLoad = getMemIsLoad(inst.opcode);
65,974✔
324
            if (isLoad && inst.dst == R10_STACK_POINTER) {
65,974✔
325
                throw InvalidInstruction(pc, "cannot modify r10");
2✔
326
            }
327
            const bool isImm = !(inst.opcode & 1);
65,972✔
328
            if (isImm && inst.src != 0) {
65,972✔
329
                throw InvalidInstruction(pc, inst.opcode);
8✔
330
            }
331
            if (!isImm && inst.imm != 0) {
65,949✔
332
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
16✔
333
            }
334

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

338
            if (basereg == R10_STACK_POINTER &&
88,181✔
339
                (inst.offset + opcode_to_width(inst.opcode) > 0 || inst.offset < -EBPF_TOTAL_STACK_SIZE)) {
44,466✔
340
                note("Stack access out of bounds");
16✔
341
            }
342
            auto res = Mem{
32,974✔
343
                .access =
344
                    Deref{
345
                        .width = width,
346
                        .basereg = Reg{basereg},
347
                        .offset = inst.offset,
65,948✔
348
                    },
349
                .value = isLoad  ? Value{Reg{inst.dst}}
13,829✔
350
                         : isImm ? Value{Imm{zero_extend(inst.imm)}}
38,290✔
351
                                 : Value{Reg{inst.src}},
19,130✔
352
                .is_load = isLoad,
353
            };
104,238✔
354
            return res;
65,948✔
355
        }
356

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

382
    auto makeAluOp(const size_t pc, const EbpfInst inst) -> Instruction {
157,324✔
383
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
157,324✔
384
        if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64
178,590✔
385
                                                : bpf_conformance_groups_t::base32)) {
386
            throw InvalidInstruction(pc, inst.opcode);
96✔
387
        }
388
        if (inst.dst == R10_STACK_POINTER) {
157,228✔
389
            throw InvalidInstruction(pc, "invalid target r10");
2✔
390
        }
391
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
157,226✔
392
            throw InvalidInstruction(pc, "bad register");
210✔
393
        }
394
        return std::visit(
78,508✔
395
            Overloaded{[&](const Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; },
81,405✔
396
                       [&](const Bin::Op op) -> Instruction {
153,808✔
397
                           Bin res{
153,808✔
398
                               .op = op,
399
                               .dst = Reg{inst.dst},
153,808✔
400
                               .v = getBinValue(pc, inst),
153,808✔
401
                               .is64 = is64,
153,686✔
402
                           };
153,808✔
403
                           if (!thread_local_options.allow_division_by_zero &&
153,686✔
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;
230,529✔
412
                       }},
413
            getAluOp(pc, inst));
235,400✔
414
    }
415

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

439
        switch (inst.src) {
18,402✔
440
        case INST_LD_MODE_IMM:
5,822✔
441
            return Bin{
14,555✔
442
                .op = Bin::Op::MOV,
443
                .dst = Reg{inst.dst},
5,822✔
444
                .v = Imm{merge(inst.imm, next_imm)},
8,733✔
445
                .is64 = true,
446
                .lddw = true,
447
            };
5,822✔
448
        case INST_LD_MODE_MAP_FD: {
12,568✔
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) {
12,568✔
452
                throw InvalidInstruction(pc, "lddw uses reserved fields");
2✔
453
            }
454
            return LoadMapFd{.dst = Reg{inst.dst}, .mapfd = inst.imm};
12,566✔
455
        }
456
        case INST_LD_MODE_MAP_VALUE: return LoadMapAddress{.dst = Reg{inst.dst}, .mapfd = inst.imm, .offset = next_imm};
12✔
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) {
49,616✔
462
        switch (t) {
49,616✔
463
        case EBPF_ARGUMENT_TYPE_ANYTHING: return ArgSingle::Kind::ANYTHING;
7,028✔
464
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP: return ArgSingle::Kind::MAP_FD;
6,466✔
465
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS: return ArgSingle::Kind::MAP_FD_PROGRAMS;
193✔
466
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY: return ArgSingle::Kind::PTR_TO_MAP_KEY;
5,882✔
467
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE: return ArgSingle::Kind::PTR_TO_MAP_VALUE;
2,527✔
468
        case EBPF_ARGUMENT_TYPE_PTR_TO_CTX: return ArgSingle::Kind::PTR_TO_CTX;
2,712✔
469
        default: break;
470
        }
471
        return {};
472
    }
473

474
    static ArgPair::Kind toArgPairKind(const ebpf_argument_type_t t) {
4,732✔
475
        switch (t) {
4,732✔
476
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL:
1,847✔
477
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM: return ArgPair::Kind::PTR_TO_READABLE_MEM;
1,847✔
478
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL:
1,038✔
479
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM: return ArgPair::Kind::PTR_TO_WRITABLE_MEM;
1,038✔
480
        default: break;
481
        }
482
        return {};
483
    }
484

485
    [[nodiscard]]
486
    auto makeCall(const int32_t imm) const -> Call {
21,008✔
487
        const EbpfHelperPrototype proto = info.platform->get_helper_prototype(imm);
21,008✔
488
        if (proto.return_type == EBPF_RETURN_TYPE_UNSUPPORTED) {
21,008✔
489
            throw std::runtime_error(std::string("unsupported function: ") + proto.name);
×
490
        }
491
        Call res;
21,008✔
492
        res.func = imm;
21,008✔
493
        res.name = proto.name;
21,008✔
494
        res.reallocate_packet = proto.reallocate_packet;
21,008✔
495
        res.is_map_lookup = proto.return_type == EBPF_RETURN_TYPE_PTR_TO_MAP_VALUE_OR_NULL;
21,008✔
496
        const std::array<ebpf_argument_type_t, 7> args = {
21,008✔
497
            {EBPF_ARGUMENT_TYPE_DONTCARE, proto.argument_type[0], proto.argument_type[1], proto.argument_type[2],
21,008✔
498
             proto.argument_type[3], proto.argument_type[4], EBPF_ARGUMENT_TYPE_DONTCARE}};
21,008✔
499
        for (size_t i = 1; i < args.size() - 1; i++) {
113,034✔
500
            switch (args[i]) {
71,948✔
501
            case EBPF_ARGUMENT_TYPE_UNSUPPORTED: {
×
502
                throw std::runtime_error(std::string("unsupported function: ") + proto.name);
×
503
            }
504
            case EBPF_ARGUMENT_TYPE_DONTCARE: return res;
8,800✔
505
            case EBPF_ARGUMENT_TYPE_ANYTHING:
49,616✔
506
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP:
24,808✔
507
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS:
24,808✔
508
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY:
24,808✔
509
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE:
24,808✔
510
            case EBPF_ARGUMENT_TYPE_PTR_TO_CTX:
24,808✔
511
                res.singles.push_back({toArgSingleKind(args[i]), Reg{gsl::narrow<uint8_t>(i)}});
49,616✔
512
                break;
49,616✔
513
            case EBPF_ARGUMENT_TYPE_CONST_SIZE: {
×
514
                // Sanity check: This argument should never be seen in isolation.
515
                throw std::runtime_error(
516
                    std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE: ") +
×
517
                    proto.name);
×
518
            }
519
            case EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: {
×
520
                // Sanity check: This argument should never be seen in isolation.
521
                throw std::runtime_error(
522
                    std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
×
523
                    proto.name);
×
524
            }
525
            case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL:
4,732✔
526
            case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM:
2,366✔
527
            case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL:
2,366✔
528
            case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM:
2,366✔
529
                // Sanity check: This argument must be followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or
530
                // EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO.
531
                if (args.size() - i < 2) {
4,732✔
532
                    throw std::runtime_error(
533
                        std::string(
534
                            "missing EBPF_ARGUMENT_TYPE_CONST_SIZE or EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
535
                        proto.name);
536
                }
537
                if (args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE &&
5,869✔
538
                    args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO) {
2,274✔
539
                    throw std::runtime_error(
540
                        std::string("Pointer argument not followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or "
×
541
                                    "EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
×
542
                        proto.name);
×
543
                }
544
                const bool can_be_zero = (args[i + 1] == EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO);
4,732✔
545
                const bool or_null = args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL ||
6,590✔
546
                                     args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL;
3,716✔
547
                res.pairs.push_back({toArgPairKind(args[i]), or_null, Reg{gsl::narrow<uint8_t>(i)},
7,617✔
548
                                     Reg{gsl::narrow<uint8_t>(i + 1)}, can_be_zero});
4,732✔
549
                i++;
4,732✔
550
                break;
4,732✔
551
            }
552
        }
553
        return res;
1,704✔
554
    }
×
555

556
    /// Given a program counter and an offset, get the label of the target instruction.
557
    static Label getJumpTarget(const int32_t offset, const vector<EbpfInst>& insts, const Pc pc) {
30,498✔
558
        const Pc new_pc = pc + 1 + offset;
30,498✔
559
        if (new_pc >= insts.size()) {
30,498✔
560
            throw InvalidInstruction(pc, "jump out of bounds");
138✔
561
        }
562
        if (insts[new_pc].opcode == 0) {
30,360✔
563
            throw InvalidInstruction(pc, "jump to middle of lddw");
×
564
        }
565
        return Label{gsl::narrow<int>(new_pc)};
30,360✔
566
    }
567

568
    static auto makeCallLocal(const EbpfInst inst, const vector<EbpfInst>& insts, const Pc pc) -> CallLocal {
22✔
569
        if (inst.opcode & INST_SRC_REG) {
22✔
570
            throw InvalidInstruction(pc, inst.opcode);
2✔
571
        }
572
        if (inst.dst != 0) {
20✔
573
            throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
574
        }
575
        return CallLocal{.target = getJumpTarget(inst.imm, insts, pc)};
18✔
576
    }
577

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

596
    [[nodiscard]]
597
    auto makeJmp(const EbpfInst inst, const vector<EbpfInst>& insts, const Pc pc) const -> Instruction {
52,724✔
598
        switch ((inst.opcode >> 4) & 0xF) {
52,724✔
599
        case INST_CALL:
20,038✔
600
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) {
20,038✔
601
                throw InvalidInstruction(pc, inst.opcode);
4✔
602
            }
603
            if (!info.platform->supports_group(bpf_conformance_groups_t::callx) && (inst.opcode & INST_SRC_REG)) {
20,034✔
604
                throw InvalidInstruction(pc, inst.opcode);
4✔
605
            }
606
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
20,030✔
607
                throw InvalidInstruction(pc, inst.opcode);
4✔
608
            }
609
            if (inst.src >= INST_CALL_BTF_HELPER) {
20,026✔
610
                throw InvalidInstruction(pc, inst.opcode);
2✔
611
            }
612
            if (inst.offset != 0) {
20,024✔
613
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
614
            }
615
            if (inst.src == INST_CALL_LOCAL) {
20,018✔
616
                return makeCallLocal(inst, insts, pc);
30✔
617
            }
618
            if (inst.opcode & INST_SRC_REG) {
19,996✔
619
                return makeCallx(inst, pc);
22✔
620
            }
621
            if (inst.dst != 0) {
19,974✔
622
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
623
            }
624
            if (!info.platform->is_helper_usable(inst.imm)) {
19,972✔
625
                throw InvalidInstruction(pc, "invalid helper function id " + std::to_string(inst.imm));
2✔
626
            }
627
            return makeCall(inst.imm);
29,955✔
628
        case INST_EXIT:
1,994✔
629
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
1,994✔
630
                throw InvalidInstruction(pc, inst.opcode);
2✔
631
            }
632
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) {
1,992✔
633
                throw InvalidInstruction(pc, inst.opcode);
6✔
634
            }
635
            if (inst.src != 0) {
1,986✔
636
                throw InvalidInstruction(pc, inst.opcode);
2✔
637
            }
638
            if (inst.dst != 0) {
1,984✔
639
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
640
            }
641
            if (inst.imm != 0) {
1,982✔
642
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
2✔
643
            }
644
            if (inst.offset != 0) {
1,980✔
645
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
2✔
646
            }
647
            return Exit{};
1,978✔
648
        case INST_JA:
4,564✔
649
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) {
4,564✔
650
                throw InvalidInstruction(pc, inst.opcode);
×
651
            }
652
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
4,564✔
653
                throw InvalidInstruction(pc, inst.opcode);
4✔
654
            }
655
            if (inst.opcode & INST_SRC_REG) {
4,560✔
656
                throw InvalidInstruction(pc, inst.opcode);
4✔
657
            }
658
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP && (inst.imm != 0)) {
4,556✔
659
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
2✔
660
            }
661
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP32 && (inst.offset != 0)) {
4,554✔
662
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
2✔
663
            }
664
            if (inst.dst != 0) {
4,552✔
665
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
4✔
666
            }
667
        default: {
15,338✔
668
            // First validate the opcode, src, and imm.
669
            const auto is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP;
30,676✔
670
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64
35,427✔
671
                                                    : bpf_conformance_groups_t::base32)) {
672
                throw InvalidInstruction(pc, inst.opcode);
88✔
673
            }
674
            const auto op = getJmpOp(pc, inst.opcode);
30,588✔
675
            if (!(inst.opcode & INST_SRC_REG) && (inst.src != 0)) {
30,572✔
676
                throw InvalidInstruction(pc, inst.opcode);
48✔
677
            }
678
            if ((inst.opcode & INST_SRC_REG) && (inst.imm != 0)) {
30,524✔
679
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
44✔
680
            }
681

682
            const int32_t offset = (inst.opcode == INST_OP_JA32) ? inst.imm : inst.offset;
30,480✔
683
            const Label target = getJumpTarget(offset, insts, pc);
30,480✔
684
            if (inst.opcode != INST_OP_JA16 && inst.opcode != INST_OP_JA32) {
30,344✔
685
                if (inst.dst > R10_STACK_POINTER) {
25,804✔
686
                    throw InvalidInstruction(pc, "bad register");
88✔
687
                }
688
                if ((inst.opcode & INST_SRC_REG) && inst.src > R10_STACK_POINTER) {
25,716✔
689
                    throw InvalidInstruction(pc, "bad register");
44✔
690
                }
691
            }
692

693
            const auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32)
27,946✔
694
                                  ? std::optional<Condition>{}
30,212✔
695
                                  : Condition{.op = op,
12,836✔
696
                                              .left = Reg{inst.dst},
25,672✔
697
                                              .right = (inst.opcode & INST_SRC_REG) ? Value{Reg{inst.src}}
25,672✔
698
                                                                                    : Value{Imm{sign_extend(inst.imm)}},
20,524✔
699
                                              .is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP};
30,212✔
700
            return Jmp{.cond = cond, .target = target};
45,384✔
701
        }
30,278✔
702
        }
703
    }
704

705
    vector<LabeledInstruction> unmarshal(vector<EbpfInst> const& insts) {
3,666✔
706
        vector<LabeledInstruction> prog;
3,666✔
707
        int exit_count = 0;
3,666✔
708
        if (insts.empty()) {
3,666✔
709
            throw std::invalid_argument("Zero length programs are not allowed");
×
710
        }
711
        for (size_t pc = 0; pc < insts.size();) {
292,792✔
712
            const EbpfInst inst = insts[pc];
290,952✔
713
            Instruction new_ins;
290,952✔
714
            bool skip_instruction = false;
290,952✔
715
            bool fallthrough = true;
290,952✔
716
            switch (inst.opcode & INST_CLS_MASK) {
290,952✔
717
            case INST_CLS_LD:
18,864✔
718
                if (inst.opcode == INST_OP_LDDW_IMM) {
18,864✔
719
                    const int32_t next_imm = pc < insts.size() - 1 ? insts[pc + 1].imm : 0;
18,456✔
720
                    new_ins = makeLddw(inst, next_imm, insts, pc);
27,656✔
721
                    skip_instruction = true;
18,400✔
722
                    break;
18,400✔
723
                }
724
                // fallthrough
725
            case INST_CLS_LDX:
33,579✔
726
            case INST_CLS_ST:
33,579✔
727
            case INST_CLS_STX: new_ins = makeMemOp(pc, inst); break;
100,460✔
728

729
            case INST_CLS_ALU:
152,614✔
730
            case INST_CLS_ALU64: {
76,307✔
731
                new_ins = makeAluOp(pc, inst);
228,582✔
732

733
                // Merge (rX <<= 32; rX >>>= 32) into wX = rX
734
                //       (rX <<= 32; rX >>= 32)  into rX s32= rX
735
                if (pc >= insts.size() - 1) {
151,936✔
736
                    break;
101✔
737
                }
738
                const EbpfInst next = insts[pc + 1];
151,734✔
739
                auto dst = Reg{inst.dst};
151,734✔
740

741
                if (new_ins != shift32(dst, Bin::Op::LSH)) {
151,734✔
742
                    break;
72,991✔
743
                }
744

745
                if ((next.opcode & INST_CLS_MASK) != INST_CLS_ALU64) {
5,752✔
746
                    break;
521✔
747
                }
748
                auto next_ins = makeAluOp(pc + 1, next);
4,710✔
749
                if (next_ins == shift32(dst, Bin::Op::RSH)) {
4,710✔
750
                    new_ins = Bin{.op = Bin::Op::MOV, .dst = dst, .v = dst, .is64 = false};
1,554✔
751
                    skip_instruction = true;
1,554✔
752
                } else if (next_ins == shift32(dst, Bin::Op::ARSH)) {
3,156✔
753
                    new_ins = Bin{.op = Bin::Op::MOVSX32, .dst = dst, .v = dst, .is64 = true};
2,266✔
754
                    skip_instruction = true;
2,266✔
755
                }
756

757
                break;
4,710✔
758
            }
4,710✔
759

760
            case INST_CLS_JMP32:
52,724✔
761
            case INST_CLS_JMP: {
26,362✔
762
                new_ins = makeJmp(inst, insts, pc);
78,817✔
763
                if (std::holds_alternative<Exit>(new_ins)) {
52,186✔
764
                    fallthrough = false;
1,978✔
765
                    exit_count++;
1,978✔
766
                }
767
                if (const auto pjmp = std::get_if<Jmp>(&new_ins)) {
52,186✔
768
                    if (!pjmp->cond) {
30,212✔
769
                        fallthrough = false;
146,833✔
770
                    }
771
                }
772
                break;
26,093✔
773
            }
774
            default: CRAB_ERROR("invalid class: ", inst.opcode & INST_CLS_MASK);
144,563✔
775
            }
776
            if (pc == insts.size() - 1 && fallthrough) {
289,126✔
777
                note("fallthrough in last instruction");
1,569✔
778
            }
779

780
            std::optional<btf_line_info_t> current_line_info = {};
289,126✔
781

782
            if (pc < info.line_info.size()) {
289,126✔
783
                current_line_info = info.line_info.at(pc);
154,138✔
784
            }
785

786
            prog.emplace_back(Label(gsl::narrow<int>(pc)), new_ins, current_line_info);
433,689✔
787

788
            pc++;
289,126✔
789
            note_next_pc();
289,126✔
790
            if (skip_instruction) {
289,126✔
791
                pc++;
22,220✔
792
                note_next_pc();
155,673✔
793
            }
794
        }
290,952✔
795
        if (exit_count == 0) {
1,840✔
796
            note("no exit instruction");
668✔
797
        }
798
        return prog;
1,840✔
799
    }
1,826✔
800
};
801

802
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog, vector<vector<string>>& notes) {
3,666✔
803
    thread_local_program_info = raw_prog.info;
3,666✔
804
    try {
1,833✔
805
        return Unmarshaller{notes, raw_prog.info}.unmarshal(raw_prog.prog);
5,499✔
806
    } catch (InvalidInstruction& arg) {
1,826✔
807
        std::ostringstream ss;
1,826✔
808
        ss << arg.pc << ": " << arg.what() << "\n";
1,826✔
809
        return ss.str();
1,826✔
810
    }
1,826✔
811
}
812

813
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog) {
3,666✔
814
    vector<vector<string>> notes;
3,666✔
815
    return unmarshal(raw_prog, notes);
7,332✔
816
}
3,666✔
817

818
Call make_call(const int imm, const ebpf_platform_t& platform) {
1,038✔
819
    vector<vector<string>> notes;
1,038✔
820
    const ProgramInfo info{.platform = &platform};
1,038✔
821
    return Unmarshaller{notes, info}.makeCall(imm);
2,595✔
822
}
1,038✔
823
} // 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