-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbpf2c.cpp
372 lines (344 loc) · 14.8 KB
/
bpf2c.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
// Copyright (c) eBPF for Windows contributors
// SPDX-License-Identifier: MIT
#include "bpf_code_generator.h"
// #include "ebpf_api.h"
// #include "ebpf_program_types.h"
// #include "hash.h"
// #include <Windows.h>
// #include <ElfWrapper.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#define elf_everparse_error ElfEverParseError
#define elf_everparse_verify ElfCheckElf
#pragma comment(lib, "Bcrypt.lib")
const char copyright_notice[] = "// Copyright (c) eBPF for Windows contributors\n// SPDX-License-Identifier: MIT\n";
// const char bpf2c_driver[] =
// #include "bpf2c_driver.template"
// ;
// const char bpf2c_dll[] =
// #include "bpf2c_dll.template"
// ;
void
emit_skeleton(std::ostream& out_stream, const std::string& c_name, const std::string& code)
{
auto output = std::regex_replace(code, std::regex(std::string("___METADATA_TABLE___")), c_name);
output = output.substr(strlen(copyright_notice) + 1);
out_stream << output << std::endl;
}
std::string
load_file_to_memory(const std::string& path)
{
struct stat st;
if (stat(path.c_str(), &st)) {
throw std::runtime_error(std::string("Failed to read file: ") + path);
}
if (std::ifstream stream{path, std::ios::in | std::ios::binary}) {
std::string data;
data.resize(st.st_size);
if (!stream.read(data.data(), data.size())) {
throw std::runtime_error(std::string("Failed to read file: ") + path);
}
return data;
}
throw std::runtime_error(std::string("Failed to read file: ") + path);
}
extern "C" void
elf_everparse_error(_In_ const char* struct_name, _In_ const char* field_name, _In_ const char* reason);
void
elf_everparse_error(_In_ const char* struct_name, _In_ const char* field_name, _In_ const char* reason)
{
std::cerr << "Failed parsing in struct " << struct_name << " field " << field_name << " reason " << reason
<< std::endl;
}
std::vector<uint8_t>
get_program_info_type_hash(const std::vector<int32_t>& actual_helper_ids, const std::string& algorithm)
{
std::map<uint32_t, size_t> helper_id_ordering;
size_t actual_helper_id_count = actual_helper_ids.size();
const ebpf_program_info_t* program_info;
ebpf_result_t result = ebpf_get_program_info_from_verifier(&program_info);
if (result != EBPF_SUCCESS) {
throw std::runtime_error(std::string("Failed to get program information"));
}
// Note:
// Only the helper functions which are actually called by the eBPF program are to be included in the hash.
//
// Order and fields being hashed is important. The order and fields being hashed must match the order and fields
// being hashed in _ebpf_program_verify_program_info_hash. If new fields are added to the program info, then the
// hash must be updated to include the new fields, both here and in _ebpf_program_verify_program_info_hash.
hash_t::byte_range_t byte_range;
hash_t::append_byte_range(byte_range, program_info->program_type_descriptor->name);
hash_t::append_byte_range(byte_range, *program_info->program_type_descriptor->context_descriptor);
hash_t::append_byte_range(byte_range, program_info->program_type_descriptor->program_type);
hash_t::append_byte_range(byte_range, program_info->program_type_descriptor->bpf_prog_type);
hash_t::append_byte_range(byte_range, program_info->program_type_descriptor->is_privileged);
hash_t::append_byte_range(byte_range, actual_helper_id_count);
// First, create a map of helper_id to index in the program_type_specific_helper_prototype array.
// Only include the helper IDs which are actually called by the eBPF program.
if (actual_helper_id_count > 0) {
for (size_t index = 0; index < program_info->count_of_program_type_specific_helpers; index++) {
uint32_t helper_id = program_info->program_type_specific_helper_prototype[index].helper_id;
if (std::find(actual_helper_ids.begin(), actual_helper_ids.end(), helper_id) != actual_helper_ids.end()) {
helper_id_ordering[helper_id] = index;
}
}
// Hash helper ids in increasing helper_id order
for (auto [helper_id, index] : helper_id_ordering) {
hash_t::append_byte_range(
byte_range, program_info->program_type_specific_helper_prototype[index].helper_id);
hash_t::append_byte_range(byte_range, program_info->program_type_specific_helper_prototype[index].name);
hash_t::append_byte_range(
byte_range, program_info->program_type_specific_helper_prototype[index].return_type);
for (size_t argument = 0;
argument < _countof(program_info->program_type_specific_helper_prototype[index].arguments);
argument++) {
hash_t::append_byte_range(
byte_range, program_info->program_type_specific_helper_prototype[index].arguments[argument]);
}
// This check for flags is temporary, until https://github.com/microsoft/ebpf-for-windows/issues/3429 is
// fixed.
if (program_info->program_type_specific_helper_prototype[index].flags.reallocate_packet != 0) {
hash_t::append_byte_range(
byte_range, program_info->program_type_specific_helper_prototype[index].flags);
}
}
}
hash_t hash(algorithm);
return hash.hash_byte_ranges(byte_range);
}
int
main(int argc, char** argv)
{
try {
enum class output_type
{
Bare,
KernelPE,
UserPE,
} type = output_type::Bare;
std::string file;
std::string output_file_name;
std::string type_string = "";
std::string hash_algorithm = EBPF_HASH_ALGORITHM;
bool verify_programs = true;
std::vector<std::string> parameters(argv + 1, argv + argc);
auto iter = parameters.begin();
auto iter_end = parameters.end();
std::map<std::string, std::tuple<std::string, std::function<bool()>>> options = {
{"--sys",
{"Generate code for a Windows driver with optional output file name",
[&]() {
type = output_type::KernelPE;
if ((iter + 1 != iter_end) && !(*(iter + 1)).empty() && (*(iter + 1))[0] != '-') {
++iter;
output_file_name = *iter;
}
return true;
}}},
{"--dll",
{"Generate code for a Windows DLL with optional output file name",
[&]() {
type = output_type::UserPE;
if ((iter + 1 != iter_end) && !(*(iter + 1)).empty() && (*(iter + 1))[0] != '-') {
++iter;
output_file_name = *iter;
}
return true;
}}},
{"--raw",
{"Generate code without any platform wrapper with optional output file name",
[&]() {
type = output_type::Bare;
if ((iter + 1 != iter_end) && !(*(iter + 1)).empty() && (*(iter + 1))[0] != '-') {
++iter;
output_file_name = *iter;
}
return true;
}}},
#if defined(ENABLE_SKIP_VERIFY)
{"--no-verify",
{"Skip validating code using verifier",
[&]() {
verify_programs = false;
return true;
}}},
#endif
{"--bpf",
{"Input ELF file containing BPF byte code",
[&]() {
++iter;
if (iter == iter_end) {
std::cerr << "Invalid --bpf option" << std::endl;
return false;
} else {
file = *iter;
return true;
}
}}},
{"--type",
{"Type string for the eBPF programs",
[&]() {
++iter;
if (iter == iter_end) {
std::cerr << "Invalid --type option" << std::endl;
return false;
} else {
type_string = *iter;
return true;
}
}}},
{"--hash",
{"Algorithm used to hash ELF file",
[&]() {
++iter;
if (iter == iter_end) {
std::cerr << "Invalid --hash option" << std::endl;
return false;
} else {
hash_algorithm = *iter;
return true;
}
}}},
{"--help",
{"This help menu",
[&]() {
std::cerr << argv[0]
<< " is a tool to generate C code"
" from an ELF file containing BPF byte code."
<< std::endl;
std::cerr << "Options are:" << std::endl;
for (auto [option, tuple] : options) {
auto [help, _] = tuple;
std::cerr << option.c_str() << "\t" << help.c_str() << std::endl;
}
return false;
}}},
};
for (; iter != iter_end; ++iter) {
auto option = options.find(*iter);
if (option == options.end()) {
option = options.find("--help");
}
auto [_, function] = option->second;
if (!function()) {
return 1;
}
}
if (file.empty()) {
std::get<1>(options["--help"])();
return 1;
}
std::string c_name = file.substr(file.find_last_of("\\") + 1);
c_name = c_name.substr(0, c_name.find("."));
auto data = load_file_to_memory(file);
std::optional<std::vector<uint8_t>> hash_value;
if (hash_algorithm != "none") {
_hash hash(hash_algorithm);
hash_value = hash.hash_string(data);
}
auto stream = std::stringstream(data);
if (!ElfCheckElf(data.size(), reinterpret_cast<uint8_t*>(data.data()), static_cast<uint32_t>(data.size()))) {
std::cerr << "ELF file is invalid" << std::endl;
return 1;
}
// Capture list of programs.
ebpf_api_program_info_t* infos = nullptr;
const char* error_message = nullptr;
ebpf_result_t result = ebpf_enumerate_programs(file.c_str(), false, &infos, &error_message);
if ((result != EBPF_SUCCESS) && verify_programs) {
std::cerr << error_message << std::endl;
ebpf_free_string(error_message);
return 1;
}
bpf_code_generator generator(stream, c_name, {hash_value});
// Parse global data.
generator.parse();
// Get global program and attach types, if any.
ebpf_program_type_t program_type;
ebpf_attach_type_t attach_type;
bool global_program_type_set = false;
if (type_string != "") {
if (ebpf_get_program_type_by_name(type_string.c_str(), &program_type, &attach_type) != EBPF_SUCCESS) {
std::cerr << "Program type not found for type string " << type_string << std::endl;
ebpf_free_programs(infos);
ebpf_free_string(error_message);
return 1;
}
global_program_type_set = true;
}
// Parse per-program data.
for (const ebpf_api_program_info_t* program = infos; program; program = program->next) {
const char* report = nullptr;
ebpf_api_verifier_stats_t stats;
std::optional<std::vector<uint8_t>> program_info_hash;
if (verify_programs && ebpf_api_elf_verify_program_from_memory(
data.c_str(),
data.size(),
program->section_name,
program->program_name,
(global_program_type_set) ? &program_type : &program->program_type,
EBPF_VERIFICATION_VERBOSITY_NORMAL,
&report,
&error_message,
&stats) != 0) {
report = ((report == nullptr) ? "" : report);
throw std::runtime_error(
std::string("Verification failed for ") + std::string(program->program_name) +
std::string(" with error ") + std::string(error_message) + std::string("\n Report:\n") +
std::string(report));
}
ebpf_free_string(report);
ebpf_free_string(error_message);
error_message = nullptr;
generator.parse(
program,
(global_program_type_set) ? program_type : program->program_type,
(global_program_type_set) ? attach_type : program->expected_attach_type,
hash_algorithm);
generator.generate(program->section_name, program->program_name);
if (verify_programs && (hash_algorithm != "none")) {
std::vector<int32_t> helper_ids = generator.get_helper_ids();
program_info_hash = get_program_info_type_hash(helper_ids, hash_algorithm);
generator.set_program_hash_info(program_info_hash);
}
}
ebpf_free_programs(infos);
ebpf_free_string(error_message);
std::ofstream output_file;
if (!output_file_name.empty()) {
output_file.open(output_file_name, std::ios::out | std::ios::trunc);
if (!output_file.is_open()) {
std::cerr << "Failed to open output file " << output_file_name << std::endl;
return 1;
}
}
std::ostream& out_stream = output_file_name.empty() ? std::cout : output_file;
out_stream << copyright_notice << std::endl;
out_stream << "// Do not alter this generated file." << std::endl;
out_stream << "// This file was generated from " << file << std::endl << std::endl;
switch (type) {
case output_type::Bare:
break;
case output_type::KernelPE:
emit_skeleton(out_stream, c_name, bpf2c_driver);
break;
case output_type::UserPE:
emit_skeleton(out_stream, c_name, bpf2c_dll);
break;
default:
throw std::runtime_error("Invalid output type");
}
generator.emit_c_code(out_stream);
} catch (std::runtime_error err) {
std::cerr << err.what() << std::endl;
return 1;
}
return 0;
}