Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

#110

Fuzz the metadata and workflow parsers

merged · by keenan 10d ago · merged into main at d9f5a930 by keenan 10d ago

keenan opened this 10d ago

Closes #97.

Why

A push decides how much of this code runs. Frontmatter becomes the object every page renders, and workflow and image YAML becomes the job the runner executes. Both accept bytes from anyone allowed to push, and neither was fuzzed.

What changed

Four targets, beside the parsers they drive:

Target Property
FuzzParseIssue An accepted issue renders and re-parses identically — header, body, labels — and a second write does not change a byte
FuzzParseComment Same, for comments, which hold code blocks and pasted frontmatter
FuzzParseWorkflow An accepted workflow is one the runner can act on: right version, at least one job, every job naming an image; and the same file validates the same way twice
FuzzParseImage An accepted definition renders a Dockerfile with exactly the lines its env and run account for, so a value cannot add an instruction

The round-trip property is the one that matters: the server rewrites the file on every edit, so an issue it can read but not write back loses whatever it could not express. Refusing is always allowed; accepting and then failing to write is not.

The one asymmetry found, and why it stays

FuzzParseComment failed in 0.37 s on a hand-written comment with no trailing newline: the body came back as "0", and after one write as "0\n".

That is the format working as designed rather than a defect. Doc.Bytes ends a non-empty file with a newline, Split keeps it in the body, and every writer trims trailing newlines before it writes (renderIssue, RenderComment, EditIssueComment). So the file the server wrote is a fixed point, and a hand-written file is normalized once. Nothing compares raw bodies for equality, so nothing observable depends on the extra newline.

The property now says that in the test: the body comparison is on the trimmed form, and a second write must be byte-identical. The failing input is committed as metadata/testdata/fuzz/FuzzParseComment/bb6303671997e95a, so it runs as a seed on every push.

The YAML bomb

TestParseRefusesUnboundedAliasing asserts two different things, because one would have hidden the other:

  • Through Parse, a nine-level bomb is refused — but on its unknown keys, not on the aliasing, since KnownFields(true) rejects the top-level anchors before expansion is reached.
  • Through yaml.v3 directly, the decoder refuses with document contains excessive aliasing. That limit is what covers a bomb placed inside a field the schema does know, and it is the reason this code can decode repository YAML at all.

Verification

  • 60 seconds per target: FuzzParseIssue 4.2 M execs, FuzzParseComment 6.7 M, FuzzParseWorkflow 346 K, FuzzParseImage 5.3 M — all clean after the normalization above.
  • vole workflow run verify in the pinned image: green, corpus included.
  • CONTRIBUTING.md now names metadata and ci among the packages whose seed corpora gate a push.

Discussion (0)

No discussion yet.