From 7b3f682dedffabf2a8d9f943eec549f93ab28662 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Mon, 3 Jun 2024 08:28:04 -0700 Subject: [PATCH] Implement libbpf autoload APIs (#3592) * Implement libbpf autoload APIs Fixes #3555 Signed-off-by: Dave Thaler * Suppress spurious compiler warning Signed-off-by: Dave Thaler * Fix test failure Signed-off-by: Dave Thaler * Update tests Files with no program sections succeed loading Signed-off-by: Dave Thaler * Address PR comment from Anurag Signed-off-by: Dave Thaler * PR feedback Signed-off-by: Dave Thaler * Fix test Signed-off-by: Dave Thaler * Prevent changing prog type of a native program Signed-off-by: Dave Thaler * PR feedback Signed-off-by: Dave Thaler * Remove unused program_type from native load ioctl Signed-off-by: Dave Thaler * Update default autoload value Signed-off-by: Dave Thaler * PR feedback Signed-off-by: Dave Thaler * Fix test Signed-off-by: Dave Thaler * Add check to unit_test to match api_test Signed-off-by: Dave Thaler * Fix api_test Signed-off-by: Dave Thaler --------- Signed-off-by: Dave Thaler --- ebpfapi/Source.def | 2 + include/bpf/libbpf.h | 29 ++++++ libs/api/Verifier.cpp | 4 +- libs/api/ebpf_api.cpp | 104 +++++++++------------ libs/api/libbpf_program.cpp | 23 ++++- libs/execution_context/ebpf_core.c | 19 ++-- libs/execution_context/ebpf_native.c | 17 ++-- libs/execution_context/ebpf_protocol.h | 1 - libs/runtime/ebpf_object.c | 2 +- tests/api_test/api_test.cpp | 42 ++++++--- tests/end_to_end/end_to_end.cpp | 43 +++++---- tests/libs/util/ioctl_helper.cpp | 2 - tests/libs/util/ioctl_helper.h | 1 - tests/unit/libbpf_test.cpp | 119 +++++++++++++++++++++++++ 14 files changed, 289 insertions(+), 119 deletions(-) diff --git a/ebpfapi/Source.def b/ebpfapi/Source.def index bdee3747da..57c580b5fe 100644 --- a/ebpfapi/Source.def +++ b/ebpfapi/Source.def @@ -77,6 +77,7 @@ EXPORTS bpf_prog_test_run_opts bpf_program__attach bpf_program__attach_xdp + bpf_program__autoload bpf_program__fd bpf_program__get_expected_attach_type bpf_program__get_type=bpf_program__type @@ -87,6 +88,7 @@ EXPORTS bpf_program__pin bpf_program__prev bpf_program__section_name + bpf_program__set_autoload bpf_program__set_expected_attach_type bpf_program__set_type bpf_program__type diff --git a/include/bpf/libbpf.h b/include/bpf/libbpf.h index cc6e92f948..eb0d8aa684 100644 --- a/include/bpf/libbpf.h +++ b/include/bpf/libbpf.h @@ -514,6 +514,35 @@ bpf_program__attach(const struct bpf_program* prog); struct bpf_link* bpf_program__attach_xdp(struct bpf_program* prog, int ifindex); +/** + * @brief Get whether an eBPF program will be loaded when the object is loaded. + * + * @param[in] prog The program to check. + * + * @returns True if the program will be loaded, false if not. + * + * @sa bpf_object__load + * @sa bpf_object__load_xattr + * @sa bpf_program__set_autoload + */ +bool +bpf_program__autoload(const struct bpf_program* prog); + +/** + * @brief Set whether an eBPF program will be loaded when the object is loaded. + * + * @param[in] prog The program to update. + * + * @retval 0 The operation was successful. + * @retval <0 An error occured, and errno was set. + * + * @sa bpf_object__load + * @sa bpf_object__load_xattr + * @sa bpf_program__autoload + */ +int +bpf_program__set_autoload(struct bpf_program* prog, bool autoload); + /** * @brief Attach an eBPF program to an attach point. * diff --git a/libs/api/Verifier.cpp b/libs/api/Verifier.cpp index c50a798e4e..73f8117559 100644 --- a/libs/api/Verifier.cpp +++ b/libs/api/Verifier.cpp @@ -330,9 +330,7 @@ load_byte_code( program->handle = ebpf_handle_invalid; program->program_type = *(const GUID*)raw_program.info.type.platform_specific_data; - - // Only autoload programs with a defined program type. - program->autoload = memcmp(&program->program_type, &empty_program_type, sizeof(empty_program_type)) != 0; + program->autoload = true; program->section_name = cxplat_duplicate_string(raw_program.section_name.c_str()); program->program_name = cxplat_duplicate_string(raw_program.function_name.c_str()); diff --git a/libs/api/ebpf_api.cpp b/libs/api/ebpf_api.cpp index 66bf5d4bcf..4c9d407f45 100644 --- a/libs/api/ebpf_api.cpp +++ b/libs/api/ebpf_api.cpp @@ -128,12 +128,7 @@ static std::mutex _pe_parse_mutex; static ebpf_result_t _ebpf_program_load_native( - _In_z_ const char* file_name, - _In_opt_ const ebpf_program_type_t* program_type, - _In_opt_ const ebpf_attach_type_t* attach_type, - ebpf_execution_type_t execution_type, - _Inout_ struct bpf_object* object, - _Out_ fd_t* program_fd) noexcept; + _In_z_ const char* file_name, ebpf_execution_type_t execution_type, _Inout_ struct bpf_object* object) noexcept; static _Ret_z_ const char* _ebpf_get_section_string( @@ -2097,14 +2092,23 @@ CATCH_NO_MEMORY_EBPF_RESULT static ebpf_result_t _initialize_ebpf_programs_native( size_t count_of_programs, - _In_reads_(count_of_programs) ebpf_handle_t* program_handles, + _Inout_updates_(count_of_programs) ebpf_handle_t* program_handles, _Inout_ std::vector& programs) NO_EXCEPT_TRY { EBPF_LOG_ENTRY(); - ebpf_assert(program_handles); + ebpf_assert(count_of_programs == 0 || program_handles); ebpf_result_t result = EBPF_SUCCESS; for (int i = 0; i < count_of_programs; i++) { + ebpf_program_t* program = programs[i]; + if (!program->autoload) { +#pragma warning(suppress : 6001) // The SAL annotation checks that program_handles[i] is ok. + if (program_handles[i] != ebpf_handle_invalid) { + Platform::CloseHandle(program_handles[i]); + program_handles[i] = ebpf_handle_invalid; + } + continue; + } if (program_handles[i] == ebpf_handle_invalid) { result = EBPF_INVALID_ARGUMENT; goto Exit; @@ -2116,7 +2120,6 @@ _initialize_ebpf_programs_native( goto Exit; } - ebpf_program_t* program = programs[i]; program->fd = _create_file_descriptor_for_handle(program_handles[i]); if (program->fd == ebpf_fd_invalid) { result = EBPF_NO_MEMORY; @@ -2149,7 +2152,7 @@ _initialize_ebpf_object_native( ebpf_result_t result = EBPF_SUCCESS; ebpf_assert(count_of_maps == 0 || map_handles); - ebpf_assert(program_handles); + ebpf_assert(count_of_programs == 0 || program_handles); object.native_module_fd = native_module_fd; @@ -2199,6 +2202,7 @@ _initialize_ebpf_object_from_native_file( _Outptr_result_maybenull_z_ const char** error_message) NO_EXCEPT_TRY { ebpf_program_t* program = nullptr; + ebpf_program_type_t empty_program_type{}; EBPF_LOG_ENTRY(); ebpf_assert(file_name); @@ -2223,6 +2227,7 @@ _initialize_ebpf_object_from_native_file( program->program_type = info->program_type; program->attach_type = info->expected_attach_type; program->fd = ebpf_fd_invalid; + program->autoload = true; program->section_name = cxplat_duplicate_string(info->section_name); if (program->section_name == nullptr) { @@ -3324,18 +3329,8 @@ ebpf_object_load(_Inout_ struct bpf_object* object) NO_EXCEPT_TRY } if (Platform::_is_native_program(object->file_name)) { - struct bpf_program* program = bpf_object__next_program(object, nullptr); - if (program == nullptr) { - EBPF_RETURN_RESULT(EBPF_INVALID_ARGUMENT); - } - fd_t program_fd; - return _ebpf_program_load_native( - object->file_name, - &program->program_type, - &program->attach_type, - object->execution_type, - object, - &program_fd); + result = _ebpf_program_load_native(object->file_name, object->execution_type, object); + EBPF_RETURN_RESULT(result); } try { @@ -3485,9 +3480,6 @@ _load_native_module( * @brief Create maps and load programs from a loaded native module. * * @param[in] module_id Module ID corresponding to the native module. - * @param[in] program_type Optionally, the program type to use when loading - * the eBPF program. If program type is not supplied, it is derived from - * the section prefix in the ELF file. * @param[in] count_of_maps Count of maps present in the native module. * @param[out] map_handles Array of size count_of_maps which contains the map handles. * @param[in] count_of_programs Count of programs present in the native module. @@ -3504,7 +3496,6 @@ _load_native_module( static ebpf_result_t _load_native_programs( _In_ const GUID* module_id, - _In_opt_ const ebpf_program_type_t* program_type, size_t count_of_maps, _Out_writes_(count_of_maps) ebpf_handle_t* map_handles, size_t count_of_programs, @@ -3516,9 +3507,8 @@ _load_native_programs( // Map count can be 0 (a program without any maps is a valid use case). ebpf_assert(count_of_maps == 0 || map_handles); - // Program count *must* be non-zero. - ebpf_assert(count_of_programs); - ebpf_assert(program_handles); + // Program count can be 0 (a map without any programs is a valid use case). + ebpf_assert(count_of_programs == 0 || program_handles); ebpf_result_t result = EBPF_SUCCESS; uint32_t error = ERROR_SUCCESS; @@ -3532,7 +3522,9 @@ _load_native_programs( if (map_handles) { memset(map_handles, 0, map_handles_size); } - memset(program_handles, 0, program_handles_size); + if (program_handles) { + memset(program_handles, 0, program_handles_size); + } size_t buffer_size = offsetof(ebpf_operation_load_native_programs_reply_t, data) + handles_size; reply_buffer.resize(buffer_size); @@ -3541,7 +3533,6 @@ _load_native_programs( request.header.id = ebpf_operation_id_t::EBPF_OPERATION_LOAD_NATIVE_PROGRAMS; request.header.length = sizeof(ebpf_operation_load_native_programs_request_t); request.module_id = *module_id; - request.program_type = program_type ? *program_type : GUID_NULL; error = invoke_ioctl(request, reply_buffer); if (error != ERROR_SUCCESS) { @@ -3563,7 +3554,9 @@ _load_native_programs( if (count_of_maps) { memcpy(map_handles, reply->data, map_handles_size); } - memcpy(program_handles, reply->data + map_handles_size, program_handles_size); + if (count_of_programs) { + memcpy(program_handles, reply->data + map_handles_size, program_handles_size); + } Done: EBPF_RETURN_RESULT(result); @@ -3571,20 +3564,13 @@ _load_native_programs( static ebpf_result_t _ebpf_program_load_native( - _In_z_ const char* file_name, - _In_opt_ const ebpf_program_type_t* program_type, - _In_opt_ const ebpf_attach_type_t* attach_type, - ebpf_execution_type_t execution_type, - _Inout_ struct bpf_object* object, - _Out_ fd_t* program_fd) NO_EXCEPT_TRY + _In_z_ const char* file_name, ebpf_execution_type_t execution_type, _Inout_ struct bpf_object* object) NO_EXCEPT_TRY { EBPF_LOG_ENTRY(); - UNREFERENCED_PARAMETER(attach_type); UNREFERENCED_PARAMETER(execution_type); ebpf_assert(file_name); ebpf_assert(object); - ebpf_assert(program_fd); ebpf_result_t result = EBPF_SUCCESS; uint32_t error; @@ -3679,26 +3665,18 @@ _ebpf_program_load_native( native_module_handle = ebpf_handle_invalid; - if (count_of_programs == 0) { - result = EBPF_INVALID_OBJECT; - EBPF_LOG_MESSAGE_STRING( - EBPF_TRACELOG_LEVEL_ERROR, - EBPF_TRACELOG_KEYWORD_API, - "_ebpf_program_load_native: O programs found", - file_name); - goto Done; - } - - // Allocate buffer for program and map handles. - program_handles = (ebpf_handle_t*)ebpf_allocate(count_of_programs * sizeof(ebpf_handle_t)); - if (program_handles == nullptr) { - result = EBPF_NO_MEMORY; - EBPF_LOG_MESSAGE_STRING( - EBPF_TRACELOG_LEVEL_ERROR, - EBPF_TRACELOG_KEYWORD_API, - "_ebpf_program_load_native: program handle buffer allocation failed.", - file_name); - goto Done; + // Allocate buffers for program and map handles. + if (count_of_programs > 0) { + program_handles = (ebpf_handle_t*)ebpf_allocate(count_of_programs * sizeof(ebpf_handle_t)); + if (program_handles == nullptr) { + result = EBPF_NO_MEMORY; + EBPF_LOG_MESSAGE_STRING( + EBPF_TRACELOG_LEVEL_ERROR, + EBPF_TRACELOG_KEYWORD_API, + "_ebpf_program_load_native: program handle buffer allocation failed.", + file_name); + goto Done; + } } if (count_of_maps > 0) { @@ -3714,8 +3692,8 @@ _ebpf_program_load_native( } } - result = _load_native_programs( - &provider_module_id, program_type, count_of_maps, map_handles, count_of_programs, program_handles); + result = + _load_native_programs(&provider_module_id, count_of_maps, map_handles, count_of_programs, program_handles); if (result != EBPF_SUCCESS) { EBPF_LOG_MESSAGE_STRING( EBPF_TRACELOG_LEVEL_ERROR, @@ -3736,8 +3714,6 @@ _ebpf_program_load_native( goto Done; } native_module_fd = ebpf_fd_invalid; - - *program_fd = object->programs[0]->fd; } catch (const std::bad_alloc&) { result = EBPF_NO_MEMORY; goto Done; diff --git a/libs/api/libbpf_program.cpp b/libs/api/libbpf_program.cpp index 5745bcf2f1..2246855a3b 100644 --- a/libs/api/libbpf_program.cpp +++ b/libs/api/libbpf_program.cpp @@ -161,6 +161,23 @@ bpf_program__section_name(const struct bpf_program* program) return program->section_name; } +bool +bpf_program__autoload(const struct bpf_program* program) +{ + return program->autoload; +} + +int +bpf_program__set_autoload(struct bpf_program* program, bool autoload) +{ + if (program->object->loaded) { + return libbpf_err(-EINVAL); + } + + program->autoload = autoload; + return 0; +} + size_t bpf_program__size(const struct bpf_program* program) { @@ -488,9 +505,13 @@ bpf_program__set_type(struct bpf_program* program, enum bpf_prog_type type) if (program->object->loaded) { return libbpf_err(-EBUSY); } + if (program->object->execution_type == EBPF_EXECUTION_NATIVE) { + // Native BPF programs have already passed verification and so cannot + // have their program type changed. + return libbpf_err(-EINVAL); + } const ebpf_program_type_t* program_type = ebpf_get_ebpf_program_type(type); program->program_type = (program_type != nullptr) ? *program_type : EBPF_PROGRAM_TYPE_UNSPECIFIED; - program->autoload = true; // TODO(issue #3555): remove this once bpf_program__set_autoload is supported. return 0; } diff --git a/libs/execution_context/ebpf_core.c b/libs/execution_context/ebpf_core.c index 053b97025d..71f5715d4d 100644 --- a/libs/execution_context/ebpf_core.c +++ b/libs/execution_context/ebpf_core.c @@ -715,11 +715,6 @@ _ebpf_core_protocol_load_native_programs( goto Done; } - if (count_of_program_handles == 0) { - result = EBPF_INVALID_ARGUMENT; - goto Done; - } - result = ebpf_safe_size_t_multiply(count_of_map_handles, sizeof(ebpf_handle_t), &map_handles_size); if (result != EBPF_SUCCESS) { goto Done; @@ -754,10 +749,12 @@ _ebpf_core_protocol_load_native_programs( } } - program_handles = ebpf_allocate_with_tag(sizeof(ebpf_handle_t) * count_of_program_handles, EBPF_POOL_TAG_CORE); - if (program_handles == NULL) { - result = EBPF_NO_MEMORY; - goto Done; + if (count_of_program_handles) { + program_handles = ebpf_allocate_with_tag(sizeof(ebpf_handle_t) * count_of_program_handles, EBPF_POOL_TAG_CORE); + if (program_handles == NULL) { + result = EBPF_NO_MEMORY; + goto Done; + } } result = ebpf_native_load_programs( @@ -772,7 +769,9 @@ _ebpf_core_protocol_load_native_programs( if (map_handles) { memcpy(reply->data, map_handles, map_handles_size); } - memcpy(reply->data + map_handles_size, program_handles, program_handles_size); + if (program_handles) { + memcpy(reply->data + map_handles_size, program_handles, program_handles_size); + } Done: ebpf_free(map_handles); diff --git a/libs/execution_context/ebpf_native.c b/libs/execution_context/ebpf_native.c index f2156a31b6..b6cc7eb311 100644 --- a/libs/execution_context/ebpf_native.c +++ b/libs/execution_context/ebpf_native.c @@ -1233,7 +1233,10 @@ _ebpf_native_load_programs(_Inout_ ebpf_native_module_t* module) // Get the programs. module->table.programs(&programs, &program_count); - if (program_count == 0 || programs == NULL) { + if (program_count == 0) { + EBPF_RETURN_RESULT(EBPF_SUCCESS); + } + if (programs == NULL) { return EBPF_INVALID_OBJECT; } @@ -1491,11 +1494,13 @@ _ebpf_native_initialize_handle_cleanup_context( } cleanup_context->handle_information->count_of_map_handles = map_handle_count; - cleanup_context->handle_information->program_handles = - (ebpf_handle_t*)ebpf_allocate(sizeof(ebpf_handle_t) * program_handle_count); - if (cleanup_context->handle_information->program_handles == NULL) { - result = EBPF_NO_MEMORY; - goto Done; + if (program_handle_count > 0) { + cleanup_context->handle_information->program_handles = + (ebpf_handle_t*)ebpf_allocate(sizeof(ebpf_handle_t) * program_handle_count); + if (cleanup_context->handle_information->program_handles == NULL) { + result = EBPF_NO_MEMORY; + goto Done; + } } cleanup_context->handle_information->count_of_program_handles = program_handle_count; diff --git a/libs/execution_context/ebpf_protocol.h b/libs/execution_context/ebpf_protocol.h index 9478bbd313..ce4f7ff20d 100644 --- a/libs/execution_context/ebpf_protocol.h +++ b/libs/execution_context/ebpf_protocol.h @@ -409,7 +409,6 @@ typedef struct _ebpf_operation_load_native_module_reply typedef struct _ebpf_operation_load_native_programs_request { struct _ebpf_operation_header header; - ebpf_program_type_t program_type; GUID module_id; } ebpf_operation_load_native_programs_request_t; diff --git a/libs/runtime/ebpf_object.c b/libs/runtime/ebpf_object.c index 3bd36bb5ca..5d8d221586 100644 --- a/libs/runtime/ebpf_object.c +++ b/libs/runtime/ebpf_object.c @@ -16,7 +16,7 @@ static const uint32_t _ebpf_object_marker = 'eobj'; static ebpf_lock_t _ebpf_object_tracking_list_lock = {0}; /** - * @brief Objects are allocated an entry in the the ID + * @brief Objects are allocated an entry in the ID * table when they are initialized. Along with a pointer to the * object, each id table entry maintains its own ref-count that * starts off at 1 when it is assigned to a new object. The diff --git a/tests/api_test/api_test.cpp b/tests/api_test/api_test.cpp index a40e30b8f6..ae1b5f6f1f 100644 --- a/tests/api_test/api_test.cpp +++ b/tests/api_test/api_test.cpp @@ -69,17 +69,18 @@ static service_install_helper _ebpf_service_helper(EBPF_SERVICE_NAME, EBPF_SERVICE_BINARY_NAME, SERVICE_WIN32_OWN_PROCESS); #endif -static int -_program_load_helper( - const char* file_name, +static _Success_(return == 0) int _program_load_helper( + _In_z_ const char* file_name, bpf_prog_type prog_type, ebpf_execution_type_t execution_type, - struct bpf_object** object, - fd_t* program_fd) + _Outptr_ struct bpf_object** object, + _Out_ fd_t* program_fd) // File descriptor of first program in the object. { + *program_fd = ebpf_fd_invalid; + *object = nullptr; struct bpf_object* new_object = bpf_object__open(file_name); if (new_object == nullptr) { - return EBPF_FAILED; + return -EINVAL; } REQUIRE(ebpf_object_set_execution_type(new_object, execution_type) == EBPF_SUCCESS); @@ -96,7 +97,9 @@ _program_load_helper( return error; } - *program_fd = bpf_program__fd(program); + if (program != nullptr) { + *program_fd = bpf_program__fd(program); + } *object = new_object; return 0; } @@ -1084,6 +1087,26 @@ DECLARE_REGRESSION_TEST_CASE("0.11.0") // only for Debug build. #ifdef _DEBUG +// Load a native module that has 0 programs. +// TODO(#3597): The empty file should pass bpf2c so should be enabled for Release as well. +TEST_CASE("load_native_program_empty", "[native_tests]") +{ + bpf_object* object = bpf_object__open("empty.sys"); + REQUIRE(object != nullptr); + + REQUIRE(bpf_object__load(object) == 0); + + // Verify that no programs exist but at least one map exists. + uint32_t next_id; + REQUIRE(bpf_prog_get_next_id(0, &next_id) == -ENOENT); + REQUIRE(bpf_map_get_next_id(0, &next_id) == 0); + + bpf_object__close(object); + + // Verify no maps exist. + REQUIRE(bpf_map_get_next_id(0, &next_id) == -ENOENT); +} + static void _load_invalid_program(_In_z_ const char* file_name, ebpf_execution_type_t execution_type, int expected_result) { @@ -1094,6 +1117,7 @@ _load_invalid_program(_In_z_ const char* file_name, ebpf_execution_type_t execut result = _program_load_helper(file_name, BPF_PROG_TYPE_UNSPEC, execution_type, &object, &program_fd); REQUIRE(result == expected_result); + REQUIRE(program_fd == ebpf_fd_invalid); if (result != 0) { // If load failed, no programs or maps should be loaded. @@ -1114,10 +1138,6 @@ TEST_CASE("load_native_program_invalid3", "[native][negative]") { _load_invalid_program("invalid_helpers.sys", EBPF_EXECUTION_NATIVE, -EINVAL); } -TEST_CASE("load_native_program_invalid4", "[native][negative]") -{ - _load_invalid_program("empty.sys", EBPF_EXECUTION_NATIVE, -EINVAL); -} TEST_CASE("load_native_program_invalid5", "[native][negative]") { _load_invalid_program("invalid_maps3.sys", EBPF_EXECUTION_NATIVE, -EINVAL); diff --git a/tests/end_to_end/end_to_end.cpp b/tests/end_to_end/end_to_end.cpp index 880a84f762..614e2eccc5 100644 --- a/tests/end_to_end/end_to_end.cpp +++ b/tests/end_to_end/end_to_end.cpp @@ -351,7 +351,7 @@ ebpf_program_load( bpf_prog_type prog_type, ebpf_execution_type_t execution_type, _Out_ bpf_object_ptr* unique_object, - _Out_ fd_t* program_fd, + _Out_ fd_t* program_fd, // File descriptor of first program in the object. _Outptr_opt_result_maybenull_z_ const char** log_buffer) { *program_fd = ebpf_fd_invalid; @@ -385,7 +385,9 @@ ebpf_program_load( return error; } - *program_fd = bpf_program__fd(program); + if (program != nullptr) { + *program_fd = bpf_program__fd(program); + } unique_object->reset(new_object); return 0; } @@ -2554,8 +2556,7 @@ TEST_CASE("load_native_program_negative3", "[end-to-end]") // Try to load the programs from the same module again. It should fail. REQUIRE( - test_ioctl_load_native_programs( - &provider_module_id, nullptr, MAP_COUNT, map_handles, PROGRAM_COUNT, program_handles) == + test_ioctl_load_native_programs(&provider_module_id, MAP_COUNT, map_handles, PROGRAM_COUNT, program_handles) == ERROR_OBJECT_ALREADY_EXISTS); bpf_object__close(unique_object.release()); @@ -2563,8 +2564,8 @@ TEST_CASE("load_native_program_negative3", "[end-to-end]") // Now that we have closed the object, try to load programs from the same module again. This should // fail as the module should now be marked as "unloading". REQUIRE( - test_ioctl_load_native_programs( - &provider_module_id, nullptr, MAP_COUNT, map_handles, PROGRAM_COUNT, program_handles) != ERROR_SUCCESS); + test_ioctl_load_native_programs(&provider_module_id, MAP_COUNT, map_handles, PROGRAM_COUNT, program_handles) != + ERROR_SUCCESS); } // Load native module and then try to load programs with incorrect params. @@ -2587,7 +2588,7 @@ TEST_CASE("load_native_program_negative4", "[end-to-end]") // First try to load native program without loading the native module. REQUIRE( - test_ioctl_load_native_programs(&provider_module_id, nullptr, 0, nullptr, PROGRAM_COUNT, program_handles) == + test_ioctl_load_native_programs(&provider_module_id, 0, nullptr, PROGRAM_COUNT, program_handles) == ERROR_PATH_NOT_FOUND); // Creating valid service with valid driver. @@ -2606,7 +2607,7 @@ TEST_CASE("load_native_program_negative4", "[end-to-end]") // Try to load the programs by passing wrong map and program handles size. This should fail. REQUIRE( - test_ioctl_load_native_programs(&provider_module_id, nullptr, 0, nullptr, PROGRAM_COUNT, program_handles) == + test_ioctl_load_native_programs(&provider_module_id, 0, nullptr, PROGRAM_COUNT, program_handles) == ERROR_INVALID_PARAMETER); // Delete the created service. @@ -2690,8 +2691,9 @@ TEST_CASE("load_native_program_negative6", "[end-to-end]") // only for Debug build. #ifdef _DEBUG -// Load programs from a native module which has 0 programs. -TEST_CASE("load_native_program_negative8", "[end-to-end]") +// Load a native module that has 0 programs. +// TODO(#3597): The empty file should pass bpf2c so should be enabled for Release as well. +TEST_CASE("load_native_program_empty", "[end-to-end]") { _test_helper_end_to_end test_helper; test_helper.initialize(); @@ -2701,9 +2703,8 @@ TEST_CASE("load_native_program_negative8", "[end-to-end]") std::wstring service_path(SERVICE_PATH_PREFIX); size_t count_of_maps = 0; size_t count_of_programs = 0; - std::wstring file_path(L"test_sample_ebpf_um.dll"); - ebpf_handle_t map_handles; - ebpf_handle_t program_handles; + ebpf_handle_t map_handles = ebpf_handle_invalid; + ebpf_handle_t program_handles = ebpf_handle_invalid; _test_handle_helper module_handle; REQUIRE(UuidCreate(&provider_module_id) == RPC_S_OK); @@ -2723,8 +2724,8 @@ TEST_CASE("load_native_program_negative8", "[end-to-end]") // Try to load the programs from the module with 0 programs. REQUIRE( - test_ioctl_load_native_programs(&provider_module_id, nullptr, 1, &map_handles, 1, &program_handles) == - ERROR_INVALID_PARAMETER); + test_ioctl_load_native_programs(&provider_module_id, 1, &map_handles, 0, &program_handles) == ERROR_SUCCESS); + REQUIRE(map_handles != ebpf_handle_invalid); // Delete the created service. Platform::_delete_service(service_handle); @@ -2738,12 +2739,20 @@ _load_invalid_program(_In_z_ const char* file_name, ebpf_execution_type_t execut int result; bpf_object_ptr unique_object; fd_t program_fd; + uint32_t next_id; program_info_provider_t bind_program_info; REQUIRE(bind_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS); result = ebpf_program_load(file_name, BPF_PROG_TYPE_UNSPEC, execution_type, &unique_object, &program_fd, nullptr); REQUIRE(result == expected_result); + REQUIRE(program_fd == ebpf_fd_invalid); + + if (result != 0) { + // If load failed, no programs or maps should be loaded. + REQUIRE(bpf_map_get_next_id(0, &next_id) == -ENOENT); + REQUIRE(bpf_prog_get_next_id(0, &next_id) == -ENOENT); + } } static void @@ -2766,10 +2775,6 @@ TEST_CASE("load_native_program_invalid3", "[end-to-end]") { _test_load_invalid_program("invalid_helpers_um.dll", EBPF_EXECUTION_NATIVE, -EINVAL); } -TEST_CASE("load_native_program_invalid4", "[end-to-end]") -{ - _test_load_invalid_program("empty_um.dll", EBPF_EXECUTION_NATIVE, -EINVAL); -} TEST_CASE("load_native_program_invalid5", "[end-to-end]") { _test_load_invalid_program("invalid_maps3_um.dll", EBPF_EXECUTION_NATIVE, -EINVAL); diff --git a/tests/libs/util/ioctl_helper.cpp b/tests/libs/util/ioctl_helper.cpp index 15683917d0..1f02ac93b8 100644 --- a/tests/libs/util/ioctl_helper.cpp +++ b/tests/libs/util/ioctl_helper.cpp @@ -61,7 +61,6 @@ test_ioctl_load_native_module( uint32_t test_ioctl_load_native_programs( _In_ const GUID* module_id, - _In_opt_ const ebpf_program_type_t* program_type, size_t count_of_maps, _Out_writes_(count_of_maps) ebpf_handle_t* map_handles, size_t count_of_programs, @@ -82,7 +81,6 @@ test_ioctl_load_native_programs( request.header.id = EBPF_OPERATION_LOAD_NATIVE_PROGRAMS; request.header.length = sizeof(ebpf_operation_load_native_programs_request_t); request.module_id = *module_id; - request.program_type = program_type ? *program_type : GUID_NULL; error = invoke_ioctl(request, reply_buffer); if (error != ERROR_SUCCESS) { diff --git a/tests/libs/util/ioctl_helper.h b/tests/libs/util/ioctl_helper.h index d207322f5b..83470cfeae 100644 --- a/tests/libs/util/ioctl_helper.h +++ b/tests/libs/util/ioctl_helper.h @@ -14,7 +14,6 @@ test_ioctl_load_native_module( uint32_t test_ioctl_load_native_programs( _In_ const GUID* module_id, - _In_opt_ const ebpf_program_type_t* program_type, size_t count_of_maps, _Out_writes_(count_of_maps) ebpf_handle_t* map_handles, size_t count_of_programs, diff --git a/tests/unit/libbpf_test.cpp b/tests/unit/libbpf_test.cpp index ccd651fe4a..2eda9c26ef 100644 --- a/tests/unit/libbpf_test.cpp +++ b/tests/unit/libbpf_test.cpp @@ -507,6 +507,64 @@ TEST_CASE("libbpf program", "[libbpf]") bpf_object__close(object); } +static void +_test_program_autoload(ebpf_execution_type_t execution_type) +{ + _test_helper_end_to_end test_helper; + test_helper.initialize(); + program_info_provider_t sample_program_info; + REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS); + + const char* file_name = + (execution_type == EBPF_EXECUTION_NATIVE ? "tail_call_same_section_um.dll" : "tail_call_same_section.o"); + struct bpf_object* object = bpf_object__open(file_name); + REQUIRE(object != nullptr); + struct bpf_program* caller = bpf_object__find_program_by_name(object, "caller"); + REQUIRE(caller != nullptr); + int caller_fd = bpf_program__fd(const_cast(caller)); + REQUIRE(caller_fd == ebpf_fd_invalid); + struct bpf_program* callee = bpf_object__find_program_by_name(object, "callee"); + REQUIRE(callee != nullptr); + int callee_fd = bpf_program__fd(const_cast(callee)); + REQUIRE(callee_fd == ebpf_fd_invalid); + + // Check initial autoload values. + REQUIRE(bpf_program__autoload(caller) == true); + REQUIRE(bpf_program__autoload(callee) == true); + + // Update an autoload value. + REQUIRE(bpf_program__set_autoload(caller, false) == 0); + REQUIRE(bpf_program__autoload(caller) == false); + REQUIRE(bpf_program__autoload(callee) == true); + + // Load the program(s). + REQUIRE(bpf_object__load(object) == 0); + + // Verify what programs were loaded. + caller_fd = bpf_program__fd(const_cast(caller)); + REQUIRE(caller_fd == ebpf_fd_invalid); + callee_fd = bpf_program__fd(const_cast(callee)); + REQUIRE(callee_fd > 0); + + // Verify we cannot change autoload values after loading. + int error = bpf_program__set_autoload(caller, false); + REQUIRE(error < 0); + REQUIRE(errno == EINVAL); + error = bpf_program__set_autoload(caller, true); + REQUIRE(error < 0); + REQUIRE(errno == EINVAL); + error = bpf_program__set_autoload(callee, false); + REQUIRE(error < 0); + REQUIRE(errno == EINVAL); + error = bpf_program__set_autoload(callee, true); + REQUIRE(error < 0); + REQUIRE(errno == EINVAL); + + bpf_object__close(object); +} + +DECLARE_ALL_TEST_CASES("libbpf program autoload", "[libbpf]", _test_program_autoload); + TEST_CASE("libbpf program pinning", "[libbpf]") { _test_helper_libbpf test_helper; @@ -2520,6 +2578,67 @@ TEST_CASE("bpf_object__open_file with .dll", "[libbpf]") bpf_object__close(object); } +TEST_CASE("bpf_object__load with .dll", "[libbpf]") +{ + _test_helper_libbpf test_helper; + test_helper.initialize(); + + const char* my_object_name = "my_object_name"; + struct bpf_object_open_opts opts = {0}; + opts.object_name = my_object_name; + struct bpf_object* object = bpf_object__open_file("droppacket_um.dll", &opts); + REQUIRE(object != nullptr); + + REQUIRE(strcmp(bpf_object__name(object), my_object_name) == 0); + + struct bpf_program* program = bpf_object__find_program_by_name(object, "DropPacket"); + REQUIRE(program != nullptr); + + REQUIRE(bpf_program__fd(program) == ebpf_fd_invalid); + REQUIRE(bpf_program__type(program) == BPF_PROG_TYPE_XDP); + + // Make sure we cannot override the program type, since this is a native program. + REQUIRE(bpf_program__set_type(program, BPF_PROG_TYPE_BIND) < 0); + REQUIRE(errno == EINVAL); + REQUIRE(bpf_program__type(program) == BPF_PROG_TYPE_XDP); + + struct bpf_map* map = bpf_object__next_map(object, nullptr); + REQUIRE(map != nullptr); + REQUIRE(strcmp(bpf_map__name(map), "interface_index_map") == 0); + REQUIRE(bpf_map__fd(map) == ebpf_fd_invalid); + map = bpf_object__next_map(object, map); + REQUIRE(strcmp(bpf_map__name(map), "dropped_packet_map") == 0); + REQUIRE(bpf_map__fd(map) == ebpf_fd_invalid); + map = bpf_object__next_map(object, map); + REQUIRE(map == nullptr); + + // Trying to attach the program should fail since it's not loaded yet. + bpf_link_ptr link(bpf_program__attach(program)); + REQUIRE(link == nullptr); + REQUIRE(libbpf_get_error(link.get()) == -EINVAL); + + // Load the program. + REQUIRE(bpf_object__load(object) == 0); + + // Attach should now succeed. + link.reset(bpf_program__attach(program)); + REQUIRE(link != nullptr); + + // The maps should now have FDs. + map = bpf_object__next_map(object, nullptr); + REQUIRE(map != nullptr); + REQUIRE(strcmp(bpf_map__name(map), "interface_index_map") == 0); + REQUIRE(bpf_map__fd(map) != ebpf_fd_invalid); + map = bpf_object__next_map(object, map); + REQUIRE(strcmp(bpf_map__name(map), "dropped_packet_map") == 0); + REQUIRE(bpf_map__fd(map) != ebpf_fd_invalid); + map = bpf_object__next_map(object, map); + REQUIRE(map == nullptr); + + REQUIRE(bpf_link__destroy(link.release()) == 0); + bpf_object__close(object); +} + #if !defined(CONFIG_BPF_JIT_DISABLED) TEST_CASE("bpf_object__load with .o", "[libbpf]") {