From 550e46b259ff349135dfac0bd78ee05438f37548 Mon Sep 17 00:00:00 2001 From: Ivan Solovev Date: Fri, 21 Aug 2026 16:15:15 +0200 Subject: [PATCH] QTextCodec: fix OOB write in ICONV codec The ICONV codec has a size miscalculation error when encoding continues with a high surrogate kept in the state after encoding of the previous chunk. The code was requesting a resize based on the length in UTF-16 code points instead of the length in bytes. As a result, we were requesting half of the bytes that we actually needed. Note that since resize() allocates more than requested, the code could sometimes execute successfully. The test specifically adds a large tail to the input, so that memcpy() clearly triggers an out-of-bounds write. The test was triggering ASAN before the fix. Disclaimer: the tests were developed with the help of AI. Fixes: QTBUG-149214 Pick-to: 6.5 5.15 Change-Id: I8d32b737630efc092d3f99eb45982f30106e29d3 Reviewed-by: Thiago Macieira Reviewed-by: Marc Mutz (cherry picked from commit cf3f5fbc8bae37cd358b8157cff11b329cca02aa) Reviewed-by: Qt Cherry-pick Bot (cherry picked from commit 724025a7c05596961cc870ef703a2f2b14e55c88) (cherry picked from commit 81cbf0a59c311ec17cae4d2e4d40bd93635f6d8f) --- diff --git a/src/core5/codecs/qiconvcodec.cpp b/src/core5/codecs/qiconvcodec.cpp index 0f2a566..adc269c 100644 --- a/src/core5/codecs/qiconvcodec.cpp +++ b/src/core5/codecs/qiconvcodec.cpp @@ -296,7 +296,7 @@ QByteArray in; if (convState && convState->remainingChars) { // we have one surrogate char to be prepended - in.resize(sizeof(QChar) + len); + in.resize(sizeof(QChar) + inBytesLeft); inBytes = in.data(); QChar remaining(convState->state_data[0]); diff --git a/tests/auto/core5/codecs/qtextcodec/tst_qtextcodec.cpp b/tests/auto/core5/codecs/qtextcodec/tst_qtextcodec.cpp index f00bdba..d3181a0 100644 --- a/tests/auto/core5/codecs/qtextcodec/tst_qtextcodec.cpp +++ b/tests/auto/core5/codecs/qtextcodec/tst_qtextcodec.cpp @@ -95,6 +95,8 @@ void tsciiEncoderDoesNotReadPastTheEnd_data(); void tsciiEncoderDoesNotReadPastTheEnd(); + + void iconvEncoderPendingSurrogateDoesNotOverflow(); }; void tst_QTextCodec::toUnicode_data() @@ -2888,6 +2890,47 @@ QCOMPARE(stateless, expected); } +/* + This test catches a size miscalculation error in the Iconv codec + in the case when encoding continues with a high surrogate kept + in the state from the encoding of the previous chunk. +*/ +void tst_QTextCodec::iconvEncoderPendingSurrogateDoesNotOverflow() +{ + constexpr char32_t Ucs4NonBmp = 0x10437; // DESERET SMALL LETTER YEE + // Before the fix such tail was leading to allocating 4096 bytes, + // while the full buffer required 4086 _QChars_ (so, almost twice more) + constexpr int IconvTailLength = 4064; + + // QIconvCodec registers itself as "System", but it's only available + // when the configuration disables ICU and explicitly enables iconv. + QTextCodec *codec = QTextCodec::codecForName("System"); + if (!codec) + QSKIP("QIconvCodec was not found (disable ICU and enable iconv explicitly)"); + + QString start(10, u'x'); + start += QChar::highSurrogate(Ucs4NonBmp); + + QString rest; + rest += QChar::lowSurrogate(Ucs4NonBmp); + rest += QString(IconvTailLength, u'x'); + + const QByteArray expected = codec->fromUnicode(start + rest); + + // Encode the first block that ends with the high surrogate. + // It should be stored in the converter state. + QTextCodec::ConverterState state; + QByteArray continued = codec->fromUnicode(start.constData(), int(start.size()), &state); + + // Encode the second block. + // Before the fix, this would cause an overrun in the QByteArray + // buffer. Detection left for an ASan or Valgrind-checked build. + continued += codec->fromUnicode(rest.constData(), int(rest.size()), &state); + + QCOMPARE(continued, expected); + QCOMPARE(state.remainingChars, qsizetype(0)); +} + QT_END_NAMESPACE QTEST_MAIN(tst_QTextCodec)