diff --git a/docs/BindHook.md b/docs/BindHook.md index ce9e640a01..cd2a63a868 100644 --- a/docs/BindHook.md +++ b/docs/BindHook.md @@ -201,8 +201,47 @@ typedef enum _ebpf_sock_addr_verdict } ebpf_sock_addr_verdict_t; ``` -When multiple bind programs are attached, the verdicts are combined: if any program -rejects, the bind is blocked. +When multiple bind programs are attached, the verdicts are combined using a +most-restrictive accumulation rule: + +- **Priority (highest wins)**: `REJECT` > `PROCEED_HARD` > `PROCEED_SOFT` +- The accumulated verdict is the highest-priority value returned by any + attached program. +- **Short-circuit on REJECT**: If any program returns `REJECT`, the provider + loop stops immediately — subsequent programs are not invoked. +- If no programs are attached (or all are detached), the default verdict is + `PROCEED_SOFT` (permit). +- An unknown/invalid return value from a program is treated as `REJECT`. + +The accumulated eBPF verdict decides whether the bind is permitted +(`PROCEED_SOFT` or `PROCEED_HARD`) or denied (`REJECT`, surfaced to the caller as +`WSAEACCES` / `EACCES`). Among eBPF programs the most-restrictive verdict wins, so +a later program returning `REJECT` still denies a bind that a prior program +permitted. + +### Multi-Attach Test Coverage + +The following scenarios are exercised in `tests/socket/socket_tests.cpp` +(tagged `[bind_tests][multi_attach]`), across TCP/UDP and IPv4/IPv6: + +| Scenario | Programs | Expected Result | +|---|---|---| +| All soft permits | 2× `PROCEED_SOFT` | Bind allowed | +| Second program rejects | `PROCEED_SOFT` + `REJECT` | Bind denied | +| First program rejects (short-circuit) | `REJECT` + `PROCEED_SOFT` | Bind denied | +| Soft + hard mix | `PROCEED_SOFT` + `PROCEED_HARD` | Bind allowed (hard priority) | +| Soft permits blocked by WFP | 2× `PROCEED_SOFT` + WFP block | Bind denied | +| Hard overrides WFP | 2 programs (one returns `PROCEED_HARD`) + WFP block | Bind allowed | +| Hard permit first overrides WFP | `PROCEED_HARD` + `PROCEED_SOFT` + WFP block | Bind allowed | +| Reject beats hard permit | `REJECT` + `PROCEED_HARD` | Bind denied | +| Hard permit then reject | `PROCEED_HARD` + `REJECT` | Bind denied | +| Detach middle program | 3 programs → detach REJECT middle | Bind recovers | +| Detach first program | `REJECT` + 2× `PROCEED_SOFT` → detach first | Bind recovers | +| Detach last program | 2× `PROCEED_SOFT` + `REJECT` → detach last | Bind recovers | +| Detach and reattach | Detach + reattach with new verdict | Verdict updates | +| Three soft permits | 3× `PROCEED_SOFT` | Bind allowed | +| Third program rejects | 2× `PROCEED_SOFT` + `REJECT` | Bind denied | +| Third program hard permit overrides WFP | 2× `PROCEED_SOFT` + `PROCEED_HARD` + WFP block | Bind allowed | ## Architecture diff --git a/netebpfext/net_ebpf_ext_sock_addr.c b/netebpfext/net_ebpf_ext_sock_addr.c index 2610c1efc0..7366d0fc91 100644 --- a/netebpfext/net_ebpf_ext_sock_addr.c +++ b/netebpfext/net_ebpf_ext_sock_addr.c @@ -302,7 +302,7 @@ static bool _net_ebpf_extension_sock_addr_process_verdict(_Inout_ void* program_context, int program_verdict); static bool -_net_ebpf_extension_sock_addr_bind_process_verdict(_Inout_ void* program_context, int program_verdict); +_net_ebpf_extension_sock_addr_accumulate_verdict(_Inout_ void* program_context, int program_verdict); // // sock_addr helper functions. @@ -1405,7 +1405,7 @@ net_ebpf_ext_sock_addr_register_providers() .create_filter_context = _net_ebpf_extension_sock_addr_create_filter_context, .cleanup_filter_context = _net_ebpf_extension_sock_addr_cleanup_filter_context, .validate_client_data = _net_ebpf_extension_sock_addr_validate_client_data, - .process_verdict = _net_ebpf_extension_sock_addr_bind_process_verdict, + .process_verdict = _net_ebpf_extension_sock_addr_accumulate_verdict, }; const net_ebpf_extension_hook_provider_dispatch_table_t listen_dispatch_table = { @@ -2066,26 +2066,78 @@ _net_ebpf_extension_sock_addr_process_verdict(_Inout_ void* program_context, int return TRUE; } -// Multi-attach verdict accumulator for the sock_addr bind hook. Tracks the -// most-restrictive verdict across attached programs in net_ebpf_sock_addr_t::verdict -// using _get_verdict_priority(), and returns FALSE on REJECT so the hook provider -// loop stops invoking subsequent programs. Address/port writes to the context are -// ignored at bind (the WFP ALE_RESOURCE_ASSIGNMENT layer does not support address -// rewrite), so no redirect handling is performed here. +// Multi-attach verdict accumulator for sock_addr gates that do not support +// context rewrite. Currently used only by the sock_addr bind hook +// (ALE_RESOURCE_ASSIGNMENT); the sock_addr listen hook (ALE_AUTH_LISTEN) adopts +// it when listen gains multi-attach support (see issue #5339). +// Tracks the most-restrictive normalized verdict across attached programs in +// net_ebpf_sock_addr_t::verdict using _get_verdict_priority(), and returns +// FALSE on REJECT so the hook provider loop stops invoking subsequent +// programs. +// +// Any address/port writes a program makes to the context are silently ignored +// for WFP purposes (the underlying ALE layer does not support address +// rewrite). To prevent one program's writes from being observed by subsequent +// programs in the same multi-attach invocation, the context is restored from +// net_ebpf_sock_addr_t::original_context after each program. The caller must +// set original_context to point at a pristine snapshot of bpf_sock_addr_t +// before invoking any program. static bool -_net_ebpf_extension_sock_addr_bind_process_verdict(_Inout_ void* program_context, int program_verdict) +_net_ebpf_extension_sock_addr_accumulate_verdict(_Inout_ void* program_context, int program_verdict) { bpf_sock_addr_t* sock_addr_ctx = (bpf_sock_addr_t*)program_context; net_ebpf_sock_addr_t* context = CONTAINING_RECORD(sock_addr_ctx, net_ebpf_sock_addr_t, base); + bpf_sock_addr_t* original_context = context->original_context; int normalized_verdict = _normalize_sock_addr_verdict(program_verdict); + // original_context must be set by the caller before invoking programs. + // It points to a caller's stack variable and is only valid during synchronous program invocation. + ASSERT(original_context != NULL); + if (_get_verdict_priority(normalized_verdict) > _get_verdict_priority(context->verdict)) { context->verdict = normalized_verdict; } + // Restore the context so the next attached program sees the original + // WFP-provided values, not whatever the previous program may have written + // to user_ip / user_port / msg_src_*. + *sock_addr_ctx = *original_context; + return normalized_verdict != BPF_SOCK_ADDR_VERDICT_REJECT; } +/** + * @brief Apply an accumulated eBPF verdict to the WFP classify output. + * + * Maps a BPF_SOCK_ADDR_VERDICT_* value onto the WFP action, clearing + * FWPS_RIGHT_ACTION_WRITE for the terminal verdicts (hard permit and block) so that a + * lower-weight callout cannot override them. Any verdict other than PROCEED_SOFT or + * PROCEED_HARD is treated as a block, matching _normalize_sock_addr_verdict(). + * + * Callers must hold FWPS_RIGHT_ACTION_WRITE: the classify routines bail out before invoking + * any eBPF program when a higher-weight callout has revoked it. + * + * @param[in,out] classify_output Output structure containing the action to take. + * @param[in] verdict Accumulated BPF_SOCK_ADDR_VERDICT_* value across the attached programs. + */ +static void +_net_ebpf_extension_sock_addr_apply_verdict(_Inout_ FWPS_CLASSIFY_OUT* classify_output, uint32_t verdict) +{ + switch (verdict) { + case BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT: + classify_output->actionType = FWP_ACTION_PERMIT; + break; + case BPF_SOCK_ADDR_VERDICT_PROCEED_HARD: + classify_output->actionType = FWP_ACTION_PERMIT; + classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; + break; + default: + classify_output->actionType = FWP_ACTION_BLOCK; + classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; + break; + } +} + // // WFP callout callback functions. // @@ -2128,6 +2180,15 @@ net_ebpf_extension_sock_addr_authorize_listen_classify( UNREFERENCED_PARAMETER(classify_context); UNREFERENCED_PARAMETER(flow_context); + if ((classify_output->rights & FWPS_RIGHT_ACTION_WRITE) == 0) { + // A callout with higher weight has revoked the write permission. Bail out without + // invoking any eBPF program and without touching classify_output->actionType, matching + // net_ebpf_extension_sock_addr_redirect_connection_classify(). + EBPF_EXT_LOG_MESSAGE( + EBPF_EXT_TRACELOG_LEVEL_VERBOSE, EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "No \"write\" right; exiting."); + goto Exit; + } + classify_output->actionType = FWP_ACTION_PERMIT; filter_context = (net_ebpf_extension_sock_addr_wfp_filter_context_t*)filter->context; @@ -2177,21 +2238,8 @@ net_ebpf_extension_sock_addr_authorize_listen_classify( goto Exit; } - // Set action type based on verdict. - // Clear FWPS_RIGHT_ACTION_WRITE for block and hard permit. - switch (result) { - case BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT: - classify_output->actionType = FWP_ACTION_PERMIT; - break; - case BPF_SOCK_ADDR_VERDICT_PROCEED_HARD: - classify_output->actionType = FWP_ACTION_PERMIT; - classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; - break; - default: - classify_output->actionType = FWP_ACTION_BLOCK; - classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; - break; - } + // Set action type based on the program verdict. + _net_ebpf_extension_sock_addr_apply_verdict(classify_output, result); _net_ebpf_ext_log_sock_addr_classify( "listen_classify", @@ -2243,6 +2291,15 @@ net_ebpf_extension_sock_addr_authorize_recv_accept_classify( UNREFERENCED_PARAMETER(classify_context); UNREFERENCED_PARAMETER(flow_context); + if ((classify_output->rights & FWPS_RIGHT_ACTION_WRITE) == 0) { + // A callout with higher weight has revoked the write permission. Bail out without + // invoking any eBPF program and without touching classify_output->actionType, matching + // net_ebpf_extension_sock_addr_redirect_connection_classify(). + EBPF_EXT_LOG_MESSAGE( + EBPF_EXT_TRACELOG_LEVEL_VERBOSE, EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "No \"write\" right; exiting."); + goto Exit; + } + classify_output->actionType = FWP_ACTION_PERMIT; filter_context = (net_ebpf_extension_sock_addr_wfp_filter_context_t*)filter->context; @@ -2294,21 +2351,8 @@ net_ebpf_extension_sock_addr_authorize_recv_accept_classify( goto Exit; } - // Set action type based on verdict - // Clear FWPS_RIGHT_ACTION_WRITE for block and hard permit. - switch (result) { - case BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT: - classify_output->actionType = FWP_ACTION_PERMIT; - break; - case BPF_SOCK_ADDR_VERDICT_PROCEED_HARD: - classify_output->actionType = FWP_ACTION_PERMIT; - classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; - break; - default: - classify_output->actionType = FWP_ACTION_BLOCK; - classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; - break; - } + // Set action type based on the program verdict. + _net_ebpf_extension_sock_addr_apply_verdict(classify_output, result); _net_ebpf_ext_log_sock_addr_classify( "recv_accept_classify", @@ -2363,6 +2407,15 @@ net_ebpf_extension_sock_addr_bind_classify( UNREFERENCED_PARAMETER(classify_context); UNREFERENCED_PARAMETER(flow_context); + if ((classify_output->rights & FWPS_RIGHT_ACTION_WRITE) == 0) { + // A callout with higher weight has revoked the write permission. Bail out without + // invoking any eBPF program and without touching classify_output->actionType, matching + // net_ebpf_extension_sock_addr_redirect_connection_classify(). + EBPF_EXT_LOG_MESSAGE( + EBPF_EXT_TRACELOG_LEVEL_VERBOSE, EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "No \"write\" right; exiting."); + goto Exit; + } + classify_output->actionType = FWP_ACTION_PERMIT; filter_context = (net_ebpf_extension_sock_addr_wfp_filter_context_t*)filter->context; @@ -2391,10 +2444,17 @@ net_ebpf_extension_sock_addr_bind_classify( // Initialize the accumulated verdict to PROCEED_SOFT so that if no program updates it // (e.g. all clients are filtered out), the bind defaults to permit. - // The bind process_verdict callback updates net_ebpf_sock_addr_ctx.verdict with the + // The accumulate_verdict callback updates net_ebpf_sock_addr_ctx.verdict with the // most-restrictive verdict across multi-attach programs and short-circuits on REJECT. net_ebpf_sock_addr_ctx.verdict = BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT; + // Snapshot the context so the shared accumulate_verdict callback can restore it + // between programs. The snapshot is stack-local and only valid for the synchronous + // program invocation below. + bpf_sock_addr_t sock_addr_ctx_original; + memcpy(&sock_addr_ctx_original, sock_addr_ctx, sizeof(sock_addr_ctx_original)); + net_ebpf_sock_addr_ctx.original_context = &sock_addr_ctx_original; + program_result = net_ebpf_extension_hook_expand_stack_and_invoke_programs(sock_addr_ctx, &filter_context->base, &ignored_result); if (program_result == EBPF_OBJECT_NOT_FOUND) { @@ -2407,23 +2467,11 @@ net_ebpf_extension_sock_addr_bind_classify( goto Exit; } - // Use the accumulated verdict from the bind process_verdict callback. Bind hooks do not + // Use the accumulated verdict from the accumulate_verdict callback. Bind hooks do not // support address modification: any changes the program made to user_ip/user_port are - // silently ignored. + // silently ignored (and restored between programs by the shared accumulator). verdict = net_ebpf_sock_addr_ctx.verdict; - switch (verdict) { - case BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT: - classify_output->actionType = FWP_ACTION_PERMIT; - break; - case BPF_SOCK_ADDR_VERDICT_PROCEED_HARD: - classify_output->actionType = FWP_ACTION_PERMIT; - classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; - break; - default: - classify_output->actionType = FWP_ACTION_BLOCK; - classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; - break; - } + _net_ebpf_extension_sock_addr_apply_verdict(classify_output, verdict); _net_ebpf_ext_log_sock_addr_classify( "bind_classify", @@ -2473,6 +2521,7 @@ net_ebpf_extension_sock_addr_authorize_connection_classify( bpf_sock_addr_t* sock_addr_ctx = &net_ebpf_sock_addr_ctx.base; uint32_t compartment_id = UNSPECIFIED_COMPARTMENT_ID; ebpf_result_t program_result; + bool rights_revoked = FALSE; UNREFERENCED_PARAMETER(incoming_metadata_values); UNREFERENCED_PARAMETER(layer_data); @@ -2515,9 +2564,22 @@ net_ebpf_extension_sock_addr_authorize_connection_classify( } // First, try to find and use existing connection context from redirect layer. + // This must happen before the rights check so the cached entry is always cleaned up, + // even when a higher-weight callout has revoked our write permission. verdict = _net_ebpf_ext_find_and_remove_connection_context( incoming_metadata_values->transportEndpointHandle, sock_addr_ctx); + if ((classify_output->rights & FWPS_RIGHT_ACTION_WRITE) == 0) { + // A callout with higher weight has revoked the write permission. Bail out without + // invoking any eBPF program and without touching classify_output->actionType (the + // Exit-block verdict is also skipped via rights_revoked). The cache cleanup above + // has already run. + EBPF_EXT_LOG_MESSAGE( + EBPF_EXT_TRACELOG_LEVEL_VERBOSE, EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "No \"write\" right; exiting."); + rights_revoked = TRUE; + goto Exit; + } + // CONNECT_AUTHORIZATION programs run for all non-REJECT verdicts from the redirect layer. // REJECT is already final. PROCEED_HARD and PROCEED_SOFT both allow authorization programs // to run so they can make decisions based on route-dependent metadata. @@ -2559,20 +2621,9 @@ net_ebpf_extension_sock_addr_authorize_connection_classify( } Exit: - // Set action type based on verdict. - // Clear FWPS_RIGHT_ACTION_WRITE for block and hard permit. - switch (verdict) { - case BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT: - classify_output->actionType = FWP_ACTION_PERMIT; - break; - case BPF_SOCK_ADDR_VERDICT_PROCEED_HARD: - classify_output->actionType = FWP_ACTION_PERMIT; - classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; - break; - default: - classify_output->actionType = FWP_ACTION_BLOCK; - classify_output->rights &= ~FWPS_RIGHT_ACTION_WRITE; - break; + // Set action type based on the accumulated verdict. + if (!rights_revoked) { + _net_ebpf_extension_sock_addr_apply_verdict(classify_output, verdict); } _net_ebpf_ext_log_sock_addr_classify( diff --git a/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_dll.c b/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_dll.c index e8265399d9..e5e355ddf0 100644 --- a/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_dll.c +++ b/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_dll.c @@ -63,6 +63,24 @@ static map_entry_t _maps[] = { 0, // The id of the inner map template. }, "bind_verdict_map"}, + { + {0, 0}, + { + 1, // Current Version. + 80, // Struct size up to the last field. + 80, // Total struct size including padding. + }, + { + BPF_MAP_TYPE_ARRAY, // Type of map. + 4, // Size in bytes of a map key. + 8, // Size in bytes of a map value. + 1, // Maximum number of entries allowed in the map. + 0, // Inner map index. + LIBBPF_PIN_NONE, // Pinning type for the map. + 24, // Identifier for a map template. + 0, // The id of the inner map template. + }, + "bind_invocation_count_map"}, }; #pragma data_seg(pop) @@ -70,7 +88,7 @@ static void _get_maps(_Outptr_result_buffer_maybenull_(*count) map_entry_t** maps, _Out_ size_t* count) { *maps = _maps; - *count = 1; + *count = 2; } static void @@ -96,88 +114,139 @@ static GUID authorize_bind4_attach_type_guid = { 0x0d7ce21a, 0x7773, 0x405c, {0x93, 0xb6, 0xd5, 0xbf, 0xb9, 0x2e, 0x74, 0xbc}}; static uint16_t authorize_bind4_maps[] = { 0, + 1, }; #pragma code_seg(push, "cgroup~2") static uint64_t authorize_bind4(void* context, const program_runtime_context_t* runtime_context) -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" { -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" // Prologue. -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" uint64_t stack[(UBPF_STACK_SIZE + 7) / 8]; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r0 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r1 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r2 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r3 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r4 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r5 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" + register uint64_t r6 = 0; +#line 67 "sample/cgroup_sock_addr_bind.c" + register uint64_t r7 = 0; +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r10 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" r1 = (uintptr_t)context; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" r10 = (uintptr_t)((uint8_t*)stack + sizeof(stack)); - // EBPF_OP_MOV64_IMM pc=0 dst=r2 src=r0 offset=0 imm=0 -#line 53 "sample/cgroup_sock_addr_bind.c" - r2 = IMMEDIATE(0); - // EBPF_OP_STXW pc=1 dst=r10 src=r2 offset=-4 imm=0 -#line 40 "sample/cgroup_sock_addr_bind.c" - WRITE_ONCE_32(r10, (uint32_t)r2, OFFSET(-4)); + // EBPF_OP_MOV64_IMM pc=0 dst=r7 src=r0 offset=0 imm=0 +#line 67 "sample/cgroup_sock_addr_bind.c" + r7 = IMMEDIATE(0); + // EBPF_OP_STXW pc=1 dst=r10 src=r7 offset=-4 imm=0 +#line 49 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-4)); // EBPF_OP_LDXH pc=2 dst=r2 src=r1 offset=40 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" READ_ONCE_16(r2, r1, OFFSET(40)); // EBPF_OP_STXH pc=3 dst=r10 src=r2 offset=-4 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_16(r10, (uint16_t)r2, OFFSET(-4)); // EBPF_OP_LDXW pc=4 dst=r1 src=r1 offset=44 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" READ_ONCE_32(r1, r1, OFFSET(44)); // EBPF_OP_STXB pc=5 dst=r10 src=r1 offset=-2 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_8(r10, (uint8_t)r1, OFFSET(-2)); // EBPF_OP_MOV64_REG pc=6 dst=r2 src=r10 offset=0 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 = r10; // EBPF_OP_ADD64_IMM pc=7 dst=r2 src=r0 offset=0 imm=-4 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 += IMMEDIATE(-4); // EBPF_OP_LDDW pc=8 dst=r1 src=r1 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r1 = POINTER(runtime_context->map_data[0].address); // EBPF_OP_CALL pc=10 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = runtime_context->helper_data[0].address(r1, r2, r3, r4, r5, context); - // EBPF_OP_MOV64_REG pc=11 dst=r1 src=r0 offset=0 imm=0 -#line 44 "sample/cgroup_sock_addr_bind.c" - r1 = r0; + // EBPF_OP_MOV64_REG pc=11 dst=r6 src=r0 offset=0 imm=0 +#line 53 "sample/cgroup_sock_addr_bind.c" + r6 = r0; // EBPF_OP_MOV64_IMM pc=12 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = IMMEDIATE(1); - // EBPF_OP_JEQ_IMM pc=13 dst=r1 src=r0 offset=1 imm=0 -#line 45 "sample/cgroup_sock_addr_bind.c" - if (r1 == IMMEDIATE(0)) { -#line 45 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_JEQ_IMM pc=13 dst=r6 src=r0 offset=11 imm=0 +#line 54 "sample/cgroup_sock_addr_bind.c" + if (r6 == IMMEDIATE(0)) { +#line 54 "sample/cgroup_sock_addr_bind.c" + goto label_2; +#line 54 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_STXW pc=14 dst=r10 src=r7 offset=-8 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-8)); + // EBPF_OP_MOV64_REG pc=15 dst=r2 src=r10 offset=0 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 = r10; + // EBPF_OP_ADD64_IMM pc=16 dst=r2 src=r0 offset=0 imm=-8 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 += IMMEDIATE(-8); + // EBPF_OP_LDDW pc=17 dst=r1 src=r1 offset=0 imm=2 +#line 56 "sample/cgroup_sock_addr_bind.c" + r1 = POINTER(runtime_context->map_data[1].address); + // EBPF_OP_CALL pc=19 dst=r0 src=r0 offset=0 imm=1 +#line 56 "sample/cgroup_sock_addr_bind.c" + { +#line 56 "sample/cgroup_sock_addr_bind.c" + uint32_t _array_key = *(uint32_t*)(uintptr_t)r2; +#line 56 "sample/cgroup_sock_addr_bind.c" + if (_array_key < 1) { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = (uint64_t)(uintptr_t)(runtime_context->map_data[1].array_data + (uint64_t)_array_key * 8); +#line 56 "sample/cgroup_sock_addr_bind.c" + } else { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = 0; +#line 56 "sample/cgroup_sock_addr_bind.c" + } +#line 56 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_JEQ_IMM pc=20 dst=r0 src=r0 offset=3 imm=0 +#line 57 "sample/cgroup_sock_addr_bind.c" + if (r0 == IMMEDIATE(0)) { +#line 57 "sample/cgroup_sock_addr_bind.c" goto label_1; -#line 45 "sample/cgroup_sock_addr_bind.c" +#line 57 "sample/cgroup_sock_addr_bind.c" } - // EBPF_OP_LDXW pc=14 dst=r0 src=r1 offset=0 imm=0 -#line 46 "sample/cgroup_sock_addr_bind.c" - READ_ONCE_32(r0, r1, OFFSET(0)); + // EBPF_OP_LDXDW pc=21 dst=r1 src=r0 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_64(r1, r0, OFFSET(0)); + // EBPF_OP_ADD64_IMM pc=22 dst=r1 src=r0 offset=0 imm=1 +#line 58 "sample/cgroup_sock_addr_bind.c" + r1 += IMMEDIATE(1); + // EBPF_OP_STXDW pc=23 dst=r0 src=r1 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_64(r0, (uint64_t)r1, OFFSET(0)); label_1: - // EBPF_OP_EXIT pc=15 dst=r0 src=r0 offset=0 imm=0 -#line 55 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_LDXW pc=24 dst=r0 src=r6 offset=0 imm=0 +#line 60 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_32(r0, r6, OFFSET(0)); +label_2: + // EBPF_OP_EXIT pc=25 dst=r0 src=r0 offset=0 imm=0 +#line 69 "sample/cgroup_sock_addr_bind.c" return r0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" } #pragma code_seg(pop) #line __LINE__ __FILE__ @@ -196,88 +265,139 @@ static GUID authorize_bind6_attach_type_guid = { 0x81de64c0, 0x2973, 0x468d, {0x83, 0x82, 0x67, 0x69, 0xf0, 0x33, 0xd7, 0x59}}; static uint16_t authorize_bind6_maps[] = { 0, + 1, }; #pragma code_seg(push, "cgroup~1") static uint64_t authorize_bind6(void* context, const program_runtime_context_t* runtime_context) -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" { -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" // Prologue. -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" uint64_t stack[(UBPF_STACK_SIZE + 7) / 8]; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r0 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r1 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r2 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r3 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r4 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r5 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" + register uint64_t r6 = 0; +#line 74 "sample/cgroup_sock_addr_bind.c" + register uint64_t r7 = 0; +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r10 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" r1 = (uintptr_t)context; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" r10 = (uintptr_t)((uint8_t*)stack + sizeof(stack)); - // EBPF_OP_MOV64_IMM pc=0 dst=r2 src=r0 offset=0 imm=0 -#line 60 "sample/cgroup_sock_addr_bind.c" - r2 = IMMEDIATE(0); - // EBPF_OP_STXW pc=1 dst=r10 src=r2 offset=-4 imm=0 -#line 40 "sample/cgroup_sock_addr_bind.c" - WRITE_ONCE_32(r10, (uint32_t)r2, OFFSET(-4)); + // EBPF_OP_MOV64_IMM pc=0 dst=r7 src=r0 offset=0 imm=0 +#line 74 "sample/cgroup_sock_addr_bind.c" + r7 = IMMEDIATE(0); + // EBPF_OP_STXW pc=1 dst=r10 src=r7 offset=-4 imm=0 +#line 49 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-4)); // EBPF_OP_LDXH pc=2 dst=r2 src=r1 offset=40 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" READ_ONCE_16(r2, r1, OFFSET(40)); // EBPF_OP_STXH pc=3 dst=r10 src=r2 offset=-4 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_16(r10, (uint16_t)r2, OFFSET(-4)); // EBPF_OP_LDXW pc=4 dst=r1 src=r1 offset=44 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" READ_ONCE_32(r1, r1, OFFSET(44)); // EBPF_OP_STXB pc=5 dst=r10 src=r1 offset=-2 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_8(r10, (uint8_t)r1, OFFSET(-2)); // EBPF_OP_MOV64_REG pc=6 dst=r2 src=r10 offset=0 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 = r10; // EBPF_OP_ADD64_IMM pc=7 dst=r2 src=r0 offset=0 imm=-4 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 += IMMEDIATE(-4); // EBPF_OP_LDDW pc=8 dst=r1 src=r1 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r1 = POINTER(runtime_context->map_data[0].address); // EBPF_OP_CALL pc=10 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = runtime_context->helper_data[0].address(r1, r2, r3, r4, r5, context); - // EBPF_OP_MOV64_REG pc=11 dst=r1 src=r0 offset=0 imm=0 -#line 44 "sample/cgroup_sock_addr_bind.c" - r1 = r0; + // EBPF_OP_MOV64_REG pc=11 dst=r6 src=r0 offset=0 imm=0 +#line 53 "sample/cgroup_sock_addr_bind.c" + r6 = r0; // EBPF_OP_MOV64_IMM pc=12 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = IMMEDIATE(1); - // EBPF_OP_JEQ_IMM pc=13 dst=r1 src=r0 offset=1 imm=0 -#line 45 "sample/cgroup_sock_addr_bind.c" - if (r1 == IMMEDIATE(0)) { -#line 45 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_JEQ_IMM pc=13 dst=r6 src=r0 offset=11 imm=0 +#line 54 "sample/cgroup_sock_addr_bind.c" + if (r6 == IMMEDIATE(0)) { +#line 54 "sample/cgroup_sock_addr_bind.c" + goto label_2; +#line 54 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_STXW pc=14 dst=r10 src=r7 offset=-8 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-8)); + // EBPF_OP_MOV64_REG pc=15 dst=r2 src=r10 offset=0 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 = r10; + // EBPF_OP_ADD64_IMM pc=16 dst=r2 src=r0 offset=0 imm=-8 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 += IMMEDIATE(-8); + // EBPF_OP_LDDW pc=17 dst=r1 src=r1 offset=0 imm=2 +#line 56 "sample/cgroup_sock_addr_bind.c" + r1 = POINTER(runtime_context->map_data[1].address); + // EBPF_OP_CALL pc=19 dst=r0 src=r0 offset=0 imm=1 +#line 56 "sample/cgroup_sock_addr_bind.c" + { +#line 56 "sample/cgroup_sock_addr_bind.c" + uint32_t _array_key = *(uint32_t*)(uintptr_t)r2; +#line 56 "sample/cgroup_sock_addr_bind.c" + if (_array_key < 1) { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = (uint64_t)(uintptr_t)(runtime_context->map_data[1].array_data + (uint64_t)_array_key * 8); +#line 56 "sample/cgroup_sock_addr_bind.c" + } else { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = 0; +#line 56 "sample/cgroup_sock_addr_bind.c" + } +#line 56 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_JEQ_IMM pc=20 dst=r0 src=r0 offset=3 imm=0 +#line 57 "sample/cgroup_sock_addr_bind.c" + if (r0 == IMMEDIATE(0)) { +#line 57 "sample/cgroup_sock_addr_bind.c" goto label_1; -#line 45 "sample/cgroup_sock_addr_bind.c" +#line 57 "sample/cgroup_sock_addr_bind.c" } - // EBPF_OP_LDXW pc=14 dst=r0 src=r1 offset=0 imm=0 -#line 46 "sample/cgroup_sock_addr_bind.c" - READ_ONCE_32(r0, r1, OFFSET(0)); + // EBPF_OP_LDXDW pc=21 dst=r1 src=r0 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_64(r1, r0, OFFSET(0)); + // EBPF_OP_ADD64_IMM pc=22 dst=r1 src=r0 offset=0 imm=1 +#line 58 "sample/cgroup_sock_addr_bind.c" + r1 += IMMEDIATE(1); + // EBPF_OP_STXDW pc=23 dst=r0 src=r1 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_64(r0, (uint64_t)r1, OFFSET(0)); label_1: - // EBPF_OP_EXIT pc=15 dst=r0 src=r0 offset=0 imm=0 -#line 62 "sample/cgroup_sock_addr_bind.c" - return r0; + // EBPF_OP_LDXW pc=24 dst=r0 src=r6 offset=0 imm=0 #line 60 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_32(r0, r6, OFFSET(0)); +label_2: + // EBPF_OP_EXIT pc=25 dst=r0 src=r0 offset=0 imm=0 +#line 76 "sample/cgroup_sock_addr_bind.c" + return r0; +#line 74 "sample/cgroup_sock_addr_bind.c" } #pragma code_seg(pop) #line __LINE__ __FILE__ @@ -292,10 +412,10 @@ static program_entry_t _programs[] = { "cgroup/bind4", "authorize_bind4", authorize_bind4_maps, - 1, + 2, authorize_bind4_helpers, 1, - 16, + 26, &authorize_bind4_program_type_guid, &authorize_bind4_attach_type_guid, }, @@ -307,10 +427,10 @@ static program_entry_t _programs[] = { "cgroup/bind6", "authorize_bind6", authorize_bind6_maps, - 1, + 2, authorize_bind6_helpers, 1, - 16, + 26, &authorize_bind6_program_type_guid, &authorize_bind6_attach_type_guid, }, diff --git a/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_raw.c b/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_raw.c index 7bf8951e63..6d2ef65163 100644 --- a/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_raw.c +++ b/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_raw.c @@ -33,6 +33,24 @@ static map_entry_t _maps[] = { 0, // The id of the inner map template. }, "bind_verdict_map"}, + { + {0, 0}, + { + 1, // Current Version. + 80, // Struct size up to the last field. + 80, // Total struct size including padding. + }, + { + BPF_MAP_TYPE_ARRAY, // Type of map. + 4, // Size in bytes of a map key. + 8, // Size in bytes of a map value. + 1, // Maximum number of entries allowed in the map. + 0, // Inner map index. + LIBBPF_PIN_NONE, // Pinning type for the map. + 24, // Identifier for a map template. + 0, // The id of the inner map template. + }, + "bind_invocation_count_map"}, }; #pragma data_seg(pop) @@ -40,7 +58,7 @@ static void _get_maps(_Outptr_result_buffer_maybenull_(*count) map_entry_t** maps, _Out_ size_t* count) { *maps = _maps; - *count = 1; + *count = 2; } static void @@ -66,88 +84,139 @@ static GUID authorize_bind4_attach_type_guid = { 0x0d7ce21a, 0x7773, 0x405c, {0x93, 0xb6, 0xd5, 0xbf, 0xb9, 0x2e, 0x74, 0xbc}}; static uint16_t authorize_bind4_maps[] = { 0, + 1, }; #pragma code_seg(push, "cgroup~2") static uint64_t authorize_bind4(void* context, const program_runtime_context_t* runtime_context) -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" { -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" // Prologue. -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" uint64_t stack[(UBPF_STACK_SIZE + 7) / 8]; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r0 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r1 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r2 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r3 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r4 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r5 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" + register uint64_t r6 = 0; +#line 67 "sample/cgroup_sock_addr_bind.c" + register uint64_t r7 = 0; +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r10 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" r1 = (uintptr_t)context; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" r10 = (uintptr_t)((uint8_t*)stack + sizeof(stack)); - // EBPF_OP_MOV64_IMM pc=0 dst=r2 src=r0 offset=0 imm=0 -#line 53 "sample/cgroup_sock_addr_bind.c" - r2 = IMMEDIATE(0); - // EBPF_OP_STXW pc=1 dst=r10 src=r2 offset=-4 imm=0 -#line 40 "sample/cgroup_sock_addr_bind.c" - WRITE_ONCE_32(r10, (uint32_t)r2, OFFSET(-4)); + // EBPF_OP_MOV64_IMM pc=0 dst=r7 src=r0 offset=0 imm=0 +#line 67 "sample/cgroup_sock_addr_bind.c" + r7 = IMMEDIATE(0); + // EBPF_OP_STXW pc=1 dst=r10 src=r7 offset=-4 imm=0 +#line 49 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-4)); // EBPF_OP_LDXH pc=2 dst=r2 src=r1 offset=40 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" READ_ONCE_16(r2, r1, OFFSET(40)); // EBPF_OP_STXH pc=3 dst=r10 src=r2 offset=-4 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_16(r10, (uint16_t)r2, OFFSET(-4)); // EBPF_OP_LDXW pc=4 dst=r1 src=r1 offset=44 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" READ_ONCE_32(r1, r1, OFFSET(44)); // EBPF_OP_STXB pc=5 dst=r10 src=r1 offset=-2 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_8(r10, (uint8_t)r1, OFFSET(-2)); // EBPF_OP_MOV64_REG pc=6 dst=r2 src=r10 offset=0 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 = r10; // EBPF_OP_ADD64_IMM pc=7 dst=r2 src=r0 offset=0 imm=-4 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 += IMMEDIATE(-4); // EBPF_OP_LDDW pc=8 dst=r1 src=r1 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r1 = POINTER(runtime_context->map_data[0].address); // EBPF_OP_CALL pc=10 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = runtime_context->helper_data[0].address(r1, r2, r3, r4, r5, context); - // EBPF_OP_MOV64_REG pc=11 dst=r1 src=r0 offset=0 imm=0 -#line 44 "sample/cgroup_sock_addr_bind.c" - r1 = r0; + // EBPF_OP_MOV64_REG pc=11 dst=r6 src=r0 offset=0 imm=0 +#line 53 "sample/cgroup_sock_addr_bind.c" + r6 = r0; // EBPF_OP_MOV64_IMM pc=12 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = IMMEDIATE(1); - // EBPF_OP_JEQ_IMM pc=13 dst=r1 src=r0 offset=1 imm=0 -#line 45 "sample/cgroup_sock_addr_bind.c" - if (r1 == IMMEDIATE(0)) { -#line 45 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_JEQ_IMM pc=13 dst=r6 src=r0 offset=11 imm=0 +#line 54 "sample/cgroup_sock_addr_bind.c" + if (r6 == IMMEDIATE(0)) { +#line 54 "sample/cgroup_sock_addr_bind.c" + goto label_2; +#line 54 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_STXW pc=14 dst=r10 src=r7 offset=-8 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-8)); + // EBPF_OP_MOV64_REG pc=15 dst=r2 src=r10 offset=0 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 = r10; + // EBPF_OP_ADD64_IMM pc=16 dst=r2 src=r0 offset=0 imm=-8 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 += IMMEDIATE(-8); + // EBPF_OP_LDDW pc=17 dst=r1 src=r1 offset=0 imm=2 +#line 56 "sample/cgroup_sock_addr_bind.c" + r1 = POINTER(runtime_context->map_data[1].address); + // EBPF_OP_CALL pc=19 dst=r0 src=r0 offset=0 imm=1 +#line 56 "sample/cgroup_sock_addr_bind.c" + { +#line 56 "sample/cgroup_sock_addr_bind.c" + uint32_t _array_key = *(uint32_t*)(uintptr_t)r2; +#line 56 "sample/cgroup_sock_addr_bind.c" + if (_array_key < 1) { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = (uint64_t)(uintptr_t)(runtime_context->map_data[1].array_data + (uint64_t)_array_key * 8); +#line 56 "sample/cgroup_sock_addr_bind.c" + } else { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = 0; +#line 56 "sample/cgroup_sock_addr_bind.c" + } +#line 56 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_JEQ_IMM pc=20 dst=r0 src=r0 offset=3 imm=0 +#line 57 "sample/cgroup_sock_addr_bind.c" + if (r0 == IMMEDIATE(0)) { +#line 57 "sample/cgroup_sock_addr_bind.c" goto label_1; -#line 45 "sample/cgroup_sock_addr_bind.c" +#line 57 "sample/cgroup_sock_addr_bind.c" } - // EBPF_OP_LDXW pc=14 dst=r0 src=r1 offset=0 imm=0 -#line 46 "sample/cgroup_sock_addr_bind.c" - READ_ONCE_32(r0, r1, OFFSET(0)); + // EBPF_OP_LDXDW pc=21 dst=r1 src=r0 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_64(r1, r0, OFFSET(0)); + // EBPF_OP_ADD64_IMM pc=22 dst=r1 src=r0 offset=0 imm=1 +#line 58 "sample/cgroup_sock_addr_bind.c" + r1 += IMMEDIATE(1); + // EBPF_OP_STXDW pc=23 dst=r0 src=r1 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_64(r0, (uint64_t)r1, OFFSET(0)); label_1: - // EBPF_OP_EXIT pc=15 dst=r0 src=r0 offset=0 imm=0 -#line 55 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_LDXW pc=24 dst=r0 src=r6 offset=0 imm=0 +#line 60 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_32(r0, r6, OFFSET(0)); +label_2: + // EBPF_OP_EXIT pc=25 dst=r0 src=r0 offset=0 imm=0 +#line 69 "sample/cgroup_sock_addr_bind.c" return r0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" } #pragma code_seg(pop) #line __LINE__ __FILE__ @@ -166,88 +235,139 @@ static GUID authorize_bind6_attach_type_guid = { 0x81de64c0, 0x2973, 0x468d, {0x83, 0x82, 0x67, 0x69, 0xf0, 0x33, 0xd7, 0x59}}; static uint16_t authorize_bind6_maps[] = { 0, + 1, }; #pragma code_seg(push, "cgroup~1") static uint64_t authorize_bind6(void* context, const program_runtime_context_t* runtime_context) -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" { -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" // Prologue. -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" uint64_t stack[(UBPF_STACK_SIZE + 7) / 8]; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r0 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r1 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r2 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r3 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r4 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r5 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" + register uint64_t r6 = 0; +#line 74 "sample/cgroup_sock_addr_bind.c" + register uint64_t r7 = 0; +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r10 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" r1 = (uintptr_t)context; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" r10 = (uintptr_t)((uint8_t*)stack + sizeof(stack)); - // EBPF_OP_MOV64_IMM pc=0 dst=r2 src=r0 offset=0 imm=0 -#line 60 "sample/cgroup_sock_addr_bind.c" - r2 = IMMEDIATE(0); - // EBPF_OP_STXW pc=1 dst=r10 src=r2 offset=-4 imm=0 -#line 40 "sample/cgroup_sock_addr_bind.c" - WRITE_ONCE_32(r10, (uint32_t)r2, OFFSET(-4)); + // EBPF_OP_MOV64_IMM pc=0 dst=r7 src=r0 offset=0 imm=0 +#line 74 "sample/cgroup_sock_addr_bind.c" + r7 = IMMEDIATE(0); + // EBPF_OP_STXW pc=1 dst=r10 src=r7 offset=-4 imm=0 +#line 49 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-4)); // EBPF_OP_LDXH pc=2 dst=r2 src=r1 offset=40 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" READ_ONCE_16(r2, r1, OFFSET(40)); // EBPF_OP_STXH pc=3 dst=r10 src=r2 offset=-4 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_16(r10, (uint16_t)r2, OFFSET(-4)); // EBPF_OP_LDXW pc=4 dst=r1 src=r1 offset=44 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" READ_ONCE_32(r1, r1, OFFSET(44)); // EBPF_OP_STXB pc=5 dst=r10 src=r1 offset=-2 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_8(r10, (uint8_t)r1, OFFSET(-2)); // EBPF_OP_MOV64_REG pc=6 dst=r2 src=r10 offset=0 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 = r10; // EBPF_OP_ADD64_IMM pc=7 dst=r2 src=r0 offset=0 imm=-4 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 += IMMEDIATE(-4); // EBPF_OP_LDDW pc=8 dst=r1 src=r1 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r1 = POINTER(runtime_context->map_data[0].address); // EBPF_OP_CALL pc=10 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = runtime_context->helper_data[0].address(r1, r2, r3, r4, r5, context); - // EBPF_OP_MOV64_REG pc=11 dst=r1 src=r0 offset=0 imm=0 -#line 44 "sample/cgroup_sock_addr_bind.c" - r1 = r0; + // EBPF_OP_MOV64_REG pc=11 dst=r6 src=r0 offset=0 imm=0 +#line 53 "sample/cgroup_sock_addr_bind.c" + r6 = r0; // EBPF_OP_MOV64_IMM pc=12 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = IMMEDIATE(1); - // EBPF_OP_JEQ_IMM pc=13 dst=r1 src=r0 offset=1 imm=0 -#line 45 "sample/cgroup_sock_addr_bind.c" - if (r1 == IMMEDIATE(0)) { -#line 45 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_JEQ_IMM pc=13 dst=r6 src=r0 offset=11 imm=0 +#line 54 "sample/cgroup_sock_addr_bind.c" + if (r6 == IMMEDIATE(0)) { +#line 54 "sample/cgroup_sock_addr_bind.c" + goto label_2; +#line 54 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_STXW pc=14 dst=r10 src=r7 offset=-8 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-8)); + // EBPF_OP_MOV64_REG pc=15 dst=r2 src=r10 offset=0 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 = r10; + // EBPF_OP_ADD64_IMM pc=16 dst=r2 src=r0 offset=0 imm=-8 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 += IMMEDIATE(-8); + // EBPF_OP_LDDW pc=17 dst=r1 src=r1 offset=0 imm=2 +#line 56 "sample/cgroup_sock_addr_bind.c" + r1 = POINTER(runtime_context->map_data[1].address); + // EBPF_OP_CALL pc=19 dst=r0 src=r0 offset=0 imm=1 +#line 56 "sample/cgroup_sock_addr_bind.c" + { +#line 56 "sample/cgroup_sock_addr_bind.c" + uint32_t _array_key = *(uint32_t*)(uintptr_t)r2; +#line 56 "sample/cgroup_sock_addr_bind.c" + if (_array_key < 1) { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = (uint64_t)(uintptr_t)(runtime_context->map_data[1].array_data + (uint64_t)_array_key * 8); +#line 56 "sample/cgroup_sock_addr_bind.c" + } else { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = 0; +#line 56 "sample/cgroup_sock_addr_bind.c" + } +#line 56 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_JEQ_IMM pc=20 dst=r0 src=r0 offset=3 imm=0 +#line 57 "sample/cgroup_sock_addr_bind.c" + if (r0 == IMMEDIATE(0)) { +#line 57 "sample/cgroup_sock_addr_bind.c" goto label_1; -#line 45 "sample/cgroup_sock_addr_bind.c" +#line 57 "sample/cgroup_sock_addr_bind.c" } - // EBPF_OP_LDXW pc=14 dst=r0 src=r1 offset=0 imm=0 -#line 46 "sample/cgroup_sock_addr_bind.c" - READ_ONCE_32(r0, r1, OFFSET(0)); + // EBPF_OP_LDXDW pc=21 dst=r1 src=r0 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_64(r1, r0, OFFSET(0)); + // EBPF_OP_ADD64_IMM pc=22 dst=r1 src=r0 offset=0 imm=1 +#line 58 "sample/cgroup_sock_addr_bind.c" + r1 += IMMEDIATE(1); + // EBPF_OP_STXDW pc=23 dst=r0 src=r1 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_64(r0, (uint64_t)r1, OFFSET(0)); label_1: - // EBPF_OP_EXIT pc=15 dst=r0 src=r0 offset=0 imm=0 -#line 62 "sample/cgroup_sock_addr_bind.c" - return r0; + // EBPF_OP_LDXW pc=24 dst=r0 src=r6 offset=0 imm=0 #line 60 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_32(r0, r6, OFFSET(0)); +label_2: + // EBPF_OP_EXIT pc=25 dst=r0 src=r0 offset=0 imm=0 +#line 76 "sample/cgroup_sock_addr_bind.c" + return r0; +#line 74 "sample/cgroup_sock_addr_bind.c" } #pragma code_seg(pop) #line __LINE__ __FILE__ @@ -262,10 +382,10 @@ static program_entry_t _programs[] = { "cgroup/bind4", "authorize_bind4", authorize_bind4_maps, - 1, + 2, authorize_bind4_helpers, 1, - 16, + 26, &authorize_bind4_program_type_guid, &authorize_bind4_attach_type_guid, }, @@ -277,10 +397,10 @@ static program_entry_t _programs[] = { "cgroup/bind6", "authorize_bind6", authorize_bind6_maps, - 1, + 2, authorize_bind6_helpers, 1, - 16, + 26, &authorize_bind6_program_type_guid, &authorize_bind6_attach_type_guid, }, diff --git a/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_sys.c b/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_sys.c index dc98425eca..d28cd2146f 100644 --- a/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_sys.c +++ b/tests/bpf2c_tests/expected/cgroup_sock_addr_bind_sys.c @@ -188,6 +188,24 @@ static map_entry_t _maps[] = { 0, // The id of the inner map template. }, "bind_verdict_map"}, + { + {0, 0}, + { + 1, // Current Version. + 80, // Struct size up to the last field. + 80, // Total struct size including padding. + }, + { + BPF_MAP_TYPE_ARRAY, // Type of map. + 4, // Size in bytes of a map key. + 8, // Size in bytes of a map value. + 1, // Maximum number of entries allowed in the map. + 0, // Inner map index. + LIBBPF_PIN_NONE, // Pinning type for the map. + 24, // Identifier for a map template. + 0, // The id of the inner map template. + }, + "bind_invocation_count_map"}, }; #pragma data_seg(pop) @@ -195,7 +213,7 @@ static void _get_maps(_Outptr_result_buffer_maybenull_(*count) map_entry_t** maps, _Out_ size_t* count) { *maps = _maps; - *count = 1; + *count = 2; } static void @@ -221,88 +239,139 @@ static GUID authorize_bind4_attach_type_guid = { 0x0d7ce21a, 0x7773, 0x405c, {0x93, 0xb6, 0xd5, 0xbf, 0xb9, 0x2e, 0x74, 0xbc}}; static uint16_t authorize_bind4_maps[] = { 0, + 1, }; #pragma code_seg(push, "cgroup~2") static uint64_t authorize_bind4(void* context, const program_runtime_context_t* runtime_context) -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" { -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" // Prologue. -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" uint64_t stack[(UBPF_STACK_SIZE + 7) / 8]; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r0 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r1 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r2 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r3 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r4 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r5 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" + register uint64_t r6 = 0; +#line 67 "sample/cgroup_sock_addr_bind.c" + register uint64_t r7 = 0; +#line 67 "sample/cgroup_sock_addr_bind.c" register uint64_t r10 = 0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" r1 = (uintptr_t)context; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" r10 = (uintptr_t)((uint8_t*)stack + sizeof(stack)); - // EBPF_OP_MOV64_IMM pc=0 dst=r2 src=r0 offset=0 imm=0 -#line 53 "sample/cgroup_sock_addr_bind.c" - r2 = IMMEDIATE(0); - // EBPF_OP_STXW pc=1 dst=r10 src=r2 offset=-4 imm=0 -#line 40 "sample/cgroup_sock_addr_bind.c" - WRITE_ONCE_32(r10, (uint32_t)r2, OFFSET(-4)); + // EBPF_OP_MOV64_IMM pc=0 dst=r7 src=r0 offset=0 imm=0 +#line 67 "sample/cgroup_sock_addr_bind.c" + r7 = IMMEDIATE(0); + // EBPF_OP_STXW pc=1 dst=r10 src=r7 offset=-4 imm=0 +#line 49 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-4)); // EBPF_OP_LDXH pc=2 dst=r2 src=r1 offset=40 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" READ_ONCE_16(r2, r1, OFFSET(40)); // EBPF_OP_STXH pc=3 dst=r10 src=r2 offset=-4 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_16(r10, (uint16_t)r2, OFFSET(-4)); // EBPF_OP_LDXW pc=4 dst=r1 src=r1 offset=44 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" READ_ONCE_32(r1, r1, OFFSET(44)); // EBPF_OP_STXB pc=5 dst=r10 src=r1 offset=-2 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_8(r10, (uint8_t)r1, OFFSET(-2)); // EBPF_OP_MOV64_REG pc=6 dst=r2 src=r10 offset=0 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 = r10; // EBPF_OP_ADD64_IMM pc=7 dst=r2 src=r0 offset=0 imm=-4 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 += IMMEDIATE(-4); // EBPF_OP_LDDW pc=8 dst=r1 src=r1 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r1 = POINTER(runtime_context->map_data[0].address); // EBPF_OP_CALL pc=10 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = runtime_context->helper_data[0].address(r1, r2, r3, r4, r5, context); - // EBPF_OP_MOV64_REG pc=11 dst=r1 src=r0 offset=0 imm=0 -#line 44 "sample/cgroup_sock_addr_bind.c" - r1 = r0; + // EBPF_OP_MOV64_REG pc=11 dst=r6 src=r0 offset=0 imm=0 +#line 53 "sample/cgroup_sock_addr_bind.c" + r6 = r0; // EBPF_OP_MOV64_IMM pc=12 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = IMMEDIATE(1); - // EBPF_OP_JEQ_IMM pc=13 dst=r1 src=r0 offset=1 imm=0 -#line 45 "sample/cgroup_sock_addr_bind.c" - if (r1 == IMMEDIATE(0)) { -#line 45 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_JEQ_IMM pc=13 dst=r6 src=r0 offset=11 imm=0 +#line 54 "sample/cgroup_sock_addr_bind.c" + if (r6 == IMMEDIATE(0)) { +#line 54 "sample/cgroup_sock_addr_bind.c" + goto label_2; +#line 54 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_STXW pc=14 dst=r10 src=r7 offset=-8 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-8)); + // EBPF_OP_MOV64_REG pc=15 dst=r2 src=r10 offset=0 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 = r10; + // EBPF_OP_ADD64_IMM pc=16 dst=r2 src=r0 offset=0 imm=-8 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 += IMMEDIATE(-8); + // EBPF_OP_LDDW pc=17 dst=r1 src=r1 offset=0 imm=2 +#line 56 "sample/cgroup_sock_addr_bind.c" + r1 = POINTER(runtime_context->map_data[1].address); + // EBPF_OP_CALL pc=19 dst=r0 src=r0 offset=0 imm=1 +#line 56 "sample/cgroup_sock_addr_bind.c" + { +#line 56 "sample/cgroup_sock_addr_bind.c" + uint32_t _array_key = *(uint32_t*)(uintptr_t)r2; +#line 56 "sample/cgroup_sock_addr_bind.c" + if (_array_key < 1) { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = (uint64_t)(uintptr_t)(runtime_context->map_data[1].array_data + (uint64_t)_array_key * 8); +#line 56 "sample/cgroup_sock_addr_bind.c" + } else { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = 0; +#line 56 "sample/cgroup_sock_addr_bind.c" + } +#line 56 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_JEQ_IMM pc=20 dst=r0 src=r0 offset=3 imm=0 +#line 57 "sample/cgroup_sock_addr_bind.c" + if (r0 == IMMEDIATE(0)) { +#line 57 "sample/cgroup_sock_addr_bind.c" goto label_1; -#line 45 "sample/cgroup_sock_addr_bind.c" +#line 57 "sample/cgroup_sock_addr_bind.c" } - // EBPF_OP_LDXW pc=14 dst=r0 src=r1 offset=0 imm=0 -#line 46 "sample/cgroup_sock_addr_bind.c" - READ_ONCE_32(r0, r1, OFFSET(0)); + // EBPF_OP_LDXDW pc=21 dst=r1 src=r0 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_64(r1, r0, OFFSET(0)); + // EBPF_OP_ADD64_IMM pc=22 dst=r1 src=r0 offset=0 imm=1 +#line 58 "sample/cgroup_sock_addr_bind.c" + r1 += IMMEDIATE(1); + // EBPF_OP_STXDW pc=23 dst=r0 src=r1 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_64(r0, (uint64_t)r1, OFFSET(0)); label_1: - // EBPF_OP_EXIT pc=15 dst=r0 src=r0 offset=0 imm=0 -#line 55 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_LDXW pc=24 dst=r0 src=r6 offset=0 imm=0 +#line 60 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_32(r0, r6, OFFSET(0)); +label_2: + // EBPF_OP_EXIT pc=25 dst=r0 src=r0 offset=0 imm=0 +#line 69 "sample/cgroup_sock_addr_bind.c" return r0; -#line 53 "sample/cgroup_sock_addr_bind.c" +#line 67 "sample/cgroup_sock_addr_bind.c" } #pragma code_seg(pop) #line __LINE__ __FILE__ @@ -321,88 +390,139 @@ static GUID authorize_bind6_attach_type_guid = { 0x81de64c0, 0x2973, 0x468d, {0x83, 0x82, 0x67, 0x69, 0xf0, 0x33, 0xd7, 0x59}}; static uint16_t authorize_bind6_maps[] = { 0, + 1, }; #pragma code_seg(push, "cgroup~1") static uint64_t authorize_bind6(void* context, const program_runtime_context_t* runtime_context) -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" { -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" // Prologue. -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" uint64_t stack[(UBPF_STACK_SIZE + 7) / 8]; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r0 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r1 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r2 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r3 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r4 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r5 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" + register uint64_t r6 = 0; +#line 74 "sample/cgroup_sock_addr_bind.c" + register uint64_t r7 = 0; +#line 74 "sample/cgroup_sock_addr_bind.c" register uint64_t r10 = 0; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" r1 = (uintptr_t)context; -#line 60 "sample/cgroup_sock_addr_bind.c" +#line 74 "sample/cgroup_sock_addr_bind.c" r10 = (uintptr_t)((uint8_t*)stack + sizeof(stack)); - // EBPF_OP_MOV64_IMM pc=0 dst=r2 src=r0 offset=0 imm=0 -#line 60 "sample/cgroup_sock_addr_bind.c" - r2 = IMMEDIATE(0); - // EBPF_OP_STXW pc=1 dst=r10 src=r2 offset=-4 imm=0 -#line 40 "sample/cgroup_sock_addr_bind.c" - WRITE_ONCE_32(r10, (uint32_t)r2, OFFSET(-4)); + // EBPF_OP_MOV64_IMM pc=0 dst=r7 src=r0 offset=0 imm=0 +#line 74 "sample/cgroup_sock_addr_bind.c" + r7 = IMMEDIATE(0); + // EBPF_OP_STXW pc=1 dst=r10 src=r7 offset=-4 imm=0 +#line 49 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-4)); // EBPF_OP_LDXH pc=2 dst=r2 src=r1 offset=40 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" READ_ONCE_16(r2, r1, OFFSET(40)); // EBPF_OP_STXH pc=3 dst=r10 src=r2 offset=-4 imm=0 -#line 41 "sample/cgroup_sock_addr_bind.c" +#line 50 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_16(r10, (uint16_t)r2, OFFSET(-4)); // EBPF_OP_LDXW pc=4 dst=r1 src=r1 offset=44 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" READ_ONCE_32(r1, r1, OFFSET(44)); // EBPF_OP_STXB pc=5 dst=r10 src=r1 offset=-2 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" WRITE_ONCE_8(r10, (uint8_t)r1, OFFSET(-2)); // EBPF_OP_MOV64_REG pc=6 dst=r2 src=r10 offset=0 imm=0 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 = r10; // EBPF_OP_ADD64_IMM pc=7 dst=r2 src=r0 offset=0 imm=-4 -#line 42 "sample/cgroup_sock_addr_bind.c" +#line 51 "sample/cgroup_sock_addr_bind.c" r2 += IMMEDIATE(-4); // EBPF_OP_LDDW pc=8 dst=r1 src=r1 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r1 = POINTER(runtime_context->map_data[0].address); // EBPF_OP_CALL pc=10 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = runtime_context->helper_data[0].address(r1, r2, r3, r4, r5, context); - // EBPF_OP_MOV64_REG pc=11 dst=r1 src=r0 offset=0 imm=0 -#line 44 "sample/cgroup_sock_addr_bind.c" - r1 = r0; + // EBPF_OP_MOV64_REG pc=11 dst=r6 src=r0 offset=0 imm=0 +#line 53 "sample/cgroup_sock_addr_bind.c" + r6 = r0; // EBPF_OP_MOV64_IMM pc=12 dst=r0 src=r0 offset=0 imm=1 -#line 44 "sample/cgroup_sock_addr_bind.c" +#line 53 "sample/cgroup_sock_addr_bind.c" r0 = IMMEDIATE(1); - // EBPF_OP_JEQ_IMM pc=13 dst=r1 src=r0 offset=1 imm=0 -#line 45 "sample/cgroup_sock_addr_bind.c" - if (r1 == IMMEDIATE(0)) { -#line 45 "sample/cgroup_sock_addr_bind.c" + // EBPF_OP_JEQ_IMM pc=13 dst=r6 src=r0 offset=11 imm=0 +#line 54 "sample/cgroup_sock_addr_bind.c" + if (r6 == IMMEDIATE(0)) { +#line 54 "sample/cgroup_sock_addr_bind.c" + goto label_2; +#line 54 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_STXW pc=14 dst=r10 src=r7 offset=-8 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_32(r10, (uint32_t)r7, OFFSET(-8)); + // EBPF_OP_MOV64_REG pc=15 dst=r2 src=r10 offset=0 imm=0 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 = r10; + // EBPF_OP_ADD64_IMM pc=16 dst=r2 src=r0 offset=0 imm=-8 +#line 55 "sample/cgroup_sock_addr_bind.c" + r2 += IMMEDIATE(-8); + // EBPF_OP_LDDW pc=17 dst=r1 src=r1 offset=0 imm=2 +#line 56 "sample/cgroup_sock_addr_bind.c" + r1 = POINTER(runtime_context->map_data[1].address); + // EBPF_OP_CALL pc=19 dst=r0 src=r0 offset=0 imm=1 +#line 56 "sample/cgroup_sock_addr_bind.c" + { +#line 56 "sample/cgroup_sock_addr_bind.c" + uint32_t _array_key = *(uint32_t*)(uintptr_t)r2; +#line 56 "sample/cgroup_sock_addr_bind.c" + if (_array_key < 1) { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = (uint64_t)(uintptr_t)(runtime_context->map_data[1].array_data + (uint64_t)_array_key * 8); +#line 56 "sample/cgroup_sock_addr_bind.c" + } else { +#line 56 "sample/cgroup_sock_addr_bind.c" + r0 = 0; +#line 56 "sample/cgroup_sock_addr_bind.c" + } +#line 56 "sample/cgroup_sock_addr_bind.c" + } + // EBPF_OP_JEQ_IMM pc=20 dst=r0 src=r0 offset=3 imm=0 +#line 57 "sample/cgroup_sock_addr_bind.c" + if (r0 == IMMEDIATE(0)) { +#line 57 "sample/cgroup_sock_addr_bind.c" goto label_1; -#line 45 "sample/cgroup_sock_addr_bind.c" +#line 57 "sample/cgroup_sock_addr_bind.c" } - // EBPF_OP_LDXW pc=14 dst=r0 src=r1 offset=0 imm=0 -#line 46 "sample/cgroup_sock_addr_bind.c" - READ_ONCE_32(r0, r1, OFFSET(0)); + // EBPF_OP_LDXDW pc=21 dst=r1 src=r0 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_64(r1, r0, OFFSET(0)); + // EBPF_OP_ADD64_IMM pc=22 dst=r1 src=r0 offset=0 imm=1 +#line 58 "sample/cgroup_sock_addr_bind.c" + r1 += IMMEDIATE(1); + // EBPF_OP_STXDW pc=23 dst=r0 src=r1 offset=0 imm=0 +#line 58 "sample/cgroup_sock_addr_bind.c" + WRITE_ONCE_64(r0, (uint64_t)r1, OFFSET(0)); label_1: - // EBPF_OP_EXIT pc=15 dst=r0 src=r0 offset=0 imm=0 -#line 62 "sample/cgroup_sock_addr_bind.c" - return r0; + // EBPF_OP_LDXW pc=24 dst=r0 src=r6 offset=0 imm=0 #line 60 "sample/cgroup_sock_addr_bind.c" + READ_ONCE_32(r0, r6, OFFSET(0)); +label_2: + // EBPF_OP_EXIT pc=25 dst=r0 src=r0 offset=0 imm=0 +#line 76 "sample/cgroup_sock_addr_bind.c" + return r0; +#line 74 "sample/cgroup_sock_addr_bind.c" } #pragma code_seg(pop) #line __LINE__ __FILE__ @@ -417,10 +537,10 @@ static program_entry_t _programs[] = { "cgroup/bind4", "authorize_bind4", authorize_bind4_maps, - 1, + 2, authorize_bind4_helpers, 1, - 16, + 26, &authorize_bind4_program_type_guid, &authorize_bind4_attach_type_guid, }, @@ -432,10 +552,10 @@ static program_entry_t _programs[] = { "cgroup/bind6", "authorize_bind6", authorize_bind6_maps, - 1, + 2, authorize_bind6_helpers, 1, - 16, + 26, &authorize_bind6_program_type_guid, &authorize_bind6_attach_type_guid, }, diff --git a/tests/libs/util/native_helper.cpp b/tests/libs/util/native_helper.cpp index 9512123c06..ec6f75644a 100644 --- a/tests/libs/util/native_helper.cpp +++ b/tests/libs/util/native_helper.cpp @@ -68,3 +68,25 @@ _native_module_helper::~_native_module_helper() DeleteFileA(_file_name.c_str()); } } + +_native_module_helper::_native_module_helper(_native_module_helper&& other) noexcept + : _file_name(std::move(other._file_name)), _delete_file_on_destruction(other._delete_file_on_destruction), + _is_main_thread(other._is_main_thread) +{ + other._delete_file_on_destruction = false; +} + +_native_module_helper& +_native_module_helper::operator=(_native_module_helper&& other) noexcept +{ + if (this != &other) { + if (_delete_file_on_destruction && !_file_name.empty()) { + DeleteFileA(_file_name.c_str()); + } + _file_name = std::move(other._file_name); + _delete_file_on_destruction = other._delete_file_on_destruction; + _is_main_thread = other._is_main_thread; + other._delete_file_on_destruction = false; + } + return *this; +} diff --git a/tests/libs/util/native_helper.hpp b/tests/libs/util/native_helper.hpp index 0efc64e815..21d9b11fab 100644 --- a/tests/libs/util/native_helper.hpp +++ b/tests/libs/util/native_helper.hpp @@ -21,6 +21,21 @@ typedef class _native_module_helper { public: + _native_module_helper() = default; + + // Non-copyable: the destructor deletes the underlying .sys file, so two + // instances with the same _file_name and _delete_file_on_destruction = true + // would race / double-delete. + _native_module_helper(const _native_module_helper&) = delete; + _native_module_helper& + operator=(const _native_module_helper&) = delete; + + // Movable: transfer the file ownership flag and clear it on the source so + // only the destination's destructor deletes the file. + _native_module_helper(_native_module_helper&& other) noexcept; + _native_module_helper& + operator=(_native_module_helper&& other) noexcept; + void initialize(_In_z_ const char* file_name_prefix) { diff --git a/tests/sample/cgroup_sock_addr_bind.c b/tests/sample/cgroup_sock_addr_bind.c index cd539762c6..20d36c45fc 100644 --- a/tests/sample/cgroup_sock_addr_bind.c +++ b/tests/sample/cgroup_sock_addr_bind.c @@ -34,6 +34,15 @@ struct __uint(max_entries, 256); } bind_verdict_map SEC(".maps"); +// Per-module invocation count used by multi-attach ordering tests. +struct +{ + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, uint32_t); + __type(value, uint64_t); + __uint(max_entries, 1); +} bind_invocation_count_map SEC(".maps"); + __inline int authorize_bind(bpf_sock_addr_t* ctx) { @@ -43,6 +52,11 @@ authorize_bind(bpf_sock_addr_t* ctx) uint32_t* verdict = bpf_map_lookup_elem(&bind_verdict_map, &key); if (verdict != NULL) { + uint32_t invocation_key = 0; + uint64_t* invocation_count = bpf_map_lookup_elem(&bind_invocation_count_map, &invocation_key); + if (invocation_count != NULL) { + (*invocation_count)++; + } return *verdict; } return BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT; diff --git a/tests/socket/socket_tests.cpp b/tests/socket/socket_tests.cpp index 4af20d56fe..5324ce895d 100644 --- a/tests/socket/socket_tests.cpp +++ b/tests/socket/socket_tests.cpp @@ -91,32 +91,6 @@ enum class connection_test_result block, }; -/** - * @brief Test parameters for individual connection test. - */ -struct connection_test_params -{ - std::string_view description; - - // Expected bind error for server socket (0 = expect success). - std::optional expected_server_bind_error{}; - - // Expected listen error for server socket (0 = expect success). - std::optional expected_listen_error{}; - - // Expected outcome. - connection_test_result expected_result{connection_test_result::block}; - - std::optional egress_verdict{}; ///< Egress verdict for connect hook. - std::optional ingress_verdict{}; ///< Ingress verdict for recv_accept hook. - std::optional listen_verdict{}; ///< Listen verdict for listen enforcement (sock_addr). - std::optional bind_policy{}; ///< Bind policy to apply for this test. - std::optional - bind_verdict{}; ///< Simplified bind policy (uses current process, test port, test protocol). - std::optional sock_addr_bind_verdict{}; ///< Verdict for the sock_addr-aligned bind hook - ///< (uses test port, test protocol). -}; - /** * @brief Program attach method for the connection test framework. * @@ -130,6 +104,18 @@ enum class attach_method_t bpf_prog_attach, ///< libbpf-compat API with 4-byte attach parameter containing compartment_id=0. }; +/** + * @brief Reference to a specific program within a loaded module set. + * + * Used by multi-program test infrastructure to target policy updates, + * attachment steps, and counter expectations at specific programs. + */ +struct program_ref +{ + size_t module_index{0}; ///< Index in connection_test_case::modules / loaded_modules. + size_t program_index{0}; ///< Index in module_spec::programs. +}; + /** * @brief Program specification for attaching. */ @@ -149,6 +135,82 @@ struct module_spec std::vector programs{}; ///< Programs to load from the object file. }; +/** + * @brief Per-module policy specification for multi-program tests. + * + * Each entry targets a specific module (by index) and carries the same verdict + * fields as the legacy single-program shorthand. The framework applies these + * to the target module's own maps, enabling per-program verdict control. + */ +struct program_policy_spec +{ + program_ref target{}; + + std::optional egress_verdict{}; + std::optional ingress_verdict{}; + std::optional listen_verdict{}; + std::optional bind_policy{}; + std::optional bind_verdict{}; + std::optional sock_addr_bind_verdict{}; +}; + +/** + * @brief Attachment action for dynamic attach/detach steps. + */ +enum class attachment_action +{ + do_attach, + do_detach, +}; + +/** + * @brief A single attachment state change applied before a test step. + * + * Used to express detach-first/middle/last and reattach scenarios. + */ +struct attachment_step +{ + attachment_action action{}; + program_ref target{}; +}; + +/** + * @brief Test parameters for individual connection test. + */ +struct connection_test_params +{ + std::string_view description; + + // Expected bind error for server socket (0 = expect success). + std::optional expected_server_bind_error{}; + + // Expected listen error for server socket (0 = expect success). + std::optional expected_listen_error{}; + + // Expected outcome. + connection_test_result expected_result{connection_test_result::block}; + + // Legacy single-program shorthand (unchanged for existing callers). + std::optional egress_verdict{}; ///< Egress verdict for connect hook. + std::optional ingress_verdict{}; ///< Ingress verdict for recv_accept hook. + std::optional listen_verdict{}; ///< Listen verdict for listen enforcement (sock_addr). + std::optional bind_policy{}; ///< Bind policy to apply for this test. + std::optional + bind_verdict{}; ///< Simplified bind policy (uses current process, test port, test protocol). + std::optional sock_addr_bind_verdict{}; ///< Verdict for the sock_addr-aligned bind hook + ///< (uses test port, test protocol). + + // --- Multi-program controls --- + + /// Attachment state changes to apply before this test step executes. + /// Processed in order: detach/attach programs dynamically. + std::vector before{}; + + /// Per-module policy updates. Each entry targets a specific module's maps. + /// When non-empty, legacy shorthand fields above are ignored. + std::vector program_policies{}; +}; + /** * @brief Connection test case specification. * @@ -322,8 +384,10 @@ execute_connection_attempt( * This function orchestrates a complete connection test scenario including: * - Loading eBPF modules and programs from object files * - Creating WFP filters if specified - * - Retrieving policy maps (sock_addr and bind) + * - Retrieving policy maps (sock_addr and bind) -- both per-module and legacy global * - Attaching eBPF programs to their respective attach points + * - Processing dynamic attach/detach steps per test + * - Applying per-module policy updates for multi-program scenarios * - Creating and managing client/server socket pairs * - Executing individual test steps with configured policies * - Validating connection behavior against expected results @@ -337,18 +401,31 @@ execute_connection_attempt( static void execute_connection_test(_In_ const connection_test_case& test_case) { + // Per-module map set for multi-program tests. + struct module_maps + { + bpf_map* ingress_connection_policy_map{}; + bpf_map* egress_connection_policy_map{}; + bpf_map* bind_policy_map{}; + bpf_map* connection_map{}; + bpf_map* listen_connection_policy_map{}; + bpf_map* bind_verdict_map{}; + }; + // Load modules (object files + programs). struct loaded_program { bpf_program* program; program_spec spec; bpf_link* link; + bool attached; }; struct loaded_module { native_module_helper_t helper; bpf_object_ptr object; std::vector programs; + module_maps maps; }; std::vector loaded_modules; @@ -364,12 +441,34 @@ execute_connection_test(_In_ const connection_test_case& test_case) for (const auto& prog_spec : module.programs) { auto* prog = bpf_object__find_program_by_name(obj, prog_spec.program_name.data()); SAFE_REQUIRE(prog != nullptr); - mod.programs.push_back({prog, prog_spec, nullptr}); + mod.programs.push_back({prog, prog_spec, nullptr, false}); } + // Per-module map discovery. + mod.maps.ingress_connection_policy_map = bpf_object__find_map_by_name(obj, "ingress_connection_policy_map"); + mod.maps.egress_connection_policy_map = bpf_object__find_map_by_name(obj, "egress_connection_policy_map"); + mod.maps.bind_policy_map = bpf_object__find_map_by_name(obj, "bind_policy_map"); + mod.maps.connection_map = bpf_object__find_map_by_name(obj, "connection_map"); + mod.maps.listen_connection_policy_map = bpf_object__find_map_by_name(obj, "listen_connection_policy_map"); + mod.maps.bind_verdict_map = bpf_object__find_map_by_name(obj, "bind_verdict_map"); + loaded_modules.push_back(std::move(mod)); } + // Bounds-checked program reference resolver. + auto resolve_program_ref = [&](const program_ref& ref) -> loaded_program& { + SAFE_REQUIRE(ref.module_index < loaded_modules.size()); + auto& mod = loaded_modules[ref.module_index]; + SAFE_REQUIRE(ref.program_index < mod.programs.size()); + return mod.programs[ref.program_index]; + }; + + // Resolve module maps for a program reference. + auto resolve_module_maps = [&](const program_ref& ref) -> module_maps& { + SAFE_REQUIRE(ref.module_index < loaded_modules.size()); + return loaded_modules[ref.module_index].maps; + }; + // Create WFP filters if specified. std::unique_ptr filter; if (!test_case.wfp_filters.empty()) { @@ -380,7 +479,7 @@ execute_connection_test(_In_ const connection_test_case& test_case) } } - // Get policy maps (sock_addr, bind, or sockops). + // Legacy global maps: first-match across all modules (for existing single-program callers). bpf_map* ingress_map = nullptr; bpf_map* egress_map = nullptr; bpf_map* bind_policy_map = nullptr; @@ -390,22 +489,22 @@ execute_connection_test(_In_ const connection_test_case& test_case) for (const auto& mod : loaded_modules) { if (!ingress_map) { - ingress_map = bpf_object__find_map_by_name(mod.object.get(), "ingress_connection_policy_map"); + ingress_map = mod.maps.ingress_connection_policy_map; } if (!egress_map) { - egress_map = bpf_object__find_map_by_name(mod.object.get(), "egress_connection_policy_map"); + egress_map = mod.maps.egress_connection_policy_map; } if (!bind_policy_map) { - bind_policy_map = bpf_object__find_map_by_name(mod.object.get(), "bind_policy_map"); + bind_policy_map = mod.maps.bind_policy_map; } if (!connection_map) { - connection_map = bpf_object__find_map_by_name(mod.object.get(), "connection_map"); + connection_map = mod.maps.connection_map; } if (!listen_map) { - listen_map = bpf_object__find_map_by_name(mod.object.get(), "listen_connection_policy_map"); + listen_map = mod.maps.listen_connection_policy_map; } if (!bind_verdict_map) { - bind_verdict_map = bpf_object__find_map_by_name(mod.object.get(), "bind_verdict_map"); + bind_verdict_map = mod.maps.bind_verdict_map; } } @@ -439,71 +538,98 @@ execute_connection_test(_In_ const connection_test_case& test_case) bool use_specific_family = is_listen_test || test_case.server_bind_address.has_value(); socket_family_t server_family = use_specific_family ? (test_case.address_family == AF_INET ? IPv4 : IPv6) : Dual; + // Helper: attach a single program using its spec's attach_method. + auto attach_program = [](loaded_program& lp) { + bpf_program* program = lp.program; + if (lp.spec.attach_method == attach_method_t::bpf_prog_attach) { + // libbpf-compat path: second argument is compartment_id (0 = wildcard). + int rc = ::bpf_prog_attach(bpf_program__fd(program), 0, lp.spec.attach_type, 0); + SAFE_REQUIRE(rc == 0); + } else { + // Native API path: NULL attach parameter (wildcard / unspecified compartment). + ebpf_attach_type_t attach_type_guid{}; + SAFE_REQUIRE(ebpf_get_ebpf_attach_type(lp.spec.attach_type, &attach_type_guid) == EBPF_SUCCESS); + // The legacy bind hook (BPF_ATTACH_TYPE_BIND) is deprecated and is not exposed on the + // libbpf-compat bpf_prog_attach/bpf_prog_detach2 path, so capture its link and detach it + // via the native link API at teardown. Other hooks detach uniformly via bpf_prog_detach2. + bpf_link** link = (lp.spec.attach_type == BPF_ATTACH_TYPE_BIND) ? &lp.link : nullptr; + SAFE_REQUIRE(ebpf_program_attach(program, &attach_type_guid, nullptr, 0, link) == EBPF_SUCCESS); + } + lp.attached = true; + }; + + // Helper: detach a single program. + auto detach_program = [](loaded_program& lp) { + if (!lp.attached) { + return; + } + if (lp.spec.attach_type == BPF_ATTACH_TYPE_BIND) { + // Deprecated bind hook: detach via its native link (it is not exposed on + // bpf_prog_detach2). bpf_link__destroy detaches and frees the link. + SAFE_REQUIRE(lp.link != nullptr); + SAFE_REQUIRE(bpf_link__destroy(lp.link) == 0); + lp.link = nullptr; + } else { + // Use bpf_prog_detach2 uniformly for the sock_addr hooks -- it works for both + // attach paths. For ebpf_program_attach with NULL (wildcard), compartment 0 matches. + int rc = ::bpf_prog_detach2(bpf_program__fd(lp.program), 0, lp.spec.attach_type); + SAFE_REQUIRE(rc == 0); + } + lp.attached = false; + }; + // Attach all programs before executing tests. for (auto& mod : loaded_modules) { CAPTURE(mod.helper.get_file_name()); - for (auto& loaded_program : mod.programs) { - bpf_program* program = loaded_program.program; + for (auto& loaded_prog : mod.programs) { CAPTURE( - std::string(loaded_program.spec.program_name), - loaded_program.spec.attach_type, - 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); - } 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); - } + std::string(loaded_prog.spec.program_name), + loaded_prog.spec.attach_type, + bpf_program__fd(loaded_prog.program)); + attach_program(loaded_prog); } } - // Execute tests. - std::unique_ptr client; - std::unique_ptr server; - int test_index = 0; + // Helper: apply per-module policy for a single program_policy_spec entry. + auto apply_program_policy = [&](const program_policy_spec& policy, const connection_tuple_t& conn_tuple) { + auto& maps = resolve_module_maps(policy.target); - for (const auto& test : test_case.tests) { - INFO("test " << test_index << ": " << test.description); - CAPTURE(test.expected_result); - - // Update policy maps based on test policy. - if (test.egress_verdict) { - SAFE_REQUIRE(egress_map != nullptr); - SAFE_REQUIRE(bpf_map_update_elem(bpf_map__fd(egress_map), &tuple, &(*test.egress_verdict), EBPF_ANY) == 0); + if (policy.egress_verdict) { + SAFE_REQUIRE(maps.egress_connection_policy_map != nullptr); + SAFE_REQUIRE( + bpf_map_update_elem( + bpf_map__fd(maps.egress_connection_policy_map), &conn_tuple, &(*policy.egress_verdict), EBPF_ANY) == + 0); } - if (test.ingress_verdict) { - SAFE_REQUIRE(ingress_map != nullptr); + if (policy.ingress_verdict) { + SAFE_REQUIRE(maps.ingress_connection_policy_map != nullptr); SAFE_REQUIRE( - bpf_map_update_elem(bpf_map__fd(ingress_map), &tuple, &(*test.ingress_verdict), EBPF_ANY) == 0); + bpf_map_update_elem( + bpf_map__fd(maps.ingress_connection_policy_map), + &conn_tuple, + &(*policy.ingress_verdict), + EBPF_ANY) == 0); } - if (test.bind_policy) { - SAFE_REQUIRE(bind_policy_map != nullptr); + if (policy.bind_policy) { + SAFE_REQUIRE(maps.bind_policy_map != nullptr); _update_bind_policy_map_entry( - bpf_map__fd(bind_policy_map), - test.bind_policy->process_id, - test.bind_policy->port, - test.bind_policy->protocol, - test.bind_policy->action); + bpf_map__fd(maps.bind_policy_map), + policy.bind_policy->process_id, + policy.bind_policy->port, + policy.bind_policy->protocol, + policy.bind_policy->action); } - if (test.bind_verdict) { - SAFE_REQUIRE(bind_policy_map != nullptr); + if (policy.bind_verdict) { + SAFE_REQUIRE(maps.bind_policy_map != nullptr); _update_bind_policy_map_entry( - bpf_map__fd(bind_policy_map), - 0, // process_id = 0 (wildcard). + bpf_map__fd(maps.bind_policy_map), + 0, static_cast(SOCKET_TEST_PORT), static_cast(test_case.protocol), - *test.bind_verdict); + *policy.bind_verdict); } - if (test.listen_verdict) { - SAFE_REQUIRE(listen_map != nullptr); - // Setup tuple for listen operation — key uses local address/port. - // When server_bind_address is provided, populate local_ip from it; otherwise the - // server binds to INADDR_ANY and WFP reports local_ip as zero. + if (policy.listen_verdict) { + SAFE_REQUIRE(maps.listen_connection_policy_map != nullptr); connection_tuple_t listen_tuple = {0}; if (test_case.server_bind_address) { if (test_case.address_family == AF_INET) { @@ -516,17 +642,106 @@ execute_connection_test(_In_ const connection_test_case& test_case) } } listen_tuple.local_port = htons(SOCKET_TEST_PORT); - listen_tuple.protocol = tuple.protocol; + listen_tuple.protocol = conn_tuple.protocol; SAFE_REQUIRE( - bpf_map_update_elem(bpf_map__fd(listen_map), &listen_tuple, &(*test.listen_verdict), EBPF_ANY) == 0); + bpf_map_update_elem( + bpf_map__fd(maps.listen_connection_policy_map), + &listen_tuple, + &(*policy.listen_verdict), + EBPF_ANY) == 0); } - if (test.sock_addr_bind_verdict) { - SAFE_REQUIRE(bind_verdict_map != nullptr); + if (policy.sock_addr_bind_verdict) { + SAFE_REQUIRE(maps.bind_verdict_map != nullptr); _update_sock_addr_bind_verdict_map_entry( - bpf_map__fd(bind_verdict_map), + bpf_map__fd(maps.bind_verdict_map), htons(static_cast(SOCKET_TEST_PORT)), static_cast(test_case.protocol), - *test.sock_addr_bind_verdict); + *policy.sock_addr_bind_verdict); + } + }; + + // Execute tests. + std::unique_ptr client; + std::unique_ptr server; + int test_index = 0; + + for (const auto& test : test_case.tests) { + INFO("test " << test_index << ": " << test.description); + CAPTURE(test.expected_result); + + // Process dynamic attachment steps (detach/attach) before the test step. + for (const auto& step : test.before) { + auto& lp = resolve_program_ref(step.target); + if (step.action == attachment_action::do_detach) { + detach_program(lp); + } else { + attach_program(lp); + } + } + + // Apply per-module policies if specified; otherwise fall through to legacy shorthand. + if (!test.program_policies.empty()) { + for (const auto& policy : test.program_policies) { + apply_program_policy(policy, tuple); + } + } else { + // Legacy single-program map updates (backwards-compatible path). + if (test.egress_verdict) { + SAFE_REQUIRE(egress_map != nullptr); + SAFE_REQUIRE( + bpf_map_update_elem(bpf_map__fd(egress_map), &tuple, &(*test.egress_verdict), EBPF_ANY) == 0); + } + if (test.ingress_verdict) { + SAFE_REQUIRE(ingress_map != nullptr); + SAFE_REQUIRE( + bpf_map_update_elem(bpf_map__fd(ingress_map), &tuple, &(*test.ingress_verdict), EBPF_ANY) == 0); + } + if (test.bind_policy) { + SAFE_REQUIRE(bind_policy_map != nullptr); + _update_bind_policy_map_entry( + bpf_map__fd(bind_policy_map), + test.bind_policy->process_id, + test.bind_policy->port, + test.bind_policy->protocol, + test.bind_policy->action); + } + if (test.bind_verdict) { + SAFE_REQUIRE(bind_policy_map != nullptr); + _update_bind_policy_map_entry( + bpf_map__fd(bind_policy_map), + 0, + static_cast(SOCKET_TEST_PORT), + static_cast(test_case.protocol), + *test.bind_verdict); + } + if (test.listen_verdict) { + SAFE_REQUIRE(listen_map != nullptr); + connection_tuple_t listen_tuple = {0}; + if (test_case.server_bind_address) { + if (test_case.address_family == AF_INET) { + const sockaddr_in* addr4 = + reinterpret_cast(&(*test_case.server_bind_address)); + listen_tuple.local_ip.ipv4 = addr4->sin_addr.s_addr; + } else { + const sockaddr_in6* addr6 = + reinterpret_cast(&(*test_case.server_bind_address)); + memcpy(listen_tuple.local_ip.ipv6, &addr6->sin6_addr, sizeof(listen_tuple.local_ip.ipv6)); + } + } + listen_tuple.local_port = htons(SOCKET_TEST_PORT); + listen_tuple.protocol = tuple.protocol; + SAFE_REQUIRE( + bpf_map_update_elem(bpf_map__fd(listen_map), &listen_tuple, &(*test.listen_verdict), EBPF_ANY) == + 0); + } + if (test.sock_addr_bind_verdict) { + SAFE_REQUIRE(bind_verdict_map != nullptr); + _update_sock_addr_bind_verdict_map_entry( + bpf_map__fd(bind_verdict_map), + htons(static_cast(SOCKET_TEST_PORT)), + static_cast(test_case.protocol), + *test.sock_addr_bind_verdict); + } } // Create sockets on init or after reset. @@ -560,6 +775,7 @@ execute_connection_test(_In_ const connection_test_case& test_case) if (server_bind_error != 0 || server_listen_error != 0) { server.reset(); client.reset(); + ++test_index; continue; } SAFE_REQUIRE(server != nullptr); @@ -590,6 +806,13 @@ execute_connection_test(_In_ const connection_test_case& test_case) ++test_index; } + + // Cleanup: detach all programs that are still attached. + for (auto& mod : loaded_modules) { + for (auto& lp : mod.programs) { + detach_program(lp); + } + } } // Type tuples for TEMPLATE_TEST_CASE: (address_family, protocol). @@ -604,6 +827,50 @@ using udp_v6_params = #define ALL_CONNECTION_TEST_PARAMS tcp_v4_params, tcp_v6_params, udp_v4_params, udp_v6_params +// --------------------------------------------------------------------------- +// Hook descriptor helpers for multi-program bind test scenarios. +// +// These reduce boilerplate in multi-program test cases without hiding the +// important parts (expected verdicts, expected results). Each test case still +// explicitly declares its connection_test_case -- these helpers just produce +// the repetitive hook-specific pieces. +// --------------------------------------------------------------------------- + +/** + * @brief Create a module_spec for the sock_addr-aligned bind hook. + * + * Each call produces an independent module that, when loaded by the framework, + * creates a uniquely-named .sys copy with its own bind_verdict_map. + */ +static module_spec +sock_addr_bind_module(ADDRESS_FAMILY family) +{ + return { + .object_file = "cgroup_sock_addr_bind", + .programs{ + {.program_name = (family == AF_INET) ? "authorize_bind4" : "authorize_bind6", + .attach_type = (family == AF_INET) ? BPF_CGROUP_INET4_BIND : BPF_CGROUP_INET6_BIND}}, + }; +} + +/** + * @brief Create a program_policy_spec that sets a bind verdict for a specific module. + */ +static program_policy_spec +sock_addr_bind_verdict(size_t module_index, ebpf_sock_addr_verdict_t verdict) +{ + return {.target = {.module_index = module_index}, .sock_addr_bind_verdict = verdict}; +} + +/** + * @brief Return the WFP layer GUID for bind (ALE_RESOURCE_ASSIGNMENT) given address family. + */ +static GUID +sock_addr_bind_wfp_layer(ADDRESS_FAMILY family) +{ + return (family == AF_INET) ? FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4 : FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6; +} + TEST_CASE("connection_test_attach_all", "[attach]") { execute_connection_test({ @@ -1241,6 +1508,700 @@ TEST_CASE("bind_helper_functions_validation_tcp_v6", "[bind_tests][helper_valida bind_helper_functions_validation_test(AF_INET6); } +// =========================================================================== +// Multi-program bind tests (sock_addr-aligned cgroup/bind4 / cgroup/bind6). +// +// These tests exercise verdict accumulation across multiple independently-loaded +// instances of cgroup_sock_addr_bind, each with its own bind_verdict_map. +// =========================================================================== + +// Two programs both return PROCEED_SOFT -> accumulated verdict is PROCEED_SOFT -> bind allowed. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_all_soft", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_all_soft", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .tests{{ + .description = "Two soft permits allow bind", + .expected_result = connection_test_result::allow, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }}, + }); +} + +// First program PROCEED_SOFT, second program REJECT -> accumulated verdict is REJECT -> bind denied. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_second_rejects", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_second_rejects", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .tests{{ + .description = "Second program rejects after first soft permit", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_REJECT), + }, + }}, + }); +} + +// First program REJECT -> short-circuit, second program never contributes -> bind denied. +// Verifies that a leading REJECT terminates the accumulation loop. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_first_rejects", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_first_rejects", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .tests{{ + .description = "First program rejects, short-circuits accumulation", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_REJECT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }}, + }); +} + +// Mix of PROCEED_SOFT + PROCEED_HARD -> accumulated verdict is PROCEED_HARD (higher priority) -> bind allowed. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_soft_hard_mix", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_soft_hard_mix", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .tests{{ + .description = "Soft + hard mix yields hard permit", + .expected_result = connection_test_result::allow, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_HARD), + }, + }}, + }); +} + +// Multi-program with WFP block: both programs return PROCEED_SOFT -> WFP block overrides -> bind denied. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_soft_blocked_by_wfp", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_soft_blocked_by_wfp", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .wfp_filters{{ + .layer = sock_addr_bind_wfp_layer(family), + .local_port = static_cast(SOCKET_TEST_PORT), + }}, + .tests{{ + .description = "Two soft permits cannot override WFP block", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }}, + }); +} + +// Multi-program with WFP block: one PROCEED_HARD -> hard permit overrides WFP -> bind allowed. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_hard_overrides_wfp", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_hard_overrides_wfp", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .wfp_filters{{ + .layer = sock_addr_bind_wfp_layer(family), + .local_port = static_cast(SOCKET_TEST_PORT), + }}, + .tests{{ + .description = "Hard permit from one program overrides WFP block", + .expected_result = connection_test_result::allow, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_HARD), + }, + }}, + }); +} + +// Detach middle program: start with 3 programs (PROCEED_SOFT, REJECT, PROCEED_SOFT). The middle program's REJECT +// should deny bind. After detaching the middle program, only the two PROCEED_SOFT programs remain and bind +// should succeed. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_detach_middle", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_detach_middle", + .address_family = family, + .protocol = protocol, + .modules{ + sock_addr_bind_module(family), + sock_addr_bind_module(family), + sock_addr_bind_module(family), + }, + .tests{ + { + .description = "With middle program rejecting, bind is denied", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_REJECT), + sock_addr_bind_verdict(2, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }, + { + .description = "After detaching middle program, bind succeeds", + .expected_result = connection_test_result::allow, + .before{{.action = attachment_action::do_detach, .target = {.module_index = 1}}}, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(2, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }, + }, + }); +} + +// Detach and reattach: start with 2 programs (both PROCEED_SOFT -> allow). Detach program 1, change its +// verdict to REJECT, reattach. Bind should now be denied. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_detach_reattach", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_detach_reattach", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .tests{ + { + .description = "Both programs soft permit, bind allowed", + .expected_result = connection_test_result::allow, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }, + { + .description = "After detach+reattach with reject, bind denied", + .expected_server_bind_error = WSAEACCES, + .before{ + {.action = attachment_action::do_detach, .target = {.module_index = 1}}, + {.action = attachment_action::do_attach, .target = {.module_index = 1}}, + }, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_REJECT), + }, + }, + }, + }); +} + +// Three programs all PROCEED_SOFT -> bind allowed. Tests accumulation across more than 2 programs. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_three_soft", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_three_soft", + .address_family = family, + .protocol = protocol, + .modules{ + sock_addr_bind_module(family), + sock_addr_bind_module(family), + sock_addr_bind_module(family), + }, + .tests{{ + .description = "Three soft permits allow bind", + .expected_result = connection_test_result::allow, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(2, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }}, + }); +} + +// PROCEED_HARD first, PROCEED_SOFT second, with WFP block -> PROCEED_HARD wins, bind allowed. +// Validates that HARD is accumulated regardless of program ordering (not just last-wins). +TEMPLATE_TEST_CASE( + "sock_addr_bind_multi_hard_first_overrides_wfp", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_hard_first_overrides_wfp", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .wfp_filters{{ + .layer = sock_addr_bind_wfp_layer(family), + .local_port = static_cast(SOCKET_TEST_PORT), + }}, + .tests{{ + .description = "Hard permit first, soft second, WFP block -> bind allowed", + .expected_result = connection_test_result::allow, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_HARD), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }}, + }); +} + +// REJECT + PROCEED_HARD -> REJECT wins (higher priority). Validates that PROCEED_HARD cannot override REJECT. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_reject_beats_hard", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_reject_beats_hard", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .tests{{ + .description = "Reject from first program overrides hard permit from second", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_REJECT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_HARD), + }, + }}, + }); +} + +// PROCEED_HARD + REJECT (reversed) -> REJECT wins. Tests that REJECT short-circuits even after PROCEED_HARD. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_hard_then_reject", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_hard_then_reject", + .address_family = family, + .protocol = protocol, + .modules{sock_addr_bind_module(family), sock_addr_bind_module(family)}, + .tests{{ + .description = "Hard permit then reject -> reject wins", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_HARD), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_REJECT), + }, + }}, + }); +} + +// Third program is decisive: two PROCEED_SOFT then one REJECT -> bind denied. +// Validates that the accumulator processes all N programs, not just the first two. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_third_rejects", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_third_rejects", + .address_family = family, + .protocol = protocol, + .modules{ + sock_addr_bind_module(family), + sock_addr_bind_module(family), + sock_addr_bind_module(family), + }, + .tests{{ + .description = "Third program rejects after two soft permits", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(2, BPF_SOCK_ADDR_VERDICT_REJECT), + }, + }}, + }); +} + +// Third program provides decisive PROCEED_HARD permit with WFP block -> bind allowed. +// Validates accumulator reaches the third program and PROCEED_HARD overrides WFP. +TEMPLATE_TEST_CASE( + "sock_addr_bind_multi_third_hard_overrides_wfp", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_third_hard_overrides_wfp", + .address_family = family, + .protocol = protocol, + .modules{ + sock_addr_bind_module(family), + sock_addr_bind_module(family), + sock_addr_bind_module(family), + }, + .wfp_filters{{ + .layer = sock_addr_bind_wfp_layer(family), + .local_port = static_cast(SOCKET_TEST_PORT), + }}, + .tests{{ + .description = "Third program hard permit overrides WFP block", + .expected_result = connection_test_result::allow, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(2, BPF_SOCK_ADDR_VERDICT_PROCEED_HARD), + }, + }}, + }); +} + +// Detach first program: 3 programs with first=REJECT -> denied. Detach first -> two PROCEED_SOFTs remain -> allowed. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_detach_first", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_detach_first", + .address_family = family, + .protocol = protocol, + .modules{ + sock_addr_bind_module(family), + sock_addr_bind_module(family), + sock_addr_bind_module(family), + }, + .tests{ + { + .description = "With first program rejecting, bind is denied", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_REJECT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(2, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }, + { + .description = "After detaching first program, bind succeeds", + .expected_result = connection_test_result::allow, + .before{{.action = attachment_action::do_detach, .target = {.module_index = 0}}}, + .program_policies{ + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(2, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }, + }, + }); +} + +// Detach last program: 3 programs with last=REJECT -> denied. Detach last -> two PROCEED_SOFTs remain -> allowed. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_detach_last", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + execute_connection_test({ + .name = "sock_addr_bind_multi_detach_last", + .address_family = family, + .protocol = protocol, + .modules{ + sock_addr_bind_module(family), + sock_addr_bind_module(family), + sock_addr_bind_module(family), + }, + .tests{ + { + .description = "With last program rejecting, bind is denied", + .expected_server_bind_error = WSAEACCES, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(2, BPF_SOCK_ADDR_VERDICT_REJECT), + }, + }, + { + .description = "After detaching last program, bind succeeds", + .expected_result = connection_test_result::allow, + .before{{.action = attachment_action::do_detach, .target = {.module_index = 2}}}, + .program_policies{ + sock_addr_bind_verdict(0, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + sock_addr_bind_verdict(1, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT), + }, + }, + }, + }); +} + +// =========================================================================== +// Wildcard and compartment-specific bind attach. +// +// Programs attached to either scope can affect a matching bind. +// =========================================================================== + +struct bind_filter_scope_scenario +{ + const char* name; + ebpf_sock_addr_verdict_t specific_verdicts[2]; + ebpf_sock_addr_verdict_t wildcard_verdicts[2]; + int expected_error; +}; + +// Attempt a bind on the test port and return 0 on success or the Winsock error. +// socket_family selects the socket type the way socket_helper does: IPv4 creates an AF_INET +// socket, IPv6 an AF_INET6 socket, and Dual an AF_INET6 socket with IPV6_V6ONLY cleared. The +// bound address selects the V4 vs V6 WFP layer. +static int +_bind_multi_attach_try_bind(socket_family_t socket_family, ADDRESS_FAMILY address_family, IPPROTO protocol) +{ + int sock_type = (protocol == IPPROTO_TCP) ? SOCK_STREAM : SOCK_DGRAM; + ADDRESS_FAMILY socket_address_family = (socket_family == socket_family_t::IPv4) ? AF_INET : AF_INET6; + SOCKET sock = WSASocketW(socket_address_family, sock_type, protocol, nullptr, 0, 0); + SAFE_REQUIRE(sock != INVALID_SOCKET); + + if (socket_family == socket_family_t::Dual) { + DWORD v6only = 0; + SAFE_REQUIRE( + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&v6only), sizeof(v6only)) == 0); + } + + int rc; + if (socket_family == socket_family_t::IPv4) { + sockaddr_in bind_addr = {}; + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr = in4addr_loopback; + bind_addr.sin_port = htons(SOCKET_TEST_PORT); + rc = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); + } else { + sockaddr_in6 bind_addr = {}; + if (address_family == AF_INET) { + // Dual-stack socket bound to a v4-mapped address, which selects the V4 bind layer. + IN6ADDR_SETV4MAPPED(&bind_addr, &in4addr_loopback, scopeid_unspecified, htons(SOCKET_TEST_PORT)); + } else { + IN6ADDR_SETLOOPBACK(&bind_addr); + bind_addr.sin6_port = htons(SOCKET_TEST_PORT); + } + rc = bind(sock, reinterpret_cast(&bind_addr), sizeof(bind_addr)); + } + + int error = (rc == 0) ? 0 : WSAGetLastError(); + closesocket(sock); + return error; +} + +// Detaches on scope exit so an assertion failure cannot leak an attachment. Catch2's REQUIRE +// throws, which would otherwise skip an explicit detach at the end of the test body. +// Non-copyable: assigning a braced temporary would detach as soon as that temporary died. +struct _bind_attach_guard +{ + bpf_program* program{nullptr}; + uint32_t compartment_id{0}; + bpf_attach_type attach_type{}; + bool attached{false}; + + _bind_attach_guard() = default; + _bind_attach_guard(const _bind_attach_guard&) = delete; + _bind_attach_guard& + operator=(const _bind_attach_guard&) = delete; + + void + detach() + { + SAFE_REQUIRE(attached); + SAFE_REQUIRE(bpf_prog_detach2(bpf_program__fd(program), compartment_id, attach_type) == 0); + attached = false; + } + + ~_bind_attach_guard() + { + if (attached) { + // Best effort: destructors must not throw, so the result is deliberately ignored. + bpf_prog_detach2(bpf_program__fd(program), compartment_id, attach_type); + } + } +}; + +template +static void +test_bind_multi_attach_wildcard_and_specific( + ADDRESS_FAMILY address_family, + IPPROTO protocol, + bool attach_wildcard_first, + const bind_filter_scope_scenario (&scenarios)[scenario_count]) +{ + // Loads program_count_per_group * 2 programs: program_count_per_group attached to a + // specific compartment id, and program_count_per_group attached with the wildcard + // compartment id. It then verifies that a reject from either group determines the + // bind result, using both a dual-stack socket and a socket of the address family + // under test. + // + // attach_wildcard_first verifies that the result does not depend on attach order. + constexpr uint32_t program_count_per_group = 2; + constexpr uint32_t program_count = program_count_per_group * 2; + + // The compartment the test process runs in, so that the compartment-specific filter + // matches the same bind as the wildcard filter. + constexpr uint32_t specific_compartment_id = DEFAULT_COMPARTMENT_ID; + + native_module_helper_t helpers[program_count]; + struct bpf_object* objects[program_count] = {nullptr}; + bpf_object_ptr object_ptrs[program_count]; + _bind_attach_guard attach_guards[program_count]; + fd_t verdict_map_fds[program_count] = {ebpf_fd_invalid}; + bool is_specific[program_count] = {false}; + + const char* program_name = (address_family == AF_INET) ? "authorize_bind4" : "authorize_bind6"; + bpf_attach_type attach_type = (address_family == AF_INET) ? BPF_CGROUP_INET4_BIND : BPF_CGROUP_INET6_BIND; + + // Load the programs. Each helper mangles the file name so the same object loads as an + // independent module with its own bind_verdict_map. + for (uint32_t i = 0; i < program_count; i++) { + helpers[i].initialize("cgroup_sock_addr_bind", _is_main_thread); + objects[i] = bpf_object__open(helpers[i].get_file_name().c_str()); + SAFE_REQUIRE(objects[i] != nullptr); + object_ptrs[i] = bpf_object_ptr(objects[i]); + SAFE_REQUIRE(bpf_object__load(objects[i]) == 0); + + bpf_map* verdict_map = bpf_object__find_map_by_name(objects[i], "bind_verdict_map"); + SAFE_REQUIRE(verdict_map != nullptr); + verdict_map_fds[i] = bpf_map__fd(verdict_map); + SAFE_REQUIRE(verdict_map_fds[i] != ebpf_fd_invalid); + } + + // Attach one group to a specific compartment and the other with the wildcard compartment + // id, so both filter contexts match a bind in that compartment. + for (uint32_t i = 0; i < program_count; i++) { + bpf_program* program = bpf_object__find_program_by_name(objects[i], program_name); + SAFE_REQUIRE(program != nullptr); + + const bool first_group = (i < program_count_per_group); + is_specific[i] = attach_wildcard_first ? !first_group : first_group; + const uint32_t compartment_id = is_specific[i] ? specific_compartment_id : UNSPECIFIED_COMPARTMENT_ID; + + CAPTURE(i, program_name, is_specific[i]); + int result = + bpf_prog_attach(bpf_program__fd(const_cast(program)), compartment_id, attach_type, 0); + SAFE_REQUIRE(result == 0); + + attach_guards[i].program = program; + attach_guards[i].compartment_id = compartment_id; + attach_guards[i].attach_type = attach_type; + attach_guards[i].attached = true; + } + + const socket_family_t socket_families[] = { + socket_family_t::Dual, (address_family == AF_INET) ? socket_family_t::IPv4 : socket_family_t::IPv6}; + + for (const auto& scenario : scenarios) { + INFO("Scenario: " << scenario.name); + ebpf_sock_addr_verdict_t verdicts[program_count] = {}; + uint32_t specific_index = 0; + uint32_t wildcard_index = 0; + for (uint32_t i = 0; i < program_count; i++) { + verdicts[i] = is_specific[i] ? scenario.specific_verdicts[specific_index++] + : scenario.wildcard_verdicts[wildcard_index++]; + _update_sock_addr_bind_verdict_map_entry( + verdict_map_fds[i], + htons(static_cast(SOCKET_TEST_PORT)), + static_cast(protocol), + verdicts[i]); + } + + for (socket_family_t socket_family : socket_families) { + CAPTURE(attach_wildcard_first, scenario.name, static_cast(socket_family)); + SAFE_REQUIRE( + _bind_multi_attach_try_bind(socket_family, address_family, protocol) == scenario.expected_error); + } + } + + // Explicitly verify successful cleanup. The guards remain as failure-path protection. + for (auto& attach_guard : attach_guards) { + attach_guard.detach(); + } +} + +// Verifies that a matching compartment-specific reject overrides wildcard permits in both attach orders. +TEMPLATE_TEST_CASE("sock_addr_bind_multi_specific_reject", "[bind_tests][multi_attach]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + const bind_filter_scope_scenario scenarios[] = { + { + "Specific reject overrides wildcard hard permit", + {BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT, BPF_SOCK_ADDR_VERDICT_REJECT}, + {BPF_SOCK_ADDR_VERDICT_PROCEED_HARD, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT}, + WSAEACCES, + }, + { + "Specific reject overrides wildcard soft permits", + {BPF_SOCK_ADDR_VERDICT_REJECT, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT}, + {BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT}, + WSAEACCES, + }, + }; + + SECTION("specific attached first") + { + test_bind_multi_attach_wildcard_and_specific(family, protocol, false, scenarios); + } + SECTION("wildcard attached first") + { + test_bind_multi_attach_wildcard_and_specific(family, protocol, true, scenarios); + } +} + +// Verifies that a matching wildcard reject affects the bind result in both attach orders. +// This is marked mayfail until a wildcard reject is enforced when compartment-specific programs are also attached. +TEMPLATE_TEST_CASE( + "sock_addr_bind_multi_wildcard_reject", "[bind_tests][multi_attach][!mayfail]", ALL_CONNECTION_TEST_PARAMS) +{ + constexpr ADDRESS_FAMILY family = std::tuple_element_t<0, TestType>::value; + constexpr IPPROTO protocol = std::tuple_element_t<1, TestType>::value; + const bind_filter_scope_scenario scenarios[] = { + { + "Wildcard reject overrides specific soft permits", + {BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT}, + {BPF_SOCK_ADDR_VERDICT_REJECT, BPF_SOCK_ADDR_VERDICT_REJECT}, + WSAEACCES, + }, + { + "Wildcard reject overrides specific hard permit", + {BPF_SOCK_ADDR_VERDICT_PROCEED_HARD, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT}, + {BPF_SOCK_ADDR_VERDICT_REJECT, BPF_SOCK_ADDR_VERDICT_PROCEED_SOFT}, + WSAEACCES, + }, + }; + + SECTION("specific attached first") + { + test_bind_multi_attach_wildcard_and_specific(family, protocol, false, scenarios); + } + SECTION("wildcard attached first") + { + test_bind_multi_attach_wildcard_and_specific(family, protocol, true, scenarios); + } +} + void helper_functions_validation_test( ADDRESS_FAMILY address_family,