Skip to content

neato mode=KK model=circuit: C indexes the conductance matrix by AGSEQ (1-based) and crashes

Kind
Upstream C behaviour
Severity
high
Engine
neato
Category
layout
Cases
1
Seeds
33861
Tracking
GitHub issuewon't fix
Labels
fuzzengine:neatoupstream-cwontfixcrash

With mode=KK and model=circuit, C Graphviz 16.1.0 crashes even on a connected triangle. circuit_model fills the nG x nG conductance matrix with Gm[i][j] where i, j are AGSEQ of the edge's endpoints. Node sequence numbers start at 1 (and on a packed component they are root-graph numbers), so the last node writes through Gm[nG], a pointer read past the row-pointer array. C dies with SIGSEGV/SIGBUS, or, when the stray pointer happens to be writable (as in fuzz case 33861, which crashes only in some runs), lays out from a corrupted matrix. The port indexes by the node's layout id (ND_id), as every other neato distance model does, and produces a drawing.

Reproducer

graph G {
layout=neato;
mode="KK";
model="circuit";
a -- b;
b -- c;
c -- a;
}
GVBINDIR=<pango-free plugins> dot -Kneato -Tsvg repro.gv   # C Graphviz 16.1.0
graphviz-pure -Kneato -Tsvg repro.gv                             # port

C output vs port

C:    exit status 139 (SIGSEGV), no output (every run on the 3-node reproducer;
      2-4 of 10 runs on the fuzz case s33861, the other runs print an SVG)
port: exit status 0, SVG of the triangle

Root cause

C: lib/neatogen/circuit.c:44-50 uses AGSEQ(agtail(e))/AGSEQ(aghead(e)) as matrix indices; AGSEQ comes from agnextseq (lib/cgraph/graph.c:156-159, pre-increment, so the first node is 1). new_array (lib/neatogen/stuff.c:39-51) allocates exactly nG row pointers. Called from kkNeato (lib/neatogen/neatoinit.c:1245-1256). Crash reports for the fuzz case show EXC_BAD_ACCESS in circuit_model < neatoLayout < neato_layout. (The stress-majorization path, model=circuit without mode=KK, uses lib/neatogen/stress.c's own circuit_model on the vertex array and does not crash.)

Port: crates/graphviz-pure-layout/src/neatogen/stuff.rs:445-458 (circuit_model_kk) indexes by g.nodes[..].id, the 0-based node index within the component.

The port should not reproduce a wild write. Recommend wontfix (upstream bug: use ND_id as shortest_path and subset_model do).

Cases

1 fuzz case, seeds 33861. Each regenerates byte for byte with python3 -I scripts/fuzz-gen.py --seed N (checked for all of them).

Files

tests/fuzz-open/023-neato-circuit-kk-agseq-index-crash

C and the port

No pair of C and port outputs in this reproducer.

c.stderr
[no output; dot was killed by signal 11 (SIGSEGV) in circuit_model]

Reproduce

repro.gv
graph G {
layout=neato;
mode="KK";
model="circuit";
a -- b;
b -- c;
c -- a;
}
cmd.txt
C: GVBINDIR=<pango-free plugins> dot -Kneato -Tsvg repro.gv
port: graphviz-pure -Kneato -Tsvg repro.gv
cases.txt
r4/diff/s33861.gv seed=33861

Files: c.stderr, cases.txt, cmd.txt, repro.gv, rust.svg. Source: tests/fuzz-open/023-neato-circuit-kk-agseq-index-crash.

All open issues