diff --git a/libs/execution_context/ebpf_core.c b/libs/execution_context/ebpf_core.c index bd1f3bfff3..f867bfa420 100644 --- a/libs/execution_context/ebpf_core.c +++ b/libs/execution_context/ebpf_core.c @@ -1544,12 +1544,15 @@ _ebpf_core_protocol_unlink_program(_In_ const ebpf_operation_unlink_program_requ EBPF_LOG_ENTRY(); ebpf_result_t retval = EBPF_SUCCESS; ebpf_link_t* link = NULL; + bool detach_single_link = false; + bool detached_any_link = false; if (request->link_handle != ebpf_handle_invalid) { retval = EBPF_OBJECT_REFERENCE_BY_HANDLE(request->link_handle, EBPF_OBJECT_LINK, (ebpf_core_object_t**)&link); if (retval != EBPF_SUCCESS) { goto Done; } + detach_single_link = true; } else if (request->attach_data_present) { // This path will be taken for bpf_prog_detach and bpf_prog_detach2 APIs. // Find the link object matching the unlink request parameters. @@ -1570,17 +1573,18 @@ _ebpf_core_protocol_unlink_program(_In_ const ebpf_operation_unlink_program_requ // Detach the link. Since _ebpf_core_find_matching_link takes a reference on the link object, // the detach function will not free the link object. ebpf_link_detach_program(link); + detached_any_link = true; // Pass the link object as the previous object parameter to the _ebpf_core_find_matching_link function, // which will release the reference from it. previous_link = link; } if (retval == EBPF_NO_MORE_KEYS) { // No more matching links to detach. - retval = EBPF_SUCCESS; + retval = detached_any_link ? EBPF_SUCCESS : EBPF_OBJECT_NOT_FOUND; } } - if (link != NULL) { + if (detach_single_link && link != NULL) { ebpf_link_detach_program(link); } diff --git a/tests/connect_redirect/connect_redirect_tests.cpp b/tests/connect_redirect/connect_redirect_tests.cpp index d1563744d5..9cf2133113 100644 --- a/tests/connect_redirect/connect_redirect_tests.cpp +++ b/tests/connect_redirect/connect_redirect_tests.cpp @@ -251,7 +251,7 @@ _initialize_test_globals() // Load and attach the programs. native_module_helper_t helper; - helper.initialize("cgroup_sock_addr2"); + helper.initialize("cgroup_sock_addr2", EBPF_EXECUTION_ANY, true); _globals.bpf_object.reset(bpf_object__open(helper.get_file_name().c_str())); SAFE_REQUIRE(_globals.bpf_object.get() != nullptr); SAFE_REQUIRE(bpf_object__load(_globals.bpf_object.get()) == 0); diff --git a/tests/libs/common/common_tests.h b/tests/libs/common/common_tests.h index b0fbbcdf60..f2a2fb314f 100644 --- a/tests/libs/common/common_tests.h +++ b/tests/libs/common/common_tests.h @@ -33,6 +33,95 @@ typedef struct _close_bpf_object } close_bpf_object_t; typedef std::unique_ptr bpf_object_ptr; +/** + * @brief RAII guard for legacy links created by bpf_prog_attach. + * + * bpf_prog_attach does not return a bpf_link handle, so the only way to + * clean up is to call bpf_prog_detach2 with the same parameters. This + * guard captures those parameters at attach time and calls detach in its + * destructor, ensuring cleanup even when tests exit via exceptions or + * SAFE_REQUIRE failures. + */ +class bpf_prog_attach_guard_t +{ + public: + bpf_prog_attach_guard_t() = default; + + /** + * @brief Attach a program and take ownership of detaching it. + * @param prog_fd File descriptor of the program to attach. + * @param attach_target Target (compartment ID) to attach to. + * @param attach_type The BPF attach type. + * @param flags Attach flags. + */ + bpf_prog_attach_guard_t(int prog_fd, uint32_t attach_target, bpf_attach_type attach_type, uint32_t flags = 0) + : _prog_fd(prog_fd), _attach_target(attach_target), _attach_type(attach_type) + { + _last_result = bpf_prog_attach(prog_fd, attach_target, attach_type, flags); + _attached = (_last_result == 0); + } + + ~bpf_prog_attach_guard_t() { detach(); } + + bpf_prog_attach_guard_t(const bpf_prog_attach_guard_t&) = delete; + bpf_prog_attach_guard_t& + operator=(const bpf_prog_attach_guard_t&) = delete; + + bpf_prog_attach_guard_t(bpf_prog_attach_guard_t&& other) noexcept + : _prog_fd(other._prog_fd), _attach_target(other._attach_target), _attach_type(other._attach_type), + _attached(other._attached), _last_result(other._last_result) + { + other._attached = false; + } + + bpf_prog_attach_guard_t& + operator=(bpf_prog_attach_guard_t&& other) noexcept + { + if (this != &other) { + detach(); + _prog_fd = other._prog_fd; + _attach_target = other._attach_target; + _attach_type = other._attach_type; + _attached = other._attached; + _last_result = other._last_result; + other._attached = false; + } + return *this; + } + + /** @brief Manually detach. Safe to call multiple times. */ + int + detach() + { + if (_attached) { + _attached = false; + return bpf_prog_detach2(_prog_fd, _attach_target, _attach_type); + } + return 0; + } + + /** @brief Result of the bpf_prog_attach call (0 on success). */ + int + result() const + { + return _last_result; + } + + /** @brief Whether the program is currently attached. */ + bool + attached() const + { + return _attached; + } + + private: + int _prog_fd{-1}; + uint32_t _attach_target{0}; + bpf_attach_type _attach_type{}; + bool _attached{false}; + int _last_result{-1}; +}; + void ebpf_test_pinned_map_enum(bool verify_pin_path); void diff --git a/tests/libs/util/native_helper.cpp b/tests/libs/util/native_helper.cpp index 9512123c06..302d4d6ae4 100644 --- a/tests/libs/util/native_helper.cpp +++ b/tests/libs/util/native_helper.cpp @@ -23,20 +23,6 @@ _native_module_helper::initialize( #else ebpf_execution_type_t system_default = ebpf_execution_type_t::EBPF_EXECUTION_ANY; #endif - // Clean up any previous state. - // Detach all bpf links. - uint32_t link_id; - while (bpf_link_get_next_id(0, &link_id) == 0) { - fd_t link_fd = bpf_link_get_fd_by_id(link_id); - if (link_fd < 0) { - break; - } - bpf_link_detach(link_fd); - if (link_fd >= 0) { - (void)ebpf_close_fd(link_fd); - } - } - // Set _is_main_thread before any REQUIRE is invoked. _is_main_thread = is_main_thread; diff --git a/tests/socket/socket_tests.cpp b/tests/socket/socket_tests.cpp index 4af20d56fe..0ce2fb6951 100644 --- a/tests/socket/socket_tests.cpp +++ b/tests/socket/socket_tests.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -343,6 +344,7 @@ execute_connection_test(_In_ const connection_test_case& test_case) bpf_program* program; program_spec spec; bpf_link* link; + std::optional attach_guard; ///< RAII guard for bpf_prog_attach path. }; struct loaded_module { @@ -450,14 +452,15 @@ execute_connection_test(_In_ const connection_test_case& test_case) bpf_program__fd(loaded_program.program)); if (loaded_program.spec.attach_method == attach_method_t::bpf_prog_attach) { // libbpf-compat path: passes a 4-byte attach parameter containing compartment_id=0. - int rc = ::bpf_prog_attach(bpf_program__fd(program), 0, loaded_program.spec.attach_type, 0); - SAFE_REQUIRE(rc == 0); + loaded_program.attach_guard.emplace(bpf_program__fd(program), 0, loaded_program.spec.attach_type); + SAFE_REQUIRE(loaded_program.attach_guard->result() == 0); } else { // Native API path: passes NULL attach parameter (wildcard / unspecified compartment). ebpf_attach_type_t attach_type_guid{}; SAFE_REQUIRE( ebpf_get_ebpf_attach_type(loaded_program.spec.attach_type, &attach_type_guid) == EBPF_SUCCESS); - SAFE_REQUIRE(ebpf_program_attach(program, &attach_type_guid, nullptr, 0, nullptr) == EBPF_SUCCESS); + SAFE_REQUIRE( + ebpf_program_attach(program, &attach_type_guid, nullptr, 0, &loaded_program.link) == EBPF_SUCCESS); } } } @@ -590,6 +593,17 @@ execute_connection_test(_In_ const connection_test_case& test_case) ++test_index; } + + // Detach and clean up all attached programs before unloading objects. + // bpf_prog_attach guards auto-detach in their destructors; only bpf_link* needs manual cleanup. + for (auto& mod : loaded_modules) { + for (auto& loaded_program : mod.programs) { + if (loaded_program.link != nullptr) { + bpf_link__destroy(loaded_program.link); + loaded_program.link = nullptr; + } + } + } } // Type tuples for TEMPLATE_TEST_CASE: (address_family, protocol). @@ -1049,7 +1063,8 @@ sock_addr_bind_unknown_verdict_test(ADDRESS_FAMILY address_family, IPPROTO proto const char* program_name = (address_family == AF_INET) ? "authorize_bind4" : "authorize_bind6"; bpf_program* program = bpf_object__find_program_by_name(object, program_name); SAFE_REQUIRE(program != nullptr); - SAFE_REQUIRE(bpf_prog_attach(bpf_program__fd(const_cast(program)), 0, attach_type, 0) == 0); + bpf_prog_attach_guard_t attach_guard(bpf_program__fd(const_cast(program)), 0, attach_type); + SAFE_REQUIRE(attach_guard.result() == 0); // Inject a bit pattern that is not a valid ebpf_sock_addr_verdict_t enumerator. The kernel // bind hook must treat this as REJECT (per the documented contract) and block the bind. @@ -1077,6 +1092,7 @@ sock_addr_bind_unknown_verdict_test(ADDRESS_FAMILY address_family, IPPROTO proto int rc = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); int err = (rc == 0) ? 0 : WSAGetLastError(); closesocket(sock); + SAFE_REQUIRE(rc != 0); SAFE_REQUIRE(err == WSAEACCES); } @@ -1117,8 +1133,8 @@ bind_helper_functions_validation_test(ADDRESS_FAMILY address_family) // Attach at the appropriate BIND layer. bpf_attach_type attach_type = (address_family == AF_INET) ? BPF_CGROUP_INET4_BIND : BPF_CGROUP_INET6_BIND; - int result = bpf_prog_attach(bpf_program__fd(const_cast(bind_program)), 0, attach_type, 0); - SAFE_REQUIRE(result == 0); + bpf_prog_attach_guard_t attach_guard(bpf_program__fd(const_cast(bind_program)), 0, attach_type); + SAFE_REQUIRE(attach_guard.result() == 0); // Dual-stack AF_INET6 socket; the bound address selects the V4 vs V6 WFP layer. SOCKET sock = WSASocketW(AF_INET6, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, 0); @@ -1138,7 +1154,7 @@ bind_helper_functions_validation_test(ADDRESS_FAMILY address_family) const uint32_t* ip6_dwords = reinterpret_cast(&in6addr_loopback); connection_id = (ip6_dwords[0] ^ ip6_dwords[3]) ^ (htons(SOCKET_TEST_PORT) << 16); } - result = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); + int result = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); SAFE_REQUIRE(result == 0); // Verify network context was populated. @@ -1226,6 +1242,7 @@ bind_helper_functions_validation_test(ADDRESS_FAMILY address_family) SAFE_REQUIRE(results.socket_cookie == 0); closesocket(sock); + printf( "Bind helper functions validation test completed successfully for %s\n", (address_family == AF_INET) ? "IPv4" : "IPv6"); @@ -1275,12 +1292,11 @@ helper_functions_validation_test( // Attach the connect authorization program at the appropriate CONNECT_AUTHORIZATION layer. bpf_attach_type connect_authorization_attach_type = (address_family == AF_INET) ? BPF_CGROUP_INET4_CONNECT_AUTHORIZATION : BPF_CGROUP_INET6_CONNECT_AUTHORIZATION; - int result = bpf_prog_attach( + bpf_prog_attach_guard_t attach_guard( bpf_program__fd(const_cast(connect_authorization_program)), 0, - connect_authorization_attach_type, - 0); - SAFE_REQUIRE(result == 0); + connect_authorization_attach_type); + SAFE_REQUIRE(attach_guard.result() == 0); // Post an asynchronous receive on the receiver socket. receiver_socket.post_async_receive(); @@ -1311,7 +1327,7 @@ helper_functions_validation_test( // Validate that the network context helper returned reasonable values. bpf_sock_addr_network_context_t net_ctx = {0}; - result = bpf_map_lookup_elem(bpf_map__fd(network_context_map), &connection_id, &net_ctx); + int result = bpf_map_lookup_elem(bpf_map__fd(network_context_map), &connection_id, &net_ctx); SAFE_REQUIRE(result == 0); printf( "Network context - Version: %u, Interface: %u, Tunnel: %u, Next-hop: %llu, SubInterface: %u\n", @@ -1389,12 +1405,11 @@ TEST_CASE( SAFE_REQUIRE(connection_count_map != nullptr); // Attach the conditional authorization program. - int result = bpf_prog_attach( + bpf_prog_attach_guard_t attach_guard( bpf_program__fd(const_cast(conditional_program)), 0, - BPF_CGROUP_INET4_CONNECT_AUTHORIZATION, - 0); - SAFE_REQUIRE(result == 0); + BPF_CGROUP_INET4_CONNECT_AUTHORIZATION); + SAFE_REQUIRE(attach_guard.result() == 0); // Create test sockets. stream_client_socket_t stream_client_socket(SOCK_STREAM, IPPROTO_TCP, 0); @@ -1415,7 +1430,7 @@ TEST_CASE( // Check if tunnel connections were tracked (key 100 is used for tunnel connections). uint32_t tunnel_key = 100; uint64_t tunnel_count = 0; - result = bpf_map_lookup_elem(bpf_map__fd(connection_count_map), &tunnel_key, &tunnel_count); + int result = bpf_map_lookup_elem(bpf_map__fd(connection_count_map), &tunnel_key, &tunnel_count); // For loopback connections, we don't expect tunnels, so tunnel_count should be 0 or entry not found. if (result == 0) { @@ -1786,9 +1801,9 @@ TEST_CASE("listen_helper_functions_validation_tcp_v4", "[sock_addr_tests][helper SAFE_REQUIRE(sock_addr_helper_results_map != nullptr); // Attach at INET4_LISTEN. - int result = - bpf_prog_attach(bpf_program__fd(const_cast(listen_program)), 0, BPF_CGROUP_INET4_LISTEN, 0); - SAFE_REQUIRE(result == 0); + bpf_prog_attach_guard_t attach_guard( + bpf_program__fd(const_cast(listen_program)), 0, BPF_CGROUP_INET4_LISTEN); + SAFE_REQUIRE(attach_guard.result() == 0); // Trigger listen by creating, binding, and calling listen() on a TCP socket. SOCKET sock = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, 0); @@ -1797,7 +1812,7 @@ TEST_CASE("listen_helper_functions_validation_tcp_v4", "[sock_addr_tests][helper bind_addr.sin_family = AF_INET; bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); bind_addr.sin_port = htons(SOCKET_TEST_PORT); - result = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); + int result = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); SAFE_REQUIRE(result == 0); result = listen(sock, SOMAXCONN); SAFE_REQUIRE(result == 0); @@ -1878,6 +1893,7 @@ TEST_CASE("listen_helper_functions_validation_tcp_v4", "[sock_addr_tests][helper SAFE_REQUIRE(results.socket_cookie == 0); closesocket(sock); + printf("Listen helper functions validation test completed successfully for IPv4\n"); } @@ -1901,9 +1917,9 @@ TEST_CASE("listen_helper_functions_validation_tcp_v6", "[sock_addr_tests][helper SAFE_REQUIRE(sock_addr_helper_results_map != nullptr); // Attach at INET6_LISTEN. - int result = - bpf_prog_attach(bpf_program__fd(const_cast(listen_program)), 0, BPF_CGROUP_INET6_LISTEN, 0); - SAFE_REQUIRE(result == 0); + bpf_prog_attach_guard_t attach_guard( + bpf_program__fd(const_cast(listen_program)), 0, BPF_CGROUP_INET6_LISTEN); + SAFE_REQUIRE(attach_guard.result() == 0); // Trigger listen. SOCKET sock = WSASocketW(AF_INET6, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, 0); @@ -1912,7 +1928,7 @@ TEST_CASE("listen_helper_functions_validation_tcp_v6", "[sock_addr_tests][helper bind_addr.sin6_family = AF_INET6; bind_addr.sin6_addr = in6addr_loopback; bind_addr.sin6_port = htons(SOCKET_TEST_PORT); - result = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); + int result = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); SAFE_REQUIRE(result == 0); result = listen(sock, SOMAXCONN); SAFE_REQUIRE(result == 0); @@ -1979,6 +1995,7 @@ TEST_CASE("listen_helper_functions_validation_tcp_v6", "[sock_addr_tests][helper SAFE_REQUIRE(results.socket_cookie == 0); closesocket(sock); + printf("Listen helper functions validation test completed successfully for IPv6\n"); } @@ -2217,6 +2234,25 @@ TEST_CASE("attach_sock_addr_programs", "[sock_addr_tests]") 0); SAFE_REQUIRE(result == 0); + ZeroMemory(&program_info, program_info_size); + SAFE_REQUIRE( + bpf_obj_get_info_by_fd( + bpf_program__fd(const_cast(connect6_program)), &program_info, &program_info_size) == 0); + SAFE_REQUIRE(program_info.link_count == 1); + SAFE_REQUIRE(program_info.map_ids == 0); + + result = bpf_prog_detach2( + bpf_program__fd(const_cast(connect6_program)), + DEFAULT_COMPARTMENT_ID, + BPF_CGROUP_INET6_CONNECT); + SAFE_REQUIRE(result == 0); + + ZeroMemory(&program_info, program_info_size); + SAFE_REQUIRE( + bpf_obj_get_info_by_fd( + bpf_program__fd(const_cast(connect6_program)), &program_info, &program_info_size) == 0); + SAFE_REQUIRE(program_info.link_count == 0); + bpf_program* recv_accept6_program = bpf_object__find_program_by_name(object, "authorize_recv_accept6"); SAFE_REQUIRE(recv_accept6_program != nullptr); @@ -2226,6 +2262,27 @@ TEST_CASE("attach_sock_addr_programs", "[sock_addr_tests]") BPF_CGROUP_INET6_RECV_ACCEPT, 0); SAFE_REQUIRE(result == 0); + + ZeroMemory(&program_info, program_info_size); + SAFE_REQUIRE( + bpf_obj_get_info_by_fd( + bpf_program__fd(const_cast(recv_accept6_program)), &program_info, &program_info_size) == + 0); + SAFE_REQUIRE(program_info.link_count == 1); + SAFE_REQUIRE(program_info.map_ids == 0); + + result = bpf_prog_detach2( + bpf_program__fd(const_cast(recv_accept6_program)), + DEFAULT_COMPARTMENT_ID, + BPF_CGROUP_INET6_RECV_ACCEPT); + SAFE_REQUIRE(result == 0); + + ZeroMemory(&program_info, program_info_size); + SAFE_REQUIRE( + bpf_obj_get_info_by_fd( + bpf_program__fd(const_cast(recv_accept6_program)), &program_info, &program_info_size) == + 0); + SAFE_REQUIRE(program_info.link_count == 0); } void @@ -2345,8 +2402,9 @@ connection_monitor_test( receiver_socket.post_async_receive(); // Attach the sockops program. - int result = bpf_prog_attach(bpf_program__fd(const_cast(_program)), 0, BPF_CGROUP_SOCK_OPS, 0); - SAFE_REQUIRE(result == 0); + bpf_prog_attach_guard_t attach_guard( + bpf_program__fd(const_cast(_program)), 0, BPF_CGROUP_SOCK_OPS); + SAFE_REQUIRE(attach_guard.result() == 0); // Send loopback message to test port. const char* message = CLIENT_MESSAGE; @@ -2449,8 +2507,9 @@ TEST_CASE("attach_sockops_programs", "[sock_ops_tests]") bpf_program* _program = bpf_object__find_program_by_name(object, "connection_monitor"); SAFE_REQUIRE(_program != nullptr); - int result = bpf_prog_attach(bpf_program__fd(const_cast(_program)), 0, BPF_CGROUP_SOCK_OPS, 0); - SAFE_REQUIRE(result == 0); + bpf_prog_attach_guard_t attach_guard( + bpf_program__fd(const_cast(_program)), 0, BPF_CGROUP_SOCK_OPS); + SAFE_REQUIRE(attach_guard.result() == 0); } // Custom event handler for flow ID validation @@ -2527,8 +2586,9 @@ TEST_CASE("sock_ops_flow_id_helper_test", "[sock_ops_tests]") SAFE_REQUIRE(flow_id_map != nullptr); // Attach the program. - int result = bpf_prog_attach(bpf_program__fd(const_cast(_program)), 0, BPF_CGROUP_SOCK_OPS, 0); - SAFE_REQUIRE(result == 0); + bpf_prog_attach_guard_t attach_guard( + bpf_program__fd(const_cast(_program)), 0, BPF_CGROUP_SOCK_OPS); + SAFE_REQUIRE(attach_guard.result() == 0); // Get the std::future from the promise field in ring buffer event context, which should be in ready state // once notifications for all events are received. @@ -2589,7 +2649,7 @@ TEST_CASE("sock_ops_flow_id_helper_test", "[sock_ops_tests]") // Verify that we got a flow ID stored in the map (should be non-zero). uint64_t stored_flow_id = 0; - result = bpf_map_lookup_elem(bpf_map__fd(flow_id_map), &tuple, &stored_flow_id); + int result = bpf_map_lookup_elem(bpf_map__fd(flow_id_map), &tuple, &stored_flow_id); // Verify we get a non-zero flow ID. REQUIRE(result == 0); @@ -2850,12 +2910,14 @@ multi_attach_test(uint32_t compartment_id, socket_family_t family, ADDRESS_FAMIL const char* connect_program_name = (address_family == AF_INET) ? "connect_redirect4" : "connect_redirect6"; // Attach all the programs to the same hook (i.e. same attach parameters). + // Guards ensure cleanup even if a SAFE_REQUIRE fails mid-test. + std::vector attach_guards; for (uint32_t i = 0; i < MULTIPLE_ATTACH_PROGRAM_COUNT; i++) { bpf_program* connect_program = bpf_object__find_program_by_name(objects[i], connect_program_name); SAFE_REQUIRE(connect_program != nullptr); - int result = bpf_prog_attach( - bpf_program__fd(const_cast(connect_program)), compartment_id, attach_type, 0); - SAFE_REQUIRE(result == 0); + attach_guards.emplace_back( + bpf_program__fd(const_cast(connect_program)), compartment_id, attach_type); + SAFE_REQUIRE(attach_guards.back().result() == 0); } // Configure policy maps for all programs to "allow" the connection. @@ -2901,9 +2963,9 @@ multi_attach_test(uint32_t compartment_id, socket_family_t family, ADDRESS_FAMIL // Attach the connect program at BPF_CGROUP_INET4_CONNECT / BPF_CGROUP_INET6_CONNECT. bpf_program* connect_program = bpf_object__find_program_by_name(object, connect_program_name); SAFE_REQUIRE(connect_program != nullptr); - int result = bpf_prog_attach( - bpf_program__fd(const_cast(connect_program)), compartment_id + 2, attach_type, 0); - SAFE_REQUIRE(result == 0); + bpf_prog_attach_guard_t fourth_attach_guard( + bpf_program__fd(const_cast(connect_program)), compartment_id + 2, attach_type); + SAFE_REQUIRE(fourth_attach_guard.result() == 0); // Not updating policy map for this program should mean that this program (if invoked) will block the connection. // Validate that the connection is allowed. @@ -2937,12 +2999,16 @@ multi_attach_test_redirection( } // Attach all the 3 programs to the same hook (i.e. same attach parameters). + // Guards ensure cleanup even if a SAFE_REQUIRE fails mid-test. + // Note: validate_program_redirection may detach/re-attach these programs internally as part of its test logic. + // The guards remain valid because bpf_prog_detach2 is idempotent and the programs end up re-attached. + std::vector attach_guards; for (uint32_t i = 0; i < MULTIPLE_ATTACH_PROGRAM_COUNT; i++) { bpf_program* connect_program = bpf_object__find_program_by_name(objects[i], connect_program_name); SAFE_REQUIRE(connect_program != nullptr); - int result = bpf_prog_attach( - bpf_program__fd(const_cast(connect_program)), compartment_id, attach_type, 0); - SAFE_REQUIRE(result == 0); + attach_guards.emplace_back( + bpf_program__fd(const_cast(connect_program)), compartment_id, attach_type); + SAFE_REQUIRE(attach_guards.back().result() == 0); } // Lambda function to update the policy map entry, and validate the connection. @@ -3216,15 +3282,15 @@ test_multi_attach_combined(socket_family_t family, ADDRESS_FAMILY address_family const char* connect_program_name = (address_family == AF_INET) ? "connect_redirect4" : "connect_redirect6"; // Attach all the programs. + std::vector attach_guards; for (uint32_t i = 0; i < program_count_per_hook * 2; i++) { bpf_program* connect_program = bpf_object__find_program_by_name(objects[i], connect_program_name); SAFE_REQUIRE(connect_program != nullptr); - int result = bpf_prog_attach( + attach_guards.emplace_back( bpf_program__fd(const_cast(connect_program)), i < program_count_per_hook ? 1 : UNSPECIFIED_COMPARTMENT_ID, - attach_type, - 0); - SAFE_REQUIRE(result == 0); + attach_type); + SAFE_REQUIRE(attach_guards.back().result() == 0); } // This loop will iterate over all the possible combinations of program actions for each program. @@ -3395,17 +3461,16 @@ TEST_CASE("multi_attach_test_invocation_order", "[sock_addr_tests][multi_attach_ SAFE_REQUIRE(connect_program_wildcard != nullptr); // Attach the program with specific compartment id first. - result = - bpf_prog_attach(bpf_program__fd(const_cast(connect_program_specific)), 1, attach_type, 0); - SAFE_REQUIRE(result == 0); + bpf_prog_attach_guard_t specific_attach_guard( + bpf_program__fd(const_cast(connect_program_specific)), 1, attach_type); + SAFE_REQUIRE(specific_attach_guard.result() == 0); // Attach the program with wildcard compartment id next. - result = bpf_prog_attach( + bpf_prog_attach_guard_t wildcard_attach_guard( bpf_program__fd(const_cast(connect_program_wildcard)), UNSPECIFIED_COMPARTMENT_ID, - attach_type, - 0); - SAFE_REQUIRE(result == 0); + attach_type); + SAFE_REQUIRE(wildcard_attach_guard.result() == 0); // First configure both the programs to allow the connection. bpf_map* policy_map_specific = bpf_object__find_map_by_name(object_specific, "policy_map"); @@ -3685,13 +3750,14 @@ thread_function_allow_block_connection( fd_t prog_fd = bpf_program__fd(const_cast(connect_program)); // Attach the program at BPF_CGROUP_INET4_CONNECT / BPF_CGROUP_INET6_CONNECT. - int result = bpf_prog_attach(prog_fd, compartment_id, attach_type, 0); - if (result != 0) { + // The guard ensures automatic detach on scope exit, even if an exception is thrown. + bpf_prog_attach_guard_t attach_guard(prog_fd, compartment_id, attach_type); + if (attach_guard.result() != 0) { int saved_errno = errno; std::ostringstream oss; oss << "ALLOW_BLOCK ATTACH FAILED: thread=" << std::this_thread::get_id() << " compartment=" << compartment_id << " prog_fd=" << prog_fd << " attach_type=" << static_cast(attach_type) << " protocol=" << protocol - << " result=" << result << " errno=" << saved_errno; + << " result=" << attach_guard.result() << " errno=" << saved_errno; throw test_failure(oss.str()); }