-
Notifications
You must be signed in to change notification settings - Fork 0
feat(pool): pooled-buffer view-in-a-field dangle → OWN002 (POOL005) #78
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
Merged
Changes from all commits
Commits
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| // FIXED. The callback guards on the disposed flag before it touches the view, so a late, already- | ||
| // dispatched callback bails before reading a buffer that has been returned to the pool. (Equivalently, | ||
| // drain the dispatcher queue before disposing.) The extractor sees the opening `if (_disposed) return;` | ||
| // guard precede the view read and stays silent. | ||
| using System; | ||
| using System.Buffers; | ||
|
|
||
| public sealed class FrameDecoder : IDisposable | ||
| { | ||
| private readonly IMemoryOwner<byte> _owner; | ||
| private readonly Memory<byte> _view; | ||
| private readonly IDisposable _sub; | ||
| private bool _disposed; | ||
|
|
||
| public FrameDecoder(int n, IEventBus bus) | ||
| { | ||
| _owner = MemoryPool<byte>.Shared.Rent(n); | ||
| _view = _owner.Memory; | ||
| _sub = bus.Subscribe<FrameReady>(OnFrameReady); | ||
| } | ||
|
|
||
| private void OnFrameReady(FrameReady e) | ||
| { | ||
| if (_disposed) return; // do not touch the pooled buffer after it is returned | ||
| Sink.Write(_view.Span); | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| _disposed = true; | ||
| _sub.Dispose(); | ||
| _owner.Dispose(); | ||
| } | ||
| } | ||
|
|
||
| // Minimal in-file stand-ins so the reduction is self-contained. | ||
| public interface IEventBus | ||
| { | ||
| IDisposable Subscribe<T>(Action<T> handler); | ||
| } | ||
|
|
||
| public sealed class FrameReady { } | ||
|
|
||
| internal static class Sink | ||
| { | ||
| public static void Write(ReadOnlySpan<byte> data) { } | ||
| } |
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 |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // BUGGY (representative pooled-buffer pattern; the C# extractor catches this end-to-end via its | ||
| // field-mediated use-after-dispose pass extended to pooled owners and their Memory views). | ||
| // | ||
| // A type rents a pooled buffer (`IMemoryOwner<byte>` from `MemoryPool<byte>`), keeps a `Memory<byte>` | ||
| // VIEW of it IN A FIELD, and subscribes a handler to an event source. On teardown Dispose() returns | ||
| // the buffer to the pool (disposing the owner), but a callback already queued on the dispatcher still | ||
| // runs after Dispose() and reads the field-held view — a `Memory` backed by a buffer already handed | ||
| // back to the pool: a dangling borrow / use-after-free (an `ObjectDisposedException` or stale/torn | ||
| // bytes from the next renter). The view field aliases the owner, so reading it after the owner's | ||
| // Dispose is a use of the owner after release. The fix (after.cs) guards the handler on a disposed | ||
| // flag before it touches the view. | ||
| using System; | ||
| using System.Buffers; | ||
|
|
||
| public sealed class FrameDecoder : IDisposable | ||
| { | ||
| private readonly IMemoryOwner<byte> _owner; | ||
| private readonly Memory<byte> _view; | ||
| private readonly IDisposable _sub; | ||
|
|
||
| public FrameDecoder(int n, IEventBus bus) | ||
| { | ||
| _owner = MemoryPool<byte>.Shared.Rent(n); | ||
| _view = _owner.Memory; // a view of the pooled buffer, kept in a field | ||
| _sub = bus.Subscribe<FrameReady>(OnFrameReady); | ||
| } | ||
|
|
||
| private void OnFrameReady(FrameReady e) | ||
| { | ||
| // a late, already-dispatched callback: runs after Dispose() | ||
| Sink.Write(_view.Span); // <-- reads a view of a buffer already returned to the pool | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| _sub.Dispose(); | ||
| _owner.Dispose(); // pooled buffer returned; _view now dangles | ||
| } | ||
| } | ||
|
|
||
| // Minimal in-file stand-ins so the reduction is self-contained. | ||
| public interface IEventBus | ||
| { | ||
| IDisposable Subscribe<T>(Action<T> handler); | ||
| } | ||
|
|
||
| public sealed class FrameReady { } | ||
|
|
||
| internal static class Sink | ||
| { | ||
| public static void Write(ReadOnlySpan<byte> data) { } | ||
| } |
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 |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // OwnLang model of the pooled-buffer view-in-a-field dangle. `acquire` == MemoryPool.Rent (the | ||
| // IMemoryOwner), `release` == its Dispose() in the type's Dispose() (the pooled buffer goes back to | ||
| // the pool), `use` == a late dispatcher callback (the subscribed handler) reading the owner's Memory | ||
| // VIEW — held in a field — AFTER Dispose. The view field aliases the owner, so reading it after the | ||
| // owner's release is a use of the owner after release, lowered to OWN002. (The C# bridge tags the | ||
| // synthetic flow with the generic `disposable` kind; here it is named `pooled buffer` to describe the | ||
| // modelled resource.) | ||
| module PooledViewAfterDispose | ||
|
|
||
| resource PooledBuffer { | ||
| acquire Rent | ||
| release Dispose | ||
| kind "pooled buffer" | ||
| } | ||
|
|
||
| fn OnFrameReady(bus: int) { | ||
| let owner = acquire PooledBuffer(bus); | ||
| release owner; // the type's Dispose() returns the pooled buffer | ||
| use owner; // a late callback reads the owner's Memory view (a field) after Dispose -> OWN002 | ||
| } |
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 |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| OWN002 |
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 |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # Pooled-buffer view-in-a-field, read after the owner is returned | ||
|
|
||
| **Pattern:** a type rents a pooled buffer (`IMemoryOwner<byte>` from `MemoryPool<byte>`), keeps a | ||
| `Memory<byte>` **view of it in a field**, and subscribes a handler to an event source. On teardown | ||
| `Dispose()` returns the buffer to the pool (disposing the owner), but a callback already queued on the | ||
| dispatcher still runs after `Dispose()` and reads the field-held view — a `Memory` backed by a buffer | ||
| already handed back to the pool: a dangling borrow / use-after-free (an `ObjectDisposedException`, or | ||
| stale/torn bytes once the buffer is re-rented). It is the pooled-buffer, field-stored cousin of the | ||
| local returned-view dangle (`memorypool-view-after-dispose`) and of the disposed-field UAF | ||
| (`field-use-after-dispose`). | ||
|
|
||
| **What's new — the extractor follows the view-field to its owner.** The field-mediated | ||
| use-after-dispose pass (#75/#76) is generalised to **pooled owners** and their **Memory views**: an | ||
| `IMemoryOwner<T>` field released in `Dispose()` is a released owner, and a `Memory`/`ReadOnlyMemory` | ||
| field assigned `_owner.Memory` (or `_buf.AsMemory(...)`) is recorded as a **view of that owner**. A | ||
| read of the view field (or of the owner directly) in a live subscription-target handler — directly or | ||
| one hop through a private helper — with no `if (_disposed) return;` guard before it, is lowered to a | ||
| synthetic `acquire`/`release`/`use` flow on the owner → **OWN002**, via the existing OwnIR bridge (no | ||
| new diagnostic). On the real C# the `corpus-benchmark` job scores `before.cs` as caught and the | ||
| guarded `after.cs` as silent. | ||
|
|
||
| **Precision (why it stays low-FP).** The same reachability that makes the disposed-field UAF sound: a | ||
| live (not unsubscribed) handler can fire *after* `Dispose()`, so reading the owner's buffer (through | ||
| its view field) then is a use-after-release. The guard exclusion is the canonical fix; an unsubscribed | ||
| or guarded handler never fires; the owner must be released in the dispose lifecycle; and the view→owner | ||
| alias is recognised by the resolved `IMemoryOwner<T>.Memory` / `MemoryExtensions.AsMemory` symbols, not | ||
| by name. Exposing such a view via a public getter is **not** flagged — `IMemoryOwner.Memory` is itself | ||
| a legitimate "valid until Dispose" pattern, so the exposure is sound; only a provably-post-release read | ||
| (the handler reachability) is a bug. | ||
|
|
||
| **Honesty / scope.** This slice covers an **`IMemoryOwner<T>`** owner field and its `Memory` view read | ||
| via member access (`_view.Span`, `_view.Length`, …) in a handler/helper. Honest follow-ups: an | ||
| **ArrayPool `byte[]` buffer field** returned in `Dispose()` (it interacts with the existing per-member | ||
| pool-leak pass), the view passed as a **bare argument** (`Consume(_view)`) rather than through a member | ||
| access, and **element-access** reads (`_buf[i]`). `case.own` is a faithful hand reduction of the | ||
| ownership logic; `before.cs` / `after.cs` are representative of the bug and its fix. | ||
|
|
||
| Reference: [P-007](../../../docs/proposals/P-007-arraypool-span.md); the local twin is | ||
| `memorypool-view-after-dispose`; the disposed-field twins are `field-use-after-dispose` / | ||
| `handler-use-after-dispose`. |
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.
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.