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

vbpf / ebpf-verifier / 14231336081

02 Apr 2025 11:08PM UTC coverage: 87.272% (-0.9%) from 88.177%
14231336081

push

github

web-flow
Propogate ebpf_verifier_options_t to thread_local_options (#856)

Signed-off-by: Alan Jowett <alanjo@microsoft.com>

5 of 5 new or added lines in 2 files covered. (100.0%)

58 existing lines in 19 files now uncovered.

8324 of 9538 relevant lines covered (87.27%)

4881701.3 hits per line

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

93.91
/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
int opcode_to_width(const uint8_t opcode) {
22,233✔
17
    switch (opcode & INST_SIZE_MASK) {
22,233✔
18
    case INST_SIZE_B: return 1;
19
    case INST_SIZE_H: return 2;
3,812✔
20
    case INST_SIZE_W: return 4;
5,985✔
21
    case INST_SIZE_DW: return 8;
10,219✔
22
    default: CRAB_ERROR("unexpected opcode", opcode);
×
23
    }
24
}
25

26
uint8_t width_to_opcode(const int width) {
61✔
27
    switch (width) {
61✔
28
    case 1: return INST_SIZE_B;
29
    case 2: return INST_SIZE_H;
7✔
30
    case 4: return INST_SIZE_W;
19✔
31
    case 8: return INST_SIZE_DW;
21✔
32
    default: CRAB_ERROR("unexpected width", width);
×
33
    }
34
}
35

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

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

50
struct InvalidInstruction : std::invalid_argument {
51
    size_t pc;
52
    explicit InvalidInstruction(const size_t pc, const char* what) : std::invalid_argument{what}, pc{pc} {}
716✔
53
    InvalidInstruction(const size_t pc, const std::string& what) : std::invalid_argument{what}, pc{pc} {}
66✔
54
    InvalidInstruction(const size_t pc, const uint8_t opcode)
392✔
55
        : std::invalid_argument{make_opcode_message("bad instruction", opcode)}, pc{pc} {}
784✔
56
};
57

58
struct UnsupportedMemoryMode : std::invalid_argument {
59
    explicit UnsupportedMemoryMode(const char* what) : std::invalid_argument{what} {}
60
};
61

62
static auto getMemIsLoad(const uint8_t opcode) -> bool {
32,985✔
63
    switch (opcode & INST_CLS_MASK) {
32,985✔
64
    case INST_CLS_LD:
65
    case INST_CLS_LDX: return true;
66
    case INST_CLS_ST:
19,153✔
67
    case INST_CLS_STX: return false;
19,153✔
68
    default: CRAB_ERROR("unexpected opcode", opcode);
×
69
    }
70
}
71

72
static auto getMemWidth(const uint8_t opcode) -> int {
33,514✔
73
    switch (opcode & INST_SIZE_MASK) {
33,514✔
74
    case INST_SIZE_B: return 1;
75
    case INST_SIZE_H: return 2;
5,641✔
76
    case INST_SIZE_W: return 4;
10,884✔
77
    case INST_SIZE_DW: return 8;
11,366✔
78
    default: CRAB_ERROR("unexpected opcode", opcode);
×
79
    }
80
}
81

82
static Instruction shift32(const Reg dst, const Bin::Op op) {
79,795✔
83
    return Bin{.op = op, .dst = dst, .v = Imm{32}, .is64 = true, .lddw = false};
79,795✔
84
}
85

86
struct Unmarshaller {
87
    vector<vector<string>>& notes;
88
    const program_info& info;
89
    // ReSharper disable once CppMemberFunctionMayBeConst
90
    void note(const string& what) { notes.back().emplace_back(what); }
374✔
91
    // ReSharper disable once CppMemberFunctionMayBeConst
92
    void note_next_pc() { notes.emplace_back(); }
157,560✔
93
    explicit Unmarshaller(vector<vector<string>>& notes, const program_info& info) : notes{notes}, info{info} {
1,897✔
94
        note_next_pc();
3,794✔
95
    }
1,897✔
96

97
    auto getAluOp(const size_t pc, const ebpf_inst inst) -> std::variant<Bin::Op, Un::Op> {
78,503✔
98
        // First handle instructions that support a non-zero offset.
99
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
78,503✔
100
        switch (inst.opcode & INST_ALU_OP_MASK) {
78,503✔
101
        case INST_ALU_OP_DIV:
1,188✔
102
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
2,265✔
103
                                                    : bpf_conformance_groups_t::divmul32)) {
104
                throw InvalidInstruction(pc, inst.opcode);
8✔
105
            }
106
            switch (inst.offset) {
1,180✔
107
            case 0: return Bin::Op::UDIV;
1,149✔
108
            case 1: return Bin::Op::SDIV;
27✔
109
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
8✔
110
            }
111
        case INST_ALU_OP_MOD:
67✔
112
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
101✔
113
                                                    : bpf_conformance_groups_t::divmul32)) {
114
                throw InvalidInstruction(pc, inst.opcode);
8✔
115
            }
116
            switch (inst.offset) {
59✔
117
            case 0: return Bin::Op::UMOD;
21✔
118
            case 1: return Bin::Op::SMOD;
34✔
119
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
8✔
120
            }
121
        case INST_ALU_OP_MOV:
41,134✔
122
            if (inst.offset > 0 && !(inst.opcode & INST_SRC_REG)) {
41,134✔
123
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
4✔
124
            }
125
            switch (inst.offset) {
41,132✔
126
            case 0: return Bin::Op::MOV;
41,104✔
127
            case 8: return Bin::Op::MOVSX8;
8✔
128
            case 16: return Bin::Op::MOVSX16;
8✔
129
            case 32: return Bin::Op::MOVSX32;
5✔
130
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
14✔
131
            }
132
        default: break;
36,114✔
133
        }
134

135
        // All the rest require a zero offset.
136
        if (inst.offset != 0) {
36,114✔
137
            throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
94✔
138
        }
139

140
        switch (inst.opcode & INST_ALU_OP_MASK) {
36,067✔
141
        case INST_ALU_OP_ADD: return Bin::Op::ADD;
15,194✔
142
        case INST_ALU_OP_SUB: return Bin::Op::SUB;
399✔
143
        case INST_ALU_OP_MUL:
101✔
144
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64
129✔
145
                                                    : bpf_conformance_groups_t::divmul32)) {
146
                throw InvalidInstruction(pc, inst.opcode);
4✔
147
            }
148
            return Bin::Op::MUL;
97✔
149
        case INST_ALU_OP_OR: return Bin::Op::OR;
4,510✔
150
        case INST_ALU_OP_AND: return Bin::Op::AND;
3,859✔
151
        case INST_ALU_OP_LSH: return Bin::Op::LSH;
4,916✔
152
        case INST_ALU_OP_RSH: return Bin::Op::RSH;
3,320✔
153
        case INST_ALU_OP_NEG:
78✔
154
            // Negation is a unary operation. The SRC bit, src, and imm must be all 0.
155
            if (inst.opcode & INST_SRC_REG) {
78✔
156
                throw InvalidInstruction{pc, inst.opcode};
2✔
157
            }
158
            if (inst.src != 0) {
76✔
159
                throw InvalidInstruction{pc, inst.opcode};
2✔
160
            }
161
            if (inst.imm != 0) {
74✔
162
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
4✔
163
            }
164
            return Un::Op::NEG;
72✔
165
        case INST_ALU_OP_XOR: return Bin::Op::XOR;
515✔
166
        case INST_ALU_OP_ARSH:
1,735✔
167
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU) {
1,735✔
168
                note("arsh32 is not allowed");
78✔
169
            }
170
            return Bin::Op::ARSH;
1,735✔
171
        case INST_ALU_OP_END:
1,432✔
172
            if (inst.src != 0) {
1,432✔
173
                throw InvalidInstruction{pc, inst.opcode};
9✔
174
            }
175
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) {
1,423✔
176
                if (inst.opcode & INST_END_BE) {
16✔
177
                    throw InvalidInstruction(pc, inst.opcode);
1✔
178
                }
179
                switch (inst.imm) {
15✔
180
                case 16:
4✔
181
                    if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
4✔
182
                        throw InvalidInstruction(pc, inst.opcode);
1✔
183
                    }
184
                    return Un::Op::SWAP16;
3✔
185
                case 32:
4✔
186
                    if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
4✔
187
                        throw InvalidInstruction(pc, inst.opcode);
1✔
188
                    }
189
                    return Un::Op::SWAP32;
3✔
190
                case 64:
3✔
191
                    if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) {
3✔
192
                        throw InvalidInstruction(pc, inst.opcode);
×
193
                    }
194
                    return Un::Op::SWAP64;
3✔
195
                default: throw InvalidInstruction(pc, "unsupported immediate");
4✔
196
                }
197
            }
198
            switch (inst.imm) {
1,407✔
199
            case 16:
1,320✔
200
                if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
1,320✔
201
                    throw InvalidInstruction(pc, inst.opcode);
×
202
                }
203
                return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16;
1,325✔
204
            case 32:
66✔
205
                if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
66✔
206
                    throw InvalidInstruction(pc, inst.opcode);
×
207
                }
208
                return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32;
71✔
209
            case 64:
13✔
210
                if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) {
13✔
211
                    throw InvalidInstruction(pc, inst.opcode);
2✔
212
                }
213
                return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64;
16✔
214
            default: throw InvalidInstruction(pc, "unsupported immediate");
8✔
215
            }
216
        case 0xe0: throw InvalidInstruction{pc, inst.opcode};
4✔
217
        case 0xf0: throw InvalidInstruction{pc, inst.opcode};
4✔
218
        default: return {};
×
219
        }
220
    }
221

222
    static auto getAtomicOp(const size_t pc, const ebpf_inst inst) -> Atomic::Op {
214✔
223
        switch (const auto op = gsl::narrow<Atomic::Op>(inst.imm & ~INST_FETCH)) {
214✔
224
        case Atomic::Op::XCHG:
18✔
225
        case Atomic::Op::CMPXCHG:
18✔
226
            if ((inst.imm & INST_FETCH) == 0) {
18✔
227
                throw InvalidInstruction(pc, "unsupported immediate");
4✔
228
            }
229
        case Atomic::Op::ADD:
198✔
230
        case Atomic::Op::OR:
198✔
231
        case Atomic::Op::AND:
198✔
232
        case Atomic::Op::XOR: return op;
198✔
233
        }
234
        throw InvalidInstruction(pc, "unsupported immediate");
12✔
235
    }
236

237
    static uint64_t sign_extend(const int32_t imm) { return crab::to_unsigned(int64_t{imm}); }
56,282✔
238

239
    static uint64_t zero_extend(const int32_t imm) { return uint64_t{crab::to_unsigned(imm)}; }
15✔
240

241
    static auto getBinValue(const pc_t pc, const ebpf_inst inst) -> Value {
76,901✔
242
        if (inst.opcode & INST_SRC_REG) {
76,901✔
243
            if (inst.imm != 0) {
30,853✔
244
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
33✔
245
            }
246
            return Reg{inst.src};
30,820✔
247
        }
248
        if (inst.src != 0) {
46,048✔
249
            throw InvalidInstruction{pc, inst.opcode};
28✔
250
        }
251
        // Imm is a signed 32-bit number.  Sign extend it to 64-bits for storage.
252
        return Imm{sign_extend(inst.imm)};
46,020✔
253
    }
254

255
    static auto getJmpOp(const size_t pc, const uint8_t opcode) -> Condition::Op {
15,294✔
256
        using Op = Condition::Op;
15,294✔
257
        switch ((opcode >> 4) & 0xF) {
15,294✔
258
        case 0x0: return {}; // goto
259
        case 0x1: return Op::EQ;
260
        case 0x2: return Op::GT;
1,105✔
261
        case 0x3: return Op::GE;
168✔
262
        case 0x4: return Op::SET;
33✔
263
        case 0x5: return Op::NE;
4,466✔
264
        case 0x6: return Op::SGT;
979✔
265
        case 0x7: return Op::SGE;
36✔
266
        case 0x8: return {}; // call
267
        case 0x9: return {}; // exit
268
        case 0xa: return Op::LT;
257✔
269
        case 0xb: return Op::LE;
34✔
270
        case 0xc: return Op::SLT;
637✔
271
        case 0xd: return Op::SLE;
36✔
272
        case 0xe: throw InvalidInstruction(pc, opcode);
4✔
273
        case 0xf: throw InvalidInstruction(pc, opcode);
4✔
274
        default: return {};
275
        }
276
    }
277

278
    auto makeMemOp(const pc_t pc, const ebpf_inst inst) -> Instruction {
33,577✔
279
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
33,577✔
280
            throw InvalidInstruction(pc, "bad register");
63✔
281
        }
282

283
        const int width = getMemWidth(inst.opcode);
33,514✔
284
        if (!info.platform->supports_group((width == sizeof(uint64_t)) ? bpf_conformance_groups_t::base64
55,662✔
285
                                                                       : bpf_conformance_groups_t::base32)) {
286
            throw InvalidInstruction(pc, inst.opcode);
12✔
287
        }
288
        const bool isLD = (inst.opcode & INST_CLS_MASK) == INST_CLS_LD;
33,502✔
289
        switch (inst.opcode & INST_MODE_MASK) {
33,502✔
290
        case INST_MODE_IMM: throw InvalidInstruction(pc, inst.opcode);
15✔
291

292
        case INST_MODE_ABS:
81✔
293
            if (!info.platform->supports_group(bpf_conformance_groups_t::packet) || !isLD || (width == 8)) {
81✔
294
                throw InvalidInstruction(pc, inst.opcode);
31✔
295
            }
296
            if (inst.dst != 0) {
50✔
297
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
3✔
298
            }
299
            if (inst.src > 0) {
47✔
300
                throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode));
3✔
301
            }
302
            if (inst.offset != 0) {
44✔
303
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
3✔
304
            }
305
            return Packet{.width = width, .offset = inst.imm, .regoffset = {}};
41✔
306

307
        case INST_MODE_IND:
121✔
308
            if (!info.platform->supports_group(bpf_conformance_groups_t::packet) || !isLD || (width == 8)) {
121✔
309
                throw InvalidInstruction(pc, inst.opcode);
26✔
310
            }
311
            if (inst.dst != 0) {
95✔
312
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
3✔
313
            }
314
            if (inst.src > R10_STACK_POINTER) {
92✔
315
                throw InvalidInstruction(pc, "bad register");
316
            }
317
            if (inst.offset != 0) {
92✔
318
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
3✔
319
            }
320
            return Packet{.width = width, .offset = inst.imm, .regoffset = Reg{inst.src}};
89✔
321

322
        case INST_MODE_MEM: {
32,989✔
323
            if (isLD) {
32,989✔
324
                throw InvalidInstruction(pc, inst.opcode);
4✔
325
            }
326
            const bool isLoad = getMemIsLoad(inst.opcode);
32,985✔
327
            if (isLoad && inst.dst == R10_STACK_POINTER) {
32,985✔
328
                throw InvalidInstruction(pc, "cannot modify r10");
1✔
329
            }
330
            const bool isImm = !(inst.opcode & 1);
32,984✔
331
            if (isImm && inst.src != 0) {
32,984✔
332
                throw InvalidInstruction(pc, inst.opcode);
4✔
333
            }
334
            if (!isImm && inst.imm != 0) {
32,965✔
335
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
8✔
336
            }
337

338
            assert(!(isLoad && isImm));
32,972✔
339
            const uint8_t basereg = isLoad ? inst.src : inst.dst;
32,972✔
340

341
            if (basereg == R10_STACK_POINTER &&
32,972✔
342
                (inst.offset + opcode_to_width(inst.opcode) > 0 || inst.offset < -EBPF_TOTAL_STACK_SIZE)) {
22,233✔
343
                note("Stack access out of bounds");
8✔
344
            }
345
            auto res = Mem{
32,972✔
346
                .access =
347
                    Deref{
348
                        .width = width,
349
                        .basereg = Reg{basereg},
350
                        .offset = inst.offset,
32,972✔
351
                    },
352
                .value = isLoad  ? Value{Reg{inst.dst}}
13,827✔
353
                         : isImm ? Value{Imm{zero_extend(inst.imm)}}
19,145✔
354
                                 : Value{Reg{inst.src}},
19,130✔
355
                .is_load = isLoad,
356
            };
52,117✔
357
            return res;
32,972✔
358
        }
359

360
        case INST_MODE_ATOMIC:
248✔
361
            if (((inst.opcode & INST_CLS_MASK) != INST_CLS_STX) ||
248✔
362
                ((inst.opcode & INST_SIZE_MASK) != INST_SIZE_W && (inst.opcode & INST_SIZE_MASK) != INST_SIZE_DW)) {
236✔
363
                throw InvalidInstruction(pc, inst.opcode);
14✔
364
            }
365
            if (!info.platform->supports_group(((inst.opcode & INST_SIZE_MASK) == INST_SIZE_DW)
285✔
366
                                                   ? bpf_conformance_groups_t::atomic64
367
                                                   : bpf_conformance_groups_t::atomic32)) {
368
                throw InvalidInstruction(pc, inst.opcode);
20✔
369
            }
370
            return Atomic{
412✔
371
                .op = getAtomicOp(pc, inst),
214✔
372
                .fetch = (inst.imm & INST_FETCH) == INST_FETCH,
198✔
373
                .access =
374
                    Deref{
375
                        .width = width,
376
                        .basereg = Reg{inst.dst},
198✔
377
                        .offset = inst.offset,
198✔
378
                    },
379
                .valreg = Reg{inst.src},
198✔
380
            };
198✔
381
        default: throw InvalidInstruction(pc, inst.opcode);
48✔
382
        }
383
    }
384

385
    auto makeAluOp(const size_t pc, const ebpf_inst inst) -> Instruction {
78,657✔
386
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
78,657✔
387
        if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64
99,921✔
388
                                                : bpf_conformance_groups_t::base32)) {
389
            throw InvalidInstruction(pc, inst.opcode);
48✔
390
        }
391
        if (inst.dst == R10_STACK_POINTER) {
78,609✔
392
            throw InvalidInstruction(pc, "invalid target r10");
1✔
393
        }
394
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
78,608✔
395
            throw InvalidInstruction(pc, "bad register");
105✔
396
        }
397
        return std::visit(
78,503✔
398
            overloaded{[&](const Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; },
79,857✔
399
                       [&](const Bin::Op op) -> Instruction {
76,901✔
400
                           Bin res{
76,901✔
401
                               .op = op,
402
                               .dst = Reg{inst.dst},
76,901✔
403
                               .v = getBinValue(pc, inst),
76,901✔
404
                               .is64 = is64,
76,840✔
405
                           };
76,901✔
406
                           if (!thread_local_options.allow_division_by_zero &&
76,840✔
407
                               (op == Bin::Op::UDIV || op == Bin::Op::UMOD)) {
×
408
                               if (const auto pimm = std::get_if<Imm>(&res.v)) {
×
409
                                   if (pimm->v == 0) {
×
410
                                       note("division by zero");
×
411
                                   }
412
                               }
413
                           }
414
                           return res;
76,840✔
415
                       }},
416
            getAluOp(pc, inst));
156,821✔
417
    }
418

419
    [[nodiscard]]
420
    auto makeLddw(const ebpf_inst inst, const int32_t next_imm, const vector<ebpf_inst>& insts,
9,228✔
421
                  const pc_t pc) const -> Instruction {
422
        if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) {
9,228✔
423
            throw InvalidInstruction{pc, inst.opcode};
3✔
424
        }
425
        if (pc >= insts.size() - 1) {
9,225✔
426
            throw InvalidInstruction(pc, "incomplete lddw");
9✔
427
        }
428
        const ebpf_inst next = insts[pc + 1];
9,216✔
429
        if (next.opcode != 0 || next.dst != 0 || next.src != 0 || next.offset != 0) {
9,216✔
430
            throw InvalidInstruction(pc, "invalid lddw");
8✔
431
        }
432
        if (inst.src > INST_LD_MODE_MAP_VALUE) {
9,208✔
433
            throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode));
1✔
434
        }
435
        if (inst.offset != 0) {
9,207✔
436
            throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
3✔
437
        }
438
        if (inst.dst > R10_STACK_POINTER) {
9,204✔
439
            throw InvalidInstruction(pc, "bad register");
3✔
440
        }
441

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

464
    static ArgSingle::Kind toArgSingleKind(const ebpf_argument_type_t t) {
23,610✔
465
        switch (t) {
23,610✔
466
        case EBPF_ARGUMENT_TYPE_ANYTHING: return ArgSingle::Kind::ANYTHING;
467
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP: return ArgSingle::Kind::MAP_FD;
468
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS: return ArgSingle::Kind::MAP_FD_PROGRAMS;
469
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY: return ArgSingle::Kind::PTR_TO_MAP_KEY;
470
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE: return ArgSingle::Kind::PTR_TO_MAP_VALUE;
471
        case EBPF_ARGUMENT_TYPE_PTR_TO_CTX: return ArgSingle::Kind::PTR_TO_CTX;
472
        default: break;
473
        }
474
        return {};
475
    }
476

477
    static ArgPair::Kind toArgPairKind(const ebpf_argument_type_t t) {
2,226✔
478
        switch (t) {
2,226✔
479
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL: return ArgPair::Kind::PTR_TO_READABLE_MEM_OR_NULL;
480
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM: return ArgPair::Kind::PTR_TO_READABLE_MEM;
481
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM: return ArgPair::Kind::PTR_TO_WRITABLE_MEM;
459✔
482
        default: break;
483
        }
484
        return {};
485
    }
486

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

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

567
    static auto makeCallLocal(const ebpf_inst inst, const vector<ebpf_inst>& insts, const pc_t pc) -> CallLocal {
11✔
568
        if (inst.opcode & INST_SRC_REG) {
11✔
569
            throw InvalidInstruction(pc, inst.opcode);
1✔
570
        }
571
        if (inst.dst != 0) {
10✔
572
            throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
1✔
573
        }
574
        return CallLocal{.target = getJumpTarget(inst.imm, insts, pc)};
9✔
575
    }
576

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

595
    [[nodiscard]]
596
    auto makeJmp(const ebpf_inst inst, const vector<ebpf_inst>& insts, const pc_t pc) const -> Instruction {
26,359✔
597
        switch ((inst.opcode >> 4) & 0xF) {
26,359✔
598
        case INST_CALL:
10,019✔
599
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) {
10,019✔
600
                throw InvalidInstruction(pc, inst.opcode);
2✔
601
            }
602
            if (!info.platform->supports_group(bpf_conformance_groups_t::callx) && (inst.opcode & INST_SRC_REG)) {
10,017✔
603
                throw InvalidInstruction(pc, inst.opcode);
2✔
604
            }
605
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
10,015✔
606
                throw InvalidInstruction(pc, inst.opcode);
2✔
607
            }
608
            if (inst.src >= INST_CALL_BTF_HELPER) {
10,013✔
609
                throw InvalidInstruction(pc, inst.opcode);
1✔
610
            }
611
            if (inst.offset != 0) {
10,012✔
612
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
3✔
613
            }
614
            if (inst.src == INST_CALL_LOCAL) {
10,009✔
615
                return makeCallLocal(inst, insts, pc);
19✔
616
            }
617
            if (inst.opcode & INST_SRC_REG) {
9,998✔
618
                return makeCallx(inst, pc);
11✔
619
            }
620
            if (inst.dst != 0) {
9,987✔
621
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
1✔
622
            }
623
            if (!info.platform->is_helper_usable(inst.imm)) {
9,986✔
624
                throw InvalidInstruction(pc, "invalid helper function id " + std::to_string(inst.imm));
1✔
625
            }
626
            return makeCall(inst.imm);
19,970✔
627
        case INST_EXIT:
994✔
628
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
994✔
629
                throw InvalidInstruction(pc, inst.opcode);
1✔
630
            }
631
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) {
993✔
632
                throw InvalidInstruction(pc, inst.opcode);
3✔
633
            }
634
            if (inst.src != 0) {
990✔
635
                throw InvalidInstruction(pc, inst.opcode);
1✔
636
            }
637
            if (inst.dst != 0) {
989✔
638
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
1✔
639
            }
640
            if (inst.imm != 0) {
988✔
641
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
1✔
642
            }
643
            if (inst.offset != 0) {
987✔
644
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
1✔
645
            }
646
            return Exit{};
986✔
647
        case INST_JA:
2,282✔
648
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) {
2,282✔
649
                throw InvalidInstruction(pc, inst.opcode);
×
650
            }
651
            if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) {
2,282✔
652
                throw InvalidInstruction(pc, inst.opcode);
2✔
653
            }
654
            if (inst.opcode & INST_SRC_REG) {
2,280✔
655
                throw InvalidInstruction(pc, inst.opcode);
2✔
656
            }
657
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP && (inst.imm != 0)) {
2,278✔
658
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
1✔
659
            }
660
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP32 && (inst.offset != 0)) {
2,277✔
661
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
1✔
662
            }
663
            if (inst.dst != 0) {
2,276✔
664
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
665
            }
666
        default: {
15,338✔
667
            // First validate the opcode, src, and imm.
668
            const auto is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP;
15,338✔
669
            if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64
20,089✔
670
                                                    : bpf_conformance_groups_t::base32)) {
671
                throw InvalidInstruction(pc, inst.opcode);
44✔
672
            }
673
            const auto op = getJmpOp(pc, inst.opcode);
15,294✔
674
            if (!(inst.opcode & INST_SRC_REG) && (inst.src != 0)) {
15,286✔
675
                throw InvalidInstruction(pc, inst.opcode);
24✔
676
            }
677
            if ((inst.opcode & INST_SRC_REG) && (inst.imm != 0)) {
15,262✔
678
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
22✔
679
            }
680

681
            const int32_t offset = (inst.opcode == INST_OP_JA32) ? inst.imm : inst.offset;
15,240✔
682
            const label_t target = getJumpTarget(offset, insts, pc);
15,240✔
683
            if (inst.opcode != INST_OP_JA16 && inst.opcode != INST_OP_JA32) {
15,172✔
684
                if (inst.dst > R10_STACK_POINTER) {
12,902✔
685
                    throw InvalidInstruction(pc, "bad register");
44✔
686
                }
687
                if ((inst.opcode & INST_SRC_REG) && inst.src > R10_STACK_POINTER) {
12,858✔
688
                    throw InvalidInstruction(pc, "bad register");
22✔
689
                }
690
            }
691

692
            const auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32)
15,106✔
693
                                  ? std::optional<Condition>{}
15,106✔
694
                                  : Condition{.op = op,
12,836✔
695
                                              .left = Reg{inst.dst},
12,836✔
696
                                              .right = (inst.opcode & INST_SRC_REG) ? Value{Reg{inst.src}}
12,836✔
697
                                                                                    : Value{Imm{sign_extend(inst.imm)}},
10,262✔
698
                                              .is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP};
15,106✔
699
            return Jmp{.cond = cond, .target = target};
30,278✔
700
        }
15,106✔
701
        }
702
    }
703

704
    vector<LabeledInstruction> unmarshal(vector<ebpf_inst> const& insts) {
1,830✔
705
        vector<LabeledInstruction> prog;
1,830✔
706
        int exit_count = 0;
1,830✔
707
        if (insts.empty()) {
1,830✔
708
            throw std::invalid_argument("Zero length programs are not allowed");
×
709
        }
710
        for (size_t pc = 0; pc < insts.size();) {
146,383✔
711
            const ebpf_inst inst = insts[pc];
145,466✔
712
            Instruction new_ins;
145,466✔
713
            bool skip_instruction = false;
145,466✔
714
            bool fallthrough = true;
145,466✔
715
            switch (inst.opcode & INST_CLS_MASK) {
145,466✔
716
            case INST_CLS_LD:
9,432✔
717
                if (inst.opcode == INST_OP_LDDW_IMM) {
9,432✔
718
                    const int32_t next_imm = pc < insts.size() - 1 ? insts[pc + 1].imm : 0;
9,228✔
719
                    new_ins = makeLddw(inst, next_imm, insts, pc);
18,428✔
720
                    skip_instruction = true;
9,200✔
721
                    break;
9,200✔
722
                }
723
                // fallthrough
724
            case INST_CLS_LDX:
33,577✔
725
            case INST_CLS_ST:
33,577✔
726
            case INST_CLS_STX: new_ins = makeMemOp(pc, inst); break;
66,877✔
727

728
            case INST_CLS_ALU:
76,302✔
729
            case INST_CLS_ALU64: {
76,302✔
730
                new_ins = makeAluOp(pc, inst);
152,265✔
731

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

740
                if (new_ins != shift32(dst, Bin::Op::LSH)) {
75,862✔
741
                    break;
742
                }
743

744
                if ((next.opcode & INST_CLS_MASK) != INST_CLS_ALU64) {
2,876✔
745
                    break;
746
                }
747
                auto next_ins = makeAluOp(pc + 1, next);
2,355✔
748
                if (next_ins == shift32(dst, Bin::Op::RSH)) {
2,355✔
749
                    new_ins = Bin{.op = Bin::Op::MOV, .dst = dst, .v = dst, .is64 = false};
777✔
750
                    skip_instruction = true;
777✔
751
                } else if (next_ins == shift32(dst, Bin::Op::ARSH)) {
1,578✔
752
                    new_ins = Bin{.op = Bin::Op::MOVSX32, .dst = dst, .v = dst, .is64 = true};
1,133✔
753
                    skip_instruction = true;
1,133✔
754
                }
755

756
                break;
2,355✔
757
            }
2,355✔
758

759
            case INST_CLS_JMP32:
26,359✔
760
            case INST_CLS_JMP: {
26,359✔
761
                new_ins = makeJmp(inst, insts, pc);
52,449✔
762
                if (std::holds_alternative<Exit>(new_ins)) {
26,090✔
763
                    fallthrough = false;
986✔
764
                    exit_count++;
986✔
765
                }
766
                if (const auto pjmp = std::get_if<Jmp>(&new_ins)) {
26,090✔
767
                    if (!pjmp->cond) {
15,106✔
768
                        fallthrough = false;
144,553✔
769
                    }
770
                }
771
                break;
772
            }
773
            default: CRAB_ERROR("invalid class: ", inst.opcode & INST_CLS_MASK);
144,553✔
774
            }
775
            if (pc == insts.size() - 1 && fallthrough) {
144,553✔
776
                note("fallthrough in last instruction");
1,241✔
777
            }
778

779
            std::optional<btf_line_info_t> current_line_info = {};
144,553✔
780

781
            if (pc < info.line_info.size()) {
144,553✔
782
                current_line_info = info.line_info.at(pc);
77,069✔
783
            }
784

785
            prog.emplace_back(label_t(gsl::narrow<int>(pc)), new_ins, current_line_info);
289,106✔
786

787
            pc++;
144,553✔
788
            note_next_pc();
144,553✔
789
            if (skip_instruction) {
144,553✔
790
                pc++;
11,110✔
791
                note_next_pc();
144,553✔
792
            }
793
        }
145,466✔
794
        if (exit_count == 0) {
917✔
795
            note("no exit instruction");
334✔
796
        }
797
        return prog;
917✔
798
    }
913✔
799
};
800

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

812
std::variant<InstructionSeq, std::string> unmarshal(const raw_program& raw_prog) {
1,830✔
813
    vector<vector<string>> notes;
1,830✔
814
    return unmarshal(raw_prog, notes);
3,660✔
815
}
1,830✔
816

817
Call make_call(const int imm, const ebpf_platform_t& platform) {
67✔
818
    vector<vector<string>> notes;
67✔
819
    const program_info info{.platform = &platform};
67✔
820
    return Unmarshaller{notes, info}.makeCall(imm);
201✔
821
}
67✔
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