diff --git a/ebpf_ffi/cbpf.cc b/ebpf_ffi/cbpf.cc index 74cbb33..8b855b8 100644 --- a/ebpf_ffi/cbpf.cc +++ b/ebpf_ffi/cbpf.cc @@ -20,10 +20,10 @@ #include #include -bool load_cbpf_program(void *prog_buff, size_t size, std::string *error, +bool load_cbpf_program(void *prog_buff, size_t size, std::string &error, int *socks) { if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socks) < 0) { - *error = strerror(errno); + error = strerror(errno); return false; } // cBPF programs have two relevant structures: sock_filter, and sock_fprog @@ -40,12 +40,12 @@ bool load_cbpf_program(void *prog_buff, size_t size, std::string *error, tv.tv_usec = 10000; if (setsockopt(socks[1], SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) < 0) { - *error = strerror(errno); + error = strerror(errno); return false; } if (setsockopt(socks[1], SOL_SOCKET, SO_ATTACH_FILTER, &program, sizeof(program)) < 0) { - *error = strerror(errno); + error = strerror(errno); return false; } return true; @@ -72,7 +72,7 @@ struct bpf_result ffi_load_cbpf_program(void *prog_buff, size_t size, ValidationResult vres; int socks[2] = {-1, -1}; - if (!load_cbpf_program(prog_buff, size, &error_message, socks)) { + if (!load_cbpf_program(prog_buff, size, error_message, socks)) { // Return why we failed to load the program. return validation_error(error_message, &vres); } @@ -101,15 +101,15 @@ struct bpf_result ffi_load_cbpf_program(void *prog_buff, size_t size, bool execute_cbpf_program(int socket_write, int socket_read, uint8_t *input, uint8_t *output, int input_length, - std::string *error_message) { + std::string &error_message) { if (write(socket_write, input, input_length) != input_length) { - *error_message = "Could not write all data to socket"; + error_message = "Could not write all data to socket"; return false; } close(socket_write); if (read(socket_read, output, input_length) != input_length) { - *error_message = "Could not read all data to socket"; + error_message = "Could not read all data to socket"; } close(socket_read); @@ -153,7 +153,7 @@ struct bpf_result ffi_execute_cbpf_program(void *serialized_proto, memset(read_data, 0x00, data_size + 1); std::string error_message; if (!execute_cbpf_program(socket_write, socket_read, data, read_data, - data_size, &error_message)) { + data_size, error_message)) { return return_error(error_message, &execution_result); } diff --git a/ebpf_ffi/cbpf.h b/ebpf_ffi/cbpf.h index d3e31db..c1bc374 100644 --- a/ebpf_ffi/cbpf.h +++ b/ebpf_ffi/cbpf.h @@ -24,7 +24,7 @@ extern "C" { // Actual implementation of load program. The split between ffi and // implementation is done so the impl code can be shared with other parts of the // codebase also written in C++. -bool load_cbpf_program(void *prog_buff, size_t size, std::string *error, +bool load_cbpf_program(void *prog_buff, size_t size, std::string &error, int *socks); // Loads a bpf program specified by |prog_buff| with |size| and returns struct @@ -34,7 +34,7 @@ struct bpf_result ffi_load_cbpf_program(void *prog_buff, size_t size, uint64_t coverage_size); bool execute_cbpf_program(int prog_fd, uint8_t *input, uint8_t *output, - int input_length, std::string *error_message); + int input_length, std::string &error_message); // Runs the specified cbpf program by sending some data to a socket. // Serialized proto is of type ExecutionRequest. diff --git a/ebpf_ffi/ebpf.cc b/ebpf_ffi/ebpf.cc index 7a139b7..b695f77 100644 --- a/ebpf_ffi/ebpf.cc +++ b/ebpf_ffi/ebpf.cc @@ -19,10 +19,33 @@ namespace ebpf_ffi { // This constant was determined arbitrarily, the number of 0's has incremented // when the size was no longer enough for the verifier logs. constexpr size_t kLogBuffSize = 100000000; +// This constnat was determined arbitrarily for the btf logs +constexpr size_t btfKLogBuffSize = 1024; } // namespace ebpf_ffi -int load_ebpf_program(void *prog_buff, size_t prog_size, - std::string *verifier_log, std::string *error) { +// btf_buff: Pointer to a buffer where the BTF data is stored +// btf_size: Size of the BTF data in bytes +int btf_load(void *btf_buff, size_t btf_size, std::string &error) { + union bpf_attr btf_attr; + memset(&btf_attr, 0, sizeof(btf_attr)); + btf_attr.btf = (uint64_t)btf_buff; + btf_attr.btf_size = btf_size; + + char *btf_log_buf = (char *)malloc(ebpf_ffi::btfKLogBuffSize); + memset(btf_log_buf, 0, ebpf_ffi::btfKLogBuffSize); + btf_attr.btf_log_buf = (uint64_t)btf_log_buf; + btf_attr.btf_log_size = ebpf_ffi::btfKLogBuffSize; + btf_attr.btf_log_level = 2; + + int btf_fd = syscall(SYS_bpf, BPF_BTF_LOAD, &btf_attr, sizeof(btf_attr)); + if (btf_fd < 0) { + error = strerror(errno); + } + return btf_fd; +} + +int load_ebpf_program(EncodedProgram program, size_t size, + std::string &verifier_log, std::string &error) { struct bpf_insn *insn; union bpf_attr attr = {}; @@ -30,10 +53,21 @@ int load_ebpf_program(void *prog_buff, size_t prog_size, unsigned char *log_buf = (unsigned char *)malloc(ebpf_ffi::kLogBuffSize); memset(log_buf, 0, ebpf_ffi::kLogBuffSize); - insn = (struct bpf_insn *)prog_buff; + int btf_fd = btf_load(((uint8_t *)(program.btf().c_str())), + (program.btf().length()), error); + if (!(btf_fd < 0)) { + struct bpf_func_info *func = + (struct bpf_func_info *)((uint8_t *)(program.function().c_str())); + attr.prog_btf_fd = btf_fd; + attr.func_info_rec_size = sizeof(struct bpf_func_info); + attr.func_info = (uint64_t)(func); + attr.func_info_cnt = + ((program.function().length()) / sizeof(struct bpf_func_info)); + } + insn = (struct bpf_insn *)((uint8_t *)(program.program().c_str())); attr.prog_type = BPF_PROG_TYPE_SOCKET_FILTER; attr.insns = (uint64_t)insn; - attr.insn_cnt = (prog_size * sizeof(uint64_t)) / (sizeof(struct bpf_insn)); + attr.insn_cnt = ((program.program().length()) / (sizeof(struct bpf_insn))); attr.license = (uint64_t) "GPL"; attr.log_size = ebpf_ffi::kLogBuffSize; attr.log_buf = (uint64_t)log_buf; @@ -41,29 +75,35 @@ int load_ebpf_program(void *prog_buff, size_t prog_size, int program_fd = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); if (program_fd < 0) { - *error = strerror(errno); + error = strerror(errno); } - *verifier_log = + verifier_log = std::string((const char *)log_buf, strlen((const char *)log_buf)); free(log_buf); return program_fd; } -struct bpf_result ffi_load_ebpf_program(void *prog_buff, size_t size, +struct bpf_result ffi_load_ebpf_program(void *serialized_proto, size_t size, int coverage_enabled, uint64_t coverage_size) { std::string verifier_log, error_message; + struct coverage_data cover; memset(&cover, 0, sizeof(struct coverage_data)); cover.fd = -1; cover.coverage_size = coverage_size; if (coverage_enabled) enable_coverage(&cover); + std::string serialized_proto_string( + reinterpret_cast(serialized_proto), size); + EncodedProgram program; + if (!program.ParseFromString(serialized_proto_string)) { + error_message = "Could not parse EncodedProgram proto"; + } int program_fd = - load_ebpf_program(prog_buff, size, &verifier_log, &error_message); - + load_ebpf_program(program, size, verifier_log, error_message); ValidationResult vres; if (coverage_enabled) get_coverage_and_free_resources(&cover, &vres); @@ -91,7 +131,7 @@ struct bpf_result ffi_load_ebpf_program(void *prog_buff, size_t size, } bool get_map_elements(int map_fd, size_t map_size, std::vector *res, - std::string *error) { + std::string &error) { for (uint64_t key = 0; key < map_size; key++) { uint64_t element = 0; union bpf_attr lookup_map = {.map_fd = static_cast(map_fd), @@ -100,7 +140,7 @@ bool get_map_elements(int map_fd, size_t map_size, std::vector *res, int err = syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &lookup_map, sizeof(lookup_map)); if (err < 0) { - *error = strerror(errno); + error = strerror(errno); return false; } res->push_back(element); @@ -139,7 +179,7 @@ struct bpf_result ffi_get_map_elements(int map_fd, uint64_t map_size) { MapElements res; std::vector elements; std::string error_message; - if (!get_map_elements(map_fd, map_size, &elements, &error_message)) { + if (!get_map_elements(map_fd, map_size, &elements, error_message)) { res.set_error_message(error_message); return serialize_proto(res); } @@ -149,7 +189,7 @@ struct bpf_result ffi_get_map_elements(int map_fd, uint64_t map_size) { } bool execute_ebpf_program(int prog_fd, uint8_t *input, int input_length, - std::string *error_message) { + std::string &error_message) { int socks[2] = {}; if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socks) != 0) { return execute_error(error_message, strerror(errno), NULL); @@ -193,7 +233,7 @@ struct bpf_result ffi_execute_ebpf_program(void *serialized_proto, } std::string error_message; - if (!execute_ebpf_program(prog_fd, data, data_size, &error_message)) { + if (!execute_ebpf_program(prog_fd, data, data_size, error_message)) { return return_error(error_message, &execution_result); } diff --git a/ebpf_ffi/ebpf.h b/ebpf_ffi/ebpf.h index d47d792..7b60f9f 100644 --- a/ebpf_ffi/ebpf.h +++ b/ebpf_ffi/ebpf.h @@ -30,17 +30,17 @@ extern "C" { // Actual implementation of load program. The split between ffi and // implementation is done so the impl code can be shared with other parts of the // codebase also written in C++. -int load_ebpf_program(void *prog_buff, size_t prog_size, - std::string *verifier_log, std::string *error); +int load_ebpf_program(EncodedProgram program, size_t size, + std::string &verifier_log, std::string &error); // Loads a bpf program specified by |prog_buff| with |size| and returns struct // with a serialized ValidationResult proto. -struct bpf_result ffi_load_ebpf_program(void *prog_buff, size_t size, +struct bpf_result ffi_load_ebpf_program(void *serialized_proto, size_t size, int coverage_enabled, uint64_t coverage_size); bool get_map_elements(int map_fd, size_t map_size, std::vector *res, - std::string *error); + std::string &error); // Sets the value at key |key| in the map described by |map_fd| to |value|. int ffi_update_map_element(int map_fd, int key, uint64_t value); @@ -56,7 +56,7 @@ int ffi_create_bpf_map(size_t size); struct bpf_result ffi_get_map_elements(int map_fd, uint64_t map_size); bool execute_ebpf_program(int prog_fd, uint8_t *input, int input_length, - std::string *error_message); + std::string &error_message); /// Runs the specified ebpf program by sending some data to a socket. // Serialized proto is of type ExecutionRequest. diff --git a/ebpf_ffi/ffi.cc b/ebpf_ffi/ffi.cc index 32c7112..51f7c3e 100644 --- a/ebpf_ffi/ffi.cc +++ b/ebpf_ffi/ffi.cc @@ -103,13 +103,13 @@ void get_coverage_and_free_resources(struct coverage_data *cstruct, munmap(cstruct->coverage_buffer, cstruct->coverage_size * sizeof(uint64_t)); } -bool execute_error(std::string *error_message, const char *strerr, +bool execute_error(std::string &error_message, const char *strerr, int *sockets) { if (sockets != nullptr) { close(sockets[0]); close(sockets[1]); } - *error_message = strerr; + error_message = strerr; return false; } diff --git a/ebpf_ffi/ffi.h b/ebpf_ffi/ffi.h index cbcb65e..5fdf37a 100644 --- a/ebpf_ffi/ffi.h +++ b/ebpf_ffi/ffi.h @@ -49,6 +49,7 @@ #define KCOV_TRACE_CMP 1 using ebpf_fuzzer::CbpfExecutionRequest; +using ebpf_fuzzer::EncodedProgram; using ebpf_fuzzer::ExecutionRequest; using ebpf_fuzzer::ExecutionResult; using ebpf_fuzzer::MapElements; @@ -70,7 +71,7 @@ void enable_coverage(struct coverage_data *coverage_info); void get_coverage_and_free_resources(struct coverage_data *cstruct, ValidationResult *vres); -bool execute_error(std::string *error_message, const char *strerr, +bool execute_error(std::string &error_message, const char *strerr, int *sockets); struct bpf_result return_error(std::string error_message, diff --git a/pkg/ebpf/BUILD b/pkg/ebpf/BUILD index 4c8a24e..9ba4200 100644 --- a/pkg/ebpf/BUILD +++ b/pkg/ebpf/BUILD @@ -24,6 +24,7 @@ go_library( name = "ebpf", srcs = [ "alu_instructions.go", + "btf.go", "constants.go", "encoding_functions.go", "instruction_generators.go", @@ -39,8 +40,10 @@ go_library( importpath = "buzzer/pkg/ebpf/ebpf", deps = [ "//pkg/rand", + "//proto:btf_go_proto", "//proto:ebpf_go_proto", "@com_github_golang_protobuf//jsonpb", + "@com_github_golang_protobuf//proto", ], ) diff --git a/pkg/ebpf/btf.go b/pkg/ebpf/btf.go new file mode 100644 index 0000000..f33b111 --- /dev/null +++ b/pkg/ebpf/btf.go @@ -0,0 +1,229 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ebpf + +import ( + pb "buzzer/proto/btf_go_proto" + "bytes" + "encoding/binary" + "fmt" + proto "github.com/golang/protobuf/proto" +) + +// typeSection constructs a pb.TypeSection containing a set of basic types +// used to represent functions in eBPF programs +// +// The generated types are: +// +// - Func_Proto: A base type for function prototypes +// - Func: A function type, referencing a Func_Proto +// - Int: 32-bit signed integer type +// - Struct: A struct containing a single Int member +// - Ptr: A pointer type +// - Func_Proto (second occurrence): Function prototype with two parameters: an Int and a Ptr +// - Func (second occurrence): Function type referencing the second Func_Proto +// +// These types are defined according to the BTF type encoding documented at +// https://docs.kernel.org/bpf/btf.html#type-encoding +// +// The generated TypeSection will be used by eBPF programs to provide BTF +// information about functions, e.g. enabling helper functions +func typeSection() *pb.TypeSection { + + types := []*pb.BtfType{} + + // 1: Func_Proto + types = append(types, &pb.BtfType{ + NameOff: 0x0, + Info: 0x0d000000, + SizeOrType: 0x0, + Extra: &pb.BtfType_Empty{ + Empty: &pb.Empty{}, + }, + }) + + // 2: Func + types = append(types, &pb.BtfType{ + NameOff: 0x1, + Info: 0x0c000000, + SizeOrType: 0x01, + Extra: &pb.BtfType_Empty{ + Empty: &pb.Empty{}, + }, + }) + + // 3: Int + types = append(types, &pb.BtfType{ + NameOff: 0x1, + Info: 0x01000000, + SizeOrType: 0x4, + Extra: &pb.BtfType_IntTypeData{ + IntTypeData: &pb.IntTypeData{IntInfo: 0x01000020}, + }, + }) + + // 4: Struct + types = append(types, &pb.BtfType{ + NameOff: 0x1, + Info: 0x04000001, + SizeOrType: 0x4, + Extra: &pb.BtfType_StructTypeData{ + StructTypeData: &pb.StructTypeData{ + NameOff: 0x1, + StructType: 0x3, + Offset: 0x0, + }, + }, + }) + + // 5: Ptr + types = append(types, &pb.BtfType{ + NameOff: 0x0, + Info: 0x02000000, + SizeOrType: 0x4, + Extra: &pb.BtfType_Empty{ + Empty: &pb.Empty{}, + }, + }) + + // 6: Func_Proto + types = append(types, &pb.BtfType{ + NameOff: 0x0, + Info: 0x0d000002, + SizeOrType: 0x3, + Extra: &pb.BtfType_FuncProtoTypeData{ + FuncProtoTypeData: &pb.FuncProtoTypeData{ + Param: []*pb.BtfParam{ + {NameOff: 0x1, ParamType: 0x3}, + {NameOff: 0x1, ParamType: 0x5}, + }, + }, + }, + }) + + // 7: Func + types = append(types, &pb.BtfType{ + NameOff: 0x1, + Info: 0x0c000000, + SizeOrType: 0x6, + Extra: &pb.BtfType_Empty{ + Empty: &pb.Empty{}, + }, + }) + return &pb.TypeSection{BtfType: types} +} + +func stringSection() *pb.StringSection { + // For buzzer's BTF Implementation the string sections is not currently + // in used. + return &pb.StringSection{Str: "buzzer"} +} + +func getBtf() *pb.Btf { + // https://docs.kernel.org/bpf/btf.html#btf-type-and-string-encoding + type_section := typeSection() + string_section := stringSection() + header := &pb.Header{ + Magic: 0xeb9f, + Version: 0x01, + Flags: 0x00, + HdrLen: 0x18, + TypeOff: 0x0, + TypeLen: int32(proto.Size(type_section)), + StrOff: int32(proto.Size(type_section)), + StrLen: int32(proto.Size(string_section)), + } + return &pb.Btf{ + Header: header, + TypeSection: type_section, + StringSection: string_section, + } +} + +// GenerateBtf returns a byte array containing the serialized BTF data from a BTF proto. +func GenerateBtf() ([]byte, error) { + btf_proto := getBtf() + var btf_buff bytes.Buffer + var err error + + var type_data = []any{} + for _, t := range btf_proto.TypeSection.BtfType { + type_data = append(type_data, t.NameOff) + type_data = append(type_data, t.Info) + type_data = append(type_data, t.SizeOrType) + switch e := t.Extra.(type) { + case *pb.BtfType_IntTypeData: + type_data = append(type_data, e.IntTypeData.IntInfo) + case *pb.BtfType_StructTypeData: + type_data = append(type_data, e.StructTypeData.NameOff) + type_data = append(type_data, e.StructTypeData.StructType) + type_data = append(type_data, e.StructTypeData.Offset) + case *pb.BtfType_FuncProtoTypeData: + for _, param := range e.FuncProtoTypeData.Param { + type_data = append(type_data, param.NameOff) + type_data = append(type_data, param.ParamType) + } + } + } + var types_buff bytes.Buffer + //types_buff := new(bytes.Buffer) + for _, types := range type_data { + err = binary.Write(&types_buff, binary.LittleEndian, types) + if err != nil { + fmt.Println("binary.Write failed:", err) + return nil, err + } + } + + string_data := []byte(btf_proto.StringSection.Str) + var string_buff bytes.Buffer + // The first string in the string section must be a null string + string_buff.Write([]byte{0}) + for _, strings := range string_data { + err = binary.Write(&string_buff, binary.LittleEndian, strings) + if err != nil { + fmt.Println("binary.Write failed:", err) + return nil, err + } + string_buff.Write([]byte{0}) + } + + btf_proto.Header.TypeLen = int32(len(types_buff.Bytes())) + btf_proto.Header.StrOff = int32(len(types_buff.Bytes())) + btf_proto.Header.StrLen = int32(len(string_buff.Bytes())) + + var header_data = []any{ + uint16(btf_proto.Header.Magic), + uint8(btf_proto.Header.Version), + uint8(btf_proto.Header.Flags), + btf_proto.Header.HdrLen, + btf_proto.Header.TypeOff, + btf_proto.Header.TypeLen, + btf_proto.Header.StrOff, + btf_proto.Header.StrLen, + } + for _, header := range header_data { + err = binary.Write(&btf_buff, binary.LittleEndian, header) + if err != nil { + fmt.Println("binary.Write failed:", err) + return nil, err + } + } + + btf_buff.Write(types_buff.Bytes()) + btf_buff.Write(string_buff.Bytes()) + return btf_buff.Bytes(), nil + +} diff --git a/pkg/ebpf/encoding_functions.go b/pkg/ebpf/encoding_functions.go index f0ff13f..19ab2f3 100644 --- a/pkg/ebpf/encoding_functions.go +++ b/pkg/ebpf/encoding_functions.go @@ -16,6 +16,8 @@ package ebpf import ( pb "buzzer/proto/ebpf_go_proto" + "bytes" + "encoding/binary" "fmt" ) @@ -58,17 +60,44 @@ func encodeMemOpcode(op *pb.MemOpcode) (uint8, error) { return opcode, nil } -// EncodeInstructions transforms the given array to ebpf bytecode. -func EncodeInstructions(program *pb.Program) ([]uint64, error) { - result := []uint64{} - for _, instruction := range program.Instructions { - encoding, err := encodeInstruction(instruction) +// EncodeInstructions transforms the given array of functions with instructions +// and function info to ebpf bytecode and func_info bytecode, respectively. +func EncodeInstructions(program *pb.Program) ([]byte, []byte, error) { + prog_buff := new(bytes.Buffer) + func_buff := new(bytes.Buffer) + + // The first function info must be with offset 0 and type_id to a function + for _, functions := range program.Functions { + var err error + for _, instruction := range functions.Instructions { + encoding, err := encodeInstruction(instruction) + if err != nil { + return nil, nil, err + } + err = binary.Write(prog_buff, binary.LittleEndian, encoding) + if err != nil { + fmt.Println("binary.Write failed:", err) + return nil, nil, err + } + } + + if functions.FuncInfo == nil { + continue + } + + err = binary.Write(func_buff, binary.LittleEndian, functions.FuncInfo.InsnOff) if err != nil { - return nil, err + fmt.Println("binary.Write failed:", err) + return nil, nil, err + } + err = binary.Write(func_buff, binary.LittleEndian, functions.FuncInfo.TypeId) + if err != nil { + fmt.Println("binary.Write failed:", err) + return nil, nil, err } - result = append(result, encoding...) } - return result, nil + + return prog_buff.Bytes(), func_buff.Bytes(), nil } // To understand what each part of the encoding mean, please refer to diff --git a/pkg/ebpf/jmp_instructions.go b/pkg/ebpf/jmp_instructions.go index 1bda36a..1e37764 100644 --- a/pkg/ebpf/jmp_instructions.go +++ b/pkg/ebpf/jmp_instructions.go @@ -123,6 +123,41 @@ func Call(functionValue int32) *pb.Instruction { return newJmpInstruction(pb.JmpOperationCode_JmpCALL, pb.InsClass_InsClassJmp, pb.Reg_R0, functionValue, int16(UnusedField)) } +func LdFunctionPtr(Imm int32) *pb.Instruction { + return &pb.Instruction{ + Opcode: &pb.Instruction_MemOpcode{ + MemOpcode: &pb.MemOpcode{ + Mode: pb.StLdMode_StLdModeIMM, + Size: pb.StLdSize_StLdSizeDW, + InstructionClass: pb.InsClass_InsClassLd, + }, + }, + DstReg: R2, + SrcReg: pb.Reg_R4, + Offset: 0, + Immediate: Imm, + PseudoInstruction: &pb.Instruction_PseudoValue{ + PseudoValue: &pb.Instruction{ + Opcode: &pb.Instruction_MemOpcode{ + MemOpcode: &pb.MemOpcode{ + Mode: 0, + Size: 0, + InstructionClass: 0, + }, + }, + DstReg: 0, + SrcReg: 0, + Offset: 0, + Immediate: 0, + PseudoInstruction: &pb.Instruction_Empty{ + Empty: &pb.Empty{}, + }, + }, + }, + } + +} + // LdMapElement loads a map element ptr to R0. // It does the following operations: // - Set R1 to the pointer of the target map. diff --git a/pkg/strategies/BUILD b/pkg/strategies/BUILD index ce776c5..8d9b452 100644 --- a/pkg/strategies/BUILD +++ b/pkg/strategies/BUILD @@ -36,6 +36,7 @@ go_library( "//pkg/ebpf", "//pkg/rand", "//pkg/units", + "//proto:btf_go_proto", "//proto:cbpf_go_proto", "//proto:ebpf_go_proto", "//proto:ffi_go_proto", diff --git a/pkg/strategies/coverage_based.go b/pkg/strategies/coverage_based.go index 869f9ef..b4fa03d 100644 --- a/pkg/strategies/coverage_based.go +++ b/pkg/strategies/coverage_based.go @@ -233,7 +233,9 @@ func (cv *CoverageBased) GenerateProgram(ffi *units.FFI) (*pb.Program, error) { prog := &pb.Program{ Program: &pb.Program_Ebpf{ Ebpf: &epb.Program{ - Instructions: append(mutatedProgram, footer...), + Functions: []*epb.Functions{ + {Instructions: append(mutatedProgram, footer...)}, + }, }, }, } diff --git a/pkg/strategies/playground.go b/pkg/strategies/playground.go index e6feafb..1f2d75d 100644 --- a/pkg/strategies/playground.go +++ b/pkg/strategies/playground.go @@ -20,8 +20,9 @@ type Playground struct { } func (pg *Playground) GenerateProgram(ffi *units.FFI) (*pb.Program, error) { + insn, err := InstructionSequence( - Mov64(R0, 0), + Mov(R0, 0), Exit(), ) if err != nil { @@ -29,7 +30,11 @@ func (pg *Playground) GenerateProgram(ffi *units.FFI) (*pb.Program, error) { } prog := &pb.Program{ Program: &pb.Program_Ebpf{ - Ebpf: &epb.Program{Instructions: insn}, + Ebpf: &epb.Program{ + Functions: []*epb.Functions{ + {Instructions: insn}, + }, + }, }} return prog, nil } diff --git a/pkg/strategies/pointer_arithmetic.go b/pkg/strategies/pointer_arithmetic.go index a25d21d..b464dde 100644 --- a/pkg/strategies/pointer_arithmetic.go +++ b/pkg/strategies/pointer_arithmetic.go @@ -119,12 +119,13 @@ func (pa *PointerArithmetic) GenerateProgram(ffi *units.FFI) (*pb.Program, error } header = append(header, body...) header = append(header, footer...) - p := &epb.Program{ - Instructions: header, - } prog := &pb.Program{ Program: &pb.Program_Ebpf{ - Ebpf: p, + Ebpf: &epb.Program{ + Functions: []*epb.Functions{ + {Instructions: header}, + }, + }, }} return prog, nil } diff --git a/pkg/units/control.go b/pkg/units/control.go index adde3a5..c607a0d 100644 --- a/pkg/units/control.go +++ b/pkg/units/control.go @@ -121,7 +121,8 @@ func (cu *Control) RunFuzzer() error { } func (cu *Control) runEbpf(prog *epb.Program) error { - encodedProg, err := ebpf.EncodeInstructions(prog) + encodedProg, encodedFuncInfo, err := ebpf.EncodeInstructions(prog) + if err != nil { fmt.Printf("Encoding error: %v\n", err) if !cu.strat.OnError(err) { @@ -129,7 +130,19 @@ func (cu *Control) runEbpf(prog *epb.Program) error { } } - validationResult, err := cu.ffi.ValidateEbpfProgram(encodedProg) + var encodedBtf []byte + if prog.Btf { + encodedBtf, err = ebpf.GenerateBtf() + if err != nil { + fmt.Printf("Btf error: %v\n", err) + } + } + encodedProgram := &fpb.EncodedProgram{ + Program: encodedProg, + Btf: encodedBtf, + Function: encodedFuncInfo, + } + validationResult, err := cu.ffi.ValidateEbpfProgram(encodedProgram) if err != nil { fmt.Printf("Validation error: %v\n", err) if !cu.strat.OnError(err) { diff --git a/pkg/units/ffi.go b/pkg/units/ffi.go index 72dd680..d289b54 100644 --- a/pkg/units/ffi.go +++ b/pkg/units/ffi.go @@ -23,7 +23,7 @@ package units //}; //struct bpf_result ffi_load_cbpf_program(void* prog_buff, size_t size, int coverage_enabled, unsigned long coverage_size); //struct bpf_result ffi_execute_cbpf_program(void* serialized_proto, size_t length); -//struct bpf_result ffi_load_ebpf_program(void* prog_buff, size_t size, int coverage_enabled, unsigned long coverage_size); +//struct bpf_result ffi_load_ebpf_program(void* serialized_proto, size_t size, int coverage_enabled, unsigned long coverage_size); //struct bpf_result ffi_execute_ebpf_program(void* serialized_proto, size_t length); //struct bpf_result ffi_get_map_elements(int map_fd, uint64_t map_size); //int ffi_create_bpf_map(size_t size); @@ -136,8 +136,8 @@ func (e *FFI) SetMapElement(fd int, key uint32, value uint64) int { // ValidateProgram passes the program through the bpf verifier without executing // it. Returns feedback to the generator so it can adjust the generation // settings. -func (e *FFI) ValidateEbpfProgram(prog []uint64) (*fpb.ValidationResult, error) { - if len(prog) == 0 { +func (e *FFI) ValidateEbpfProgram(encodedProgram *fpb.EncodedProgram) (*fpb.ValidationResult, error) { + if len(encodedProgram.Program) == 0 && encodedProgram != nil { return nil, fmt.Errorf("cannot run empty program") } shouldCollect, coverageSize := e.MetricsUnit.ShouldGetCoverage() @@ -145,7 +145,9 @@ func (e *FFI) ValidateEbpfProgram(prog []uint64) (*fpb.ValidationResult, error) if shouldCollect { cbool = 1 } - bpfVerifyResult := C.ffi_load_ebpf_program(unsafe.Pointer(&prog[0]), C.ulong(len(prog)), C.int(cbool) /*coverage_size=*/, C.ulong(coverageSize)) + serializedProto, err := proto.Marshal(encodedProgram) + bpfVerifyResult := C.ffi_load_ebpf_program(unsafe.Pointer(&serializedProto[0]), C.ulong(len(serializedProto)), + C.int(cbool), C.ulong(coverageSize)) res, err := validationProtoFromStruct(&bpfVerifyResult) if err != nil { return nil, err diff --git a/proto/BUILD b/proto/BUILD index d05958b..293bfe0 100644 --- a/proto/BUILD +++ b/proto/BUILD @@ -24,14 +24,14 @@ package( proto_library( name = "ebpf_proto", srcs = ["ebpf.proto"], - deps = [], + deps = [":btf_proto"], ) go_proto_library( name = "ebpf_go_proto", importpath = "buzzer/proto/ebpf_go_proto", protos = [":ebpf_proto"], - deps = [], + deps = [":btf_go_proto"], ) cc_proto_library( @@ -98,3 +98,21 @@ cc_proto_library( name = "program_cc_proto", deps = [":program_proto"], ) + +proto_library( + name = "btf_proto", + srcs = ["btf.proto"], + deps = [], +) + +go_proto_library( + name = "btf_go_proto", + importpath = "buzzer/proto/btf_go_proto", + protos = [":btf_proto"], + deps = [], +) + +cc_proto_library( + name = "btf_cc_proto", + deps = [":btf_proto"], +) diff --git a/proto/btf.proto b/proto/btf.proto new file mode 100644 index 0000000..2d1b905 --- /dev/null +++ b/proto/btf.proto @@ -0,0 +1,124 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +// Most of the things here are documented at +// https://docs.kernel.org/bpf/btf.html +package btf; + +message Empty {} + +// Type Ids to be reference in function info, the type Ids are definined in +// pkg/ebpf/btf.go +// https://docs.kernel.org/bpf/btf.html#btf-type-and-string-encoding +enum TypeId { + EMPTY = 0; + NA = 2; + FUNC_PTR_INT = 7; +} + +// btf_param struct for BTF kind function proto +// https://docs.kernel.org/bpf/btf.html#btf-kind-func-proto +message BtfParam { + int32 name_off = 1; + int32 param_type = 2; +} + +// BTF func proto extra data +// https://docs.kernel.org/bpf/btf.html#btf-kind-func-proto +message FuncProtoTypeData { + repeated BtfParam param = 1; +} + +// BTF kind struct extra data +// https://docs.kernel.org/bpf/btf.html#btf-kind-struct +message StructTypeData { + int32 name_off = 1; + int32 struct_type = 2; + int32 offset = 3; +} + +// BTF Kind int extra data +// https://docs.kernel.org/bpf/btf.html#btf-kind-int +message IntTypeData { + int32 int_info = 1; +} + +// Type Encoding +// https://docs.kernel.org/bpf/btf.html#btf-kind-func-proto +message BtfType { + // Specifies the offset in the string table + int32 name_off = 1; + // "info" bits arrangement + // - bits 0-15: vlen (e.g. # of struct's members) + // - bits 16-23: unused + // - bits 24-28: kind (e.g. int, ptr, array...etc) + // - bits 29-30: unused + // - bit 31: kind_flag, currently used by struct, union, fwd, enum and enum64. + // https://docs.kernel.org/bpf/btf.html#type-encoding + int32 info = 2; + // "size" is used by INT, ENUM, STRUCT, UNION and ENUM64. + // "size" tells the size of the type it is describing. + // "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT, + // FUNC, FUNC_PROTO, DECL_TAG and TYPE_TAG. + // "type" is a type_id referring to another type. + // https://docs.kernel.org/bpf/btf.html#type-encoding + int32 size_or_type = 3; + // For certain kinds, the common data are followed by kind-specific data. + // https://docs.kernel.org/bpf/btf.html#type-encoding + oneof extra { + Empty empty = 4; + IntTypeData int_type_data = 5; + FuncProtoTypeData func_proto_type_data = 6; + StructTypeData struct_type_data = 7; + } +} + +// The beginning of the BTF Data +// https://docs.kernel.org/bpf/btf.html#btf-type-and-string-encoding +message Header { + int32 magic = 1; // Indicates big- or little-endian target + int32 version = 2; + int32 flags = 3; + int32 hdr_len = 4; + + int32 type_off = 5; // offset of type section + int32 type_len = 6; // length of type section + int32 str_off = 7; // offset of string section + int32 str_len = 8; // length of string section +} + +// https://docs.kernel.org/bpf/btf.html#type-encoding +message TypeSection { + repeated BtfType btf_type = 1; +} + +// https://docs.kernel.org/bpf/btf.html#string-encoding +message StringSection { + string str = 1; +} + +// https://docs.kernel.org/bpf/btf.html#bpf-type-format-btf +message Btf { + Header header = 1; + TypeSection type_section = 2; + StringSection string_section = 3; +} + +// https://docs.kernel.org/bpf/btf.html#bpf-prog-load:~:text=The%20func_info%20and%20line_info%20are%20an%20array%20of%20below%2C%20respectively. +message FuncInfo { + int32 insn_off = 1; + int32 type_id = 2; +} diff --git a/proto/ebpf.proto b/proto/ebpf.proto index 83581c4..881a843 100644 --- a/proto/ebpf.proto +++ b/proto/ebpf.proto @@ -14,6 +14,8 @@ syntax = "proto3"; +import "proto/btf.proto"; + // Most of the things here represent the encoding documented at // https://www.kernel.org/doc/html/v5.17/bpf/instruction-set.html package ebpf; @@ -158,6 +160,12 @@ message Instruction { } } -message Program { +message Functions { repeated Instruction instructions = 1; + btf.FuncInfo func_info = 2; +} + +message Program { + bool btf = 1; + repeated Functions functions = 2; } diff --git a/proto/ffi.proto b/proto/ffi.proto index 2ad538f..f14c5ec 100644 --- a/proto/ffi.proto +++ b/proto/ffi.proto @@ -61,3 +61,12 @@ message ValidationResult { int64 socket_write = 9; // cbpf only int64 socket_read = 10; // cbpf only } + +message EncodedProgram { + // Array of bytes with the encoded eBPF instructions + bytes program = 1; + // Array of bytes with encoded BTF + bytes btf = 2; + // Array of bytes with the encoded function info for the program's functions + bytes function = 3; +} diff --git a/tools/BUILD b/tools/BUILD index 4109d2c..0001194 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -34,7 +34,9 @@ go_binary( deps = [ "//pkg/ebpf", "//proto:ebpf_go_proto", + "//proto:ffi_go_proto", "@com_github_golang_protobuf//jsonpb", + "@com_github_golang_protobuf//proto", ], ) diff --git a/tools/ffi.go b/tools/ffi.go index 3738a29..d480b9d 100644 --- a/tools/ffi.go +++ b/tools/ffi.go @@ -2,11 +2,12 @@ package main import ( "buzzer/pkg/ebpf/ebpf" - "fmt" - "unsafe" - epb "buzzer/proto/ebpf_go_proto" + fpb "buzzer/proto/ffi_go_proto" + "fmt" jsonpb "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "unsafe" ) //#include @@ -14,7 +15,8 @@ import ( import "C" //export EncodeEBPF -func EncodeEBPF(serializedProgram unsafe.Pointer, serializedProgramSize C.int, encodingResult, encodingResultSize unsafe.Pointer) { +func EncodeEBPF(serializedProgram unsafe.Pointer, serializedProgramSize C.int, + serialized_proto unsafe.Pointer, size unsafe.Pointer) { // First reconstruct the proto. encodedPb := C.GoBytes(serializedProgram, serializedProgramSize) @@ -24,27 +26,35 @@ func EncodeEBPF(serializedProgram unsafe.Pointer, serializedProgramSize C.int, e fmt.Println(err) return } - // Serialize it. - encodedProg, err := ebpf.EncodeInstructions(program) + encodedProg, encodedfunc, err := ebpf.EncodeInstructions(program) if err != nil { fmt.Println(err) return } + encodedBtf, err := ebpf.GenerateBtf() + if err != nil { + fmt.Println(err) + return + } + result := &fpb.EncodedProgram{ + Program: encodedProg, + Function: encodedfunc, + Btf: encodedBtf, + } // Then do magic to return it to C++ - // The 8 here is because every bpf instruction is an 8 byte number. - cLength := C.ulong(len(encodedProg) * 8) + serializedProto, err := proto.Marshal(result) + serializedSize := C.ulong(len(serializedProto)) - // C++ will free the memory. - resBuffer := C.malloc(cLength) - C.memcpy(resBuffer, unsafe.Pointer(&encodedProg[0]), cLength) + serializedBuffer := C.malloc(serializedSize) + C.memcpy(serializedBuffer, unsafe.Pointer(&serializedProto[0]), serializedSize) - resultPtr := (**uint64)(encodingResult) - *resultPtr = (*uint64)(resBuffer) + serializedPtr := (**uint64)(serialized_proto) + *serializedPtr = (*uint64)(serializedBuffer) - resultSizePtr := (*uint64)(encodingResultSize) - *resultSizePtr = uint64(len(encodedProg)) + sizePtr := (*uint64)(size) + *sizePtr = uint64(len(serializedProto)) } func main() {} diff --git a/tools/loader.cc b/tools/loader.cc index b557f0a..79bbaa5 100644 --- a/tools/loader.cc +++ b/tools/loader.cc @@ -23,12 +23,11 @@ int main(int argc, char **argv) { std::string content((std::istreambuf_iterator(input)), std::istreambuf_iterator()); - uint64_t *ebpf_instructions = NULL; - uint64_t array_length = 0; - EncodeEBPF(content.data(), content.length(), &ebpf_instructions, - &array_length); + void *serialized_proto = NULL; + size_t size = 0; + EncodeEBPF(content.data(), content.length(), &serialized_proto, &size); - if (!ebpf_instructions) { + if (!serialized_proto) { std::cerr << "failed to decode ebpf program" << std::endl; return -1; } @@ -37,8 +36,13 @@ int main(int argc, char **argv) { int map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(uint32_t), sizeof(uint64_t), map_size); std::string verifier_log, error_message; - int prog_fd = load_ebpf_program(ebpf_instructions, array_length, - &verifier_log, &error_message); + std::string serialized_proto_string( + reinterpret_cast(serialized_proto), size); + EncodedProgram program; + if (!program.ParseFromString(serialized_proto_string)) { + std::cout << "Could not parse EncodedProgram proto" << std::endl; + } + int prog_fd = load_ebpf_program(program, size, verifier_log, error_message); std::cout << "Verifier log: " << std::endl << verifier_log; if (prog_fd < 0) { @@ -48,13 +52,13 @@ int main(int argc, char **argv) { uint8_t socket_input[2] = {0xAA, 0xAA}; if (!execute_ebpf_program(prog_fd, socket_input, sizeof(socket_input), - &error_message)) { + error_message)) { std::cerr << "error executing program " << error_message << std::endl; return -1; } std::vector map_elements; - if (!get_map_elements(map_fd, map_size, &map_elements, &error_message)) { + if (!get_map_elements(map_fd, map_size, &map_elements, error_message)) { std::cerr << "Could not get map elements: " << error_message << std::endl; return -1; } @@ -64,6 +68,6 @@ int main(int argc, char **argv) { std::cout << "element: " << element << std::endl; } - free(ebpf_instructions); + free(serialized_proto); return 0; }