• 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

97.55
/src/test/test_marshal.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <catch2/catch_all.hpp>
4

5
#include "asm_marshal.hpp"
6
#include "asm_unmarshal.hpp"
7

8
// Below we define a tample of instruction templates that specify
9
// what values each field are allowed to contain.  We first define
10
// a set of sentinel values that mean certain types of wildcards.
11
// For example, MEM_OFFSET and JMP_OFFSET are different wildcards
12
// for the 'offset' field of an instruction.  Any non-sentinel values
13
// in an instruction template are treated as literals.
14

15
constexpr int MEM_OFFSET = 3;                           // Any valid memory offset value.
16
constexpr int JMP_OFFSET = 5;                           // Any valid jump offset value.
17
constexpr int DST = 7;                                  // Any destination register number.
18
constexpr int HELPER_ID = 8;                            // Any helper ID.
19
constexpr int SRC = 9;                                  // Any source register number.
20
constexpr int IMM = -1;                                 // Any imm value.
21
constexpr int INVALID_REGISTER = R10_STACK_POINTER + 1; // Not a valid register.
22

23
struct ebpf_instruction_template_t {
24
    ebpf_inst inst;
25
    bpf_conformance_groups_t groups;
26
};
27

28
// The following table is derived from the table in the Appendix of the
29
// BPF ISA specification (https://datatracker.ietf.org/doc/draft-ietf-bpf-isa/).
30
static const ebpf_instruction_template_t instruction_template[] = {
31
    // {opcode, dst, src, offset, imm}, group
32
    {{0x04, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
33
    {{0x05, 0, 0, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
34
    {{0x06, 0, 0, 0, JMP_OFFSET}, bpf_conformance_groups_t::base32},
35
    {{0x07, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
36
    {{0x0c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
37
    {{0x0f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
38
    {{0x14, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
39
    {{0x15, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
40
    {{0x16, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
41
    {{0x17, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
42
    {{0x18, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
43
    {{0x18, DST, 1, 0, IMM}, bpf_conformance_groups_t::base64},
44
    {{0x18, DST, 2, 0, IMM}, bpf_conformance_groups_t::base64},
45
    // TODO(issue #533): add support for LDDW with src_reg > 2.
46
    // {{0x18, DST, 3, 0, IMM}, bpf_conformance_groups_t::base64},
47
    // {{0x18, DST, 4, 0, IMM}, bpf_conformance_groups_t::base64},
48
    // {{0x18, DST, 5, 0, IMM}, bpf_conformance_groups_t::base64},
49
    // {{0x18, DST, 6, 0, IMM}, bpf_conformance_groups_t::base64},
50
    {{0x1c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
51
    {{0x1d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
52
    {{0x1e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
53
    {{0x1f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
54
    {{0x20, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet},
55
    {{0x24, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32},
56
    {{0x25, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
57
    {{0x26, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
58
    {{0x27, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64},
59
    {{0x28, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet},
60
    {{0x2c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32},
61
    {{0x2d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
62
    {{0x2e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
63
    {{0x2f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64},
64
    {{0x30, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet},
65
    {{0x34, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32},
66
    {{0x34, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul32},
67
    {{0x35, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
68
    {{0x36, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
69
    {{0x37, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64},
70
    {{0x37, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul64},
71
    {{0x3c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32},
72
    {{0x3c, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul32},
73
    {{0x3d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
74
    {{0x3e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
75
    {{0x3f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64},
76
    {{0x3f, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul64},
77
    {{0x40, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet},
78
    {{0x44, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
79
    {{0x45, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
80
    {{0x46, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
81
    {{0x47, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
82
    {{0x48, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet},
83
    {{0x4c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
84
    {{0x4d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
85
    {{0x4e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
86
    {{0x4f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
87
    {{0x50, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet},
88
    {{0x54, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
89
    {{0x55, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
90
    {{0x56, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
91
    {{0x57, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
92
    {{0x5c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
93
    {{0x5d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
94
    {{0x5e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
95
    {{0x5f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
96
    {{0x61, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
97
    {{0x62, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32},
98
    {{0x63, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
99
    {{0x64, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
100
    {{0x65, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
101
    {{0x66, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
102
    {{0x67, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
103
    {{0x69, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
104
    {{0x6a, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32},
105
    {{0x6b, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
106
    {{0x6c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
107
    {{0x6d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
108
    {{0x6e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
109
    {{0x6f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
110
    {{0x71, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
111
    {{0x72, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32},
112
    {{0x73, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
113
    {{0x74, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
114
    {{0x75, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
115
    {{0x76, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
116
    {{0x77, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
117
    {{0x79, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64},
118
    {{0x7a, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base64},
119
    {{0x7b, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64},
120
    {{0x7c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
121
    {{0x7d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
122
    {{0x7e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
123
    {{0x7f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
124
    {{0x84, DST, 0, 0, 0}, bpf_conformance_groups_t::base32},
125
    {{0x85, 0, 0, 0, HELPER_ID}, bpf_conformance_groups_t::base32},
126
    {{0x85, 0, 1, 0, JMP_OFFSET}, bpf_conformance_groups_t::base32},
127
    // TODO(issue #590): Add support for calling a helper function by BTF ID.
128
    // {{0x85, 0, 2, 0, IMM}, bpf_conformance_groups_t::base32},
129
    {{0x87, DST, 0, 0, 0}, bpf_conformance_groups_t::base64},
130
    {{0x8d, DST, 0, 0, 0}, bpf_conformance_groups_t::callx},
131
    {{0x94, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32},
132
    {{0x94, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul32},
133
    {{0x95, 0, 0, 0, 0}, bpf_conformance_groups_t::base32},
134
    {{0x97, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64},
135
    {{0x97, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul64},
136
    {{0x9c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32},
137
    {{0x9c, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul32},
138
    {{0x9f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64},
139
    {{0x9f, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul64},
140
    {{0xa4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
141
    {{0xa5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
142
    {{0xa6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
143
    {{0xa7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
144
    {{0xac, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
145
    {{0xad, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
146
    {{0xae, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
147
    {{0xaf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
148
    {{0xb4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
149
    {{0xb5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
150
    {{0xb6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
151
    {{0xb7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
152
    {{0xbc, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
153
    {{0xbc, DST, SRC, 8, 0}, bpf_conformance_groups_t::base32},
154
    {{0xbc, DST, SRC, 16, 0}, bpf_conformance_groups_t::base32},
155
    {{0xbd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
156
    {{0xbe, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
157
    {{0xbf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
158
    {{0xbf, DST, SRC, 8, 0}, bpf_conformance_groups_t::base64},
159
    {{0xbf, DST, SRC, 16, 0}, bpf_conformance_groups_t::base64},
160
    {{0xbf, DST, SRC, 32, 0}, bpf_conformance_groups_t::base64},
161
    {{0xc3, DST, SRC, MEM_OFFSET, 0x00}, bpf_conformance_groups_t::atomic32},
162
    {{0xc3, DST, SRC, MEM_OFFSET, 0x01}, bpf_conformance_groups_t::atomic32},
163
    {{0xc3, DST, SRC, MEM_OFFSET, 0x40}, bpf_conformance_groups_t::atomic32},
164
    {{0xc3, DST, SRC, MEM_OFFSET, 0x41}, bpf_conformance_groups_t::atomic32},
165
    {{0xc3, DST, SRC, MEM_OFFSET, 0x50}, bpf_conformance_groups_t::atomic32},
166
    {{0xc3, DST, SRC, MEM_OFFSET, 0x51}, bpf_conformance_groups_t::atomic32},
167
    {{0xc3, DST, SRC, MEM_OFFSET, 0xa0}, bpf_conformance_groups_t::atomic32},
168
    {{0xc3, DST, SRC, MEM_OFFSET, 0xa1}, bpf_conformance_groups_t::atomic32},
169
    {{0xc3, DST, SRC, MEM_OFFSET, 0xe1}, bpf_conformance_groups_t::atomic32},
170
    {{0xc3, DST, SRC, MEM_OFFSET, 0xf1}, bpf_conformance_groups_t::atomic32},
171
    {{0xc4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
172
    {{0xc5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
173
    {{0xc6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
174
    {{0xc7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
175
    {{0xcc, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
176
    {{0xcd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
177
    {{0xce, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
178
    {{0xcf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
179
    {{0xd4, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32},
180
    {{0xd4, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32},
181
    {{0xd4, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64},
182
    {{0xd5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
183
    {{0xd6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
184
    {{0xd7, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32},
185
    {{0xd7, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32},
186
    {{0xd7, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64},
187
    {{0xdb, DST, SRC, MEM_OFFSET, 0x00}, bpf_conformance_groups_t::atomic64},
188
    {{0xdb, DST, SRC, MEM_OFFSET, 0x01}, bpf_conformance_groups_t::atomic64},
189
    {{0xdb, DST, SRC, MEM_OFFSET, 0x40}, bpf_conformance_groups_t::atomic64},
190
    {{0xdb, DST, SRC, MEM_OFFSET, 0x41}, bpf_conformance_groups_t::atomic64},
191
    {{0xdb, DST, SRC, MEM_OFFSET, 0x50}, bpf_conformance_groups_t::atomic64},
192
    {{0xdb, DST, SRC, MEM_OFFSET, 0x51}, bpf_conformance_groups_t::atomic64},
193
    {{0xdb, DST, SRC, MEM_OFFSET, 0xa0}, bpf_conformance_groups_t::atomic64},
194
    {{0xdb, DST, SRC, MEM_OFFSET, 0xa1}, bpf_conformance_groups_t::atomic64},
195
    {{0xdb, DST, SRC, MEM_OFFSET, 0xe1}, bpf_conformance_groups_t::atomic64},
196
    {{0xdb, DST, SRC, MEM_OFFSET, 0xf1}, bpf_conformance_groups_t::atomic64},
197
    {{0xdc, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32},
198
    {{0xdc, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32},
199
    {{0xdc, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64},
200
    {{0xdd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
201
    {{0xde, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
202
};
203

204
// Verify that we can successfully unmarshal an instruction.
205
static void check_unmarshal_succeed(const ebpf_inst& ins, const ebpf_platform_t& platform = g_ebpf_platform_linux) {
160✔
206
    const program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
160✔
207
    constexpr ebpf_inst exit{.opcode = INST_OP_EXIT};
160✔
208
    const InstructionSeq parsed =
160✔
209
        std::get<InstructionSeq>(unmarshal(raw_program{"", "", 0, "", {ins, exit, exit}, info}));
320✔
210
    REQUIRE(parsed.size() == 3);
160✔
211
}
160✔
212

213
// Verify that we can successfully unmarshal a 64-bit immediate instruction.
214
static void check_unmarshal_succeed(ebpf_inst inst1, ebpf_inst inst2,
3✔
215
                                    const ebpf_platform_t& platform = g_ebpf_platform_linux) {
216
    const program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
3✔
217
    constexpr ebpf_inst exit{.opcode = INST_OP_EXIT};
3✔
218
    const InstructionSeq parsed =
3✔
219
        std::get<InstructionSeq>(unmarshal(raw_program{"", "", 0, "", {inst1, inst2, exit, exit}, info}));
6✔
220
    REQUIRE(parsed.size() == 3);
3✔
221
}
3✔
222

223
// Verify that if we unmarshal an instruction and then re-marshal it,
224
// we get what we expect.
225
static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result,
10✔
226
                                      const ebpf_platform_t& platform = g_ebpf_platform_linux) {
227
    program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
10✔
228
    constexpr ebpf_inst exit{.opcode = INST_OP_EXIT};
10✔
229
    const InstructionSeq inst_seq =
10✔
230
        std::get<InstructionSeq>(unmarshal(raw_program{"", "", 0, "", {ins, exit, exit}, info}));
20✔
231
    REQUIRE(inst_seq.size() == 3);
10✔
232
    auto [_, single, _2] = inst_seq.front();
10✔
233
    (void)_;  // unused
10✔
234
    (void)_2; // unused
10✔
235
    std::vector<ebpf_inst> marshaled = marshal(single, 0);
10✔
236
    REQUIRE(marshaled.size() == 1);
10✔
237
    ebpf_inst result = marshaled.back();
10✔
238
    REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0);
10✔
239
}
10✔
240

241
// Verify that if we unmarshal two instructions and then re-marshal it,
242
// we get what we expect.
243
static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& ins2, const ebpf_inst& expected_result) {
2✔
244
    program_info info{.platform = &g_ebpf_platform_linux,
2✔
245
                      .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")};
2✔
246
    constexpr ebpf_inst exit{.opcode = INST_OP_EXIT};
2✔
247
    InstructionSeq parsed =
2✔
248
        std::get<InstructionSeq>(unmarshal(raw_program{"", "", 0, "", {ins1, ins2, exit, exit}, info}));
4✔
249
    REQUIRE(parsed.size() == 3);
2✔
250
    auto [_, single, _2] = parsed.front();
2✔
251
    (void)_;  // unused
2✔
252
    (void)_2; // unused
2✔
253
    std::vector<ebpf_inst> marshaled = marshal(single, 0);
2✔
254
    REQUIRE(marshaled.size() == 1);
2✔
255
    ebpf_inst result = marshaled.back();
2✔
256
    REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0);
2✔
257
}
2✔
258

259
// Verify that if we unmarshal a 64-bit immediate instruction and then re-marshal it,
260
// we get what we expect.
261
static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& ins2, const ebpf_inst& expected_result1,
2✔
262
                                      const ebpf_inst& expected_result2) {
263
    program_info info{.platform = &g_ebpf_platform_linux,
2✔
264
                      .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")};
2✔
265
    constexpr ebpf_inst exit{.opcode = INST_OP_EXIT};
2✔
266
    const InstructionSeq inst_seq =
2✔
267
        std::get<InstructionSeq>(unmarshal(raw_program{"", "", 0, "", {ins1, ins2, exit, exit}, info}));
4✔
268
    REQUIRE(inst_seq.size() == 3);
2✔
269
    auto [_, single, _2] = inst_seq.front();
2✔
270
    (void)_;  // unused
2✔
271
    (void)_2; // unused
2✔
272
    std::vector<ebpf_inst> marshaled = marshal(single, 0);
2✔
273
    REQUIRE(marshaled.size() == 2);
2✔
274
    ebpf_inst result1 = marshaled.front();
2✔
275
    REQUIRE(memcmp(&expected_result1, &result1, sizeof(result1)) == 0);
2✔
276
    ebpf_inst result2 = marshaled.back();
2✔
277
    REQUIRE(memcmp(&expected_result2, &result2, sizeof(result2)) == 0);
2✔
278
}
2✔
279

280
// Verify that if we marshal an instruction and then unmarshal it,
281
// we get the original.
282
static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false,
168✔
283
                                      const ebpf_platform_t& platform = g_ebpf_platform_linux) {
284
    program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
168✔
285
    const InstructionSeq inst_seq =
168✔
286
        std::get<InstructionSeq>(unmarshal(raw_program{"", "", 0, "", marshal(ins, 0), info}));
336✔
287
    REQUIRE(inst_seq.size() == 1);
168✔
288
    auto [_, single, _2] = inst_seq.back();
168✔
289
    (void)_;  // unused
168✔
290
    (void)_2; // unused
168✔
291
    REQUIRE(single == ins);
168✔
292
}
168✔
293

294
static void check_marshal_unmarshal_fail(const Instruction& ins, const std::string& expected_error_message,
28✔
295
                                         const ebpf_platform_t& platform = g_ebpf_platform_linux) {
296
    const program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
28✔
297
    auto result = unmarshal(raw_program{"", "", 0, "", marshal(ins, 0), info});
28✔
298
    auto* error_message = std::get_if<std::string>(&result);
28✔
299
    REQUIRE(error_message != nullptr);
28✔
300
    REQUIRE(*error_message == expected_error_message);
28✔
301
}
28✔
302

303
static void check_unmarshal_fail(ebpf_inst inst, const std::string& expected_error_message,
735✔
304
                                 const ebpf_platform_t& platform = g_ebpf_platform_linux) {
305
    program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
735✔
306
    std::vector insns = {inst};
735✔
307
    auto result = unmarshal(raw_program{"", "", 0, "", insns, info});
735✔
308
    auto* error_message = std::get_if<std::string>(&result);
735✔
309
    REQUIRE(error_message != nullptr);
735✔
310
    REQUIRE(*error_message == expected_error_message);
735✔
311
}
735✔
312

313
static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expected_error_message,
113✔
314
                                      const ebpf_platform_t& platform = g_ebpf_platform_linux) {
315
    program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
113✔
316
    constexpr ebpf_inst exit{.opcode = INST_OP_EXIT};
113✔
317
    std::vector insns{inst, exit, exit};
113✔
318
    auto result = unmarshal(raw_program{"", "", 0, "", insns, info});
113✔
319
    auto* error_message = std::get_if<std::string>(&result);
113✔
320
    REQUIRE(error_message != nullptr);
113✔
321
    REQUIRE(*error_message == expected_error_message);
113✔
322
}
113✔
323

324
// Check that unmarshaling a 64-bit immediate instruction fails.
325
static void check_unmarshal_fail(ebpf_inst inst1, ebpf_inst inst2, const std::string& expected_error_message,
16✔
326
                                 const ebpf_platform_t& platform = g_ebpf_platform_linux) {
327
    program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
16✔
328
    std::vector insns{inst1, inst2};
16✔
329
    auto result = unmarshal(raw_program{"", "", 0, "", insns, info});
16✔
330
    auto* error_message = std::get_if<std::string>(&result);
16✔
331
    REQUIRE(error_message != nullptr);
16✔
332
    REQUIRE(*error_message == expected_error_message);
16✔
333
}
16✔
334

335
static constexpr auto ws = {1, 2, 4, 8};
336

337
TEST_CASE("disasm_marshal", "[disasm][marshal]") {
14✔
338
    SECTION("Bin") {
14✔
339
        SECTION("Reg src") {
3✔
340
            auto ops = {Bin::Op::MOV,  Bin::Op::ADD,  Bin::Op::SUB,    Bin::Op::MUL,     Bin::Op::UDIV,   Bin::Op::UMOD,
1✔
341
                        Bin::Op::OR,   Bin::Op::AND,  Bin::Op::LSH,    Bin::Op::RSH,     Bin::Op::ARSH,   Bin::Op::XOR,
342
                        Bin::Op::SDIV, Bin::Op::SMOD, Bin::Op::MOVSX8, Bin::Op::MOVSX16, Bin::Op::MOVSX32};
1✔
343
            for (const auto op : ops) {
18✔
344
                compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Reg{2}, .is64 = true});
17✔
345
                compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Reg{2}, .is64 = false});
34✔
346
            }
347
        }
3✔
348
        SECTION("Imm src") {
3✔
349
            // MOVSX* instructions are not defined for Imm, only Reg.
350
            auto ops = {Bin::Op::MOV,  Bin::Op::ADD, Bin::Op::SUB,  Bin::Op::MUL, Bin::Op::UDIV,
2✔
351
                        Bin::Op::UMOD, Bin::Op::OR,  Bin::Op::AND,  Bin::Op::LSH, Bin::Op::RSH,
352
                        Bin::Op::ARSH, Bin::Op::XOR, Bin::Op::SDIV, Bin::Op::SMOD};
2✔
353
            for (const auto op : ops) {
30✔
354
                compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Imm{2}, .is64 = false});
28✔
355
                compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Imm{2}, .is64 = true});
56✔
356
            }
357
            SECTION("LDDW") {
2✔
UNCOV
358
                compare_marshal_unmarshal(
×
359
                    Bin{.op = Bin::Op::MOV, .dst = Reg{1}, .v = Imm{2}, .is64 = true, .lddw = true}, true);
2✔
360
            }
2✔
361
            SECTION("r10") {
2✔
362
                check_marshal_unmarshal_fail(Bin{.op = Bin::Op::ADD, .dst = Reg{10}, .v = Imm{4}, .is64 = true},
2✔
363
                                             "0: invalid target r10\n");
364
            }
2✔
365
        }
3✔
366
    }
14✔
367
    SECTION("Neg") {
14✔
368
        compare_marshal_unmarshal(Un{.op = Un::Op::NEG, .dst = Reg{1}, .is64 = false});
1✔
369
        compare_marshal_unmarshal(Un{.op = Un::Op::NEG, .dst = Reg{1}, .is64 = true});
2✔
370
    }
14✔
371
    SECTION("Endian") {
14✔
372
        // FIX: `.is64` comes from the instruction class (BPF_ALU or BPF_ALU64) but is unused since it can be derived
373
        // from `.op`.
374
        {
1✔
375
            auto ops = {
1✔
376
                Un::Op::BE16, Un::Op::BE32, Un::Op::BE64, Un::Op::LE16, Un::Op::LE32, Un::Op::LE64,
377
            };
1✔
378
            for (const auto op : ops) {
7✔
379
                compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}, .is64 = false});
12✔
380
            }
381
        }
382
        {
1✔
383
            auto ops = {
1✔
384
                Un::Op::SWAP16,
385
                Un::Op::SWAP32,
386
                Un::Op::SWAP64,
387
            };
1✔
388
            for (const auto op : ops) {
4✔
389
                compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}, .is64 = true});
6✔
390
            }
391
        }
392
    }
14✔
393

394
    SECTION("LoadMapFd") { compare_marshal_unmarshal(LoadMapFd{.dst = Reg{1}, .mapfd = 1}, true); }
15✔
395
    SECTION("LoadMapAddress") {
14✔
396
        compare_marshal_unmarshal(LoadMapAddress{.dst = Reg{1}, .mapfd = 1, .offset = 4}, true);
2✔
397
    }
14✔
398

399
    SECTION("Jmp") {
14✔
400
        auto ops = {Condition::Op::EQ, Condition::Op::GT, Condition::Op::GE, Condition::Op::SET,
3✔
401
                    // Condition::Op::NSET, does not exist in ebpf
402
                    Condition::Op::NE, Condition::Op::SGT, Condition::Op::SGE, Condition::Op::LT, Condition::Op::LE,
403
                    Condition::Op::SLT, Condition::Op::SLE};
3✔
404
        SECTION("goto offset") {
3✔
405
            ebpf_inst jmp_offset{.opcode = INST_OP_JA16, .offset = 1};
1✔
406
            compare_unmarshal_marshal(jmp_offset, jmp_offset);
1✔
407

408
            // JA32 +1 is equivalent to JA16 +1 since the offset fits in 16 bits.
409
            compare_unmarshal_marshal(ebpf_inst{.opcode = INST_OP_JA32, .imm = 1}, jmp_offset);
1✔
410
        }
3✔
411
        SECTION("Reg right") {
3✔
412
            for (const auto op : ops) {
12✔
413
                Condition cond{.op = op, .left = Reg{1}, .right = Reg{2}, .is64 = true};
11✔
414
                compare_marshal_unmarshal(Jmp{.cond = cond, .target = label_t(0)});
33✔
415

416
                // The following should fail unmarshalling since it jumps past the end of the instruction set.
417
                check_marshal_unmarshal_fail(Jmp{.cond = cond, .target = label_t(1)}, "0: jump out of bounds\n");
33✔
418
            }
419
        }
3✔
420
        SECTION("Imm right") {
3✔
421
            for (const auto op : ops) {
12✔
422
                Condition cond{.op = op, .left = Reg{1}, .right = Imm{2}, .is64 = true};
11✔
423
                compare_marshal_unmarshal(Jmp{.cond = cond, .target = label_t(0)});
33✔
424

425
                // The following should fail unmarshalling since it jumps past the end of the instruction set.
426
                check_marshal_unmarshal_fail(Jmp{.cond = cond, .target = label_t(1)}, "0: jump out of bounds\n");
33✔
427
            }
428
        }
3✔
429
    }
14✔
430

431
    SECTION("Call") {
14✔
432
        for (int func : {1, 17}) {
3✔
433
            compare_marshal_unmarshal(Call{func});
4✔
434
        }
435

436
        // Test callx without support.
437
        std::ostringstream oss;
1✔
438
        oss << "0: bad instruction op 0x" << std::hex << INST_OP_CALLX << std::endl;
1✔
439
        check_unmarshal_fail(ebpf_inst{.opcode = INST_OP_CALLX}, oss.str());
1✔
440

441
        // Test callx with support.  Note that callx puts the register number in 'dst' not 'src'.
442
        ebpf_platform_t platform = g_ebpf_platform_linux;
1✔
443
        platform.supported_conformance_groups |= bpf_conformance_groups_t::callx;
1✔
444
        compare_marshal_unmarshal(Callx{8}, false, platform);
1✔
445
        ebpf_inst callx{.opcode = INST_OP_CALLX, .dst = 8};
1✔
446
        compare_unmarshal_marshal(callx, callx, platform);
1✔
447
        check_unmarshal_fail({.opcode = INST_OP_CALLX, .dst = 11}, "0: bad register\n", platform);
1✔
448
        check_unmarshal_fail({.opcode = INST_OP_CALLX, .dst = 8, .imm = 8}, "0: nonzero imm for op 0x8d\n", platform);
1✔
449

450
        // clang prior to v19 put the register into 'imm' instead of 'dst' so we treat it as equivalent.
451
        compare_unmarshal_marshal(ebpf_inst{.opcode = /* 0x8d */ INST_OP_CALLX, .imm = 8}, callx, platform);
1✔
452
        check_unmarshal_fail({.opcode = INST_OP_CALLX, .imm = 11}, "0: bad register\n", platform);
1✔
453
        check_unmarshal_fail({.opcode = INST_OP_CALLX, .imm = -1}, "0: bad register\n", platform);
1✔
454
    }
15✔
455

456
    SECTION("Exit") { compare_marshal_unmarshal(Exit{}); }
15✔
457

458
    SECTION("Packet") {
14✔
459
        for (int w : ws) {
5✔
460
            if (w != 8) {
4✔
461
                compare_marshal_unmarshal(Packet{.width = w, .offset = 7, .regoffset = {}});
3✔
462
                compare_marshal_unmarshal(Packet{.width = w, .offset = 7, .regoffset = Reg{2}});
6✔
463
            }
464
        }
465
    }
14✔
466

467
    SECTION("Atomic") {
14✔
468
        for (int w : ws) {
5✔
469
            if (w == 4 || w == 8) {
4✔
470
                Deref access{.width = w, .basereg = Reg{2}, .offset = 17};
2✔
UNCOV
471
                compare_marshal_unmarshal(
×
472
                    Atomic{.op = Atomic::Op::ADD, .fetch = false, .access = access, .valreg = Reg{1}});
2✔
UNCOV
473
                compare_marshal_unmarshal(
×
474
                    Atomic{.op = Atomic::Op::ADD, .fetch = true, .access = access, .valreg = Reg{1}});
2✔
UNCOV
475
                compare_marshal_unmarshal(
×
476
                    Atomic{.op = Atomic::Op::OR, .fetch = false, .access = access, .valreg = Reg{1}});
2✔
UNCOV
477
                compare_marshal_unmarshal(
×
478
                    Atomic{.op = Atomic::Op::OR, .fetch = true, .access = access, .valreg = Reg{1}});
2✔
UNCOV
479
                compare_marshal_unmarshal(
×
480
                    Atomic{.op = Atomic::Op::AND, .fetch = false, .access = access, .valreg = Reg{1}});
2✔
UNCOV
481
                compare_marshal_unmarshal(
×
482
                    Atomic{.op = Atomic::Op::AND, .fetch = true, .access = access, .valreg = Reg{1}});
2✔
UNCOV
483
                compare_marshal_unmarshal(
×
484
                    Atomic{.op = Atomic::Op::XOR, .fetch = false, .access = access, .valreg = Reg{1}});
2✔
UNCOV
485
                compare_marshal_unmarshal(
×
486
                    Atomic{.op = Atomic::Op::XOR, .fetch = true, .access = access, .valreg = Reg{1}});
2✔
487
                check_marshal_unmarshal_fail(
4✔
488
                    Atomic{.op = Atomic::Op::XCHG, .fetch = false, .access = access, .valreg = Reg{1}},
2✔
489
                    "0: unsupported immediate\n");
UNCOV
490
                compare_marshal_unmarshal(
×
491
                    Atomic{.op = Atomic::Op::XCHG, .fetch = true, .access = access, .valreg = Reg{1}});
2✔
492
                check_marshal_unmarshal_fail(
4✔
493
                    Atomic{.op = Atomic::Op::CMPXCHG, .fetch = false, .access = access, .valreg = Reg{1}},
2✔
494
                    "0: unsupported immediate\n");
495
                compare_marshal_unmarshal(
2✔
496
                    Atomic{.op = Atomic::Op::CMPXCHG, .fetch = true, .access = access, .valreg = Reg{1}});
2✔
497
            }
498
        }
499
    }
14✔
500
}
14✔
501

502
TEST_CASE("marshal", "[disasm][marshal]") {
4✔
503
    SECTION("Load") {
4✔
504
        Deref access{.width = 1, .basereg = Reg{4}, .offset = 6};
1✔
505
        Mem m{.access = access, .value = Reg{3}, .is_load = true};
1✔
506
        auto ins = marshal(m, 0).at(0);
2✔
507
        ebpf_inst expect{
1✔
508
            .opcode = gsl::narrow<uint8_t>(INST_CLS_LD | INST_MODE_MEM | width_to_opcode(1) | 0x1),
1✔
509
            .dst = 3,
510
            .src = 4,
511
            .offset = 6,
512
            .imm = 0,
513
        };
1✔
514
        REQUIRE(ins.dst == expect.dst);
1✔
515
        REQUIRE(ins.src == expect.src);
1✔
516
        REQUIRE(ins.offset == expect.offset);
1✔
517
        REQUIRE(ins.imm == expect.imm);
1✔
518
        REQUIRE(ins.opcode == expect.opcode);
1✔
519
    }
4✔
520
    SECTION("Load Imm") {
4✔
521
        Deref access{.width = 1, .basereg = Reg{4}, .offset = 6};
1✔
522
        REQUIRE_THROWS(marshal(Mem{.access = access, .value = Imm{3}, .is_load = true}, 0));
2✔
523
    }
4✔
524
    SECTION("Store") {
4✔
525
        Deref access{.width = 1, .basereg = Reg{4}, .offset = 6};
1✔
526
        auto ins = marshal(Mem{.access = access, .value = Reg{3}, .is_load = false}, 0).at(0);
2✔
527
        REQUIRE(ins.src == 3);
1✔
528
        REQUIRE(ins.dst == 4);
1✔
529
        REQUIRE(ins.offset == 6);
1✔
530
        REQUIRE(ins.imm == 0);
1✔
531
        REQUIRE(ins.opcode == (uint8_t)(INST_CLS_ST | INST_MODE_MEM | width_to_opcode(1) | 0x1));
1✔
532
    }
4✔
533
    SECTION("StoreImm") {
4✔
534
        Deref access{.width = 1, .basereg = Reg{4}, .offset = 6};
1✔
535
        auto ins = marshal(Mem{.access = access, .value = Imm{3}, .is_load = false}, 0).at(0);
2✔
536
        REQUIRE(ins.src == 0);
1✔
537
        REQUIRE(ins.dst == 4);
1✔
538
        REQUIRE(ins.offset == 6);
1✔
539
        REQUIRE(ins.imm == 3);
1✔
540
        REQUIRE(ins.opcode == (uint8_t)(INST_CLS_ST | INST_MODE_MEM | width_to_opcode(1) | 0x0));
1✔
541
    }
4✔
542
}
4✔
543

544
TEST_CASE("disasm_marshal_Mem", "[disasm][marshal]") {
4✔
545
    SECTION("Load") {
4✔
546
        for (const int w : ws) {
5✔
547
            Deref access;
4✔
548
            access.basereg = Reg{4};
4✔
549
            access.offset = 6;
4✔
550
            access.width = w;
4✔
551
            compare_marshal_unmarshal(Mem{.access = access, .value = Reg{3}, .is_load = true});
8✔
552
        }
553
    }
4✔
554
    SECTION("Load R10") {
4✔
555
        Deref access;
1✔
556
        access.basereg = Reg{0};
1✔
557
        access.offset = 0;
1✔
558
        access.width = 8;
1✔
559
        check_marshal_unmarshal_fail(Mem{.access = access, .value = Reg{10}, .is_load = true},
2✔
560
                                     "0: cannot modify r10\n");
561
    }
4✔
562
    SECTION("Store Register") {
4✔
563
        for (const int w : ws) {
5✔
564
            Deref access;
4✔
565
            access.basereg = Reg{9};
4✔
566
            access.offset = 8;
4✔
567
            access.width = w;
4✔
568
            compare_marshal_unmarshal(Mem{.access = access, .value = Reg{4}, .is_load = false});
8✔
569
        }
570
    }
4✔
571
    SECTION("Store Immediate") {
4✔
572
        for (const int w : ws) {
5✔
573
            Deref access;
4✔
574
            access.basereg = Reg{10};
4✔
575
            access.offset = 2;
4✔
576
            access.width = w;
4✔
577
            compare_marshal_unmarshal(Mem{.access = access, .value = Imm{5}, .is_load = false});
8✔
578
        }
579
    }
4✔
580
}
4✔
581

582
TEST_CASE("unmarshal extension opcodes", "[disasm][marshal]") {
1✔
583
    // Merge (rX <<= 32; rX >>>= 32) into wX = rX.
584
    compare_unmarshal_marshal(ebpf_inst{.opcode = INST_ALU_OP_LSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32},
2✔
585
                              ebpf_inst{.opcode = INST_ALU_OP_RSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32},
2✔
586
                              ebpf_inst{.opcode = INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU, .dst = 1, .src = 1});
1✔
587

588
    // Merge (rX <<= 32; rX >>= 32)  into rX s32= rX.
589
    compare_unmarshal_marshal(
1✔
590
        ebpf_inst{.opcode = INST_ALU_OP_LSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32},
2✔
591
        ebpf_inst{.opcode = INST_ALU_OP_ARSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32},
2✔
592
        ebpf_inst{.opcode = INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU64, .dst = 1, .src = 1, .offset = 32});
1✔
593
}
1✔
594

595
// Check that unmarshaling an invalid instruction fails with a given message.
596
static void check_unmarshal_instruction_fail(ebpf_inst& inst, const std::string& message,
540✔
597
                                             const ebpf_platform_t& platform = g_ebpf_platform_linux) {
598
    if (inst.offset == JMP_OFFSET) {
540✔
599
        inst.offset = 1;
113✔
600
        check_unmarshal_fail_goto(inst, message);
113✔
601
    } else if (inst.opcode == INST_OP_LDDW_IMM) {
427✔
602
        check_unmarshal_fail(inst, ebpf_inst{}, message, platform);
7✔
603
    } else {
604
        check_unmarshal_fail(inst, message, platform);
420✔
605
    }
606
}
540✔
607

608
static ebpf_platform_t get_template_platform(const ebpf_instruction_template_t& previous_template) {
652✔
609
    ebpf_platform_t platform = g_ebpf_platform_linux;
652✔
610
    platform.supported_conformance_groups |= previous_template.groups;
652✔
611
    return platform;
652✔
612
}
613

614
// Check whether an instruction matches an instruction template that may have wildcards.
615
static bool matches_template_inst(const ebpf_inst inst, const ebpf_inst template_inst) {
270✔
616
    if (inst.opcode != template_inst.opcode) {
270✔
617
        return false;
618
    }
619
    if (inst.dst != template_inst.dst && template_inst.dst != DST) {
69✔
620
        return false;
621
    }
622
    if (inst.src != template_inst.src && template_inst.src != SRC) {
68✔
623
        return false;
624
    }
625
    if (inst.offset != template_inst.offset && template_inst.offset != MEM_OFFSET &&
55✔
626
        template_inst.offset != JMP_OFFSET) {
627
        return false;
628
    }
629
    if (inst.imm != template_inst.imm && template_inst.imm != IMM && template_inst.imm != JMP_OFFSET) {
35✔
630
        return false;
16✔
631
    }
632
    return true;
633
}
634

635
// Check that various 'dst' variations between two valid instruction templates fail.
636
static void check_instruction_dst_variations(const ebpf_instruction_template_t& previous_template,
163✔
637
                                             const std::optional<const ebpf_instruction_template_t> next_template) {
638
    ebpf_inst inst = previous_template.inst;
163✔
639
    const ebpf_platform_t platform = get_template_platform(previous_template);
163✔
640
    if (inst.dst == DST) {
163✔
641
        inst.dst = INVALID_REGISTER;
152✔
642
        check_unmarshal_instruction_fail(inst, "0: bad register\n", platform);
304✔
643
    } else {
644
        // This instruction doesn't put a register number in the 'dst' field.
645
        // Just try the next value unless that's what the next template has.
646
        inst.dst++;
11✔
647
        if (!next_template || !matches_template_inst(inst, next_template->inst)) {
11✔
648
            std::ostringstream oss;
11✔
649
            if (inst.dst == 1) {
11✔
650
                oss << "0: nonzero dst for register op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
11✔
651
            } else {
652
                oss << "0: bad instruction op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
×
653
            }
654
            check_unmarshal_instruction_fail(inst, oss.str(), platform);
11✔
655
        }
11✔
656
    }
657
}
163✔
658

659
// Check that various 'src' variations between two valid instruction templates fail.
660
static void check_instruction_src_variations(const ebpf_instruction_template_t& previous_template,
163✔
661
                                             const std::optional<const ebpf_instruction_template_t> next_template) {
662
    ebpf_inst inst = previous_template.inst;
163✔
663
    const ebpf_platform_t platform = get_template_platform(previous_template);
163✔
664
    if (inst.src == SRC) {
163✔
665
        inst.src = INVALID_REGISTER;
86✔
666
        check_unmarshal_instruction_fail(inst, "0: bad register\n", platform);
172✔
667
    } else {
668
        // This instruction doesn't put a register number in the 'src' field.
669
        // Just try the next value unless that's what the next template has.
670
        inst.src++;
77✔
671
        if (!next_template || !matches_template_inst(inst, next_template->inst)) {
77✔
672
            std::ostringstream oss;
74✔
673
            oss << "0: bad instruction op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
74✔
674
            check_unmarshal_instruction_fail(inst, oss.str(), platform);
74✔
675
        }
74✔
676
    }
677
}
163✔
678

679
// Check that various 'offset' variations between two valid instruction templates fail.
680
static void check_instruction_offset_variations(const ebpf_instruction_template_t& previous_template,
163✔
681
                                                const std::optional<const ebpf_instruction_template_t> next_template) {
682
    ebpf_inst inst = previous_template.inst;
163✔
683
    const ebpf_platform_t platform = get_template_platform(previous_template);
163✔
684
    if (inst.offset == JMP_OFFSET) {
163✔
685
        inst.offset = 0; // Not a valid jump offset.
45✔
686
        check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n", platform);
90✔
687
    } else if (inst.offset != MEM_OFFSET) {
118✔
688
        // This instruction limits what can appear in the 'offset' field.
689
        // Just try the next value unless that's what the next template has.
690
        inst.offset++;
86✔
691
        if (!next_template || !matches_template_inst(inst, next_template->inst)) {
86✔
692
            std::ostringstream oss;
78✔
693
            if (inst.offset == 1 &&
78✔
694
                (!next_template || next_template->inst.opcode != inst.opcode || next_template->inst.offset == 0)) {
65✔
695
                oss << "0: nonzero offset for op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
63✔
696
            } else {
697
                oss << "0: invalid offset for op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
15✔
698
            }
699
            check_unmarshal_instruction_fail(inst, oss.str(), platform);
78✔
700
        }
78✔
701
    }
702
}
163✔
703

704
// Check that various 'imm' variations between two valid instruction templates fail.
705
static void check_instruction_imm_variations(const ebpf_instruction_template_t& previous_template,
163✔
706
                                             const std::optional<const ebpf_instruction_template_t> next_template) {
707
    ebpf_inst inst = previous_template.inst;
163✔
708
    const ebpf_platform_t platform = get_template_platform(previous_template);
163✔
709
    if (inst.imm == JMP_OFFSET) {
163✔
710
        inst.imm = 0; // Not a valid jump offset.
2✔
711
        check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n", platform);
4✔
712
    } else if (inst.imm != IMM && inst.imm != HELPER_ID) {
161✔
713
        // This instruction limits what can appear in the 'imm' field.
714
        // Just try the next value unless that's what the next template has.
715
        inst.imm++;
97✔
716
        if (!next_template || !matches_template_inst(inst, next_template->inst)) {
97✔
717
            std::ostringstream oss;
89✔
718
            if (inst.imm == 1) {
89✔
719
                oss << "0: nonzero imm for op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
68✔
720
            } else {
721
                oss << "0: unsupported immediate" << std::endl;
21✔
722
            }
723
            check_unmarshal_instruction_fail(inst, oss.str(), platform);
89✔
724
        }
89✔
725
    }
726

727
    // Some instructions only permit non-zero imm values.
728
    // If the next template is for one of those, check the zero value now.
729
    if (next_template && (previous_template.inst.opcode != next_template->inst.opcode) &&
163✔
730
        (next_template->inst.imm > 0) && (next_template->inst.imm != HELPER_ID) &&
285✔
731
        (next_template->inst.imm != JMP_OFFSET)) {
4✔
732
        inst = next_template->inst;
3✔
733
        inst.imm = 0;
3✔
734
        check_unmarshal_instruction_fail(inst, "0: unsupported immediate\n");
6✔
735
    }
736
}
163✔
737

738
// Check that various variations between two valid instruction templates fail.
739
static void check_instruction_variations(const std::optional<const ebpf_instruction_template_t> previous_template,
164✔
740
                                         const std::optional<const ebpf_instruction_template_t> next_template) {
741
    if (previous_template) {
164✔
742
        check_instruction_dst_variations(*previous_template, next_template);
163✔
743
        check_instruction_src_variations(*previous_template, next_template);
163✔
744
        check_instruction_offset_variations(*previous_template, next_template);
163✔
745
        check_instruction_imm_variations(*previous_template, next_template);
163✔
746
    }
747

748
    // Check any invalid opcodes in between the previous and next templates.
749
    const int previous_opcode = previous_template ? previous_template->inst.opcode : -1;
164✔
750
    const int next_opcode = next_template ? next_template->inst.opcode : 0x100;
164✔
751
    for (int opcode = previous_opcode + 1; opcode < next_opcode; opcode++) {
297✔
752
        const ebpf_inst inst{.opcode = static_cast<uint8_t>(opcode)};
133✔
753
        std::ostringstream oss;
133✔
754
        oss << "0: bad instruction op 0x" << std::hex << opcode << std::endl;
133✔
755
        check_unmarshal_fail(inst, oss.str());
133✔
756
    }
133✔
757
}
164✔
758

759
TEST_CASE("fail unmarshal bad instructions", "[disasm][marshal]") {
1✔
760
    constexpr size_t template_count = std::size(instruction_template);
1✔
761

762
    // Check any variations before the first template.
763
    check_instruction_variations({}, instruction_template[0]);
1✔
764

765
    for (size_t index = 1; index < template_count; index++) {
163✔
766
        check_instruction_variations(instruction_template[index - 1], instruction_template[index]);
162✔
767
    }
768

769
    // Check any remaining variations after the last template.
770
    check_instruction_variations(instruction_template[template_count - 1], {});
1✔
771
}
1✔
772

773
TEST_CASE("check unmarshal conformance groups", "[disasm][marshal]") {
1✔
774
    for (const auto& current : instruction_template) {
164✔
775
        // Try unmarshaling without support.
776
        ebpf_platform_t platform = g_ebpf_platform_linux;
163✔
777
        platform.supported_conformance_groups &= ~current.groups;
163✔
778
        std::ostringstream oss;
163✔
779
        oss << "0: bad instruction op 0x" << std::hex << static_cast<int>(current.inst.opcode) << std::endl;
163✔
780
        check_unmarshal_fail(current.inst, oss.str(), platform);
163✔
781

782
        // Try unmarshaling with support.
783
        platform.supported_conformance_groups |= current.groups;
163✔
784
        ebpf_inst inst = current.inst;
163✔
785
        if (inst.offset == JMP_OFFSET) {
163✔
786
            inst.offset = 1;
45✔
787
        }
788
        if (inst.imm == JMP_OFFSET) {
163✔
789
            inst.imm = 1;
2✔
790
        }
791
        if (inst.opcode == INST_OP_LDDW_IMM) {
163✔
792
            check_unmarshal_succeed(inst, ebpf_inst{}, platform);
3✔
793
        } else {
794
            check_unmarshal_succeed(inst, platform);
160✔
795
        }
796
    }
163✔
797
}
1✔
798

799
TEST_CASE("check unmarshal legacy opcodes", "[disasm][marshal]") {
1✔
800
    // The following opcodes are deprecated and should no longer be used.
801
    static uint8_t supported_legacy_opcodes[] = {0x20, 0x28, 0x30, 0x40, 0x48, 0x50};
1✔
802
    for (const uint8_t opcode : supported_legacy_opcodes) {
7✔
803
        compare_unmarshal_marshal(ebpf_inst{.opcode = opcode}, ebpf_inst{.opcode = opcode});
6✔
804
    }
805

806
    // Disable legacy packet instruction support.
807
    ebpf_platform_t platform = g_ebpf_platform_linux;
1✔
808
    platform.supported_conformance_groups &= ~bpf_conformance_groups_t::packet;
1✔
809
    for (const uint8_t opcode : supported_legacy_opcodes) {
7✔
810
        std::ostringstream oss;
6✔
811
        oss << "0: bad instruction op 0x" << std::hex << static_cast<int>(opcode) << std::endl;
6✔
812
        check_unmarshal_fail(ebpf_inst{.opcode = opcode}, oss.str(), platform);
6✔
813
    }
6✔
814
}
1✔
815

816
TEST_CASE("unmarshal 64bit immediate", "[disasm][marshal]") {
1✔
817
    compare_unmarshal_marshal(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{.imm = 2},
2✔
818
                              ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1},
2✔
819
                              ebpf_inst{.imm = 2});
1✔
820
    compare_unmarshal_marshal(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{},
2✔
821
                              ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{});
1✔
822

823
    for (uint8_t src = 0; src <= 7; src++) {
9✔
824
        check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, "0: incomplete lddw\n");
8✔
825
        check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src},
16✔
826
                             ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM}, "0: invalid lddw\n");
827
    }
828

829
    // When src = {1, 3, 4, 5}, next_imm must be 0.
830
    // TODO(issue #533): add support for LDDW with src_reg > 1.
831
    check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 1}, ebpf_inst{.imm = 1},
1✔
832
                         "0: lddw uses reserved fields\n");
833
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc