release: 0.2.1#117
Merged
Merged
Conversation
`gencost_row` reads NCOST from the file as an f64 truncated to usize, so a huge or non-finite value saturates near usize::MAX. The width requirement was then computed as `start + want` (with `want = 2*ncost` for piecewise costs), which overflows: an add-overflow panic under debug overflow-checks, and in release a wraparound that makes the `require` length check pass and then panics on the reversed `row[start..start + want]` slice range. A crafted MATPOWER `mpc.gencost` row (e.g. NCOST = 1e20) therefore panics on every build profile. Through the C ABI / Python / Julia the panic is caught at the FFI boundary and degraded to a generic "panic while parsing", but the pure Rust API and the CLI take an uncaught panic — a denial of service on untrusted input. It is not a memory-safety issue: the release wraparound lands on a bounds-checked slice, so it panics rather than reading out of bounds. Size the requirement with saturating arithmetic so an implausible NCOST is rejected by the existing length check as a loud `ShortRow` error, the parser's normal malformed-input signal, on every profile and through every binding. Found by malformed-input fuzzing of the parser surface. https://claude.ai/code/session_013KSDeKD9C3YsGaR67RDKhr
copy_to_buf clipped error/warning messages at a raw byte count, which could split a multi-byte UTF-8 codepoint and hand consumers an invalid UTF-8 string. Back the truncation point up to a character boundary so a clipped message is always valid UTF-8, and pin the behavior with a test. https://claude.ai/code/session_01KxR1fuH4L8XHHZXtNYgrG8
… input Backported from the v0.3.0 candidate (5d43fcc plus the 12d48ba follow-up, pwd.rs hunks only). The private byte accessors return Option instead of indexing, so every offset derived from untrusted file bytes carries its bounds check structurally rather than per call site, and the record scan retains decoded coordinates instead of re-reading them. parse_pwd's signature is unchanged; the differential oracle tests pass unchanged. Adds a corruption sweep test pinning the invariant: any truncation or byte flip of a real display file returns Ok or a structured Err, never a panic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…C block comment early The pio_read_gridfm doc's directory-glob example put a literal */ inside the header's block comment, so any C consumer compiling with -DPIO_GRIDFM got 'unknown type name raw' at powerio.h:243. The guard kept the default build (and the CI smoke test) green, which is how it shipped in 0.2.0. Backport of the v0.3.0 candidate's 97b8732, reworded for the v3 names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Patch release bump to v0.2.1 that cherry-picks several hardening fixes from the v0.3.0 candidate into the v0.2.x line, aiming to prevent panics on malformed/untrusted inputs and to fix a C header documentation issue without changing the C ABI (PIO_ABI_VERSION remains 3).
Changes:
- Harden MATPOWER
gencostparsing against NCOST overflow via saturating arithmetic, plus regression test. - Make PowerWorld
.pwdparsing byte reads total (Option) and add a corruption sweep test asserting “never panic”. - Ensure C ABI message truncation preserves UTF-8 validity and fix
powerio.hgridfm doc comment that broke compilation under-DPIO_GRIDFM; bump versions + changelog for 0.2.1.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| powerio/tests/powerworld_pwd.rs | Adds corruption/truncation sweep test for .pwd parser panic-invariant. |
| powerio/src/format/powerworld/pwd.rs | Converts byte accessors to total reads (Option), retains decoded coords, and updates identity/link scanning to avoid panics. |
| powerio/src/format/matpower/tests.rs | Adds regression test for oversized gencost NCOST overflow/panic. |
| powerio/src/format/matpower/rows.rs | Uses saturating arithmetic when computing required gencost row width. |
| powerio-capi/src/lib.rs | Makes copy_to_buf truncation UTF-8-safe and adds a unit test for boundary behavior. |
| powerio-capi/include/powerio.h | Fixes gridfm doc comment wording to avoid */ comment termination issues. |
| CHANGELOG.md | Adds 0.2.1 release notes describing the hardening fixes. |
| Cargo.toml | Bumps workspace/package and workspace dependency pins to 0.2.1. |
| Cargo.lock | Updates locked crate versions to 0.2.1. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Patch release: the nonbreaking hardening fixes from the v0.3.0 candidate (#116), shippable now. No API or ABI change;
PIO_ABI_VERSIONstays 3, so the binaries are drop in for PowerIO.jl.ShortRowparse error (cherry-pick of 57269e9)..pwdreader: byte accessors are total (Option, no direct indexing), with a corruption sweep test; extracted from the v4 commits,parse_pwdunchanged.powerio.h: the 0.2.0 header fails to compile under-DPIO_GRIDFM— the gridfm doc's*/raw/example terminated the block comment (unknown type name 'raw'at line 243). The feature guard kept the default build and the CI smoke test green, which is how it shipped.Excluded by design: ABI v4, the fuzz harnesses (not a shipped artifact; one target needs the v4 format), and the snapshot fix (v4 only code). All stay in #116, which takes a main merge back after this lands (clean: identical patches, identical pwd.rs).
Release runbook after merge:
git tag v0.2.1 && git push origin v0.2.1→ release-binaries stages the draft release → publish → crates.io and PyPI fire on the release event → PowerIO.jlgen/update_artifacts.jl v0.2.1.🤖 Generated with Claude Code