-
Notifications
You must be signed in to change notification settings - Fork 311
Add bind multi-attach test suite and framework extension (#5338) #5381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Anurag Saxena (saxena-anurag)
merged 32 commits into
microsoft:main
from
mikeagun:pr1-bind-multi-attach
Aug 29, 2026
Merged
Changes from 11 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
d2c6079
tests/util: Make native_module_helper_t safely movable
cd1b65c
netebpfext: Restore sock_addr context between multi-attach programs
3793f4a
netebpfext: Check FWPS_RIGHT_ACTION_WRITE in sock_addr classify funct…
627a706
tests/util: Add optional compartment_id to socket_helper constructors
dd25e29
tests/socket: Extend execute_connection_test for multi-program scenarios
023c28e
tests/socket: Add bind multi-program test suite and scenario helpers
fc8c798
docs: Document bind hook multi-attach verdict combination semantics
feb487c
tests/socket: Address adversarial review findings in multi-attach tests
8f1802a
netebpfext: Fix auth-connect rights check skipping connection context…
85a1999
tests/socket: Fix bpf_prog_detach2 failure for BPF_ATTACH_TYPE_BIND p…
3e0f198
tests/socket: Remove compartment_id infrastructure from socket helpers
d09ba99
Merge branch 'main' into pr1-bind-multi-attach
Alan-Jowett c7930a5
tests/socket: detach legacy bind natively; drop libbpf-compat support…
777221f
tests/socket: use ASCII characters in multi-attach test comments
04c0c45
docs: clarify bind hook multi-attach verdict and WFP semantics
0f2aa0d
docs: state bind verdict as permit/deny and link WFP arbitration docs
cc9eaac
netebpfext: invoke sock_addr programs for observation when write-righ…
baa4375
netebpfext: factor out sock_addr verdict-to-WFP mapping
9f6540f
docs: list all 16 bind multi-attach scenarios in the coverage table
eb9897e
netebpfext: skip sock_addr programs when the write right is revoked
4a09f26
tests/socket: verify bind wildcard attachment precedence
377a3d1
Update wildcard and specific bind expectations
Copilot f19d5ea
Validate wildcard attach test updates
Copilot 22f3919
Revert "Validate wildcard attach test updates"
Copilot 25c79fc
Split required wildcard attach scenarios
Copilot 55103dc
Clarify wildcard reject test intent
Copilot 78b77e6
Clarify expected wildcard failure
Copilot d8d81c1
Clarify required reject scenario
Copilot 112a52d
Describe required scope interaction
Copilot c8c9236
Keep bind scenarios test-local
Copilot 019feaf
Update docs and tests to match intended behavior
mikeagun b402380
Merge branch 'main' into pr1-bind-multi-attach
saxena-anurag File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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_authorize_process_verdict(_Inout_ void* program_context, int program_verdict); | ||
|
|
||
| // | ||
| // sock_addr helper functions. | ||
|
|
@@ -1402,7 +1402,7 @@ net_ebpf_ext_sock_addr_register_providers() | |
| .create_filter_context = _net_ebpf_extension_sock_addr_create_filter_context, | ||
| .delete_filter_context = _net_ebpf_extension_sock_addr_delete_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_authorize_process_verdict, | ||
| }; | ||
|
|
||
| const net_ebpf_extension_hook_provider_dispatch_table_t listen_dispatch_table = { | ||
|
|
@@ -2058,23 +2058,42 @@ _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 authorization gates that do | ||
| // not support context rewrite, such as the sock_addr bind hook | ||
| // (ALE_RESOURCE_ASSIGNMENT) and the sock_addr listen hook (ALE_AUTH_LISTEN). | ||
|
mikeagun marked this conversation as resolved.
Outdated
lakshk98 marked this conversation as resolved.
Outdated
|
||
| // 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_authorize_process_verdict(_Inout_ void* program_context, int program_verdict) | ||
|
mikeagun marked this conversation as resolved.
Outdated
|
||
| { | ||
| 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; | ||
|
mikeagun marked this conversation as resolved.
|
||
|
|
||
| return normalized_verdict != BPF_SOCK_ADDR_VERDICT_REJECT; | ||
| } | ||
|
|
||
|
|
@@ -2120,6 +2139,14 @@ 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) { | ||
|
mikeagun marked this conversation as resolved.
|
||
| // A callout with higher weight has revoked the write permission. Bail out | ||
| // without touching classify_output->actionType. | ||
| 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; | ||
|
|
@@ -2235,6 +2262,14 @@ 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 touching classify_output->actionType. | ||
| EBPF_EXT_LOG_MESSAGE( | ||
| EBPF_EXT_TRACELOG_LEVEL_VERBOSE, EBPF_EXT_TRACELOG_KEYWORD_SOCK_ADDR, "No \"write\" right; exiting."); | ||
| goto Exit; | ||
| } | ||
|
Comment on lines
+2294
to
+2301
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make a macro out of this. |
||
|
|
||
| classify_output->actionType = FWP_ACTION_PERMIT; | ||
|
|
||
| filter_context = (net_ebpf_extension_sock_addr_wfp_filter_context_t*)filter->context; | ||
|
|
@@ -2355,6 +2390,14 @@ 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 touching classify_output->actionType. | ||
| 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; | ||
|
|
@@ -2383,10 +2426,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 authorize_process_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 authorize_process_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) { | ||
|
|
@@ -2399,9 +2449,9 @@ 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 authorize_process_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: | ||
|
|
@@ -2465,6 +2515,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); | ||
|
|
@@ -2506,9 +2557,21 @@ 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 touching classify_output->actionType (the Exit-block switch 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. | ||
|
|
@@ -2552,18 +2615,20 @@ 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; | ||
| if (!rights_revoked) { | ||
| 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_ext_log_sock_addr_classify( | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.