Not experienced with col, and not looked into too much detail, but this
fix looks wrong to me. main() tells flush_lines() to flush a number of
lines, but there's not that many lines available. So simply ignoring a
NULL-pointer, because the math is off, means it can go off the rails
at some other point. In this case flush_lines() gets a value of 60,
which clearly doesn't match the input.
By the looks of it the counting goes off the rails at line 302, which
means that there's lots of unexplained lines being linked. Can you look
at where these come from?
martijn@
On 4/2/26 07:48, Renaud Allard wrote:
> My previous report for col(1) was wrong. I described a signed
> integer overflow in the counting sort, but the actual bug is a
> null pointer dereference in flush_lines().
>
> Apologies for the confusion. Corrected report follows.
>
> The flush_lines() function dereferences a null pointer when
> nflush exceeds the number of available lines.
>
> At col.c:325-326:
>
> l = lines;
> lines = l->l_next;
>
> A reverse line feed (vertical tab, \v) near the start of input
> causes extra lines to be allocated before the first line. When
> these extra lines are later flushed, the line list is exhausted
> before nflush reaches zero, leaving lines == NULL.
>
> The 5-byte input "1\v2\n\n" reproduces the crash.
>
> Confirmed with UBSan on OpenBSD 7.9/amd64 and 7.9/i386:
>
> ubsan: type-mismatch by 0x...
> Abort trap (core dumped)
>
> Also confirmed with SIGSEGV on the system binary on OpenBSD
> 7.8/arm64, 7.9/i386, and 7.9/amd64.
>
> Fix: check for NULL before dereferencing.
>
> Found by AFL++ fuzzing.
>
> Index: usr.bin/col/col.c
> ===================================================================
> RCS file: /cvs/src/usr.bin/col/col.c,v
> retrieving revision 1.20
> diff -u -p -r1.20 col.c
> --- usr.bin/col/col.c 4 Dec 2022 23:50:47 -0000 1.20
> +++ usr.bin/col/col.c
> @@ -322,7 +322,8 @@ flush_lines(int nflush)
> LINE *l;
>
> while (--nflush >= 0) {
> - l = lines;
> + if ((l = lines) == NULL)
> + break;
> lines = l->l_next;
> if (l->l_line) {
> flush_blanks();
>