The c_pos and c_width fields in the CHAR structure and the local
variables w and wt in mfilter() are signed int.  Repeated tab
characters cause c_pos to accumulate without bound, eventually
overflowing.

The tab expansion code at line 202 computes:

  wt = (obuf[col - 1].c_pos + 8) & ~7;

and then at line 213:

  obuf[col++].c_pos = ++w;

where w starts from the previous c_pos.  With enough tabs
(especially combined with carriage returns that reset the column
but not the position), c_pos overflows signed int.

The same overflow occurs at lines 284, 309, and 314 where c_pos
is computed as the previous position plus a character width.

Fix: change c_pos, c_width, w, and wt from int to unsigned int.
These values are always non-negative and the unsigned arithmetic
wraps deterministically instead of being undefined behavior.

Found by AFL++ fuzzing with UBSan.

Index: usr.bin/ul/ul.c
===================================================================
RCS file: /cvs/src/usr.bin/ul/ul.c,v
retrieving revision 1.23
diff -u -p -r1.23 ul.c
--- usr.bin/ul/ul.c     16 Oct 2016 11:28:54 -0000      1.23
+++ usr.bin/ul/ul.c
@@ -65,8 +65,8 @@ int   must_use_uc, must_overstrike;
 struct CHAR    {
        char    c_mode;
        wchar_t c_char;
-       int     c_width;
-       int     c_pos;
+       unsigned int    c_width;
+       unsigned int    c_pos;
 } ;

 struct CHAR    obuf[MAXBUF];
@@ -164,7 +164,8 @@ mfilter(FILE *f)
 {
        struct CHAR     *cp;
        wint_t           c;
-       int              skip_bs, w, wt;
+       int              skip_bs;
+       unsigned int     w, wt;

        col = 1;
        skip_bs = 0;

Reply via email to