Package: posh
Version: 0.14.2
Severity: serious
Tags: security patch

X-Debbugs-CC: [email protected]

Dear Maintainer,

While building posh 0.14.2 from the AUR on CachyOS, the compiler reports a 
clear heap buffer overflow:

tree.c: In function 'vfptreef':
tree.c:422:13: warning: writing 1 byte into a region of size 0 
[-Wstringop-overflow=]
422 | p[20] = '\0';
| ^
tree.c:420:18: note: at offset 20 into destination object of size 20 allocated 
by 'malloc'
420 | p = q = malloc(20);
| ^

Exact buggy code with context (tree.c lines 416–426):

 case 'd': case 'u': /* decimal */
n = (c == 'd') ? va_arg(va, int)
: va_arg(va, unsigned int);
neg = c=='d' && n<0;
p = q = malloc(20);
snprintf(p, 19, "%ld", (neg) ? -n : n);
p[20] = '\0';

if (neg)
*--p = '-';
while (*p)
tputc(*p++, shf);

free(q);
break;

The manual `p[20] = '\0'` is redundant because `snprintf(p, 19, ...)` already 
guarantees NULL-termination within the 20-byte buffer. Snipping relevant bits 
from the POSIX manual (https://www.unix.com/man_page/posix/3posix/snprintf/):

 
>       int snprintf(char *restrict s, size_t n,
>             const char *restrict format, ...);

[...]

>       The  snprintf() function shall be equivalent to sprintf(), with the 
> addition of the n argument which states the size of the buffer referred
>       to by s. If n is zero, nothing shall be written and s may be a null 
> pointer.  Otherwise, output bytes beyond the n-1st shall  be  discarded
>       instead of being written to the array, and a null byte is written at 
> the end of the bytes actually written into the array.


(Similar, arguably more clear, language is included in manual pages of more 
modern C Library implementations, and the functionality of \0' termination in 
snprintf() has been carried forward -- but the POSIX manual is quoted here 
because posh aims to be maximally compatible.)

The minimal and obviously correct fix is to just delete the offending line 
(tested: warning disappears):

--- a/tree.c
+++ b/tree.c
@@ -419,8 +419,6 @@
neg = c=='d' && n<0;
p = q = malloc(20);
snprintf(p, 19, "%ld", (neg) ? -n : n);
- p[20] = '\0';
-
if (neg)
*--p = '-';
while (*p)

Suggested Future Robustness Improvements:

1. The snprintf() above may fail for large numbers due to the "19" being passed 
as buffer size... It would be safest, less confusing and likely most performant 
to both malloc() a buffer of 24 bytes and pass the buffer size of 24 to 
snprintf(). The number of bytes that may be needed by snprintf() to convert a 
long positive integer on a 64-bit CPU to (NULL-terminated) string is not 19 
bytes, but 20 bytes (it's 21 bytes if we were to need the negative sign); 
however, it would IMO be preferable to use a buffer of 24 as such an allocation 
is byte-aligned to 64 bits and stops us from trying to be too clever in the 
code. I'd think readers would also appreciate if the buffer size requirement 
would be commented in the code so that folks (like me) would not need or be too 
tempted to go GROK-ing for the answer.

2. The return of malloc() should be checked that it is non-NULL universally 
throughout the code.

3. The return of snprintf() should also be sanity-checked universally 
throughout the code.

Because this is possibly a reachable heap buffer overflow [depending on the 
behavior of malloc()], I have marked it "serious" and CC'd the security team.

Thank you!

Kind regards,

Michael Back <[email protected]>

Reply via email to