Hello posh folks,

I took another look at this section of code, and found another out-of-bounds... 
but this time an underflow:

tree.c:425: *--p = '-';

The above writes a '-' 1 byte before the beginning of the malloc'd buffer 
pointed to by p. So, it looks like this implementation potentially exposes a 
buffer underflow and overflow. I have a patch to offer here that fixes all 
(hopefully email wont mangle it this time:

```
--- src/posh-0.14.2/tree.c 2013-05-13 02:16:42.000000000 +0800
+++ tree.c 2025-12-02 17:31:53.332119894 +0800
@@ -2,6 +2,7 @@
* command tree climbing
*/

+#include <alloca.h>
#include "sh.h"

#define INDENT 4
@@ -393,12 +394,12 @@
static void vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
{
int c;
+ char *buf; /* for integer -> string conversions */
+ const size_t sz = 24; /* safe for integer conversions */ 

while ((c = *fmt++))
if (c == '%') {
- long n;
- char *p, *q;
- int neg;
+ char *p;

switch ((c = *fmt++)) {
case 'c':
@@ -413,20 +414,21 @@
p = va_arg(va, char *);
tputS(p, shf);
break;
- 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 = '-';
+ case 'd':
+ if (buf == NULL)
+ buf = alloca(sz);
+ p = buf;
+ snprintf(p, sz, "%d", va_arg(va, int));
+ while (*p)
+ tputc(*p++, shf);
+ break;
+ case 'u':
+ if (buf == NULL)
+ buf = alloca(sz);
+ p = buf;
+ snprintf(p, sz, "%u", va_arg(va, unsigned int));
while (*p)
tputc(*p++, shf);
-
- free(q);
break;
case 'T': /* format tree */
ptree(va_arg(va, struct op *), indent, shf);
```

Notes about the patch:

The previous implementation ostensibly didn't trust that snprintf() would 
correctly translate negative numbers & attempted to support them with some 
extra logic and manipulation via pointers. Likely, the original code posh 
inherited was built on sprintf() -- that had this issue in very early versions 
of the C library. However... we are now using snprintf(). By the time that 
snprintf() was included in the C standard library, that particular behavior was 
fixed. We should be able to safely simplify the code such that we just let 
snprintf() handle negative numbers for us.

Also, because we are using snprintf() in the code, another C lib feature also 
matured at the same time -- that being the behavior of alloca() -- that enables 
stack allocation rather than heap allocation. In the new implementation, we 
continue with the spirit of the code in terms of being very resource stingy and 
only alloca() when we need to; and, additionally, we keep and re-use the 
allocation until we exit the function to keep it fast -- and it should also be 
faster than the heap based method where we malloc'd and free'd every loop 
iteration.

Finally, I broke out case 'u' from case 'd', as the very minor duplication 
makes the code brain-dead simple to reason about.

Review notes:

- I pulled the 'l' from the format string in snprintf()... The reason I did so 
is that the types are defined in the va_arg(), and I don't think we need to use 
the larger type.

- A second note for review is that we stick with the original approach of not 
checking the result of snprintf() > 0 -- This is in the spirit of the original 
code, and I don't know that it matters.

- A final review note is that though we do not check the result of alloca() I 
believe that is not as important as checking the result of malloc()... An 
alloca() with such a small allocation should never overflow the stack -- and 
the host would have a much bigger systemic problem if it did.

Michael Back <[email protected]>

Reply via email to