Don't allocate in qt_asciiToDouble()

The sscanf implementation ensured NUL-termination of the input data,
by copying it, and appending NUL.

Since this function is ignoring trailing garbage and reports the
progress back, we could be parsing the first double in a multi-MiB
buffer. And we'd been copying and copying the buffer for every double
scanned. This is clearly not acceptable.

An alternative is to use the max-field-width feature of scanf. By
giving the size of the input data as the maximum field width in the
format string, we stop sscanf from reading more than the available
data.

This code should let everyone's alarm bells go off: a format string
constructed at run-time is really the last thing one should consider,
but I haven't found a way to pass the field width as an argument, so
bite the bullet and go through with it. Copying potentially MiBs of
data is the worse of the two evils.

Pick-to: 6.3
Fixes: QTBUG-101178
Change-Id: Ibaf8142f6b3dab4d5e3631c3cc8cc6699bceb320
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Marc Mutz 2022-02-23 11:57:00 +01:00
parent 64d65a645c
commit 5a39173c34

View File

@ -286,7 +286,7 @@ double qt_asciiToDouble(const char *num, qsizetype numLen, bool &ok, int &proces
return needleLen == haystackLen && memcmp(needle, haystack, haystackLen) == 0; return needleLen == haystackLen && memcmp(needle, haystack, haystackLen) == 0;
}; };
if (numLen == 0) { if (numLen <= 0) {
ok = false; ok = false;
processed = 0; processed = 0;
return 0.0; return 0.0;
@ -350,24 +350,15 @@ double qt_asciiToDouble(const char *num, qsizetype numLen, bool &ok, int &proces
} }
} }
#else #else
// need to ensure that our input is null-terminated for sscanf // ::digits10 is 19, but ::max() is 18'446'744'073'709'551'615ULL - go, figure...
// (this is a QVarLengthArray<char, 128> but this code here is too low-level for QVLA) constexpr auto maxDigitsForULongLong = 1 + std::numeric_limits<unsigned long long>::digits10;
char reasonableBuffer[128]; // need to ensure that we don't read more than numLen of input:
char *buffer; char fmt[1 + maxDigitsForULongLong + 4 + 1];
if (numLen < qsizetype(sizeof(reasonableBuffer)) - 1) sprintf(fmt, "%s%llu%s", "%", static_cast<unsigned long long>(numLen), "lf%n");
buffer = reasonableBuffer;
else
buffer = static_cast<char *>(malloc(numLen + 1));
Q_CHECK_PTR(buffer);
memcpy(buffer, num, numLen);
buffer[numLen] = '\0';
if (qDoubleSscanf(buffer, QT_CLOCALE, "%lf%n", &d, &processed) < 1) if (qDoubleSscanf(num, QT_CLOCALE, fmt, &d, &processed) < 1)
processed = 0; processed = 0;
if (buffer != reasonableBuffer)
free(buffer);
if ((strayCharMode == TrailingJunkProhibited && processed != numLen) || qIsNaN(d)) { if ((strayCharMode == TrailingJunkProhibited && processed != numLen) || qIsNaN(d)) {
// Implementation defined nan symbol or garbage found. We don't accept it. // Implementation defined nan symbol or garbage found. We don't accept it.
processed = 0; processed = 0;