Hi,
more is broken in printf(3).
Ingo Schwarze wrote on Fri, Dec 25, 2015 at 12:30:29AM +0100:
> Fourth file, fourth broken file.
> This is the worst bug found so far.
[...]
> When fprintf(fp, "...%ls...", ...) encounters an encoding error,
> it trashes fp BEYOND REPAIR, clearing all FILE flags, making the
> file unreadable, unwriteable, and probably breaking even more than
> that. Calling clearerr(3) is by far insufficient to undo the
> devastating damage done.
In addition to that, the format string is parsed with mbrtowc(3),
even if PRINTF_WIDE_CHAR is *not* defined, and the failure is *not*
detected. That is, invalid bytes in the format string (for example,
0xff in an UTF-8 locale or ISO-Latin bytes in the C locale) are
silently treated as the end of the format string, without reporting
an error, only clobbering errno.
In addition to that, __find_arguments() also returns successfully
in this case, and its return value - which can already be -1 on
malloc failure - is never checked. This can result in access to
invalid memory.
Fix this by always checking the mbrtowc(3) return value against -1,
by failing at once as soon as that happens, and by always checking
__find_arguments() for success.
The diff below includes my previous diff, both are indeed related.
The new chunks are marked with ***NEW***.
OK?
Ingo
Index: stdio/vfprintf.c
===================================================================
RCS file: /cvs/src/lib/libc/stdio/vfprintf.c,v
retrieving revision 1.69
diff -u -p -r1.69 vfprintf.c
--- stdio/vfprintf.c 29 Sep 2015 03:19:24 -0000 1.69
+++ stdio/vfprintf.c 25 Dec 2015 13:33:57 -0000
@@ -165,10 +165,8 @@ __wcsconv(wchar_t *wcsarg, int prec)
memset(&mbs, 0, sizeof(mbs));
p = wcsarg;
nbytes = wcsrtombs(NULL, (const wchar_t **)&p, 0, &mbs);
- if (nbytes == (size_t)-1) {
- errno = EILSEQ;
+ if (nbytes == (size_t)-1)
return (NULL);
- }
} else {
/*
* Optimisation: if the output precision is small enough,
@@ -188,10 +186,8 @@ __wcsconv(wchar_t *wcsarg, int prec)
break;
nbytes += clen;
}
- if (clen == (size_t)-1) {
- errno = EILSEQ;
+ if (clen == (size_t)-1)
return (NULL);
- }
}
}
if ((convbuf = malloc(nbytes + 1)) == NULL)
@@ -203,7 +199,6 @@ __wcsconv(wchar_t *wcsarg, int prec)
if ((nbytes = wcsrtombs(convbuf, (const wchar_t **)&p,
nbytes, &mbs)) == (size_t)-1) {
free(convbuf);
- errno = EILSEQ;
return (NULL);
}
convbuf[nbytes] = '\0';
@@ -438,7 +433,11 @@ __vfprintf(FILE *fp, const char *fmt0, *** NEW ***
int hold = nextarg; \
if (argtable == NULL) { \
argtable = statargtable; \
- __find_arguments(fmt0, orgap, &argtable, &argtablesiz);
\
+ if (__find_arguments(fmt0, orgap, &argtable, \
+ &argtablesiz) == -1) { \
+ ret = -1; \
+ goto error; \
+ } \
} \
nextarg = n2; \
val = GETARG(int); \
@@ -494,6 +493,10 @@ __vfprintf(FILE *fp, const char *fmt0, *** NEW ***
break;
}
}
+ if (n < 0) {
+ ret = -1;
+ goto error;
+ }
if (fmt != cp) {
ptrdiff_t m = fmt - cp;
if (m < 0 || m > INT_MAX - ret)
@@ -501,7 +504,7 @@ __vfprintf(FILE *fp, const char *fmt0, *** NEW ***
PRINT(cp, m);
ret += m;
}
- if (n <= 0)
+ if (n == 0)
goto done;
fmt++; /* skip over '%' */
@@ -564,8 +567,11 @@ reswitch: switch (ch) { *** NEW ***
nextarg = n;
if (argtable == NULL) {
argtable = statargtable;
- __find_arguments(fmt0, orgap,
- &argtable, &argtablesiz);
+ if (__find_arguments(fmt0, orgap,
+ &argtable, &argtablesiz) == -1) {
+ ret = -1;
+ goto error;
+ }
}
goto rflag;
}
@@ -590,8 +596,11 @@ reswitch: switch (ch) { *** NEW ***
nextarg = n;
if (argtable == NULL) {
argtable = statargtable;
- __find_arguments(fmt0, orgap,
- &argtable, &argtablesiz);
+ if (__find_arguments(fmt0, orgap,
+ &argtable, &argtablesiz) == -1) {
+ ret = -1;
+ goto error;
+ }
}
goto rflag;
}
@@ -640,8 +649,7 @@ reswitch: switch (ch) {
mbseqlen = wcrtomb(buf,
(wchar_t)GETARG(wint_t), &mbs);
if (mbseqlen == (size_t)-1) {
- fp->_flags |= __SERR;
- errno = EILSEQ;
+ ret = -1;
goto error;
}
cp = buf;
@@ -855,7 +863,7 @@ fp_common:
} else {
convbuf = __wcsconv(wcp, prec);
if (convbuf == NULL) {
- fp->_flags = __SERR;
+ ret = -1;
goto error;
}
cp = convbuf;
@@ -1214,7 +1222,9 @@ __find_arguments(const char *fmt0, *** NEW ***
break;
}
}
- if (n <= 0)
+ if (n < 0)
+ return (-1);
+ if (n == 0)
goto done;
fmt++; /* skip over '%' */