372 строки
15 KiB
C++
372 строки
15 KiB
C++
// 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(), (int32_t)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;
|
|
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;
|
|
}}},
|
|
{"--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) {
|
|
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 (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;
|
|
|
|
const ebpf_program_info_t* program_info = nullptr;
|
|
result = ebpf_get_program_info_from_verifier(&program_info);
|
|
if (result != EBPF_SUCCESS) {
|
|
throw std::runtime_error(std::string("Failed to get program information"));
|
|
}
|
|
generator.parse(
|
|
program,
|
|
program_info,
|
|
(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 (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;
|
|
}
|