Skip to content

release: 0.2.1#117

Merged
samtalki merged 6 commits into
mainfrom
release/0.2.1
Jun 12, 2026
Merged

release: 0.2.1#117
samtalki merged 6 commits into
mainfrom
release/0.2.1

Conversation

@samtalki

Copy link
Copy Markdown
Member

Patch release: the nonbreaking hardening fixes from the v0.3.0 candidate (#116), shippable now. No API or ABI change; PIO_ABI_VERSION stays 3, so the binaries are drop in for PowerIO.jl.

  • MATPOWER: crafted gencost NCOST overflowed the row width arithmetic and panicked on every profile; now a ShortRow parse error (cherry-pick of 57269e9).
  • C ABI: message truncation lands on a UTF-8 character boundary instead of splitting a codepoint (cherry-pick of f76149d).
  • .pwd reader: byte accessors are total (Option, no direct indexing), with a corruption sweep test; extracted from the v4 commits, parse_pwd unchanged.
  • 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.jl gen/update_artifacts.jl v0.2.1.

🤖 Generated with Claude Code

claude and others added 5 commits June 12, 2026 15:13
`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>

Copilot AI 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.

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 gencost parsing against NCOST overflow via saturating arithmetic, plus regression test.
  • Make PowerWorld .pwd parsing 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.h gridfm 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.

Comment thread powerio/src/format/powerworld/pwd.rs
Comment thread powerio/tests/powerworld_pwd.rs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@samtalki samtalki merged commit 966fa9b into main Jun 12, 2026
21 checks passed
@samtalki samtalki deleted the release/0.2.1 branch June 12, 2026 19:36
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.

3 participants