From 229a429e322b072081b42b568d3c789f2f05d3d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 06:12:45 +0000 Subject: [PATCH 1/3] Fix critical bugs and add comprehensive code review documentation This commit addresses multiple critical bugs and code quality issues identified during a thorough code review of the SimpleIni library. ## Critical Bug Fixes: 1. **Buffer underflow in IsSingleLineQuotedValue() and IsMultiLineData()** - Fixed pointer decrement without bounds checking (lines 1806, 1841) - Added proper bounds checking to prevent reading before buffer start - Prevents potential crashes and security vulnerabilities 2. **Integer overflow in LoadFile() and LoadData()** - Added overflow checks before memory allocation (lines 1462, 1520) - Added parentheses for clarity in overflow check (line 1520) - Prevents allocation of incorrectly sized buffers - Protects against heap corruption from oversized allocations 3. **Logic error in GetDoubleValue()** - Fixed strtod error checking (line 2267) - Properly detects when no conversion was performed - Ensures invalid numeric strings return default value 4. **Null pointer check in DeleteString()** - Added check for null m_pData before pointer arithmetic (line 2839) - Prevents undefined behavior during string deletion ## High Priority Fixes: 5. **Error handling in AddEntry()** - Added error check after CopyString() call (line 2098) - Prevents use-after-free if memory allocation fails 6. **LoadOrder comparator** - Fixed incorrect comparator usage (line 360) - Now properly calls KeyOrder with Entry objects instead of pointers ## Code Quality Improvements: 7. **Removed unused loop variables** - Cleaned up unused 'n' variable in GetAllSections, GetAllKeys, GetSectionSize - Eliminates compiler warnings and dead code ## Documentation: Added comprehensive CODE_REVIEW_FINDINGS.md document detailing: - 20+ identified issues with severity classifications - Detailed impact analysis for each issue - Specific recommendations for fixes - Testing recommendations - Summary of positive aspects of the codebase ## Review Feedback Addressed: - Removed redundant lSize == LONG_MAX check (line 1462) - Added parentheses for clarity in overflow check (line 1520) - Note: OutputMultiLineText const_cast is safe as ConvertToStore makes internal copy All fixes have been tested to ensure they don't break existing functionality while addressing the identified security and correctness issues. --- CODE_REVIEW_FINDINGS.md | 444 ++++++++++++++++++++++++++++++++++++++++ SimpleIni.h | 50 +++-- 2 files changed, 476 insertions(+), 18 deletions(-) create mode 100644 CODE_REVIEW_FINDINGS.md diff --git a/CODE_REVIEW_FINDINGS.md b/CODE_REVIEW_FINDINGS.md new file mode 100644 index 0000000..ec3142e --- /dev/null +++ b/CODE_REVIEW_FINDINGS.md @@ -0,0 +1,444 @@ +# SimpleIni Code Review - Detailed Findings + +## Executive Summary +This document contains a comprehensive code review of the SimpleIni library (SimpleIni.h). The library is generally well-written and functional, but several issues were identified ranging from critical bugs to performance optimizations and code quality improvements. + +**Severity Levels:** +- **CRITICAL**: Could cause crashes, undefined behavior, or security vulnerabilities +- **HIGH**: Logic errors or significant performance issues +- **MEDIUM**: Code quality, maintainability, or minor bugs +- **LOW**: Style, documentation, or optimization opportunities + +--- + +## CRITICAL Issues + +### 1. Undefined Behavior: const_cast and Modification of const Data +**Location**: Lines 2710, 2714 in `OutputMultiLineText()` +**Severity**: CRITICAL + +```cpp +*const_cast(pEndOfLine) = 0; +// ... use the string ... +*const_cast(pEndOfLine) = cEndOfLineChar; +``` + +**Issue**: The function temporarily modifies const data by casting away constness. This is undefined behavior if the actual data resides in read-only memory (e.g., string literals or memory-mapped files). + +**Impact**: Could cause segmentation faults on some platforms or with certain compiler optimizations. + +**Recommendation**: +- Avoid modifying the source string +- Use a different approach like calculating substring lengths +- Or make copies when needed for multi-line output + +### 2. Buffer Underflow in IsSingleLineQuotedValue() and IsMultiLineData() +**Location**: Lines 1806, 1841 +**Severity**: CRITICAL + +```cpp +// Line 1806 in IsMultiLineData +if (IsSpace(*--a_pData)) { + return true; +} + +// Line 1841 in IsSingleLineQuotedValue +if (IsSpace(*--a_pData)) { + return true; +} +``` + +**Issue**: The pointer is decremented without checking if it has gone below the start of the string. For single-character strings, this reads before the buffer. + +**Impact**: Buffer underflow, potential crash or security vulnerability. + +**Recommendation**: +```cpp +// Store the start position and check bounds +const SI_CHAR* pStart = a_pData; +// ... loop to end ... +if (a_pData > pStart && IsSpace(*(a_pData - 1))) { + return true; +} +``` + +### 3. Integer Overflow in LoadFile() +**Location**: Lines 1462, 1516 +**Severity**: CRITICAL + +```cpp +char * pData = new(std::nothrow) char[lSize+static_cast(1)]; +// ... +SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen+1]; +``` + +**Issue**: If `lSize` is `LONG_MAX` or near the maximum value, adding 1 causes integer overflow, potentially allocating a tiny buffer. + +**Impact**: Buffer overflow when writing to the undersized buffer, heap corruption. + +**Recommendation**: +```cpp +if (lSize == LONG_MAX || static_cast(lSize) > SIZE_MAX - 1) { + return SI_NOMEM; +} +char * pData = new(std::nothrow) char[static_cast(lSize) + 1]; +``` + +### 4. Wide File Path Buffer Overflow +**Location**: Lines 1435-1436, 2526-2527 +**Severity**: CRITICAL + +```cpp +char szFile[256]; +u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); +return LoadFile(szFile); +``` + +**Issue**: Fixed 256-byte buffer for file path conversion. Long file paths (which can be 4096+ characters on Unix) will be truncated without error checking. + +**Impact**: Silent truncation of file paths, loading/saving wrong files. + +**Recommendation**: +```cpp +// Calculate required size first +int32_t nLen = u_strlen(a_pwszFile); +std::vector szFile(nLen + 1); +u_austrncpy(szFile.data(), a_pwszFile, szFile.size()); +return LoadFile(szFile.data()); +``` + +--- + +## HIGH Severity Issues + +### 5. Logic Error in strtod Error Checking +**Location**: Line 2259 in `GetDoubleValue()` +**Severity**: HIGH + +```cpp +char * pszSuffix = NULL; +double nValue = strtod(szValue, &pszSuffix); + +// any invalid strings will return the default value +if (!pszSuffix || *pszSuffix) { + return a_nDefault; +} +``` + +**Issue**: `strtod()` never sets `pszSuffix` to `NULL`, it always sets it to point into the string. The check `!pszSuffix` is always false. The correct check should be `pszSuffix == szValue` which indicates no conversion occurred. + +**Impact**: Won't detect conversion failures where no digits were converted. + +**Recommendation**: +```cpp +if (pszSuffix == szValue || *pszSuffix) { + return a_nDefault; +} +``` + +### 6. Performance Issue in DeleteString() +**Location**: Lines 2808-2825 +**Severity**: HIGH + +```cpp +void DeleteString(const SI_CHAR * a_pString) { + if (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) { + delete[] const_cast(i->pItem); + m_strings.erase(i); + break; + } + } + } +} +``` + +**Issue**: O(n) linear search for every string deletion. When deleting multiple entries, this becomes O(n*m) where n is the number of strings and m is the number of deletions. + +**Impact**: Severe performance degradation for INI files with many entries. + +**Recommendation**: Use an `std::map` or `std::unordered_set` to track allocated strings for O(1) or O(log n) lookups. + +### 7. Incorrect Comparator Usage in Entry::LoadOrder +**Location**: Line 360 +**Severity**: HIGH + +```cpp +struct LoadOrder { + bool operator()(const Entry & lhs, const Entry & rhs) const { + if (lhs.nOrder != rhs.nOrder) { + return lhs.nOrder < rhs.nOrder; + } + return KeyOrder()(lhs.pItem, rhs.pItem); // Should pass Entry objects + } +}; +``` + +**Issue**: `KeyOrder()` is called with `lhs.pItem` and `rhs.pItem` (pointers), but it expects `Entry` objects based on line 348's operator definition. + +**Impact**: Incorrect sorting, potential compilation issues with strict compilers. + +**Recommendation**: +```cpp +return KeyOrder()(lhs, rhs); +``` + +### 8. Potential Use-After-Delete in AddEntry() +**Location**: Lines 2083-2086 +**Severity**: HIGH + +```cpp +if (pComment) { + DeleteString(a_pComment); + a_pComment = pComment; + CopyString(a_pComment); // Could fail, leaving invalid pointer +} +``` + +**Issue**: If `CopyString()` fails (returns SI_NOMEM), `a_pComment` points to deleted memory. The error is not checked before the function continues. + +**Impact**: Use of freed memory, potential crash. + +**Recommendation**: +```cpp +if (pComment) { + DeleteString(a_pComment); + a_pComment = pComment; + rc = CopyString(a_pComment); + if (rc < 0) return rc; +} +``` + +--- + +## MEDIUM Severity Issues + +### 9. Insufficient Buffer Size for Double Formatting +**Location**: Line 2282 in `SetDoubleValue()` +**Severity**: MEDIUM + +```cpp +char szInput[64]; +snprintf(szInput, sizeof(szInput), "%f", a_nValue); +``` + +**Issue**: The `%f` format can produce strings longer than 64 bytes for very large or very small numbers (e.g., numbers near DBL_MAX or with many digits). + +**Impact**: Buffer overflow or truncated values. + +**Recommendation**: Use `%g` format or increase buffer size to at least 32 + DBL_MAX_10_EXP bytes (~350). + +### 10. Missing Null Pointer Check +**Location**: Line 2815 +**Severity**: MEDIUM + +```cpp +if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { +``` + +**Issue**: If `m_pData` is `NULL` (which it is initially), the pointer arithmetic is undefined behavior. + +**Impact**: Undefined behavior in edge cases. + +**Recommendation**: +```cpp +if (!m_pData || a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { +``` + +### 11. Inconsistent Error Handling in LoadMultiLineText() +**Location**: Lines 1887-1917 +**Severity**: MEDIUM + +**Issue**: Function returns boolean but doesn't propagate internal error conditions. It's unclear what "false" means in different contexts (no comment found vs. error). + +**Recommendation**: Add documentation or use a more expressive return type (e.g., tri-state enum). + +### 12. Resource Leak Potential in LoadFile() +**Location**: Lines 1449-1480 +**Severity**: MEDIUM + +```cpp +fseek(a_fpFile, 0, SEEK_SET); +size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); +if (uRead != (size_t) lSize) { + delete[] pData; + return SI_FILE; +} +``` + +**Issue**: If `fread` fails, we don't know if it's a true error or EOF. Using `ferror()` would be more appropriate. + +**Recommendation**: +```cpp +if (uRead != (size_t) lSize) { + delete[] pData; + return ferror(a_fpFile) ? SI_FILE : SI_FAIL; +} +``` + +### 13. Unused Variable in GetSectionSize() +**Location**: Line 2419 +**Severity**: MEDIUM + +```cpp +for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { +``` + +**Issue**: Variable `n` is incremented but never used. + +**Impact**: Dead code, compiler warnings. + +**Recommendation**: Remove unused variable: +```cpp +for (; iKeyVal != section.end(); ++iKeyVal) { +``` + +--- + +## LOW Severity Issues / Code Quality + +### 14. Copy Constructor Should Use Initializer List +**Location**: Line 332, 438, 2907 +**Severity**: LOW + +```cpp +Entry(const Entry & rhs) { operator=(rhs); } +``` + +**Issue**: Using assignment operator in copy constructor is less efficient and can cause issues if operator= has preconditions. + +**Recommendation**: +```cpp +Entry(const Entry & rhs) + : pItem(rhs.pItem), pComment(rhs.pComment), nOrder(rhs.nOrder) { } +``` + +### 15. Magic String "END_OF_TEXT" +**Location**: Line 2673, 2677 +**Severity**: LOW + +```cpp +a_oOutput.Write("<< 2GB) for integer overflow scenarios +3. **Long file paths** (> 256 characters) on systems that support them +4. **Malformed multi-line values** with embedded END_OF_TEXT tags +5. **Numeric edge cases**: LONG_MAX, LONG_MIN, DBL_MAX, DBL_MIN, infinity, NaN +6. **Large INI files** (>10000 entries) for performance testing of DeleteString +7. **Concurrent access** (even though not thread-safe, document behavior) +8. **Invalid UTF-8 sequences** for robust error handling +9. **Memory allocation failures** (simulate OOM conditions) +10. **File I/O errors** (permissions, disk full, etc.) + +--- + +## Conclusion + +The SimpleIni library is a solid, mature codebase with good design principles. However, several critical bugs were identified that could cause crashes or undefined behavior. The most serious issues involve buffer underflows, integer overflows, and undefined behavior from const_cast operations. + +The performance issue in `DeleteString()` should also be addressed for applications dealing with large INI files or frequent modifications. + +With these fixes applied, the library would be significantly more robust and safer for production use. diff --git a/SimpleIni.h b/SimpleIni.h index 386f446..cd69230 100644 --- a/SimpleIni.h +++ b/SimpleIni.h @@ -357,7 +357,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 +1457,14 @@ CSimpleIniTempl::LoadFile( if (lSize == 0) { return SI_OK; } - + + // check for integer overflow before allocation + if (static_cast(lSize) > SIZE_MAX - 1) { + return SI_NOMEM; + } + // 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 +1516,18 @@ CSimpleIniTempl::LoadData( return SI_FAIL; } + // check for integer overflow before allocation + if (uLen >= (SIZE_MAX / sizeof(SI_CHAR)) - 1) { + return SI_NOMEM; + } + // 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 +1805,7 @@ CSimpleIniTempl::IsMultiLineData( } // embedded newlines + const SI_CHAR * pStart = a_pData; while (*a_pData) { if (IsNewLineChar(*a_pData)) { return true; @@ -1802,8 +1813,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 +1827,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 +1841,7 @@ CSimpleIniTempl::IsSingleLineQuotedValue( } // embedded newlines + const SI_CHAR * pStart = a_pData; while (*a_pData) { if (IsNewLineChar(*a_pData)) { return false; @@ -1837,8 +1849,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 +2095,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 +2265,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 +2430,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 +2462,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 +2488,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 +2826,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) { From 6e17e1addae5646196ea34b8f9c4e4acaa502625 Mon Sep 17 00:00:00 2001 From: brofield Date: Thu, 13 Nov 2025 18:36:25 +1000 Subject: [PATCH 2/3] Remove findings document --- CODE_REVIEW_FINDINGS.md | 444 ---------------------------------------- 1 file changed, 444 deletions(-) delete mode 100644 CODE_REVIEW_FINDINGS.md diff --git a/CODE_REVIEW_FINDINGS.md b/CODE_REVIEW_FINDINGS.md deleted file mode 100644 index ec3142e..0000000 --- a/CODE_REVIEW_FINDINGS.md +++ /dev/null @@ -1,444 +0,0 @@ -# SimpleIni Code Review - Detailed Findings - -## Executive Summary -This document contains a comprehensive code review of the SimpleIni library (SimpleIni.h). The library is generally well-written and functional, but several issues were identified ranging from critical bugs to performance optimizations and code quality improvements. - -**Severity Levels:** -- **CRITICAL**: Could cause crashes, undefined behavior, or security vulnerabilities -- **HIGH**: Logic errors or significant performance issues -- **MEDIUM**: Code quality, maintainability, or minor bugs -- **LOW**: Style, documentation, or optimization opportunities - ---- - -## CRITICAL Issues - -### 1. Undefined Behavior: const_cast and Modification of const Data -**Location**: Lines 2710, 2714 in `OutputMultiLineText()` -**Severity**: CRITICAL - -```cpp -*const_cast(pEndOfLine) = 0; -// ... use the string ... -*const_cast(pEndOfLine) = cEndOfLineChar; -``` - -**Issue**: The function temporarily modifies const data by casting away constness. This is undefined behavior if the actual data resides in read-only memory (e.g., string literals or memory-mapped files). - -**Impact**: Could cause segmentation faults on some platforms or with certain compiler optimizations. - -**Recommendation**: -- Avoid modifying the source string -- Use a different approach like calculating substring lengths -- Or make copies when needed for multi-line output - -### 2. Buffer Underflow in IsSingleLineQuotedValue() and IsMultiLineData() -**Location**: Lines 1806, 1841 -**Severity**: CRITICAL - -```cpp -// Line 1806 in IsMultiLineData -if (IsSpace(*--a_pData)) { - return true; -} - -// Line 1841 in IsSingleLineQuotedValue -if (IsSpace(*--a_pData)) { - return true; -} -``` - -**Issue**: The pointer is decremented without checking if it has gone below the start of the string. For single-character strings, this reads before the buffer. - -**Impact**: Buffer underflow, potential crash or security vulnerability. - -**Recommendation**: -```cpp -// Store the start position and check bounds -const SI_CHAR* pStart = a_pData; -// ... loop to end ... -if (a_pData > pStart && IsSpace(*(a_pData - 1))) { - return true; -} -``` - -### 3. Integer Overflow in LoadFile() -**Location**: Lines 1462, 1516 -**Severity**: CRITICAL - -```cpp -char * pData = new(std::nothrow) char[lSize+static_cast(1)]; -// ... -SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen+1]; -``` - -**Issue**: If `lSize` is `LONG_MAX` or near the maximum value, adding 1 causes integer overflow, potentially allocating a tiny buffer. - -**Impact**: Buffer overflow when writing to the undersized buffer, heap corruption. - -**Recommendation**: -```cpp -if (lSize == LONG_MAX || static_cast(lSize) > SIZE_MAX - 1) { - return SI_NOMEM; -} -char * pData = new(std::nothrow) char[static_cast(lSize) + 1]; -``` - -### 4. Wide File Path Buffer Overflow -**Location**: Lines 1435-1436, 2526-2527 -**Severity**: CRITICAL - -```cpp -char szFile[256]; -u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); -return LoadFile(szFile); -``` - -**Issue**: Fixed 256-byte buffer for file path conversion. Long file paths (which can be 4096+ characters on Unix) will be truncated without error checking. - -**Impact**: Silent truncation of file paths, loading/saving wrong files. - -**Recommendation**: -```cpp -// Calculate required size first -int32_t nLen = u_strlen(a_pwszFile); -std::vector szFile(nLen + 1); -u_austrncpy(szFile.data(), a_pwszFile, szFile.size()); -return LoadFile(szFile.data()); -``` - ---- - -## HIGH Severity Issues - -### 5. Logic Error in strtod Error Checking -**Location**: Line 2259 in `GetDoubleValue()` -**Severity**: HIGH - -```cpp -char * pszSuffix = NULL; -double nValue = strtod(szValue, &pszSuffix); - -// any invalid strings will return the default value -if (!pszSuffix || *pszSuffix) { - return a_nDefault; -} -``` - -**Issue**: `strtod()` never sets `pszSuffix` to `NULL`, it always sets it to point into the string. The check `!pszSuffix` is always false. The correct check should be `pszSuffix == szValue` which indicates no conversion occurred. - -**Impact**: Won't detect conversion failures where no digits were converted. - -**Recommendation**: -```cpp -if (pszSuffix == szValue || *pszSuffix) { - return a_nDefault; -} -``` - -### 6. Performance Issue in DeleteString() -**Location**: Lines 2808-2825 -**Severity**: HIGH - -```cpp -void DeleteString(const SI_CHAR * a_pString) { - if (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) { - delete[] const_cast(i->pItem); - m_strings.erase(i); - break; - } - } - } -} -``` - -**Issue**: O(n) linear search for every string deletion. When deleting multiple entries, this becomes O(n*m) where n is the number of strings and m is the number of deletions. - -**Impact**: Severe performance degradation for INI files with many entries. - -**Recommendation**: Use an `std::map` or `std::unordered_set` to track allocated strings for O(1) or O(log n) lookups. - -### 7. Incorrect Comparator Usage in Entry::LoadOrder -**Location**: Line 360 -**Severity**: HIGH - -```cpp -struct LoadOrder { - bool operator()(const Entry & lhs, const Entry & rhs) const { - if (lhs.nOrder != rhs.nOrder) { - return lhs.nOrder < rhs.nOrder; - } - return KeyOrder()(lhs.pItem, rhs.pItem); // Should pass Entry objects - } -}; -``` - -**Issue**: `KeyOrder()` is called with `lhs.pItem` and `rhs.pItem` (pointers), but it expects `Entry` objects based on line 348's operator definition. - -**Impact**: Incorrect sorting, potential compilation issues with strict compilers. - -**Recommendation**: -```cpp -return KeyOrder()(lhs, rhs); -``` - -### 8. Potential Use-After-Delete in AddEntry() -**Location**: Lines 2083-2086 -**Severity**: HIGH - -```cpp -if (pComment) { - DeleteString(a_pComment); - a_pComment = pComment; - CopyString(a_pComment); // Could fail, leaving invalid pointer -} -``` - -**Issue**: If `CopyString()` fails (returns SI_NOMEM), `a_pComment` points to deleted memory. The error is not checked before the function continues. - -**Impact**: Use of freed memory, potential crash. - -**Recommendation**: -```cpp -if (pComment) { - DeleteString(a_pComment); - a_pComment = pComment; - rc = CopyString(a_pComment); - if (rc < 0) return rc; -} -``` - ---- - -## MEDIUM Severity Issues - -### 9. Insufficient Buffer Size for Double Formatting -**Location**: Line 2282 in `SetDoubleValue()` -**Severity**: MEDIUM - -```cpp -char szInput[64]; -snprintf(szInput, sizeof(szInput), "%f", a_nValue); -``` - -**Issue**: The `%f` format can produce strings longer than 64 bytes for very large or very small numbers (e.g., numbers near DBL_MAX or with many digits). - -**Impact**: Buffer overflow or truncated values. - -**Recommendation**: Use `%g` format or increase buffer size to at least 32 + DBL_MAX_10_EXP bytes (~350). - -### 10. Missing Null Pointer Check -**Location**: Line 2815 -**Severity**: MEDIUM - -```cpp -if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { -``` - -**Issue**: If `m_pData` is `NULL` (which it is initially), the pointer arithmetic is undefined behavior. - -**Impact**: Undefined behavior in edge cases. - -**Recommendation**: -```cpp -if (!m_pData || a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { -``` - -### 11. Inconsistent Error Handling in LoadMultiLineText() -**Location**: Lines 1887-1917 -**Severity**: MEDIUM - -**Issue**: Function returns boolean but doesn't propagate internal error conditions. It's unclear what "false" means in different contexts (no comment found vs. error). - -**Recommendation**: Add documentation or use a more expressive return type (e.g., tri-state enum). - -### 12. Resource Leak Potential in LoadFile() -**Location**: Lines 1449-1480 -**Severity**: MEDIUM - -```cpp -fseek(a_fpFile, 0, SEEK_SET); -size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); -if (uRead != (size_t) lSize) { - delete[] pData; - return SI_FILE; -} -``` - -**Issue**: If `fread` fails, we don't know if it's a true error or EOF. Using `ferror()` would be more appropriate. - -**Recommendation**: -```cpp -if (uRead != (size_t) lSize) { - delete[] pData; - return ferror(a_fpFile) ? SI_FILE : SI_FAIL; -} -``` - -### 13. Unused Variable in GetSectionSize() -**Location**: Line 2419 -**Severity**: MEDIUM - -```cpp -for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { -``` - -**Issue**: Variable `n` is incremented but never used. - -**Impact**: Dead code, compiler warnings. - -**Recommendation**: Remove unused variable: -```cpp -for (; iKeyVal != section.end(); ++iKeyVal) { -``` - ---- - -## LOW Severity Issues / Code Quality - -### 14. Copy Constructor Should Use Initializer List -**Location**: Line 332, 438, 2907 -**Severity**: LOW - -```cpp -Entry(const Entry & rhs) { operator=(rhs); } -``` - -**Issue**: Using assignment operator in copy constructor is less efficient and can cause issues if operator= has preconditions. - -**Recommendation**: -```cpp -Entry(const Entry & rhs) - : pItem(rhs.pItem), pComment(rhs.pComment), nOrder(rhs.nOrder) { } -``` - -### 15. Magic String "END_OF_TEXT" -**Location**: Line 2673, 2677 -**Severity**: LOW - -```cpp -a_oOutput.Write("<< 2GB) for integer overflow scenarios -3. **Long file paths** (> 256 characters) on systems that support them -4. **Malformed multi-line values** with embedded END_OF_TEXT tags -5. **Numeric edge cases**: LONG_MAX, LONG_MIN, DBL_MAX, DBL_MIN, infinity, NaN -6. **Large INI files** (>10000 entries) for performance testing of DeleteString -7. **Concurrent access** (even though not thread-safe, document behavior) -8. **Invalid UTF-8 sequences** for robust error handling -9. **Memory allocation failures** (simulate OOM conditions) -10. **File I/O errors** (permissions, disk full, etc.) - ---- - -## Conclusion - -The SimpleIni library is a solid, mature codebase with good design principles. However, several critical bugs were identified that could cause crashes or undefined behavior. The most serious issues involve buffer underflows, integer overflows, and undefined behavior from const_cast operations. - -The performance issue in `DeleteString()` should also be addressed for applications dealing with large INI files or frequent modifications. - -With these fixes applied, the library would be significantly more robust and safer for production use. From 69f3734934eab04b5e9d00bb147ec609089ef042 Mon Sep 17 00:00:00 2001 From: brofield Date: Thu, 13 Nov 2025 18:50:54 +1000 Subject: [PATCH 3/3] Remove usage of SIZE_MAX --- SimpleIni.h | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/SimpleIni.h b/SimpleIni.h index cd69230..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 @@ -1458,9 +1465,9 @@ CSimpleIniTempl::LoadFile( return SI_OK; } - // check for integer overflow before allocation - if (static_cast(lSize) > SIZE_MAX - 1) { - return SI_NOMEM; + // 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 @@ -1516,9 +1523,9 @@ CSimpleIniTempl::LoadData( return SI_FAIL; } - // check for integer overflow before allocation - if (uLen >= (SIZE_MAX / sizeof(SI_CHAR)) - 1) { - return SI_NOMEM; + // 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