The chars[] lookup table in deroff(1) is declared as char chars[128] but is indexed by byte values from input which range from 0 to 255. Any input byte >= 128 causes an out-of-bounds read.
The table is accessed at 13 call sites, including: 606: if (chars[c] == PUNCT) 751: if (c2 != -1 && chars[c2] == PUNCT) 860: if (... chars[(unsigned char)s[0]] == LETTER && 897: while (chars[(unsigned char)*p1] < DIGIT) 901: for (p = p1 ; (i = chars[(unsigned char)*p]) != SPECIAL ; ++p) All indices can be 0-255 (from getc() or unsigned char casts). Values >= 128 read past the end of the 128-byte array. The out-of-bounds read at line 897 and 901 is particularly dangerous: if the garbage value satisfies the loop condition, the loop walks past the string's null terminator, eventually reading unmapped memory. AFL++ fuzzing found 64 crashes (37 SIGSEGV, 27 UBSan/SIGILL). Fix: expand chars[] from 128 to 256 entries. Entries 128-255 are zero-initialized (SPECIAL), which is correct: non-ASCII bytes should not be treated as letters, digits, or punctuation. Index: usr.bin/deroff/deroff.c =================================================================== RCS file: /cvs/src/usr.bin/deroff/deroff.c,v retrieving revision 1.18 diff -u -p -r1.18 deroff.c --- usr.bin/deroff/deroff.c 27 Sep 2023 21:06:33 -0000 1.18 +++ usr.bin/deroff/deroff.c @@ -133,7 +133,7 @@ int intable; int keepblock; /* keep blocks of text; normally false when msflag */ -char chars[128]; /* SPECIAL, PUNCT, APOS, DIGIT, or LETTER */ +char chars[256]; /* SPECIAL, PUNCT, APOS, DIGIT, or LETTER */ size_t linesz; char *line;
