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

Alan-Jowett / ebpf-verifier / 15194704016

22 May 2025 08:53AM UTC coverage: 88.11% (-0.07%) from 88.177%
15194704016

push

github

elazarg
uniform class names and explicit constructors for adapt_sgraph.hpp

Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

27 of 30 new or added lines in 1 file covered. (90.0%)

481 existing lines in 33 files now uncovered.

8552 of 9706 relevant lines covered (88.11%)

9089054.61 hits per line

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

94.69
/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✔
UNCOV
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✔
UNCOV
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,970✔
60
    switch (opcode & INST_CLS_MASK) {
65,970✔
61
    case INST_CLS_LD:
13,832✔
62
    case INST_CLS_LDX: return true;
13,832✔
63
    case INST_CLS_ST:
38,306✔
64
    case INST_CLS_STX: return false;
38,306✔
UNCOV
65
    default: CRAB_ERROR("unexpected opcode", opcode);
×
66
    }
67
}
68

69
static auto getMemWidth(const uint8_t opcode) -> int {
67,028✔
70
    switch (opcode & INST_SIZE_MASK) {
67,028✔
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,732✔
UNCOV
75
    default: CRAB_ERROR("unexpected opcode", opcode);
×
76
    }
77
}
78

79
static Instruction shift32(const Reg dst, const Bin::Op op) {
159,590✔
80
    return Bin{.op = op, .dst = dst, .v = Imm{32}, .is64 = true, .lddw = false};
159,590✔
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(); }
315,120✔
90
    explicit Unmarshaller(vector<vector<string>>& notes, const ProgramInfo& info) : notes{notes}, info{info} {
3,794✔
91
        note_next_pc();
5,691✔
92
    }
3,794✔
93

94
    auto getAluOp(const size_t pc, const EbpfInst inst) -> std::variant<Bin::Op, Un::Op> {
157,006✔
95
        // First handle instructions that support a non-zero offset.
96
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
157,006✔
97
        switch (inst.opcode & INST_ALU_OP_MASK) {
157,006✔
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,268✔
119
            if (inst.offset > 0 && !(inst.opcode & INST_SRC_REG)) {
82,268✔
120
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
121
            }
122
            switch (inst.offset) {
82,264✔
123
            case 0: return Bin::Op::MOV;
82,208✔
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,228✔
130
        }
131

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

137
        switch (inst.opcode & INST_ALU_OP_MASK) {
72,134✔
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,864✔
169
            if (inst.src != 0) {
2,864✔
170
                throw InvalidInstruction{pc, inst.opcode};
18✔
171
            }
172
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) {
2,846✔
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✔
UNCOV
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,814✔
196
            case 16:
2,640✔
197
                if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
2,640✔
UNCOV
198
                    throw InvalidInstruction(pc, inst.opcode);
×
199
                }
200
                return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16;
2,645✔
201
            case 32:
132✔
202
                if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
132✔
UNCOV
203
                    throw InvalidInstruction(pc, inst.opcode);
×
204
                }
205
                return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32;
137✔
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✔
UNCOV
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,564✔
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,802✔
239
        if (inst.opcode & INST_SRC_REG) {
153,802✔
240
            if (inst.imm != 0) {
61,706✔
241
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
66✔
242
            }
243
            return Reg{inst.src};
61,640✔
244
        }
245
        if (inst.src != 0) {
92,096✔
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,040✔
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,154✔
276
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
67,154✔
277
            throw InvalidInstruction(pc, "bad register");
126✔
278
        }
279

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

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

338
            if (basereg == R10_STACK_POINTER &&
88,177✔
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,972✔
343
                .access =
344
                    Deref{
345
                        .width = width,
346
                        .basereg = Reg{basereg},
347
                        .offset = inst.offset,
65,944✔
348
                    },
349
                .value = isLoad  ? Value{Reg{inst.dst}}
13,827✔
350
                         : isImm ? Value{Imm{zero_extend(inst.imm)}}
38,290✔
351
                                 : Value{Reg{inst.src}},
19,130✔
352
                .is_load = isLoad,
353
            };
104,234✔
354
            return res;
65,944✔
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,314✔
383
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
157,314✔
384
        if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64
178,578✔
385
                                                : bpf_conformance_groups_t::base32)) {
386
            throw InvalidInstruction(pc, inst.opcode);
96✔
387
        }
388
        if (inst.dst == R10_STACK_POINTER) {
157,218✔
389
            throw InvalidInstruction(pc, "invalid target r10");
2✔
390
        }
391
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
157,216✔
392
            throw InvalidInstruction(pc, "bad register");
210✔
393
        }
394
        return std::visit(
78,503✔
395
            Overloaded{[&](const Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; },
81,396✔
396
                       [&](const Bin::Op op) -> Instruction {
153,802✔
397
                           Bin res{
153,802✔
398
                               .op = op,
399
                               .dst = Reg{inst.dst},
153,802✔
400
                               .v = getBinValue(pc, inst),
153,802✔
401
                               .is64 = is64,
153,680✔
402
                           };
153,802✔
403
                           if (!thread_local_options.allow_division_by_zero &&
153,680✔
UNCOV
404
                               (op == Bin::Op::UDIV || op == Bin::Op::UMOD)) {
×
UNCOV
405
                               if (const auto pimm = std::get_if<Imm>(&res.v)) {
×
UNCOV
406
                                   if (pimm->v == 0) {
×
407
                                       note("division by zero");
×
408
                                   }
409
                               }
410
                           }
411
                           return res;
230,520✔
412
                       }},
413
            getAluOp(pc, inst));
235,385✔
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) {
47,220✔
462
        switch (t) {
47,220✔
463
        case EBPF_ARGUMENT_TYPE_ANYTHING: return ArgSingle::Kind::ANYTHING;
6,734✔
464
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP: return ArgSingle::Kind::MAP_FD;
6,108✔
465
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS: return ArgSingle::Kind::MAP_FD_PROGRAMS;
179✔
466
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY: return ArgSingle::Kind::PTR_TO_MAP_KEY;
5,584✔
467
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE: return ArgSingle::Kind::PTR_TO_MAP_VALUE;
2,367✔
468
        case EBPF_ARGUMENT_TYPE_PTR_TO_CTX: return ArgSingle::Kind::PTR_TO_CTX;
2,638✔
469
        default: break;
470
        }
471
        return {};
472
    }
473

474
    static ArgPair::Kind toArgPairKind(const ebpf_argument_type_t t) {
4,452✔
475
        switch (t) {
4,452✔
476
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL: return ArgPair::Kind::PTR_TO_READABLE_MEM_OR_NULL;
508✔
477
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM: return ArgPair::Kind::PTR_TO_READABLE_MEM;
1,259✔
478
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM: return ArgPair::Kind::PTR_TO_WRITABLE_MEM;
918✔
479
        default: break;
480
        }
481
        return {};
482
    }
483

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

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

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

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

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

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

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

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

725
            case INST_CLS_ALU:
152,604✔
726
            case INST_CLS_ALU64: {
76,302✔
727
                new_ins = makeAluOp(pc, inst);
228,567✔
728

729
                // Merge (rX <<= 32; rX >>>= 32) into wX = rX
730
                //       (rX <<= 32; rX >>= 32)  into rX s32= rX
731
                if (pc >= insts.size() - 1) {
151,926✔
732
                    break;
101✔
733
                }
734
                const EbpfInst next = insts[pc + 1];
151,724✔
735
                auto dst = Reg{inst.dst};
151,724✔
736

737
                if (new_ins != shift32(dst, Bin::Op::LSH)) {
151,724✔
738
                    break;
72,986✔
739
                }
740

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

753
                break;
4,710✔
754
            }
4,710✔
755

756
            case INST_CLS_JMP32:
52,718✔
757
            case INST_CLS_JMP: {
26,359✔
758
                new_ins = makeJmp(inst, insts, pc);
78,808✔
759
                if (std::holds_alternative<Exit>(new_ins)) {
52,180✔
760
                    fallthrough = false;
1,972✔
761
                    exit_count++;
1,972✔
762
                }
763
                if (const auto pjmp = std::get_if<Jmp>(&new_ins)) {
52,180✔
764
                    if (!pjmp->cond) {
30,212✔
765
                        fallthrough = false;
146,823✔
766
                    }
767
                }
768
                break;
26,090✔
769
            }
770
            default: CRAB_ERROR("invalid class: ", inst.opcode & INST_CLS_MASK);
144,553✔
771
            }
772
            if (pc == insts.size() - 1 && fallthrough) {
289,106✔
773
                note("fallthrough in last instruction");
1,569✔
774
            }
775

776
            std::optional<btf_line_info_t> current_line_info = {};
289,106✔
777

778
            if (pc < info.line_info.size()) {
289,106✔
779
                current_line_info = info.line_info.at(pc);
154,138✔
780
            }
781

782
            prog.emplace_back(Label(gsl::narrow<int>(pc)), new_ins, current_line_info);
433,659✔
783

784
            pc++;
289,106✔
785
            note_next_pc();
289,106✔
786
            if (skip_instruction) {
289,106✔
787
                pc++;
22,220✔
788
                note_next_pc();
155,663✔
789
            }
790
        }
290,932✔
791
        if (exit_count == 0) {
1,834✔
792
            note("no exit instruction");
668✔
793
        }
794
        return prog;
1,834✔
795
    }
1,826✔
796
};
797

798
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog, vector<vector<string>>& notes) {
3,660✔
799
    thread_local_program_info = raw_prog.info;
3,660✔
800
    try {
1,830✔
801
        return Unmarshaller{notes, raw_prog.info}.unmarshal(raw_prog.prog);
5,490✔
802
    } catch (InvalidInstruction& arg) {
1,826✔
803
        std::ostringstream ss;
1,826✔
804
        ss << arg.pc << ": " << arg.what() << "\n";
1,826✔
805
        return ss.str();
1,826✔
806
    }
1,826✔
807
}
808

809
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog) {
3,660✔
810
    vector<vector<string>> notes;
3,660✔
811
    return unmarshal(raw_prog, notes);
7,320✔
812
}
3,660✔
813

814
Call make_call(const int imm, const ebpf_platform_t& platform) {
134✔
815
    vector<vector<string>> notes;
134✔
816
    const ProgramInfo info{.platform = &platform};
134✔
817
    return Unmarshaller{notes, info}.makeCall(imm);
335✔
818
}
134✔
819
} // 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