Firefox · Core
CVE-2026-7322
Memory Corruption in Core
Overview
Critical
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
xpcom/io/SlicedInputStream.cppxpcom/io/SlicedInputStream.hxpcom/io/nsICloneableInputStream.idlxpcom/tests/gtest/TestSlicedInputStream.cpp
Patch
diff --git a/xpcom/io/SlicedInputStream.cpp b/xpcom/io/SlicedInputStream.cpp
index 5a338e45d08..11da3a1395b 100644
--- a/xpcom/io/SlicedInputStream.cpp
+++ b/xpcom/io/SlicedInputStream.cpp
@@ -41,6 +41,15 @@ NS_INTERFACE_MAP_BEGIN(SlicedInputStream)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
NS_INTERFACE_MAP_END
+// It is highly unlikely for a stream to exceed INT64_MAX bytes in length, so we
+// clamp these values here to allow callers as well as SlicedInputStream to
+// avoid unnecessary integer overflow checks. Out of range start/length values
+// should behave as expected in all realistic situations.
+//
+// Some stream APIs use an int64_t (e.g. Tell), so we use INT64_MAX as the
+// maximum internal offset for start/end.
+static constexpr uint64_t kMaxStreamPos = INT64_MAX;
+
SlicedInputStream::SlicedInputStream(
already_AddRefed<nsIInputStream> aInputStream, uint64_t aStart,
uint64_t aLength)
@@ -51,8 +60,8 @@ SlicedInputStream::SlicedInputStream(
mWeakAsyncInputStream(nullptr),
mWeakInputStreamLength(nullptr),
mWeakAsyncInputStreamLength(nullptr),
- mStart(aStart),
- mLength(aLength),
+ mStart(std::clamp<uint64_t>(aStart, 0, kMaxStreamPos)),
+ mLength(std::clamp<uint64_t>(aLength, 0, kMaxStreamPos - mStart)),
mCurPos(0),
mClosed(false),
mAsyncWaitFlags(0),
@@ -489,11 +498,11 @@ bool SlicedInputStream::Deserialize(
const SlicedInputStreamParams& params = aParams.get_SlicedInputStreamParams();
- auto end = CheckedUint64(params.start()) + params.length();
- if (!end.isValid()) {
+ if (params.start() > kMaxStreamPos ||
+ params.length() > kMaxStreamPos - params.start()) {
return false;
}
- if (params.curPos() > end.value()) {
+ if (params.curPos() > params.start() + params.length()) {
return false;
}
diff --git a/xpcom/io/SlicedInputStream.h b/xpcom/io/SlicedInputStream.h
index c7a28d5ff3e..a266bbf5f7b 100644
--- a/xpcom/io/SlicedInputStream.h
+++ b/xpcom/io/SlicedInputStream.h
@@ -44,7 +44,11 @@ class SlicedInputStream final : public nsIAsyncInputStream,
// than aStart bytes, reading from SlicedInputStream returns no data. If
// aInputStream contains more than aStart bytes, but fewer than aStart +
// aLength bytes, reading from SlicedInputStream returns as many bytes as can
- // be consumed from aInputStream after reading aLength bytes.
+ // be consumed from aInputStream after reading aStart bytes.
+ //
+ // It is safe to specify an arbitrarily large aLength (e.g. UINT64_MAX). Doing
+ // so is treated as allowing an arbitrary number of additional bytes following
+ // aStart.
//
// aInputStream should not be read from after constructing a
// SlicedInputStream wrapper around it.
diff --git a/xpcom/io/nsICloneableInputStream.idl b/xpcom/io/nsICloneableInputStream.idl
index adefd0f4285..98790bd2d97 100644
--- a/xpcom/io/nsICloneableInputStream.idl
+++ b/xpcom/io/nsICloneableInputStream.idl
@@ -27,5 +27,12 @@ interface nsICloneableInputStream : nsISupports
[scriptable, builtinclass, uuid(ece853c3-aded-4cef-8f51-0d1493d60bd5)]
interface nsICloneableInputStreamWithRange : nsICloneableInputStream
{
+ // Create a copy of the input stream, but with the data range reduced to a
+ // sub-slice. The copy will begin at `start` bytes, and extends for a maximum
+ // of `length` bytes. If the underlying stream contains more than `start`
+ // bytes, but fewer than `length` bytes, reading from the stream returns all
+ // remaining bytes.
+ //
+ // See SlicedInputStream's constructor for more details.
nsIInputStream cloneWithRange(in uint64_t start, in uint64_t length);
};
diff --git a/xpcom/tests/gtest/TestSlicedInputStream.cpp b/xpcom/tests/gtest/TestSlicedInputStream.cpp
index a2c1a077e48..ea9f606da09 100644
--- a/xpcom/tests/gtest/TestSlicedInputStream.cpp
+++ b/xpcom/tests/gtest/TestSlicedInputStream.cpp
@@ -307,6 +307,41 @@ TEST(TestSlicedInputStream, LengthBiggerThan)
ASSERT_EQ((uint64_t)500, count);
}
+// Like LengthBiggerThan, but for an overflowing aStart + aLength pair.
+TEST(TestSlicedInputStream, LengthMuchBiggerThan)
+{
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(500, 100, UINT64_MAX - 1, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)500 - 100, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)(500 - 100), count);
+ ASSERT_EQ(Substring(buf, 100, count), Substring(buf2, count));
+}
+
+// Like LengthMuchBiggerThan, but with a massive aStart value.
+TEST(TestSlicedInputStream, StartMuchBiggerThan)
+{
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(500, UINT64_MAX - 1, 100, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)0, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)0, count);
+}
+
// What if the length is 0?
TEST(TestSlicedInputStream, Length0)
{
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/xpcom/tests/gtest/TestSlicedInputStream.cpp b/xpcom/tests/gtest/TestSlicedInputStream.cpp
index a2c1a077e48..ea9f606da09 100644
--- a/xpcom/tests/gtest/TestSlicedInputStream.cpp
+++ b/xpcom/tests/gtest/TestSlicedInputStream.cpp
@@ -307,6 +307,41 @@ TEST(TestSlicedInputStream, LengthBiggerThan)
ASSERT_EQ((uint64_t)500, count);
}
+// Like LengthBiggerThan, but for an overflowing aStart + aLength pair.
+TEST(TestSlicedInputStream, LengthMuchBiggerThan)
+{
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(500, 100, UINT64_MAX - 1, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)500 - 100, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)(500 - 100), count);
+ ASSERT_EQ(Substring(buf, 100, count), Substring(buf2, count));
+}
+
+// Like LengthMuchBiggerThan, but with a massive aStart value.
+TEST(TestSlicedInputStream, StartMuchBiggerThan)
+{
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(500, UINT64_MAX - 1, 100, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)0, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)0, count);
+}
+
// What if the length is 0?
TEST(TestSlicedInputStream, Length0)
{
Loading diff…
References
On This Page