Commit 24ec2b9
committed
splat3d/PR7-fix: reject overflowing PLY vertex counts before allocation
External-reviewer bug report against PR #153:
> When a malformed or fuzzed PLY header advertises a vertex count
> larger than usize::MAX / (62 * 4), this size calculation overflows
> (panics in debug, wraps in release). In release that allocates a
> too-small bytes buffer and the subsequent per-vertex loop indexes
> past it instead of returning a PlyError, so a bad input can crash
> the loader; use checked multiplication before allocating/reading
> the body.
## Root cause
`read_ply` computed the body byte count via:
let mut bytes = vec![0u8; n_vertices * PROPERTIES_PER_VERTEX * 4];
For `n_vertices > usize::MAX / 248`:
- debug: panic on the unchecked `*`.
- release: wraps to a small number, allocates a too-small buffer,
`read_exact` succeeds (reads only the wrapped count of bytes —
often zero), then the per-vertex loop indexes far past the
allocation. Crash or — worse — silent corruption if the wrapped
size happens to land at a valid index.
## Fix
Gate the body size with `checked_mul` BEFORE allocation:
let body_bytes = n_vertices
.checked_mul(PROPERTIES_PER_VERTEX)
.and_then(|n| n.checked_mul(4))
.ok_or_else(|| PlyError::BadElement(format!(
"vertex count {n_vertices} × {PROPERTIES_PER_VERTEX} props × 4 bytes \
overflows usize on this target ({} bits)", usize::BITS,
)))?;
let mut bytes = vec![0u8; body_bytes];
The downstream per-vertex `i * stride` math is now safe by
transitivity — for any `i < n_vertices`, `i * stride ≤ body_bytes ≤
usize::MAX`. No further bounds work needed.
## Regression test
`rejects_overflowing_vertex_count`:
- Computes `overflow_count = usize::MAX / (PROPERTIES_PER_VERTEX * 4) + 1`
(the smallest count that overflows on the current target).
- Builds a valid PLY header advertising that count, with NO body
bytes — the overflow check must fire BEFORE any I/O is attempted.
- Asserts `PlyError::BadElement` with a message containing "overflows".
Verified green in BOTH debug and release builds, where the wrapping
(not panicking) release path is the actual security concern.
## Test count
cargo test --features splat3d --lib hpc::splat3d::ply
→ 5 passed; 0 failed (was 4: +1 overflow regression)
cargo test --features splat3d --lib hpc::splat3d
→ 91 passed; 0 failed (was 90: +1)
cargo test --features splat3d --release --lib hpc::splat3d::ply
→ 5 passed; 0 failed (release-build confirms no wrap-then-corrupt)
https://claude.ai/code/session_017GFLBnDy23AWBqvkbHHC411 parent 9e96459 commit 24ec2b9
1 file changed
Lines changed: 63 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
185 | | - | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
186 | 208 | | |
187 | 209 | | |
188 | 210 | | |
| |||
349 | 371 | | |
350 | 372 | | |
351 | 373 | | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
352 | 414 | | |
0 commit comments