Skip to content

Fix stack-buffer-overflow in parseLine() on a face line with >16 vertices#73

Open
ZUENS2020 wants to merge 2 commits into
syoyo:masterfrom
ZUENS2020:fix-parseline-face-overflow
Open

Fix stack-buffer-overflow in parseLine() on a face line with >16 vertices#73
ZUENS2020 wants to merge 2 commits into
syoyo:masterfrom
ZUENS2020:fix-parseline-face-overflow

Conversation

@ZUENS2020

@ZUENS2020 ZUENS2020 commented Jun 15, 2026

Copy link
Copy Markdown

Problem

parseLine() stores face (f) / line (l) vertices into a fixed-size stack array f[TINYOBJ_MAX_FACES_PER_F_LINE] (= 16) with no bounds check. An OBJ face is a legal N-gon with an arbitrary vertex count, so a face line with more than 16 vertices writes past the end of the array — a stack-buffer-overflow WRITE. The guarding assert() is compiled out under -DNDEBUG (release), so there is no runtime protection. With TINYOBJ_FLAG_TRIANGULATE, the triangulation loop also writes command->f[3*n+..] (also size 16) unchecked, overflowing for faces with >7 vertices.

Reachable via the public tinyobj_parse_obj(..., TINYOBJ_FLAG_TRIANGULATE) on a valid OBJ:

f 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
$ clang -fsanitize=address -O2 -DNDEBUG -I. repro.c -o repro && ./repro
==ERROR: AddressSanitizer: stack-buffer-overflow ... WRITE of size 8
SUMMARY: AddressSanitizer: stack-buffer-overflow in tinyobj_parse_obj

(-DNDEBUG matters — it removes the asserts that would otherwise abort in debug.) Found via libFuzzer + ASan; reproduced on a clean clone of master.

Fix

Bound the writes in both the parse loop and the triangulation loop against TINYOBJ_MAX_FACES_PER_F_LINE (extra vertices dropped to preserve memory safety). Minimal memory-safety stop-gap — sizing the buffers for the actual face (an N-gon triangulates to 3*(N-2) indices) would be the complete fix and is probably best done alongside the face/triangulation rework in #60. Normal triangle/quad parsing is unchanged (verified with TINYOBJ_FLAG_TRIANGULATE).

Note

This repo has issues disabled, so filing as a PR. Not a duplicate of #55 (a different sscanf("%s") overflow) or #60 (a triangulation indexing correctness bug in the same area, which does not address this unbounded write).

…J_MAX_FACES_PER_F_LINE vertices

parseLine() stored face/line vertices into the fixed-size stack array
`f[TINYOBJ_MAX_FACES_PER_F_LINE]` (16) with no bounds check. An OBJ face is a
legal N-gon with an arbitrary vertex count, so a face line with more than 16
vertices wrote past the end of the array — a stack-buffer-overflow WRITE. The
guarding assert() is compiled out under -DNDEBUG (release), so there was no
runtime protection. With TINYOBJ_FLAG_TRIANGULATE the triangulation loop likewise
wrote command->f[3*n+..] (also size 16) unchecked, overflowing for faces with
more than 7 vertices.

Reachable via the public tinyobj_parse_obj(..., TINYOBJ_FLAG_TRIANGULATE) on a
valid OBJ:

    f 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

  => AddressSanitizer: stack-buffer-overflow WRITE in tinyobj_parse_obj
     (built -O2 -DNDEBUG so the asserts are absent)

Fix: bound the writes in both the parse loop and the triangulation loop against
TINYOBJ_MAX_FACES_PER_F_LINE (extra vertices are dropped to preserve memory
safety). This is a minimal stop-gap; sizing the buffers for the actual face
(an N-gon triangulates to 3*(N-2) indices) would be the complete fix and is best
done alongside the face/triangulation rework in syoyo#60. Normal triangle/quad parsing
is unchanged (verified with TINYOBJ_FLAG_TRIANGULATE). Found via libFuzzer + ASan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
github-actions Bot pushed a commit to ZUENS2020/Sherpa that referenced this pull request Jun 15, 2026
… + tinyobjloader finding (#473)

- S-473 (Shipped): Phase B harness-validity gate + origin-by-crashing-function
  (PRs #470/#471/#472); resolves O-6.
- O-6 (section 3): marked RESOLVED (S-473) for traceability — single-header libs
  attribute library frames to *_fuzz.c, fixed by function-based classification.
- Findings log: tinyobjloader-c parseLine stack-buffer-overflow WRITE (PR
  syoyo/tinyobjloader-c#73), source-verified, not a dup of #55/#60.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@syoyo

syoyo commented Jun 16, 2026

Copy link
Copy Markdown
Owner

you should add regression test

Adds two acutest cases to test/tinyobj_internal_tests.c covering the
stack-buffer-overflow this PR fixes:

- parseLine_face_overflow: an "f" statement with >TINYOBJ_MAX_FACES_PER_F_LINE
  vertices (untriangulated and triangulated).
- parseLine_line_overflow: an "l" (polyline) statement with >2 vertices.

Both reproduce an AddressSanitizer stack-buffer-overflow on the pre-fix
code and pass after the fix.

Writing the line-statement test exposed a second overflow the original
guard missed: the "l" branch keeps two vertices in a size-2 array f[2]
but was bounded by TINYOBJ_MAX_FACES_PER_F_LINE (16); the guard is now
correctly bounded to 2. Also relaxed two now-incorrect debug asserts that
encoded the old (buggy) "the whole n-gon always fits" precondition, which
the runtime guards already enforce.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ZUENS2020

Copy link
Copy Markdown
Author

Added regression tests in test/tinyobj_internal_tests.c (parseLine_face_overflow, parseLine_line_overflow). Both reproduce an AddressSanitizer stack-buffer-overflow on the pre-fix code and pass with the fix.

Writing the l-statement test surfaced a second overflow the first patch missed — the line branch keeps two vertices in f[2] but was still bounded by TINYOBJ_MAX_FACES_PER_F_LINE (16), so l 1 2 3 would write f[2]. It's now bounded to 2. I also relaxed two debug asserts that encoded the old "the whole n-gon always fits" precondition, which the runtime guards now enforce.

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.

2 participants