#1148725 wv: unbounded list indices from a Word document reach a wild pointer dereference in wvAssembleSimplePAP

Package:
wv
Source:
wv
Description:
Programs for accessing Microsoft Word documents
Submitter:
Alexandru-Ionut Jipa
Date:
2026-09-27 11:27:01 UTC
Severity:
normal
Tags:
#1148725#5
Date:
2026-09-22 17:56:32 UTC
From:
To:
Filed here at Moritz Muehlenhoff's request, after reporting it to
team@security.debian.org; the reply was that there is nothing to gain from
handling it privately, given upstream's state.

wv reads two list-related fields straight out of a Word binary document's
paragraph properties and uses them as array indices with no range check:

PAP.ilvl - the list level, an unsigned 8-bit value set from sprm
sprmPIlvl (0x260A). It indexes LST::lvl, an array libwv
allocates with 9 entries (or 1 for a "simple list").

PAP.ilfo - the 1-based list-format index, a signed 16-bit value set from
sprm sprmPIlfo (0x460B). It indexes ps->lfo, an array of
ps->nolfo entries.

Both are used in wvAssembleSimplePAP() in pap.c, which runs for every
paragraph of every document. With ilvl = 255 the code forms a pointer
sizeof(LVL) * 255 = 14280 bytes past the end of a 504-byte heap allocation,
reads two pointer fields through it (grpprlPapx, numbertext) and then
dereferences one of them.

Affected lines in vanilla 1.2.9 pap.c:

pap.c:381 myLFO = &ps->lfo[apap->ilfo - 1];
pap.c:391 k = ps->lfo[i].clfolvl;
pap.c:450 myLVL = &myLST->lvl[apap->ilvl];
pap.c:506 myLVL = &myLST->lvl[apap->ilvl];

Observed fault sites: pap.c:519 (read) and pap.c:527 (dereference).

It does not need a hand-crafted document. An unmodified LibreOffice
regression-test document that contains a paragraph with list level 9,
tdf104239_chapterNumberTortureTest.doc -- its own text says "outline with
Body listLvl(9)" -- crashes the stock Debian binary 30 times out of 30.

Reproduced on Debian 13 (trixie), wv 1.2.9-8+b1, amd64, stock package, no
rebuild and no sanitizers:

$ wvText tdf104239_chapterNumberTortureTest.doc /dev/stdout
Segmentation fault

Under gdb on a release build from source:

#0 wvAssembleSimplePAP (...) at pap.c:527
#1 wvDecodeSimple (ps=..., whichdoc=Dmain) at decode_simple.c:402
#2 wvHtml (ps=...) at wvHtmlEngine.c:34
#3 main (argc=4, argv=...) at wvWare.c:489

Reachability is what makes this worth grave rather than important.

wvAssembleSimplePAP() is called once per paragraph from wvDecodeSimple()
(decode_simple.c:402), the body of both wvText() and wvHtml(). Every
consumer
of the library reaches it while reading an ordinary document; nothing
unusual
has to be enabled.

The package's own mailcap entry (debian/wv.mime) is the part worth acting on
first:

application/msword; wvMime %s; description=Microsoft Word Document;
test=test -n "$DISPLAY"; priority=1
application/msword; wvText %s /dev/stdout; description=Microsoft Word
Document; copiousoutput; priority=1

copiousoutput means a mailcap-driven MUA renders an application/msword
attachment inline by running wvText on it, so a hostile .doc attachment is
parsed when the message is displayed, with no user action beyond viewing it.

Also affected: abiword build-depends on libwv-dev and its MS Word importer
(src/wp/impexp/xp/ie_imp_MsWord_97.cpp) calls wvInitParser_gsf() at line
1156,
wvSetElementHandler() at 1211 and wvText() at 1221, so opening a .doc in
AbiWord runs the same code. Server-side text-extraction pipelines that shell
out to wvText or wvHtml are in the same position.

Upstream is effectively dead: the last commit to
https://www.google.com/url?q=http://github.com/AbiWord/wv&source=gmail&ust=1790185677489000&sa=E
was in
2014-10-25 and master carries the same unfixed code; private vulnerability
reporting is off and there is no SECURITY.md. The Debian source package is
orphaned (#816327). Debian applies no security patches to this source --
debian/patches/series contains only build, manpage and cross-compilation
changes.

Suggested fix, range-checking both indices before use:

/* ilfo is a 1-based index into ps->lfo and comes straight out of
sprmPIlfo, so it has to be range checked before it is used */
if (apap->ilfo < 1 || (U32) apap->ilfo > ps->nolfo)
return ret;

myLFO = &ps->lfo[apap->ilfo - 1];
...
/* i == apap->ilfo - 1 here, which the check above kept < ps->nolfo */
k = ps->lfo[i].clfolvl;

and an equivalent bound on apap->ilvl at both myLVL assignment sites.

I have the full patch, the crashing document, an ASan/UBSan transcript and
the
gdb session, and can send any of them on request. The patch has been
applied,
rebuilt and re-tested: the crashing document is handled cleanly and ordinary
documents still convert.