1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-02-12 19:22:41 +00:00

Fix multiple buffer overflows with justified strings (-L/-R/-Z)

ksh crashed in various different and operating system-dependent
ways when attempting to create or apply justification strings
using typeset -L/-R/-Z, especially if large sizes are used.

The crashes had two immediate causes:
- In nv_newattr(), when applying justification attributes, a buffer
  was allocated for the justified string that was exactly 8 bytes
  longer than the original string. Any larger justification string
  caused a buffer overflow (!!!).
- In nv_putval(), when applying existing attributes to a new value,
  the corresponding memmove() either did not zero-terminate the
  justified string (if the original string was longer than the
  justified string) or could read memory past the original string
  (if the original string was shorter than the justified string).
  Both scenarios can cause a crash.

This commit fixes other minor issues as well, such as a mysterious
8 extra bytes allocated by several malloc/realloc calls. This may
have been some naive attempt to paper over the above bugs. It seems
no one can make any other kind of sense of it.

A readjustment bug with zero-filling was also fixed.

src/cmd/ksh93/sh/name.c:
- nv_putval():
  . Get rid of the magical +8 bytes for malloc and realloc. Just
    allocate one extra byte for the terminating zero.
  . Fix the memmove operation to use strncpy instead, so that
    buffer overflows are avoided in both scenarios described above.
    Also make it conditional upon a size adjustment actually
    happening (i.e. if 'dot' is nonzero).
  . Mild refactoring: combine two 'if(sp)' blocks into one;
    declare variables only used there locally for legibility.
- nv_newattr():
  * Replace the fatally broken "let's allocate string length + 8
    bytes no matter the size of the adjustment" routine with a new
    one based on work by @hyenias (see comments in #142). It is
    efficient with memory, taking into account numeric types,
    growing strings, and shrinking strings.
  * Fix zero-filling in readjustment after changing the initial
    size of a -Z attribute. If the number was zero, all zeros were
    still skipped, leaving an empty string.

Thanks to @hyenias for originally identifying this breakage and
laying the groundwork for fixing nv_newattr(), and to @lijog for
the crash analysis that revealed the key to the nv_putval() fix.

Resolves: https://github.com/ksh93/ksh/issues/142
Resolves: https://github.com/ksh93/ksh/issues/181
This commit is contained in:
Martijn Dekker 2021-02-20 12:27:53 +00:00
parent a959a35291
commit bdb997415d
3 changed files with 70 additions and 25 deletions

6
NEWS
View file

@ -8,6 +8,12 @@ Any uppercase BUG_* names are modernish shell bug IDs.
- Fixed a bug introduced on 2021-01-20: if a DEBUG trap action yielded exit
status 2, the execution of the next command was not skipped as documented.
- Fixed multiple buffer overflows causing crashes in typeset -L/-R-/-Z.
- Fixed typeset -Z zero-filling: if the number was zero, all zeros
were skipped when changing the initial size value of the -Z attribute,
leaving an empty string.
2021-02-18:
- A bug was fixed in the 'read' builtin that caused it to fail to process

View file

@ -1573,9 +1573,7 @@ void nv_putval(register Namval_t *np, const char *string, int flags)
Shell_t *shp = sh_getinterp();
register const char *sp=string;
register union Value *up;
register char *cp;
register int size = 0;
register int dot;
int was_local = nv_local;
union Value u;
#if SHOPT_FIXEDARRAY
@ -1788,7 +1786,7 @@ void nv_putval(register Namval_t *np, const char *string, int flags)
else
{
const char *tofree=0;
int offset=0,append;
int offset = 0;
#if _lib_pathnative
char buff[PATH_MAX];
#endif /* _lib_pathnative */
@ -1871,9 +1869,13 @@ void nv_putval(register Namval_t *np, const char *string, int flags)
tofree = 0;
if(nv_isattr(np,NV_LJUST|NV_RJUST) && nv_isattr(np,NV_LJUST|NV_RJUST)!=(NV_LJUST|NV_RJUST))
tofree = 0;
if (sp)
if(!sp)
up->cp = NIL(char*);
else
{
append=0;
char *cp = NIL(char*); /* pointer to new string */
int dot; /* attribute or type length; defaults to string length */
int append = 0; /* offset for appending */
if(sp==up->cp && !(flags&NV_APPEND))
return;
dot = strlen(sp);
@ -1953,25 +1955,22 @@ void nv_putval(register Namval_t *np, const char *string, int flags)
{
if(tofree && tofree!=Empty && tofree!=Null)
{
cp = (char*)realloc((void*)tofree,((unsigned)dot+append+8));
cp = (char*)realloc((void*)tofree,((unsigned)dot+append+1));
tofree = 0;
}
else
cp = (char*)malloc(((unsigned)dot+8));
cp = (char*)malloc(((unsigned)dot+append+1));
cp[dot+append] = 0;
nv_offattr(np,NV_NOFREE);
}
}
}
else
cp = 0;
up->cp = cp;
if(sp)
{
int c = cp[dot+append];
memmove(cp+append,sp,dot);
cp[dot+append] = c;
if(dot)
{
char c = cp[dot+append];
strncpy(cp+append, sp, dot+1);
cp[dot+append] = c;
}
up->cp = cp;
if(nv_isattr(np, NV_RJUST) && nv_isattr(np, NV_ZFILL))
rightjust(cp,size,'0');
else if(nv_isattr(np, NV_LJUST|NV_RJUST)==NV_RJUST)
@ -3029,9 +3028,41 @@ void nv_newattr (register Namval_t *np, unsigned newatts, int size)
if (sp = nv_getval(np))
{
if(nv_isattr(np,NV_ZFILL))
while(*sp=='0') sp++;
cp = (char*)malloc((n=strlen (sp)) + 8);
strcpy(cp, sp);
{
while(*sp=='0') sp++; /* skip initial zeros */
if(!*sp) sp--; /* if number was 0, leave one zero */
}
n = strlen(sp);
if(size==0 || (newatts&(NV_INTEGER|NV_BINARY)))
{
/* allocate to match existing value for numerics and auto length assignment for -L/R/Z */
cp = (char*)malloc((size_t)n + 1);
strcpy(cp, sp);
}
else if(size>=n)
{
/* growing string */
cp = (char*)malloc((size_t)size + 1);
strcpy(cp, sp);
}
else
{
/* shrinking string */
cp = (char*)malloc((size_t)size + 1);
if(newatts&NV_RJUST)
strncpy(cp, n - size + sp, size);
else
{
/* NV_LJUST and the rest */
if(newatts&NV_ZFILL)
{
while(*sp=='0') sp++; /* skip initial zeros */
if(!*sp) sp--; /* if number was 0, leave one zero */
}
strncpy(cp, sp, size);
}
cp[size] = '\0';
}
if(sp && (mp=nv_opensub(np)))
{
if(trans)

View file

@ -526,11 +526,11 @@ typeset -A expect=(
[F12]='typeset -x -r -F 12 foo'
[H]='typeset -x -r -H foo'
[L]='typeset -x -r -L 0 foo'
# [L17]='typeset -x -r -L 17 foo' # TODO: outputs '-L 0'
# [L17]='typeset -x -r -L 17 foo' # TODO: outputs 'typeset -x -r -L 0 foo'
[Mtolower]='typeset -x -r -l foo'
[Mtoupper]='typeset -x -r -u foo'
[R]='typeset -x -r -R 0 foo'
# [R17]='typeset -x -r -R 17 foo' # TODO: outputs '-L 0'
# [R17]='typeset -x -r -R 17 foo' # TODO: outputs 'typeset -x -r -R 0 foo'
[X17]='typeset -x -r -X 17 foo'
[S]='typeset -x -r foo'
[T]='typeset -x -r foo'
@ -615,19 +615,27 @@ done
unset expect
# ======
# Bug introduced in 0e4c4d61: could not alter the size of an existing justified string attribute
# https://github.com/ksh93/ksh/issues/142#issuecomment-780931533
got=$(unset s; typeset -L 100 s=h; typeset +p s; typeset -L 5 s; typeset +p s)
exp=$'typeset -L 100 s\ntypeset -L 5 s'
[[ $got == "$exp" ]] || err_exit 'cannot alter size of existing justified string attribute' \
"expected $(printf %q "$exp"), got $(printf %q "$got")"
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
got=$(unset s; typeset -L 100 s=h; typeset +p s; typeset -R 5 s; typeset +p s)
exp=$'typeset -L 100 s\ntypeset -R 5 s'
[[ $got == "$exp" ]] || err_exit 'cannot set -R after setting -L' \
"expected $(printf %q "$exp"), got $(printf %q "$got")"
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
got=$(unset s; typeset -L 100 s=h; typeset +p s; typeset -Z 5 s; typeset +p s)
exp=$'typeset -L 100 s\ntypeset -Z 5 -R 5 s'
[[ $got == "$exp" ]] || err_exit 'cannot set -Z after setting -L' \
"expected $(printf %q "$exp"), got $(printf %q "$got")"
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
# Old bug: zero-filling zero would cause the zero number to disappear
# https://github.com/ksh93/ksh/issues/142#issuecomment-782013674
got=$(typeset -i x=0; typeset -Z5 x; echo $x; typeset -Z3 x; echo $x; typeset -Z7 x; echo $x)
exp=$'00000\n000\n0000000'
[[ $got == "$exp" ]] || err_exit 'failed to zero-fill zero' \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
# ======
exit $((Errors<125?Errors:125))