Skip to content

test(fuzz): relax FuzzEtreeRoundTripIdempotent to a two-round fixed point - #1922

Open
aibeshkov wants to merge 1 commit into
mainfrom
fuzz/etree-roundtrip-two-round-fixedpoint
Open

aibeshkov wants to merge 1 commit into
mainfrom
fuzz/etree-roundtrip-two-round-fixedpoint

Conversation

@aibeshkov

Copy link
Copy Markdown
Collaborator

What

Relaxes FuzzEtreeRoundTripIdempotent's idempotence property from a one-round fixed
point (f(f(x)) == f(x)) to a two-round fixed point (f(f(f(x))) == f(f(x))), and adds
the reproducer class as permanent regression seeds. f = serialise∘parse.

Why

The rig recorded a crash on <a><![CDATA[]]></a>. Verified true positive, low severity:

  • An empty CDATA section is the only XML construct that parses to a zero-length CharData
    node
    . etree serialises the element holding it as <a></a>, but re-parsing <a></a> drops
    the empty text → <a/>. Both are the identical empty element — a serialisation-spelling
    difference, not a parse divergence. It stabilises at <a/> from round 2 on.
  • etree is transitive-only here (inside crewjam/saml + goxmldsig); keyorix's SAML
    wrapper never calls etree.WriteToBytes on a verify path. Signature bytes are canonicalised
    by goxmldsig C14N, which always emits <x></x> and never self-closing — so this
    flip-flop cannot reach canonicalised signature bytes. No XSW/signature-bypass exposure.

Full triage: claude/2026-09-17-FINDING-etree-empty-cdata-nonidempotent-roundtrip.md.

Strength preserved

The two-round form still fails on what matters for XSW:

  • Re-parseability — now asserted on both rounds (output etree cannot re-parse fails).
  • No fixed point — a serialiser that never stops changing bytes (oscillation, or an
    infoset not stable under round-trip) still fails.

Only the benign infoset-invariant spellings that settle on round 2 stop false-positing.

Verification

  • gofmt + go vet clean.
  • Seed corpus PASS, including the new <a><![CDATA[]]></a> and nested regression seeds.
  • 45s -fuzz burst PASS (143k execs, no crash) against the live corpus under the new assertion.

…oint

FuzzEtreeRoundTripIdempotent asserted a one-round idempotence fixed point
(f(f(x)) == f(x)). beevik/etree genuinely violates that for one infoset-invariant
class: an empty CDATA section `<a><![CDATA[]]></a>` is the only XML construct that
parses to a zero-length CharData node, which etree first serialises as `<a></a>`
and then, on re-parse (the empty text is dropped), as `<a/>`. Both are the identical
empty element, so this is a serialisation-spelling difference, not a parse divergence.
Verified true positive, low severity: etree is transitive-only here (inside
crewjam/saml + goxmldsig), and signature bytes on the SAML path are canonicalised by
goxmldsig C14N — which always emits `<x></x>` and never self-closing — not by
etree.WriteToBytes, so this flip-flop cannot reach canonicalised signature bytes.
(See claude/2026-09-17-FINDING-etree-empty-cdata-nonidempotent-roundtrip.)

Relax the property to a fixed point within TWO normalisation rounds
(f(f(f(x))) == f(f(x))): s2 must be stable under one further round. This keeps the
XSW-relevant strength — output etree cannot re-parse still fails (now checked on both
rounds), and a serialiser that never reaches a fixed point (oscillation, or an infoset
not stable under round-trip) still fails — while the benign spellings that settle on
round 2 no longer false-positive. Adds the empty-CDATA and nested empty-CDATA inputs as
permanent regression seeds.

Verified: gofmt + go vet clean; seed corpus PASS (incl. the new seeds); 45s -fuzz burst
PASS (143k execs, no crash).

Signed-off-by: Andrei Beshkov <andrey.beshkov@gmail.com>
@sonarqubecloud

Copy link
Copy Markdown

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.

1 participant