secure_sscanf.c (451 lines of code) (raw):

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA /** * NAME * secure_sscanf - secure replacement for sscanf() * * SYNOPSIS * #include "secure_sscanf.h" * * int * secure_sscanf(const char *pInputString, int *pStatus, const char *pFmt, ... ) * * DESCRIPTION * The secure_sscanf() function is a secure replacement for sscanf(). It * scans formatted input from the string str according to the format string * format, as described in the FORMATS section below. secure_sscanf() * differs from sscanf() in the following ways: * * 1. Types are passed using ARG_TYPE() macros. * 2. Output values are initialized even on errors. * 3. Numeric conversion errors will be propagated. * 4. The '%s' format specifier takes an additional argument, a size_t of the * destination buffer. * 5. Output is guaranteed to be \0 terminated. * 6. Max string output is capped to MAX_STRING_OUTPUT (4 * PAGE_SIZE). * * FORMATS * The format string is composed of zero or more directives: one or more * white-space characters, an ordinary character (neither '%' nor a white- * space character), or a conversion specification. Each conversion * specification is introduced by the character '%' after which the following * appear in sequence: * * %c - matches a character or a sequence of characters * %s - matches a sequence of non-whitespace characters (a string) * %d - Matches an optionally signed decimal integer * %u - Matches an unsigned decimal integer * %x - Matches an unsigned hexadecimal integer * %f - Matches an optionally signed floating-point number * * The arguments for the format string specifiers are passed through the following macros: * * ARG_STR(&buf, sizeof(buf)) for strings (%s) * ARG_INT(&int1) for integers (%d, %u, %x) * ARG_FLOAT(&float1) for floats (%f) * ARG_CHAR(&char1) for char (%c) * RETURN VALUE * The secure_sscanf() function returns the number of input items * successfully matched and assigned; this can be fewer than provided for, * or even zero, in the event of an early matching failure between the input * string and a directive. * * The pStatus argument will be set to one of the following values: * * ERROR_SUCCESS * ERROR_NUMERIC_CONVERSION * ERROR_BUFFER_TOO_SMALL * ERROR_INVALID_FMT * ERROR_INVALID_TYPE * * EXAMPLES * * ret = secure_sscanf(pInput, &status, "%s %f %c", * ARG_STR(&string1, sizeof(string1)), * ARG_FLOAT(&float1), * ARG_CHAR(&char1)); * * if (ret != 3 || status != ERROR_SUCCESS) * // handle error * * SEE ALSO * sscanf(3) * */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <math.h> #include <limits.h> #include "secure_sscanf.h" ALWAYS_INLINE char * skip_spaces(const char *pInputString) { while (isspace(*pInputString)) { ++pInputString; } return (char *)pInputString; } const char * parse_arg(int *pError, const char *pFmt, const char *pInputString, va_list *args) { const char *pCurrent = pInputString; const char *pParse = pInputString; char trimCurr[MAX_STRING_OUTPUT] = { 0 }; char *pEnd = NULL; char *pFmtEnd = NULL; void *pOutput = NULL; float fRet = 0.0f; long lRet = 0; long long llRet = 0; unsigned long ulRet = 0; unsigned long long ullRet = 0; int iBase = 10; int iType = 0; size_t cbSize = 0; unsigned int uWidth = 0; unsigned char useTrim = 0; PUT_VALUE(pError, ERROR_SUCCESS); lRet = strtol(pFmt, &pFmtEnd, 10); if (((lRet == HUGE_VALF || lRet == -HUGE_VALF) && (errno == ERANGE)) || (lRet > UINT_MAX)) { PUT_VALUE(pError, ERROR_INVALID_FMT); return pCurrent; } uWidth = lRet; if (uWidth > 0) { useTrim = 1; strncpy(trimCurr, pCurrent, uWidth); trimCurr[uWidth] = '\0'; pParse = trimCurr; } else { pParse = pCurrent; } pFmt = pFmtEnd; iType = va_arg(*args, int); long spType = TYPE_DEFAULT; switch (*pFmt) { case 'h': { pFmt++; if (*pFmt == 'h') { pFmt++; spType = TYPE_CHAR; } else { spType = TYPE_SHORT; } break; } case 'I': { pFmt++; if (*pFmt == '6') { pFmt++; if (*pFmt == '4') { pFmt++; spType = TYPE_LLONG; break; } } PUT_VALUE(pError, ERROR_INVALID_FMT); return skip_spaces(pCurrent); } case 'l': { pFmt++; if (*pFmt == 'l') { pFmt++; spType = TYPE_LLONG; } else { spType = TYPE_LONG; } break; } default: break; } switch (*pFmt) { case 'f': { VALIDATE_FMT_TYPE(TYPE_FLOAT); pOutput = (void *)va_arg(*args, float *); PUT_VALUE((float *)pOutput, 0.0f); fRet = strtof(pParse, &pEnd); if (pEnd == pParse) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } if (useTrim) { while (uWidth-- > 0 && *pParse != *pEnd) { pCurrent++; pParse++; } } else { pCurrent = pEnd; } if (((fRet == HUGE_VALF || fRet == -HUGE_VALF)) && (errno == ERANGE)) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((float *)pOutput, fRet); break; } case 'i': { if (*pCurrent == '0') { if (*(pCurrent + 1) == 'x') { iBase = 16; } else { iBase = 8; } } FALL_THROUGH; } case 'd': { VALIDATE_FMT_TYPE(spType == TYPE_DEFAULT ? TYPE_INT : spType); switch (spType) { case TYPE_CHAR: { pOutput = (void *)va_arg(*args, char *); PUT_VALUE((char *)pOutput, 0); lRet = strtol(pParse, &pEnd, iBase); break; } case TYPE_SHORT: { pOutput = (void *)va_arg(*args, short *); PUT_VALUE((short *)pOutput, 0); lRet = strtol(pParse, &pEnd, iBase); break; } case TYPE_LONG: { pOutput = (void *)va_arg(*args, long *); PUT_VALUE((long *)pOutput, 0); lRet = strtol(pParse, &pEnd, iBase); break; } case TYPE_LLONG: { pOutput = (void *)va_arg(*args, long long *); PUT_VALUE((long long *)pOutput, 0); llRet = strtoll(pParse, &pEnd, iBase); break; } case TYPE_DEFAULT: { FALL_THROUGH; } default: { pOutput = (void *)va_arg(*args, unsigned int *); PUT_VALUE((int *)pOutput, 0); lRet = strtol(pParse, &pEnd, iBase); } } if (pEnd == pParse) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } if (useTrim) { while (uWidth-- > 0 && *pParse != *pEnd) { pCurrent++; pParse++; } } else { pCurrent = pEnd; } if ((lRet == HUGE_VAL) && (errno == ERANGE)) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } switch (spType) { case TYPE_CHAR: { if (lRet > CHAR_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((char *)pOutput, lRet); break; } case TYPE_SHORT: { if (lRet > SHRT_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((short *)pOutput, lRet); break; } case TYPE_LONG: { if (lRet > LONG_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((long *)pOutput, lRet); break; } case TYPE_LLONG: { if (llRet > LLONG_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((long long *)pOutput, llRet); break; } case TYPE_DEFAULT: { FALL_THROUGH; } default: { if (lRet > INT_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((int *)pOutput, lRet); } } break; } case 'X': { FALL_THROUGH; } case 'x': { iBase = 16; FALL_THROUGH; } case 'u': { // Up by 1 to "unsigned" type unsigned long type = spType == TYPE_DEFAULT ? TYPE_UINT : spType + 1; VALIDATE_FMT_TYPE(type); switch (type) { case TYPE_UCHAR: { pOutput = (void *)va_arg(*args, unsigned char *); PUT_VALUE((unsigned char *)pOutput, 0); ulRet = strtoul(pParse, &pEnd, iBase); break; } case TYPE_USHORT: { pOutput = (void *)va_arg(*args, unsigned short *); PUT_VALUE((unsigned short *)pOutput, 0); ulRet = strtoul(pParse, &pEnd, iBase); break; } case TYPE_ULONG: { pOutput = (void *)va_arg(*args, unsigned long *); PUT_VALUE((unsigned long *)pOutput, 0); ulRet = strtoul(pParse, &pEnd, iBase); break; } case TYPE_ULLONG: { pOutput = (void *)va_arg(*args, unsigned long long *); PUT_VALUE((unsigned long long *)pOutput, 0); ullRet = strtoull(pParse, &pEnd, iBase); break; } case TYPE_DEFAULT: { FALL_THROUGH; } default: { pOutput = (void *)va_arg(*args, unsigned int *); PUT_VALUE((unsigned int *)pOutput, 0); ulRet = strtoul(pParse, &pEnd, iBase); } } if (pEnd == pCurrent) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } if (useTrim) { while (uWidth-- > 0 && *pParse != *pEnd) { pCurrent++; pParse++; } } else { pCurrent = pEnd; } if ((ulRet == HUGE_VAL) && (errno == ERANGE)) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } switch (type) { case TYPE_UCHAR: { if (ulRet > UCHAR_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((unsigned char *)pOutput, ulRet); break; } case TYPE_USHORT: { if (ulRet > USHRT_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((unsigned short *)pOutput, ulRet); break; } case TYPE_ULONG: { if (ulRet > ULONG_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((unsigned long *)pOutput, ulRet); break; } case TYPE_ULLONG: { if (ullRet > ULLONG_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((unsigned long long *)pOutput, ullRet); break; } case TYPE_DEFAULT: { FALL_THROUGH; } default: { if (lRet > UINT_MAX) { PUT_VALUE(pError, ERROR_NUMERIC_CONVERSION); return skip_spaces(pCurrent); } PUT_VALUE((unsigned int *)pOutput, ulRet); } } break; } case 'c': { VALIDATE_FMT_TYPE(TYPE_CHAR); pOutput = (void *)va_arg(*args, unsigned char *); PUT_VALUE((char *)pOutput, *pInputString); break; } case 's': { VALIDATE_FMT_TYPE(TYPE_STRING); pOutput = (void *)va_arg(*args, char *); cbSize = va_arg(*args, size_t); if (!pOutput) return pCurrent; if (cbSize > MAX_STRING_OUTPUT) { PUT_VALUE(pError, ERROR_BUFFER_TOO_SMALL); return pCurrent; } if (cbSize) { memset(pOutput, '\0', useTrim ? uWidth : cbSize); cbSize--; while (pCurrent[0] != '\0' && !isspace(pCurrent[0]) && cbSize != 0 && (useTrim ? uWidth-- > 0 : 1)) { #ifdef _MSC_VER *((char*)pOutput)++ = *pCurrent++; #else *(char *)pOutput++ = *pCurrent++; #endif cbSize--; } if (cbSize == 0) { /* Truncated ... Should this condition be propogated ? */ while (pCurrent[0] != '\0' && !isspace(pCurrent[0])) pCurrent++; } } break; } default: { PUT_VALUE(pError, ERROR_INVALID_FMT); return pCurrent; } } return skip_spaces(pCurrent); } int secure_sscanf(const char *pInputString, int *pStatus, const char *pFmt, ... ) { const char *pFmtEnd = NULL; int error = 0; int ret = 0; if (!pInputString || !pFmt || !pStatus) return 0; *pStatus = ERROR_SUCCESS; va_list args; va_start(args, pFmt); pFmtEnd = pFmt + strlen(pFmt); while (pFmt[0] != '\0' && pFmt < pFmtEnd && pInputString[0] != '\0') { if (pFmt[0] == '%') { const char *tmp = parse_arg(&error, &pFmt[1], pInputString, &args); if (error != ERROR_SUCCESS) { *pStatus = error; return ret; } if (tmp == pInputString) { break; } if (pFmt[1] != '%') { ++ret; } ++pFmt; while ((pFmt[0] >= '0' && pFmt[0] <= '9') ||pFmt[0] == 'h' || pFmt[0] == 'l') { ++pFmt; } ++pFmt; pInputString = tmp; } else if (isspace(pFmt[0])) { ++pFmt; pInputString = skip_spaces(pInputString); } else if (pFmt[0] == pInputString[0]) { ++pFmt; ++pInputString; } else { break; } } va_end(args); return ret; }