diff --git a/SimpleIni.h b/SimpleIni.h index 386f446..e8fe52e 100644 --- a/SimpleIni.h +++ b/SimpleIni.h @@ -161,6 +161,9 @@ @section notes NOTES + - The maximum supported file size is 1 GiB (SI_MAX_FILE_SIZE). Files larger + than this will be rejected with SI_FILE error to prevent excessive memory + allocation and potential denial of service attacks. - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU. - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked. @@ -261,6 +264,10 @@ constexpr int SI_FAIL = -1; //!< Generic failure constexpr int SI_NOMEM = -2; //!< Out of memory error constexpr int SI_FILE = -3; //!< File error (see errno for detail error) +//! Maximum supported file size (1 GiB). Files larger than this will be rejected +//! to prevent excessive memory allocation and potential denial of service. +constexpr size_t SI_MAX_FILE_SIZE = 1024ULL * 1024ULL * 1024ULL; + #define SI_UTF8_SIGNATURE "\xEF\xBB\xBF" #ifdef _WIN32 @@ -357,7 +364,7 @@ class CSimpleIniTempl if (lhs.nOrder != rhs.nOrder) { return lhs.nOrder < rhs.nOrder; } - return KeyOrder()(lhs.pItem, rhs.pItem); + return KeyOrder()(lhs, rhs); } }; }; @@ -1457,9 +1464,14 @@ CSimpleIniTempl::LoadFile( if (lSize == 0) { return SI_OK; } - + + // check file size is within supported limits (SI_MAX_FILE_SIZE) + if (static_cast(lSize) > SI_MAX_FILE_SIZE) { + return SI_FILE; + } + // allocate and ensure NULL terminated - char * pData = new(std::nothrow) char[lSize+static_cast(1)]; + char * pData = new(std::nothrow) char[static_cast(lSize) + 1]; if (!pData) { return SI_NOMEM; } @@ -1511,13 +1523,18 @@ CSimpleIniTempl::LoadData( return SI_FAIL; } + // check converted data size is within supported limits (SI_MAX_FILE_SIZE) + if (uLen >= (SI_MAX_FILE_SIZE / sizeof(SI_CHAR))) { + return SI_FILE; + } + // allocate memory for the data, ensure that there is a NULL // terminator wherever the converted data ends - SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen+1]; + SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen + 1]; if (!pData) { return SI_NOMEM; } - memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); + memset(pData, 0, sizeof(SI_CHAR) * (uLen + 1)); // convert the data if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { @@ -1795,6 +1812,7 @@ CSimpleIniTempl::IsMultiLineData( } // embedded newlines + const SI_CHAR * pStart = a_pData; while (*a_pData) { if (IsNewLineChar(*a_pData)) { return true; @@ -1802,8 +1820,8 @@ CSimpleIniTempl::IsMultiLineData( ++a_pData; } - // check for suffix - if (IsSpace(*--a_pData)) { + // check for suffix (ensure we don't go before start of string) + if (a_pData > pStart && IsSpace(*(a_pData - 1))) { return true; } @@ -1816,7 +1834,7 @@ CSimpleIniTempl::IsSingleLineQuotedValue( const SI_CHAR* a_pData ) const { - // data needs quoting if it starts or ends with whitespace + // data needs quoting if it starts or ends with whitespace // and doesn't have embedded newlines // empty string @@ -1830,6 +1848,7 @@ CSimpleIniTempl::IsSingleLineQuotedValue( } // embedded newlines + const SI_CHAR * pStart = a_pData; while (*a_pData) { if (IsNewLineChar(*a_pData)) { return false; @@ -1837,8 +1856,8 @@ CSimpleIniTempl::IsSingleLineQuotedValue( ++a_pData; } - // check for suffix - if (IsSpace(*--a_pData)) { + // check for suffix (ensure we don't go before start of string) + if (a_pData > pStart && IsSpace(*(a_pData - 1))) { return true; } @@ -2083,7 +2102,8 @@ CSimpleIniTempl::AddEntry( if (pComment) { DeleteString(a_pComment); a_pComment = pComment; - CopyString(a_pComment); + rc = CopyString(a_pComment); + if (rc < 0) return rc; } Delete(a_pSection, a_pKey); iKey = keyval.end(); @@ -2252,12 +2272,13 @@ CSimpleIniTempl::GetDoubleValue( return a_nDefault; } - char * pszSuffix = NULL; + char * pszSuffix = szValue; double nValue = strtod(szValue, &pszSuffix); // any invalid strings will return the default value - if (!pszSuffix || *pszSuffix) { - return a_nDefault; + // check if no conversion was performed or if there are trailing characters + if (pszSuffix == szValue || *pszSuffix) { + return a_nDefault; } return nValue; @@ -2416,7 +2437,7 @@ CSimpleIniTempl::GetSectionSize( int nCount = 0; const SI_CHAR * pLastKey = NULL; typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { + for (; iKeyVal != section.end(); ++iKeyVal) { if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { ++nCount; pLastKey = iKeyVal->first.pItem; @@ -2448,7 +2469,7 @@ CSimpleIniTempl::GetAllSections( { a_names.clear(); typename TSection::const_iterator i = m_data.begin(); - for (int n = 0; i != m_data.end(); ++i, ++n ) { + for (; i != m_data.end(); ++i) { a_names.push_back(i->first); } } @@ -2474,7 +2495,7 @@ CSimpleIniTempl::GetAllKeys( const TKeyVal & section = iSection->second; const SI_CHAR * pLastKey = NULL; typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { + for (; iKeyVal != section.end(); ++iKeyVal) { if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { a_names.push_back(iKeyVal->first); pLastKey = iKeyVal->first.pItem; @@ -2812,7 +2833,7 @@ CSimpleIniTempl::DeleteString( // strings may exist either inside the data block, or they will be // individually allocated and stored in m_strings. We only physically // delete those stored in m_strings. - if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { + if (!m_pData || a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { typename TNamesDepend::iterator i = m_strings.begin(); for (;i != m_strings.end(); ++i) { if (a_pString == i->pItem) {