summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/io/qurlidna.cpp76
-rw-r--r--tests/auto/corelib/io/qurlinternal/tst_qurlinternal.cpp2
2 files changed, 54 insertions, 24 deletions
diff --git a/src/corelib/io/qurlidna.cpp b/src/corelib/io/qurlidna.cpp
index 1f369de895..f09b949da3 100644
--- a/src/corelib/io/qurlidna.cpp
+++ b/src/corelib/io/qurlidna.cpp
@@ -42,6 +42,7 @@
#include <QtCore/qstringlist.h>
#include <QtCore/private/qstringiterator_p.h>
+#include <QtCore/private/qnumeric_p.h>
#include <algorithm>
@@ -57,7 +58,6 @@
QT_BEGIN_NAMESPACE
// needed by the punycode encoder/decoder
-#define Q_MAXINT ((uint)((uint)(-1)>>1))
static const uint base = 36;
static const uint tmin = 1;
static const uint tmax = 26;
@@ -66,6 +66,8 @@ static const uint damp = 700;
static const uint initial_bias = 72;
static const uint initial_n = 128;
+static constexpr unsigned MaxDomainLabelLength = 63;
+
struct NameprepCaseFoldingEntry {
char32_t uc;
char16_t mapping[4];
@@ -2132,7 +2134,7 @@ static const QChar *qt_find_nonstd3(QStringView in, Qt::CaseSensitivity cs)
const QChar * const uc = in.data();
const qsizetype len = in.size();
- if (len > 63)
+ if (len > MaxDomainLabelLength)
return uc;
for (qsizetype i = 0; i < len; ++i) {
@@ -2219,6 +2221,12 @@ Q_AUTOTEST_EXPORT void qt_punycodeEncoder(QStringView in, QString *output)
uint delta = 0;
uint bias = initial_bias;
+ // Do not try to encode strings that certainly will result in output
+ // that is longer than allowable domain name label length. Note that
+ // non-BMP codepoints are encoded as two QChars.
+ if (in.length() > MaxDomainLabelLength * 2)
+ return;
+
int outLen = output->length();
output->resize(outLen + in.length());
@@ -2262,39 +2270,37 @@ Q_AUTOTEST_EXPORT void qt_punycodeEncoder(QStringView in, QString *output)
// while there are still unprocessed non-basic code points left in
// the input string...
while (h < inputLength) {
- // find the character in the input string with the lowest
- // unicode value.
- uint m = Q_MAXINT;
+ // find the character in the input string with the lowest unprocessed value.
+ uint m = std::numeric_limits<uint>::max();
for (QStringIterator iter(in); iter.hasNext();) {
auto c = iter.nextUnchecked();
+ static_assert(std::numeric_limits<decltype(m)>::max()
+ >= std::numeric_limits<decltype(c)>::max(),
+ "Punycode uint should be able to cover all codepoints");
if (c >= n && c < m)
m = c;
}
- // reject out-of-bounds unicode characters
- if (m - n > (Q_MAXINT - delta) / (h + 1)) {
+ // delta = delta + (m - n) * (h + 1), fail on overflow
+ uint tmp;
+ if (mul_overflow<uint>(m - n, h + 1, &tmp) || add_overflow<uint>(delta, tmp, &delta)) {
output->truncate(outLen);
return; // punycode_overflow
}
-
- delta += (m - n) * (h + 1);
n = m;
for (QStringIterator iter(in); iter.hasNext();) {
auto c = iter.nextUnchecked();
- // increase delta until we reach the character with the
- // lowest unicode code. fail if delta overflows.
+ // increase delta until we reach the character processed in this iteration;
+ // fail if delta overflows.
if (c < n) {
- ++delta;
- if (!delta) {
+ if (add_overflow<uint>(delta, 1, &delta)) {
output->truncate(outLen);
return; // punycode_overflow
}
}
- // if j is the index of the character with the lowest
- // unicode code...
if (c == n) {
appendEncode(output, delta, bias);
@@ -2319,6 +2325,12 @@ Q_AUTOTEST_EXPORT QString qt_punycodeDecoder(const QString &pc)
uint i = 0;
uint bias = initial_bias;
+ // Do not try to decode strings longer than allowable for a domain label.
+ // Non-ASCII strings are not allowed here anyway, so there is no need
+ // to account for surrogates.
+ if (pc.length() > MaxDomainLabelLength)
+ return QString();
+
// strip any ACE prefix
int start = pc.startsWith(QLatin1String("xn--")) ? 4 : 0;
if (!start)
@@ -2352,31 +2364,48 @@ Q_AUTOTEST_EXPORT QString qt_punycodeDecoder(const QString &pc)
else if (digit - 97 < 26) digit -= 97;
else digit = base;
- // reject out of range digits
- if (digit >= base || digit > (Q_MAXINT - i) / w)
- return QStringLiteral("");
+ // Fail if the code point has no digit value
+ if (digit >= base)
+ return QString();
- i += (digit * w);
+ // i = i + digit * w, fail on overflow
+ uint tmp;
+ if (mul_overflow<uint>(digit, w, &tmp) || add_overflow<uint>(i, tmp, &i))
+ return QString();
// detect threshold to stop reading delta digits
uint t;
if (k <= bias) t = tmin;
else if (k >= bias + tmax) t = tmax;
else t = k - bias;
+
if (digit < t) break;
- w *= (base - t);
+ // w = w * (base - t), fail on overflow
+ if (mul_overflow<uint>(w, base - t, &w))
+ return QString();
}
// find new bias and calculate the next non-basic code
// character.
uint outputLength = static_cast<uint>(output.length());
bias = adapt(i - oldi, outputLength + 1, oldi == 0);
- n += i / (outputLength + 1);
+
+ // n = n + i div (length(output) + 1), fail on overflow
+ if (add_overflow<uint>(n, i / (outputLength + 1), &n))
+ return QString();
// allow the deltas to wrap around
i %= (outputLength + 1);
+ // if n is a basic code point then fail; this should not happen with
+ // correct implementation of Punycode, but check just n case.
+ if (n < initial_n) {
+ // Don't use Q_ASSERT() to avoid possibility of DoS
+ qWarning("Attempt to insert a basic codepoint. Unhandled overflow?");
+ return QString();
+ }
+
// Surrogates should normally be rejected later by other IDNA code.
// But because of Qt's use of UTF-16 to represent strings the
// IDNA code is not able to distinguish characters represented as pairs
@@ -2385,7 +2414,10 @@ Q_AUTOTEST_EXPORT QString qt_punycodeDecoder(const QString &pc)
//
// Allowing surrogates would lead to non-unique (after normalization)
// encoding of strings with non-BMP characters.
- if (QChar::isSurrogate(n))
+ //
+ // Punycode that encodes characters outside the Unicode range is also
+ // invalid and is rejected here.
+ if (QChar::isSurrogate(n) || n > QChar::LastValidCodePoint)
return QString();
// insert the character n at position i
diff --git a/tests/auto/corelib/io/qurlinternal/tst_qurlinternal.cpp b/tests/auto/corelib/io/qurlinternal/tst_qurlinternal.cpp
index 1de7c648d7..0c22235034 100644
--- a/tests/auto/corelib/io/qurlinternal/tst_qurlinternal.cpp
+++ b/tests/auto/corelib/io/qurlinternal/tst_qurlinternal.cpp
@@ -703,8 +703,6 @@ void tst_QUrlInternal::ace_testsuite()
QString domain = in + suffix;
QCOMPARE(QString::fromLatin1(QUrl::toAce(domain)), toace + suffix);
- QEXPECT_FAIL("punycode-overflow-2", "QTBUG-95689: Missing oweflow check in punycode decoder",
- Abort);
if (fromace != ".")
QCOMPARE(QUrl::fromAce(domain.toLatin1()), fromace + suffix);
QCOMPARE(QUrl::fromAce(QUrl::toAce(domain)), unicode + suffix);