-
Notifications
You must be signed in to change notification settings - Fork 0
PoC polish: gallery, rustc-style diagnostics, real .NET compile in CI, real-world corpus #6
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
Show all changes
7 commits
Select commit
Hold shift + click to select a range
e699880
examples: add a self-checking "what it catches" gallery
claude cdc017f
diagnostics: rustc-style output with source line + caret
claude 0f6db74
ci: compile & run the golden generated C# with the real .NET compiler
claude 9dc9857
corpus: seed a self-checking real-world ownership-bug corpus
claude 1d94dbd
Address CodeRabbit review on #6
claude fd91046
tests: fix gallery success-count off-by-one (self-review)
claude fd2c69e
docs: note CI compiles the golden, and the deferred action-pinning
claude 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 |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Real-world corpus | ||
|
|
||
| Не «Damn Vulnerable .NET app», а **Damn Leaky Resource Corpus**: настоящие | ||
| паттерны багов владения ресурсами из реального .NET-кода (ArrayPool, `Dispose`, | ||
| pooled buffers), сведённые к минимальной OwnLang-модели, на которой видно, как | ||
| checker их ловит. | ||
|
|
||
| Каждый кейс — папка с пятью файлами: | ||
|
|
||
| | Файл | Что это | | ||
| |------|---------| | ||
| | `before.cs` | багованный C#-фрагмент (паттерн как в реальном коде) | | ||
| | `after.cs` | исправленная версия | | ||
| | `case.own` | минимальная OwnLang-модель того же владения | | ||
| | `expected-diagnostics.txt` | коды, которые checker обязан выдать на `case.own` | | ||
| | `notes.md` | паттерн, источник и честная рамка | | ||
|
|
||
| Прогон (часть общего сьюта — `tests/test_corpus.py`): | ||
|
|
||
| ```bash | ||
| python tests/test_corpus.py | ||
| ``` | ||
|
|
||
| ## Честная рамка (читать обязательно) | ||
|
|
||
| `case.own` — это **ручная редукция** C#-паттерна, а **не** C#, который checker | ||
| прочитал: у OwnLang нет фронтенда по C#. Корпус доказывает, что **логика | ||
| владения ложится на настоящие баги** — будь это написано на OwnLang, checker бы | ||
| отверг. `before.cs`/`after.cs` репрезентативны для паттерна, это не дословный | ||
| дифф одного PR. | ||
|
|
||
| ## Что выразимо, а что нет | ||
|
|
||
| Сейчас checker берёт «голый borrow»: leak / use-after-return / double-return / | ||
| escape / release-while-borrowed. **Не** выразимо (нужны новые модели — roadmap): | ||
| over-clear по границе `Span` (нет region/length-анализа), утечка только на | ||
| exception-path (анализ не моделирует исключения), concurrent `Dispose` (нет | ||
| конкурентности), async. | ||
|
|
||
| ## Кейсы | ||
|
|
||
| | Кейс | Код | Реальный паттерн | | ||
| |------|-----|------------------| | ||
| | `arraypool-use-after-return` | OWN002 | rented-буфер вернули в пул, потом ещё читали slice | | ||
| | `arraypool-double-return` | OWN003 | один и тот же массив вернули в ArrayPool дважды ([#33767](https://github.com/dotnet/runtime/issues/33767)) | |
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,15 @@ | ||
| // AFTER (fixed): return exactly once, in finally. | ||
| using System.Buffers; | ||
|
|
||
| static void Use(int n) | ||
| { | ||
| int[] rented = ArrayPool<int>.Shared.Rent(n); | ||
| try | ||
| { | ||
| Work(rented); | ||
| } | ||
| finally | ||
| { | ||
| ArrayPool<int>.Shared.Return(rented); | ||
| } | ||
| } |
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,19 @@ | ||
| // BEFORE (buggy). Reduction of dotnet/runtime#33767 "Do not double-return | ||
| // arrays to ArrayPool": the same rented array is returned twice (here a Return | ||
| // on the success path AND a Return in finally). A double-return corrupts the | ||
| // pool — the array can later be rented out to two callers at once. | ||
| using System.Buffers; | ||
|
|
||
| static void Use(int n) | ||
| { | ||
| int[] rented = ArrayPool<int>.Shared.Rent(n); | ||
| try | ||
| { | ||
| Work(rented); | ||
| ArrayPool<int>.Shared.Return(rented); // returned here ... | ||
| } | ||
| finally | ||
| { | ||
| ArrayPool<int>.Shared.Return(rented); // <-- ... and again here (double) | ||
| } | ||
| } |
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,14 @@ | ||
| // OwnLang model: returning the same buffer twice is a double `release`. | ||
| module Corpus | ||
| resource Buffer { | ||
| acquire rent | ||
| release give | ||
| emit_type "byte[]" | ||
| emit_acquire "ArrayPool<byte>.Shared.Rent({args})" | ||
| emit_release "ArrayPool<byte>.Shared.Return({0})" | ||
| } | ||
| fn run(n: int) { | ||
| let rented = acquire Buffer(n); // ArrayPool.Rent | ||
| release rented; // ArrayPool.Return | ||
| release rented; // returned again -> OWN003 | ||
| } |
1 change: 1 addition & 0 deletions
1
corpus/real-world/arraypool-double-return/expected-diagnostics.txt
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 @@ | ||
| OWN003 |
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,15 @@ | ||
| # ArrayPool double-return | ||
|
|
||
| **Pattern:** the same rented array is returned to `ArrayPool<T>.Shared` twice — | ||
| classically a `Return` on a normal path plus another in a `finally`/`Dispose`, | ||
| or two `Dispose()` calls. The pool can then hand the same array to two renters | ||
| simultaneously. See dotnet/runtime#33767 ("Do not double-return arrays to | ||
| ArrayPool"). | ||
|
|
||
| **What the checker says:** the OwnLang model trips **OWN003** (double release). | ||
|
|
||
| **Honesty / scope.** As with the other cases, `case.own` is a faithful hand | ||
| reduction of the pattern (not C# ingested by the checker); `before.cs` / | ||
| `after.cs` are representative of the bug and its fix, not a verbatim PR diff. | ||
|
|
||
| Reference: https://github.com/dotnet/runtime/issues/33767 |
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,11 @@ | ||
| // AFTER (fixed): consume the buffer BEFORE returning it to the pool. | ||
| using System.Buffers; | ||
|
|
||
| static int[] Divide(int dividend, int divisor) | ||
| { | ||
| int[] quotient = ArrayPool<int>.Shared.Rent(Size(dividend)); | ||
| Compute(quotient, dividend, divisor); | ||
| int[] result = BuildResult(quotient); // consume first ... | ||
| ArrayPool<int>.Shared.Return(quotient); // ... then return | ||
| return result; | ||
| } |
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,14 @@ | ||
| // BEFORE (buggy). Reduction of the classic ArrayPool use-after-return bug seen | ||
| // across dotnet/runtime's buffer-pooling code (e.g. the BigInteger division | ||
| // path): a rented buffer is returned to the pool, then a slice of it is still | ||
| // read while building the result. Representative of the pattern, not verbatim | ||
| // from one PR. | ||
| using System.Buffers; | ||
|
|
||
| static int[] Divide(int dividend, int divisor) | ||
| { | ||
| int[] quotient = ArrayPool<int>.Shared.Rent(Size(dividend)); | ||
| Compute(quotient, dividend, divisor); | ||
| ArrayPool<int>.Shared.Return(quotient); // <-- returned to the pool here ... | ||
| return BuildResult(quotient); // <-- ... but still read here (UAF) | ||
| } |
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,18 @@ | ||
| // OwnLang model. `acquire` == ArrayPool.Rent, `release` == ArrayPool.Return, | ||
| // a `borrow`/call == reading a Span over the buffer. Reading after `release` | ||
| // is the use-after-return the BEFORE code commits. | ||
| module Corpus | ||
| resource Buffer { | ||
| acquire rent | ||
| release give | ||
| emit_type "byte[]" | ||
| emit_acquire "ArrayPool<byte>.Shared.Rent({args})" | ||
| emit_release "ArrayPool<byte>.Shared.Return({0})" | ||
| emit_borrow "{0}.AsSpan()" | ||
| } | ||
| extern fn BuildResult(borrow Buffer); | ||
| fn divide(n: int) { | ||
| let quotient = acquire Buffer(n); // ArrayPool.Rent | ||
| release quotient; // ArrayPool.Return <-- too early | ||
| BuildResult(quotient); // read after return -> OWN002 | ||
| } |
1 change: 1 addition & 0 deletions
1
corpus/real-world/arraypool-use-after-return/expected-diagnostics.txt
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,27 @@ | ||
| # ArrayPool use-after-return | ||
|
|
||
| **Pattern:** a buffer rented from `ArrayPool<T>.Shared` is `Return`ed to the | ||
| pool, and then a slice of it is still read. Once returned, the array may be | ||
| handed to another renter, so the later read sees torn/foreign data. This is one | ||
| of the most common real ArrayPool bugs; it shows up repeatedly in | ||
| dotnet/runtime's buffer-pooling code (the BigInteger division/GCD path is the | ||
| oft-cited example). | ||
|
|
||
| **What the checker says:** the OwnLang model trips **OWN002** (use after | ||
| release) — `release` is `ArrayPool.Return`, and `BuildResult(quotient)` reads | ||
| the buffer afterwards. | ||
|
|
||
| ```text | ||
| $ python -m ownlang check corpus/real-world/arraypool-use-after-return/case.own | ||
| case.own:14:14: error: [OWN002] borrow 'quotient' after it was released | ||
| 14 | BuildResult(quotient); // read after return -> OWN002 | ||
| ^ | ||
| ``` | ||
|
|
||
| **Honesty / scope.** `case.own` is a *hand reduction* of the C# pattern, not C# | ||
| the checker ingested — OwnLang has no C# front-end. It demonstrates that the | ||
| ownership *logic* maps onto the real bug: had the code been written in OwnLang, | ||
| the checker would have rejected it. The real-world specifics (the division math, | ||
| the exact slice bounds) are abstracted to `acquire`/`release`/`borrow`. | ||
| `before.cs` / `after.cs` capture the pattern; they are representative, not a | ||
| verbatim copy of a single PR. |
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,19 @@ | ||
| // Clean: acquire, look at it mutably then read-only, hand it back. | ||
| // Lowers to ArrayPool Rent/Return inside a try/finally (exception-safe). | ||
| module Gallery | ||
| resource Buffer { | ||
| acquire rent | ||
| release give | ||
| emit_type "byte[]" | ||
| emit_acquire "ArrayPool<byte>.Shared.Rent({args})" | ||
| emit_release "ArrayPool<byte>.Shared.Return({0})" | ||
| emit_borrow "{0}.AsSpan()" | ||
| } | ||
| extern fn Fill(borrow_mut Buffer); | ||
| extern fn Hash(borrow Buffer); | ||
| fn process(size: int) { | ||
| let buf = acquire Buffer(size); | ||
| borrow_mut buf as bytes { Fill(bytes); } | ||
| borrow buf as view { Hash(view); } | ||
| release buf; | ||
| } |
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,10 @@ | ||
| // OWN001 — leak on one path. | ||
| // Real C#: you Dispose() in the happy branch but forget it on the early-out. | ||
| module Gallery | ||
| resource Conn { acquire open release close } | ||
| fn handle(flag: int) { | ||
| let c = acquire Conn(flag); | ||
| if (flag) { | ||
| release c; // released here ... | ||
| } // ... but on the else path c is never closed -> leak | ||
| } |
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,10 @@ | ||
| // OWN002 — use after release (definite, on every path). | ||
| // Real C#: touching a stream after Dispose() -> ObjectDisposedException. | ||
| module Gallery | ||
| resource Conn { acquire open release close } | ||
| extern fn Send(borrow Conn); | ||
| fn run() { | ||
| let c = acquire Conn(1); | ||
| release c; | ||
| Send(c); // c is already closed | ||
| } |
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,9 @@ | ||
| // OWN003 — double release. | ||
| // Real C#: Dispose() called twice (and the type isn't idempotent). | ||
| module Gallery | ||
| resource Conn { acquire open release close } | ||
| fn run() { | ||
| let c = acquire Conn(1); | ||
| release c; | ||
| release c; | ||
| } |
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,11 @@ | ||
| // OWN005 — use after ownership was moved away. | ||
| // Real C#: using a buffer after you handed the only owner to someone else. | ||
| module Gallery | ||
| resource Buffer { acquire rent release give } | ||
| extern fn Hash(borrow Buffer); | ||
| fn run() { | ||
| let a = acquire Buffer(64); | ||
| let b = move a; // ownership transferred to b | ||
| Hash(a); // a is now empty | ||
| release b; | ||
| } |
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,12 @@ | ||
| // OWN008 — release while a borrow (a view) is still outstanding. | ||
| // Real C#: returning the array to ArrayPool while a Span<byte> over it is in use. | ||
| module Gallery | ||
| resource Buffer { acquire rent release give } | ||
| extern fn Fill(borrow_mut Buffer); | ||
| fn run() { | ||
| let b = acquire Buffer(64); | ||
| borrow_mut b as view { | ||
| release b; // freeing the backing store while `view` is alive | ||
| Fill(view); | ||
| } | ||
| } |
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,12 @@ | ||
| // OWN006 — exclusive (mutable) access while a shared view is live. | ||
| // Real C#: writing through one Span while another ReadOnlySpan aliases it. | ||
| module Gallery | ||
| resource Buffer { acquire rent release give } | ||
| extern fn Fill(borrow_mut Buffer); | ||
| fn run() { | ||
| let b = acquire Buffer(64); | ||
| borrow b as readers { | ||
| Fill(b); // needs exclusive access, but `readers` is live | ||
| } | ||
| release b; | ||
| } |
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,11 @@ | ||
| // OWN002 — use after ownership was consumed by a callee. | ||
| // Real C#: a method takes ownership (it will Dispose), then you touch it again. | ||
| module Gallery | ||
| resource Buffer { acquire rent release give } | ||
| extern fn Store(consume Buffer); | ||
| extern fn Hash(borrow Buffer); | ||
| fn run() { | ||
| let b = acquire Buffer(64); | ||
| Store(b); // Store now owns b (and will free it) | ||
| Hash(b); // ... but we used it afterwards | ||
| } |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pin GitHub Actions by commit SHA and disable checkout credential persistence.
Line 54 / Line 55 / Line 58 are unpinned action refs, and Line 54 should set
persist-credentials: falseto reduce token exposure risk.🧰 Tools
🪛 zizmor (1.25.2)
[warning] 54-54: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 54-54: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 55-55: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 58-58: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Source: Linters/SAST tools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Skipping for now (deliberate). SHA-pinning every action is a repo-wide policy choice that's better maintained by Dependabot than hand-pinned in one PR, and these jobs only checkout + run tests — no push, no secrets — so the credential-persistence exposure is minimal. Happy to do a dedicated hardening pass (full SHA pins +
persist-credentials: false) if you'd like that as a standing policy.Generated by Claude Code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PhysShell, that's a reasonable call — Dependabot'sactionsecosystem update is exactly the right layer for maintaining SHA pins at scale, and the exposure here really is low without push permissions or secrets in scope.Would you like me to open a follow-up GitHub issue to track the dedicated hardening pass (full SHA pins +
persist-credentials: falseacross all workflow jobs)? That way it won't get lost once the PR merges.✏️ Learnings added