Package: wv Version: 1.2.9-9 Severity: important Tags: security, upstream Hello,
I am reporting a reproducible security issue in Debian wv 1.2.9-9 and upstream
wvWare.
`wvGetBKD_PLCF()` in `bkd.c:73` frees the address of its `U32 **pos` parameter
instead of the heap buffer it points to:
wvFree(pos); /* incorrect */
wvFree(*pos); /* intended */
A crafted Word 97 document with `lcbPlcftxbxBkd=4` makes `*nobkd=0`, so
`wvMalloc(0)` returns NULL and the error path calls `free()` on a stack
address. No real memory-pressure condition is needed.
Reproduction:
wvConvert simple-bkd-len4.doc
wvRTF simple-bkd-len4.doc
Both terminate with:
free(): invalid pointer
ASan reports an attempted free of an address located in the `wvDecodeSimple`
stack frame at `generic.c:67` / `bkd.c:73`. The same input was also tested with
Ubuntu's prebuilt wv 1.2.9-4.2build1 binaries.
The same copy-paste error remains in nine other PLCF parsers: `ffn.c:253`,
`lst.c:163`, `pcd.c:96`, `fld.c:298`, `atrd.c:69`, `bte.c:88`, `frd.c:63`,
`ftxbxs.c:81`, and `fdoa.c:65`. I verified the BKD path end to end; these nine
sites are reported as the same source-level pattern, not as nine separate CVE
requests.
The current Debian 1.2.9-9 source still contains `wvFree(pos)` at `bkd.c:73`.
The attached patch replaces the erroneous operand with `wvFree(*pos)`.
This is distinct from CVE-2026-112055 (FKP stack out-of-bounds read),
CVE-2026-112056 (stylesheet heap out-of-bounds read), and Debian Bug #1148725
(pap.c list-index issue). Confirmed impact is process termination; the document
does not control an arbitrary free address or code execution.
Please assign a CVE if Debian considers this a vulnerability in the Debian
package. I am happy to retest a patched build.
Thanks,
netwind
[email protected]
# wvWare invalid free in wvGetBKD_PLCF
## Summary
`wvGetBKD_PLCF()` frees the address of its `U32 **pos` parameter instead of the
heap buffer it points to. On the allocation-failure path the code executes
`wvFree(pos)` rather than `wvFree(*pos)`, so a malformed Word document that makes
the second allocation return NULL makes wvWare call `free()` on a stack address.
The same copy-paste mistake appears in several other PLCF parsers in the
library.
## Affected
- wvWare 1.2.9 (current release)
- `wvWare`, `wvConvert`, `wvRTF` and other utilities linked against libwv
## Reproduction
```
wvConvert simple-bkd-len4.doc
wvRTF simple-bkd-len4.doc
```
Both abort with:
```
free(): invalid pointer
```
The crash also reproduces on the prebuilt `wvConvert` and `wvRTF` binaries from
the Ubuntu/Debian `wv` package (`wv_1.2.9-4.2build1`), so it is not an artifact
of the local build.
Under ASan:
```
ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed
#1 _wvFree generic.c:67
#2 wvGetBKD_PLCF bkd.c:73
```
The PoCs are normal Word 97 documents with one FIB field changed:
`lcbPlcftxbxBkd` is set to 4 (or 13). With that length the parser computes
`*nobkd = 0`, so `wvMalloc(0 * sizeof(BKD))` returns NULL and the error path
runs.
## Root cause
`bkd.c:67-75`:
```c
*bkd = (BKD *) wvMalloc (*nobkd * sizeof (BKD));
if (*bkd == NULL)
{
...
wvFree (pos); /* <-- frees the parameter, not *pos */
return (1);
}
```
`wvMalloc(0)` returns NULL by design, so any length that yields zero entries
reaches this path.
## Same pattern elsewhere
The identical `wvFree(pos)` mistake exists in these parsers:
- `ffn.c:253`
- `lst.c:163`
- `pcd.c:96`
- `fld.c:298`
- `atrd.c:69`
- `bte.c:88`
- `frd.c:63`
- `ftxbxs.c:81`
- `fdoa.c:65`
Each is reachable from a Word document by setting the matching `lcb*` field to
a value that makes the second allocation zero-sized.
## Suggested fix
Replace every `wvFree(pos)` in those error paths with `wvFree(*pos)`, and
preferably also zero the caller's pointer after freeing.
## Attachments
- `simple-bkd-len4.doc`
- `simple-bkd-len13.doc`
wv-invalid-free.patch
Description: wv-invalid-free.patch
<<attachment: simple-bkd-len4.doc>>
<<attachment: simple-bkd-len13.doc>>
wv-fix-verify.log
Description: wv-fix-verify.log
/* wvWare
* Copyright (C) Caolan McNamara, Dom Lachowicz, and others
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include "wv.h"
void
wvGetBKD (BKD * item, wvStream * fd)
{
U16 temp16;
item->ipgd_itxbxs = (S16) read_16ubit (fd);
item->dcpDepend = (S16) read_16ubit (fd);
temp16 = read_16ubit (fd);
item->icol = temp16 & 0x00FF;
item->fTableBreak = (temp16 & 0x0100) >> 8;
item->fColumnBreak = (temp16 & 0x0200) >> 9;
item->fMarked = (temp16 & 0x0400) >> 10;
item->fUnk = (temp16 & 0x0800) >> 11;
item->fTextOverflow = (temp16 & 0x1000) >> 12;
item->reserved1 = (temp16 & 0xE000) >> 13;
}
int
wvGetBKD_PLCF (BKD ** bkd, U32 ** pos, U32 * nobkd, U32 offset, U32 len,
wvStream * fd)
{
U32 i;
if (len == 0)
{
*bkd = NULL;
*pos = NULL;
*nobkd = 0;
}
else
{
*nobkd = (len - 4) / (cbBKD + 4);
*pos = (U32 *) wvMalloc ((*nobkd + 1) * sizeof (U32));
if (*pos == NULL)
{
wvError (
("NO MEM 1, failed to alloc %d bytes\n",
(*nobkd + 1) * sizeof (U32)));
return (1);
}
*bkd = (BKD *) wvMalloc (*nobkd * sizeof (BKD));
if (*bkd == NULL)
{
wvError (
("NO MEM 1, failed to alloc %d bytes\n",
*nobkd * sizeof (BKD)));
wvFree (pos);
return (1);
}
wvStream_goto (fd, offset);
for (i = 0; i < *nobkd + 1; i++)
{
(*pos)[i] = read_32ubit (fd);
}
for (i = 0; i < *nobkd; i++)
{
wvGetBKD (&((*bkd)[i]), fd);
}
}
return (0);
}

