Fix for trailing %n bug in sscanf (with help from Lorenz Meier)

git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@5754 42af7a65-404d-4744-a932-0658087f49c3
This commit is contained in:
patacongo 2013-03-18 00:41:42 +00:00
parent 3185d5492b
commit 1a67cb9e1e
2 changed files with 261 additions and 141 deletions

View File

@ -4362,3 +4362,7 @@
happen only under one condition: When the kernel system call logic calls happen only under one condition: When the kernel system call logic calls
back into user space in order to allocate user space memory. So it is back into user space in order to allocate user space memory. So it is
expected that the maximum nesting level will be only 2 (2013-03-17). expected that the maximum nesting level will be only 2 (2013-03-17).
* libc/stdio/lib_sccanf.c: Correct an error in sscanf. If %n occurs in
the format statement after the input data stream has been fully
parsed, the %n format specifier will not be handled. Reported by
Lorenz Meier (and also earlier by Kate) (2013-03-17).

View File

@ -197,12 +197,12 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
noassign = false; noassign = false;
lflag = false; lflag = false;
/* NOTE that there is a flaw in this loop logic: The fmt string often /* Loop until all characters in the fmt string have been processed. We
* terminates with %n which would have to be processes at the end of the * may have to continue loop after reaching the end the input data in
* buf string. That won't happen here. * order to handle trailing %n format specifiers.
*/ */
while (*fmt && *buf) while (*fmt)
{ {
/* Skip over white space */ /* Skip over white space */
@ -218,6 +218,7 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
lvdbg("vsscanf: Specifier found\n"); lvdbg("vsscanf: Specifier found\n");
/* Check for qualifiers on the conversion specifier */ /* Check for qualifiers on the conversion specifier */
fmt++; fmt++;
for (; *fmt; fmt++) for (; *fmt; fmt++)
{ {
@ -254,6 +255,24 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
{ {
lvdbg("vsscanf: Performing string conversion\n"); lvdbg("vsscanf: Performing string conversion\n");
/* Get a pointer to the char * value. We need to do this even
* if we have reached the end of the input data in order to
* update the 'ap' variable.
*/
tv = NULL; /* To avoid warnings about beign uninitialized */
if (!noassign)
{
tv = va_arg(ap, char*);
tv[0] = '\0';
}
/* But we only perform the data conversion is we still have
* bytes remaining in the input data stream.
*/
if (*buf)
{
while (isspace(*buf)) while (isspace(*buf))
{ {
buf++; buf++;
@ -268,15 +287,19 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
width = findwidth(buf, fmt); width = findwidth(buf, fmt);
} }
/* Copy the string (if we are making an assignment) */
if (!noassign) if (!noassign)
{ {
tv = va_arg(ap, char*);
strncpy(tv, buf, width); strncpy(tv, buf, width);
tv[width] = '\0'; tv[width] = '\0';
} }
/* Update the buffer pointer past the string in the input */
buf += width; buf += width;
} }
}
/* Process %c: Character conversion */ /* Process %c: Character conversion */
@ -284,6 +307,24 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
{ {
lvdbg("vsscanf: Performing character conversion\n"); lvdbg("vsscanf: Performing character conversion\n");
/* Get a pointer to the char * value. We need to do this even
* if we have reached the end of the input data in order to
* update the 'ap' variable.
*/
tv = NULL; /* To avoid warnings about beign uninitialized */
if (!noassign)
{
tv = va_arg(ap, char*);
tv[0] = '\0';
}
/* But we only perform the data conversion is we still have
* bytes remaining in the input data stream.
*/
if (*buf)
{
/* Was a fieldwidth specified? */ /* Was a fieldwidth specified? */
if (!width) if (!width)
@ -293,15 +334,21 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
width = 1; width = 1;
} }
/* Copy the character(s) (if we are making an assignment) */
if (!noassign) if (!noassign)
{ {
tv = va_arg(ap, char*);
strncpy(tv, buf, width); strncpy(tv, buf, width);
tv[width] = '\0'; tv[width] = '\0';
} }
/* Update the buffer pointer past the character(s) in the
* input
*/
buf += width; buf += width;
} }
}
/* Process %d, %o, %b, %x, %u: Various integer conversions */ /* Process %d, %o, %b, %x, %u: Various integer conversions */
@ -309,6 +356,37 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
{ {
lvdbg("vsscanf: Performing integer conversion\n"); lvdbg("vsscanf: Performing integer conversion\n");
/* Get a pointer to the integer value. We need to do this even
* if we have reached the end of the input data in order to
* update the 'ap' variable.
*/
FAR long *plong = NULL;
FAR int *pint = NULL;
if (!noassign)
{
/* We have to check whether we need to return a long or an
* int.
*/
if (lflag)
{
plong = va_arg(ap, long*);
*plong = 0;
}
else
{
pint = va_arg(ap, int*);
*pint = 0;
}
}
/* But we only perform the data conversion is we still have
* bytes remaining in the input data stream.
*/
if (*buf)
{
/* Skip over any white space before the integer string */ /* Skip over any white space before the integer string */
while (isspace(*buf)) while (isspace(*buf))
@ -316,8 +394,8 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
buf++; buf++;
} }
/* The base of the integer conversion depends on the specific /* The base of the integer conversion depends on the
* conversion specification. * specific conversion specification.
*/ */
if (*fmt == 'd' || *fmt == 'u') if (*fmt == 'd' || *fmt == 'u')
@ -346,7 +424,9 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
width = findwidth(buf, fmt); width = findwidth(buf, fmt);
} }
/* Copy the numeric string into a temporary working buffer. */ /* Copy the numeric string into a temporary working
* buffer.
*/
strncpy(tmp, buf, width); strncpy(tmp, buf, width);
tmp[width] = '\0'; tmp[width] = '\0';
@ -364,35 +444,68 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
#else #else
long tmplong = strtol(tmp, NULL, base); long tmplong = strtol(tmp, NULL, base);
#endif #endif
/* We have to check whether we need to return a long
* or an int.
*/
if (lflag) if (lflag)
{ {
long *plong = va_arg(ap, long*); lvdbg("vsscanf: Return %ld to 0x%p\n",
lvdbg("vsscanf: Return %ld to 0x%p\n", tmplong, plong); tmplong, plong);
*plong = tmplong; *plong = tmplong;
} }
else else
{ {
int *pint = va_arg(ap, int*); lvdbg("vsscanf: Return %ld to 0x%p\n",
lvdbg("vsscanf: Return %ld to 0x%p\n", tmplong, pint); tmplong, pint);
*pint = (int)tmplong; *pint = (int)tmplong;
} }
} }
} }
}
/* Process %f: Floating point conversion */ /* Process %f: Floating point conversion */
else if (*fmt == 'f') else if (*fmt == 'f')
{ {
#ifndef CONFIG_LIBC_FLOATINGPOINT
/* No floating point conversions */
void *pv = va_arg(ap, void*);
lvdbg("vsscanf: Return 0.0 to %p\n", pv);
*((double_t*)pv) = 0.0;
#else
lvdbg("vsscanf: Performing floating point conversion\n"); lvdbg("vsscanf: Performing floating point conversion\n");
/* Get a pointer to the double value. We need to do this even
* if we have reached the end of the input data in order to
* update the 'ap' variable.
*/
#ifdef CONFIG_HAVE_DOUBLE
FAR double_t *pd = NULL;
#endif
FAR float *pf = NULL;
if (!noassign)
{
/* We have to check whether we need to return a float or a
* double.
*/
#ifdef CONFIG_HAVE_DOUBLE
if (lflag)
{
pd = va_arg(ap, double_t*);
*pd = 0.0;
}
else
#endif
{
pf = va_arg(ap, float*);
*pf = 0.0;
}
}
#ifdef CONFIG_LIBC_FLOATINGPOINT
/* But we only perform the data conversion is we still have
* bytes remaining in the input data stream.
*/
if (*buf)
{
/* Skip over any white space before the real string */ /* Skip over any white space before the real string */
while (isspace(*buf)) while (isspace(*buf))
@ -423,28 +536,27 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
{ {
/* strtod always returns a double */ /* strtod always returns a double */
#ifdef SDCC #ifdef SDCC
char *endptr; FAR char *endptr;
double_t dvalue = strtod(tmp,&endptr); double_t dvalue = strtod(tmp,&endptr);
#else #else
double_t dvalue = strtod(tmp, NULL); double_t dvalue = strtod(tmp, NULL);
#endif #endif
void *pv = va_arg(ap, void*); /* We have to check whether we need to return a float
* or a double.
lvdbg("vsscanf: Return %f to %p\n", dvalue, pv);
/* But we have to check whether we need to return a
* float or a double.
*/ */
#ifdef CONFIG_HAVE_DOUBLE #ifdef CONFIG_HAVE_DOUBLE
if (lflag) if (lflag)
{ {
*((double_t*)pv) = dvalue; lvdbg("vsscanf: Return %f to %p\n", dvalue, pd);
*pd = dvalue;
} }
else else
#endif #endif
{ {
*((float*)pv) = (float)dvalue; lvdbg("vsscanf: Return %f to %p\n", dvalue, pf);
*pf = (float)dvalue;
}
} }
} }
#endif #endif
@ -462,12 +574,12 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
if (lflag) if (lflag)
{ {
long *plong = va_arg(ap, long*); FAR long *plong = va_arg(ap, long*);
*plong = (long)nchars; *plong = (long)nchars;
} }
else else
{ {
int *pint = va_arg(ap, int*); FAR int *pint = va_arg(ap, int*);
*pint = (int)nchars; *pint = (int)nchars;
} }
} }
@ -489,13 +601,17 @@ int vsscanf(FAR char *buf, FAR const char *fmt, va_list ap)
/* Its is not a conversion specifier */ /* Its is not a conversion specifier */
else else if (*buf)
{ {
/* Skip over any leading spaces in the input buffer */
while (isspace(*buf)) while (isspace(*buf))
{ {
buf++; buf++;
} }
/* Skip over matching characters in the buffer and format */
if (*fmt != *buf) if (*fmt != *buf)
{ {
break; break;