feat: IoBounds — type untrusted-segment bounds as VortexException (ADR 0003 Phase E)#82
Merged
Conversation
…3 Phase E) Parse-side offsets/lengths/counts from untrusted file bytes must surface as VortexException, not raw IndexOutOfBoundsException / ArithmeticException / NegativeArraySizeException. IoBounds wraps the four shapes: slice/checkRange (asSlice bounds), toIntSize (2 GB ByteBuffer/array cap, replaces Math.toIntExact), checkCount (new T[n] alloc guard). Uses the current VortexException(String) constructor — bounds messages carry only numeric offsets, no attacker strings — and migrates to the VortexError catalog when ADR 0003 Phase A lands. Extends ADR 0003 to cover the exception *type* axis alongside message sanitization; records why a static helper beats the PR #27 BoundedSegment wrapper (no new type on the zero-copy hot path). Call-site migration + the Objects.checkIndex consumer-access sweep + the checkstyle ban on raw asSlice follow in subsequent commits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… Phase E) Migrate the file-structure and decode-side bounds operations that take offsets/lengths from parsed file bytes so a malformed file throws VortexException, not a raw JDK exception: - asSlice → IoBounds.slice: VortexReader (trailer/postscript/stats/segment), VortexHttpReader, Trailer magic, ScanIterator stats, FlatSegmentDecoder buffer descriptors, PostscriptParser blob slice. - FlatSegmentDecoder and ScanIterator stats now guard the trailing u32 fbLen read (checkRange) and segLen narrowing (toIntSize) — both were previously unguarded; a crafted fbLen leaked IndexOutOfBoundsException. - Math.toIntExact(storage.length()) → IoBounds.toIntSize in the Date/Time/ Timestamp/Uuid extension decoders (ArithmeticException → VortexException on a > 2 GB declared length). reader.array .limited() re-slices are left raw: offset 0, rows < length, bounded by construction — not untrusted input (ADR 0003 Phase E item 5). Existing MalformedTrailer/Footer/ZipBomb security tests stay green (no behaviour change on already-validated paths). Follow-ups: checkstyle ban on raw asSlice, checkCount guards on new T[(int)n] alloc sites, and the Objects.checkIndex sweep of consumer-access getters. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Index (ADR 0003 Phase E)
The ~14 consumer-access getters in the Lazy*/Generic array families and
ExtensionStorage hand-rolled the same bounds check:
if (i < 0 || i >= length) {
throw new IndexOutOfBoundsException("index " + i + " out of bounds for length " + length);
}
Collapse each onto the JDK built-in Objects.checkIndex(i, length). This is the
consumer-access carve-out from ADR 0003 Phase E: a caller's bad accessor index
is consumer misuse and correctly stays IndexOutOfBoundsException (cf. List.get),
distinct from the untrusted-parse offsets that route through IoBounds and throw
VortexException. Objects.checkIndex is an @IntrinsicCandidate, so the JIT inlines
it to the same check — no regression on these scalar accessors.
12 files, 14 guards. reader 679 green; checkstyle + javadoc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…se E) End-to-end coverage of the FlatSegmentDecoder hardening: drives the public decode() with crafted segments and asserts VortexException, never a raw JDK exception. Four cases: - segment smaller than the trailing 4-byte length field - declared fbLen larger than the segment (fbStart goes negative) - negative fbLen (0xFFFFFFFF read as signed -1) - a well-formed Array FlatBuffer whose single buffer descriptor claims a 1 000 000-byte payload past the segment end (exercises IoBounds.slice in the buffer-collection loop) Builds the Array FlatBuffer with FlatBufferBuilder so the buffer-descriptor case reaches the real decode path. reader 683 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Phase E) Three follow-ups from PR review: - ScanIterator.readFlatStats: degrade to ArrayStats.empty() on a malformed stats segment instead of throwing VortexException, matching VortexReader.readFlatStats. Stats are an optional zone-map pruning optimization — a corrupt stats segment must not abort the scan. Also adds the missing segIdx bounds guard the other reader already had. - FlatSegmentDecoder: route the buffer-count allocation through IoBounds.checkCount before new MemorySegment[numBuffers]. - Date/Time/Timestamp/Uuid extension decoders: the decodeAll loops counted with a long index over an int bound (n); narrow to int i. reader 705 green; checkstyle + javadoc clean. Co-Authored-By: Claude Opus 4.8 <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.
What
Extends ADR 0003 to a second axis: the type of exception thrown on malformed input, not just message sanitization. Parse-side offsets/lengths/counts from untrusted file bytes now surface as
VortexException, never a raw JDKIndexOutOfBoundsException/ArithmeticException/NegativeArraySizeException.Commits
IoBoundshelper (core) —slice/checkRange/toIntSize/checkCount. One pure primitive reachable by every layer (reader → core, andProtoReaderlives in core). Uses the currentVortexException(String)ctor; migrates to theVortexErrorcatalog when ADR 0003 Phase A lands. + 19 unit tests.IoBounds:VortexReader,VortexHttpReader,Trailer,ScanIteratorstats,FlatSegmentDecoder,PostscriptParser.FlatSegmentDecoder+ScanIteratorstats were the genuinely unguarded paths — a crafted trailingfbLenleakedIndexOutOfBoundsException. PlusMath.toIntExact→IoBounds.toIntSizein the Date/Time/Timestamp/Uuid extension decoders.Objects.checkIndexsweep — the ~14 hand-rolledgetX(i)guards collapse onto the JDK built-in. Consumer-access carve-out: a caller's bad accessor index correctly staysIndexOutOfBoundsException(cf.List.get), distinct from untrusted-parse offsets.FlatSegmentBoundsSecurityTest— crafted segments (tiny, oversizefbLen, negativefbLen, buffer descriptor past segment end) each assertVortexException.Scope notes
reader.array.limited()re-slices left raw — offset 0,rows < length, bounded by construction, not untrusted input..asSlice(,checkCountguards onnew T[(int)n]alloc sites.Verify
core 242 + reader 683 green; checkstyle + javadoc clean. Existing MalformedTrailer/Footer/ZipBomb security tests unchanged (no behaviour regression on already-validated paths).
🤖 Generated with Claude Code