This is an automated email from the ASF dual-hosted git repository.
swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git
The following commit(s) were added to refs/heads/master by this push:
new 7f30d205 reject invalid UTF-8 lead bytes F8..FF in Transcoder::decode
(#699)
7f30d205 is described below
commit 7f30d205ca2780e3fa47031c768dcb1a761ac299
Author: metsw24-max <[email protected]>
AuthorDate: Tue Jun 2 09:13:10 2026 +0530
reject invalid UTF-8 lead bytes F8..FF in Transcoder::decode (#699)
---
src/main/cpp/transcoder.cpp | 6 +++++-
src/test/cpp/helpers/transcodertestcase.cpp | 29 +++++++++++++++++++++++++++++
2 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/src/main/cpp/transcoder.cpp b/src/main/cpp/transcoder.cpp
index fb646958..19404745 100644
--- a/src/main/cpp/transcoder.cpp
+++ b/src/main/cpp/transcoder.cpp
@@ -298,7 +298,11 @@ unsigned int Transcoder::decode(const std::string& src,
// is not a Unicode code point. Without this
bound, encodeUTF16
// later silently aliases the bogus value to a
valid in-range
// code point — a substitution-collision
filter-bypass primitive.
- if (rv > 0xFFFF && rv <= 0x10FFFF)
+ // Lead bytes F8..FF are never valid UTF-8, but
the & 0x07 mask
+ // discards their high bits, so without the
(ch1 & 0xF8) == 0xF0
+ // guard F8 BF BF BF would alias to U+3FFFF
instead of being
+ // rejected.
+ if ((ch1 & 0xF8) == 0xF0 && rv > 0xFFFF && rv
<= 0x10FFFF)
{
return rv;
}
diff --git a/src/test/cpp/helpers/transcodertestcase.cpp
b/src/test/cpp/helpers/transcodertestcase.cpp
index 1bc1431e..7887b82a 100644
--- a/src/test/cpp/helpers/transcodertestcase.cpp
+++ b/src/test/cpp/helpers/transcodertestcase.cpp
@@ -69,6 +69,7 @@ LOGUNIT_CLASS(TranscoderTestCase)
LOGUNIT_TEST(testDecodeUTF8_U0800);
LOGUNIT_TEST(testDecodeUTF8_RejectAboveMax);
LOGUNIT_TEST(testDecodeUTF8_MaxBoundary);
+ LOGUNIT_TEST(testDecodeUTF8_RejectInvalidLeadByte);
LOGUNIT_TEST(testEncodeUTF16BE_BMP);
LOGUNIT_TEST(testEncodeUTF16BE_Supplementary);
LOGUNIT_TEST(testEncodeUTF16LE_Supplementary);
@@ -436,6 +437,34 @@ public:
}
}
+ /**
+ * Lead bytes F8..FF never start a valid UTF-8 sequence. The four-byte
+ * branch masks the lead byte with 0x07, discarding those high bits, so
+ * F8 BF BF BF used to slip past the U+10FFFF bound and decode to
U+3FFFF
+ * (and FB/FC likewise to other in-range planes) — the same aliasing
+ * filter-bypass that the F5..F7 rejection guards against. Each byte of
an
+ * invalid lead sequence must be replaced with Transcoder::LOSSCHAR.
+ */
+ void testDecodeUTF8_RejectInvalidLeadByte()
+ {
+ struct { const char* bytes; size_t len; bool reject; } cases[] =
+ {
+ { "\xF0\x9F\x98\x80", 4, false }, // U+1F600 — valid
four-byte
+ { "\xF8\xBF\xBF\xBF", 4, true }, // F8 lead: masked to
U+3FFFF (reject)
+ { "\xFB\xBF\xBF\xBF", 4, true }, // FB lead: masked to
U+FFFFF (reject)
+ { "\xFC\x8F\xBF\xBF", 4, true }, // FC lead: masked to
U+10FFFF (reject)
+ { "\xFF\xBF\xBF\xBF", 4, true }, // FF lead (reject)
+ };
+ for (auto& c : cases)
+ {
+ std::string src(c.bytes, c.len);
+ LogString out;
+ Transcoder::decodeUTF8(src, out);
+ bool hasLoss = out.find(Transcoder::LOSSCHAR) !=
LogString::npos;
+ LOGUNIT_ASSERT_EQUAL(c.reject, hasLoss);
+ }
+ }
+
void testEncodeUTF16BE_BMP()
{
char raw[4] = { 0, 0, 0, 0 };