Skip to content

fix: Activity action/eligibility callback ABI (dropped AnalysisContext)#25

Merged
tinysec merged 1 commit into
masterfrom
fix/activity-callback-signature
Jul 8, 2026
Merged

fix: Activity action/eligibility callback ABI (dropped AnalysisContext)#25
tinysec merged 1 commit into
masterfrom
fix/activity-callback-signature

Conversation

@tinysec

@tinysec tinysec commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

The C core invokes an Activity's action as void(*)(void*, BNAnalysisContext*) and its eligibility as bool(*)(void*, BNActivity*, BNAnalysisContext*) (workflow.py). The binding marshaled both as Action<IntPtr> (one argument, void return), so:

  • the action never received its AnalysisContext — the entire point of the callback;
  • the eligibility handler received neither handle and returned garbage bool (reading whatever was in the return register) — eligibility was effectively random.

This made the workflow Activity API (the foundation of BN's analysis-extension model) unusable.

Changes

  • New public delegates ActivityAction(AnalysisContext) and ActivityEligibility(Activity, AnalysisContext), plus native-shaped ActivityActionDelegate / ActivityEligibilityDelegate and holder contexts that wrap the raw handles via AnalysisContext/Activity.NewFromHandle (borrowed handles; the wrapper owns the reference it adds and frees it on dispose). Mirrors the established MatchDataDelegate pattern.
  • Activity.Create / CreateWithEligibility now wrap the user delegates and root the wrappers (not the user delegates) on the instance — the wrapper's function pointer is what the core invokes.
  • BNCreateActivity / BNCreateActivityWithEligibility P/Invoke signatures are unchanged (config, ctxt, action[, eligibility]); only the delegate marshaling was wrong.

Verification

  • Binding builds clean (0 warnings, 0 errors).
  • New E2E test builds an Activity with the corrected delegates and registers it in a cloned editable workflow — exercising the real P/Invoke path (GetFunctionPointerForDelegate over the new delegate types, BNCreateActivityWithEligibility, BNWorkflowRegisterActivity) against the core.
  • Full harness regression: 39/39 pass (no regressions).

Coverage note (honest)

The callback is only invoked when the core runs the activity during analysis, which requires opening a view under a custom workflow — a workflow-machine surface (WorkflowMachine, view-creation-under-custom-workflow) not yet bound. The function workflow DAG is immutable and there is no switch-workflow request command, so firing the callback end-to-end is deferred to that binding. The delegate shapes here mirror Python's ctypes.CFUNCTYPE declarations exactly, which are the authoritative ABI spec.

The C core invokes an Activity's action as void(*)(void*, BNAnalysisContext*) and
its eligibility as bool(*)(void*, BNActivity*, BNAnalysisContext*) (workflow.py:
ctypes.CFUNCTYPE(None, c_void_p, POINTER(BNAnalysisContext)) and CFUNCTYPE(c_bool,
c_void_p, POINTER(BNActivity), POINTER(BNAnalysisContext))). The binding marshaled
both as Action<IntPtr> (one argument, void return), so:

  - the action never received its AnalysisContext (the whole point of the callback);
  - the eligibility handler received neither handle AND returned garbage bool
    (reading whatever happened to be in the return register), so eligibility was
    effectively random.

Mirror the established MatchDataDelegate pattern: new public delegates
ActivityAction(AnalysisContext) and ActivityEligibility(Activity, AnalysisContext),
native-shaped ActivityActionDelegate/ActivityEligibilityDelegate, and holder contexts
that wrap the raw handles via AnalysisContext/Activity.NewFromHandle (borrowed; the
wrapper owns the reference it adds). Create/CreateWithEligibility now wrap the user
delegates and root the wrappers (not the user delegates) on the instance, since the
wrapper's function pointer is what the core invokes.

BNCreateActivity/WithEligibility P/Invoke signatures are unchanged (config, ctxt,
action[, eligibility]); only the delegate marshaling was wrong.
@tinysec tinysec merged commit c07ecb4 into master Jul 8, 2026
1 check passed
@tinysec tinysec deleted the fix/activity-callback-signature branch July 8, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant