Skip to content

feat(pool,wpf): bare-owner using dangle + one-hop indirect field-UAF → OWN002#76

Merged
PhysShell merged 2 commits into
mainfrom
claude/owner-dangle-and-indirect-uaf
Jun 22, 2026
Merged

feat(pool,wpf): bare-owner using dangle + one-hop indirect field-UAF → OWN002#76
PhysShell merged 2 commits into
mainfrom
claude/owner-dangle-and-indirect-uaf

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Two sibling "returns/uses a dead resource" capabilities, landing in one slice, each closing a matching benchmark miss.

1. MemoryPool bare-owner using escape → OWN002

The twin of the returned-view dangle (#74). using owner = …; return owner; returns the owner itself, but the using disposes it at scope exit, so the caller gets an already-disposed IMemoryOwner<T>.

  • A using-declared MemoryPool owner returned bare now stays tracked (only this shape is exempted from the return-escape; a non-using returned owner is still a genuine ownership transfer → escaped → silent).
  • The return lowering threads a use of the returned owner after the using-desugar's scope-exit release — reusing the exact return-chain insertion that already catches the returned view. The caller's use lands after the release → OWN002.
  • New corpus case corpus/real-world/memorypool-using-owner-escape/.

2. One-hop indirect field-UAF → OWN002

The field-mediated use-after-dispose pass (#75) caught a direct _field.Member read in a live handler. It now chases a single hop: a live subscription-target handler that calls a private same-class helper (Refresh() / this.Refresh()) which itself unguardedly reads a disposed field — with no if (_disposed) return; guard before the call — is lowered to a synthetic acquire/release/use flow → OWN002.

  • Closes the corpus/wpf/handler-use-after-dispose/ miss: before.cs now reaches the disposed _conn through a Refresh() helper (caught); the guarded after.cs stays silent.
  • One hop only; two-plus-hop chains and field/property indirection stay honest misses.

Precision (stays low-FP)

  • The helper must be a private instance method (a public/virtual member has a broader contract).
  • Both the handler (before the call) and the helper must lack a disposed-guard.
  • The field read is a direct this-owned member access.
  • For the owner escape: only a using-declared MemoryPool owner is exempted from the return-escape — a non-using transfer (var o = Rent(); return o;) stays untracked/silent, and the existing returned-view escape is unchanged. I traced both directions: stricter shapes only narrow firing.

Validation

  • case.own for both → [OWN002] (test_wpf 5/5, test_corpus 18/18).
  • Full Python suite green (lifetimes, loops, spec, ownir 117/117); metamorphic 8/8 (43 .own swept); benchmark selftest OK.
  • Benchmark recall floor 20 → 22 (+1 new real-world case caught, +1 from flipping handler-use-after-dispose miss→caught). The corpus-benchmark job validates the real C# under the SDK in CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Enhanced ownership and lifetime analysis to catch bare-owner escapes from using-declared MemoryPool owners.
    • Improved detection of indirect use-after-dispose patterns in WPF handlers via private helper hops.
  • Tests
    • Added/updated MemoryPool and WPF corpus cases, including new expected OWN002 diagnostics.
  • Documentation / Chores
    • Updated case notes describing the new/clarified bug patterns.
    • Tightened the CI benchmark scoring gate by raising the minimum recall threshold.

…e a one-hop indirect field-UAF

Two sibling "returns/uses a dead resource" capabilities, plus the corpus that
closes the matching benchmark misses.

1. MemoryPool bare-owner `using` escape (`using owner = …; return owner;`) — the
   twin of the returned-view dangle (#74). A using-declared MemoryPool owner that
   is returned BARE stays tracked (only this shape is exempted from the
   return-escape; a non-using returned owner is still a genuine transfer, escaped
   and silent), and the return lowering threads a use of the returned owner after
   the using-desugar's scope-exit release — reusing the exact return-chain insert
   that already catches the returned VIEW. The caller's use lands after the release
   -> OWN002. New corpus case corpus/real-world/memorypool-using-owner-escape.

2. One-hop indirect field-UAF. The field-mediated use-after-dispose pass now chases
   a single hop: a live subscription-target handler that calls a PRIVATE same-class
   helper (`Refresh()` / `this.Refresh()`) which itself unguardedly reads a disposed
   field — with no `if (_disposed) return;` guard before the call — is lowered to a
   synthetic acquire/release/use flow -> OWN002. One hop only; deeper chains and
   field/property indirection stay honest misses. This closes the
   corpus/wpf/handler-use-after-dispose miss (before.cs now reads `_conn` through a
   helper; guarded after.cs stays silent).

Precision: the helper must be a private instance method; both handler (before the
call) and helper must lack a disposed-guard; the field read is a direct this-owned
member access. Existing cases unchanged: the returned-VIEW escape and a non-using
transfer are both preserved; stricter shapes only narrow firing.

Benchmark recall floor 20 -> 22. Python suite + metamorphic + test_corpus green;
C# validated by CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ae99ae16-20ce-4f7c-95de-13be8975d995

📥 Commits

Reviewing files that changed from the base of the PR and between d940d2a and 734b9dc.

📒 Files selected for processing (1)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

📝 Walkthrough

Walkthrough

Two new OWN002 detection patterns are added to the Roslyn extractor: (1) a using-declared IMemoryOwner<byte> returned from a method is now tracked via a usingMemoryOwners set and a synthetic use is emitted on return; (2) handler trigger detection is extended to follow a single private-helper hop for indirect field use-after-dispose. New and updated corpus cases validate both patterns, and the CI benchmark recall gate is raised from 20 to 22.

Changes

MemoryPool bare-owner using escape

Layer / File(s) Summary
Corpus case: bare-owner using escape
corpus/real-world/memorypool-using-owner-escape/before.cs, corpus/real-world/memorypool-using-owner-escape/after.cs, corpus/real-world/memorypool-using-owner-escape/case.own, corpus/real-world/memorypool-using-owner-escape/expected-diagnostics.txt, corpus/real-world/memorypool-using-owner-escape/notes.md
Adds before.cs with a using-scoped Borrow returning a disposed IMemoryOwner<byte>, after.cs with the ownership-transfer fix, case.own modeling MemoryOwner acquire/release/use-after-release, a single expected OWN002 diagnostic, and documentation of the pattern, miss rationale, and cross-references.
Extractor: usingMemoryOwners tracking and return synthetic use
frontend/roslyn/OwnSharp.Extractor/Program.cs
Adds a usingMemoryOwners set for using-declared MemoryPool owners, updates escape rules so returning a tracked using owner keeps it tracked instead of escaping it, and emits a synthetic use for bare returned owners in ReturnStatementSyntax lowering to surface OWN002.

Indirect one-hop handler use-after-dispose

Layer / File(s) Summary
Corpus case: indirect handler UAF via Refresh() helper
corpus/wpf/handler-use-after-dispose/before.cs, corpus/wpf/handler-use-after-dispose/after.cs, corpus/wpf/handler-use-after-dispose/case.own, corpus/wpf/handler-use-after-dispose/notes.md
Updates before.cs/after.cs to introduce a SqlConnection field accessed via a Refresh() helper, rewrites case.own replacing Subscription/CloseHandler with Connection/OnCustomerChanged, and updates notes.md to document one-hop indirection detection, precision constraints, and scope limits.
Extractor: one-hop helper trigger detection and useLine attribution
frontend/roslyn/OwnSharp.Extractor/Program.cs
Adds helper predicates (first unguarded disposed-field read, same-object self-call, private-instance-helper filter), a precomputed helperReads map, and replaces handler trigger detection to find either a direct unguarded field read or a self-call to a helper in helperReads, attributing the correct useLine when emitting the synthetic flow fact.

CI gate

Layer / File(s) Summary
CI --min-recall threshold bump
.github/workflows/ci.yml
Raises benchmark.py --min-recall from 20 to 22 in the corpus-benchmark job.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PhysShell/Own.NET#16: Introduces the --flow-locals pipeline and flow function structure in Program.cs that this PR extends with bare-owner synthetic use emission and one-hop helper attribution.
  • PhysShell/Own.NET#74: Adds --flow-locals support for using-scoped MemoryPool.Rent patterns; this PR builds directly on those same candidate-selection and flow-lowering mechanics to handle the bare-owner return variant.
  • PhysShell/Own.NET#75: Introduces field-mediated WPF use-after-dispose detection heuristics in Program.cs that this PR extends with one-hop private-helper indirection.

Poem

🐇 Hopping through the using scope,
A borrowed owner tries to elope —
But the extractor spies the sneak,
One helper hop? We'll track that leak!
OWN002 rings clear and true,
Recall gates rise to twenty-two! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main functional change: detection of bare-owner using dangle patterns and one-hop indirect field-UAF patterns, mapping to the OWN002 diagnostic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/owner-dangle-and-indirect-uaf

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d940d2a08b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2065 to +2067
if (node is InvocationExpressionSyntax call
&& SelfCallName(call) is { } callee
&& helperReads.TryGetValue(callee, out var hr))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match helper calls by signature

When a class has overloaded private helpers, this lookup only uses the method name returned by SelfCallName, so a subscribed handler can be attributed the disposed-field read from a different overload. For example, if Refresh() reads _conn but Refresh(CustomerChanged e) is safe, OnCustomerChanged calling Refresh(e) will still hit helperReads["Refresh"] and emit a synthetic OWN002 even though that handler does not reach the disposed field. Include arity/signature or resolve the invoked method symbol before applying the one-hop helper result.

Useful? React with 👍 / 👎.

The one-hop helper lookup keyed `helperReads` by method name, so with overloaded
private helpers a handler calling the SAFE overload (`Refresh(e)`) could be
attributed the UNSAFE overload's disposed-field read (`Refresh()`) and emit a
spurious OWN002 — a false positive. Key the map by the helper's method SYMBOL and
resolve the invoked call's symbol, so an overload is matched exactly. Same-class
methods are in-source, so their symbols resolve even when the field's type does
not; `SelfCallName` stays as the this/bare self-receiver gate (a same-typed
`other.Refresh()` resolves to the same symbol but is not a self call).

Behaviour on the corpus is unchanged: the single-overload `Refresh()` resolves to
the same symbol it is keyed under. C# validated by CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/roslyn/OwnSharp.Extractor/Program.cs`:
- Around line 2032-2039: The helperReads dictionary in the
MethodDeclarationSyntax processing loop uses only the method name
(hm.Identifier.Text) as the key, which causes overloaded private helper methods
to conflate and overwrite each other in the dictionary. To fix this, change the
dictionary key from a simple string name to include the method signature (such
as combining the method name with its parameter count or full parameter types)
so that each overload is tracked separately. Also update the corresponding
lookup logic (around line 2065-2069) where self-call matching occurs to use the
same signature-based key when retrieving from helperReads, ensuring the correct
overload is matched.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: db051329-0fa3-4fc7-9348-61bfa63ddaf6

📥 Commits

Reviewing files that changed from the base of the PR and between 3b6dd4b and d940d2a.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • corpus/real-world/memorypool-using-owner-escape/after.cs
  • corpus/real-world/memorypool-using-owner-escape/before.cs
  • corpus/real-world/memorypool-using-owner-escape/case.own
  • corpus/real-world/memorypool-using-owner-escape/expected-diagnostics.txt
  • corpus/real-world/memorypool-using-owner-escape/notes.md
  • corpus/wpf/handler-use-after-dispose/after.cs
  • corpus/wpf/handler-use-after-dispose/before.cs
  • corpus/wpf/handler-use-after-dispose/case.own
  • corpus/wpf/handler-use-after-dispose/notes.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

Comment thread frontend/roslyn/OwnSharp.Extractor/Program.cs Outdated
@PhysShell PhysShell merged commit eef2e0c into main Jun 22, 2026
22 checks passed
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.

2 participants