Write beyond bounds caused by AppendEncodedAttributeValue() et al
Categories
(Core :: DOM: Serializers, defect)
Tracking
()
People
(Reporter: mozillabugs, Assigned: mccr8)
References
Details
(Keywords: csectype-intoverflow, reporter-external, sec-high, Whiteboard: [adv-main124+][adv-esr115.9+])
Attachments
(6 files, 1 obsolete file)
680.16 KB,
application/x-zip-compressed
|
Details | |
48 bytes,
text/x-phabricator-request
|
diannaS
:
approval-mozilla-beta+
RyanVM
:
approval-mozilla-esr115+
tjr
:
sec-approval+
|
Details | Review |
48 bytes,
text/x-phabricator-request
|
diannaS
:
approval-mozilla-beta+
RyanVM
:
approval-mozilla-esr115+
tjr
:
sec-approval+
|
Details | Review |
16.56 KB,
text/html
|
Details | |
253 bytes,
application/x-javascript
|
Details | |
275 bytes,
text/plain
|
Details |
AppendEncodedAttributeValue()
, ExtraSpaceNeededForAttrEncoding()
and AppendEncodedCharacters()
(dom/base/nsContentUtils.cpp
) can experience integer overflows, causing underallocation of an output buffer and subsequent writes beyond bounds when StringBuilder::ToString()
is called to obtain the resulting string. The write occurs in a content process, and is of arbitrary attacker-provided data arbitrarily intermixed with the repeated string "
or one of a few other escaped sequences. The length of the attacker-provided data and the length of the repeated strings must sum to >= 0x100000000
.
Attached is a POC that demonstrates the bugs in the context of AppendEncodedAttributeValue()
.
The bugs are that the problem functions add uint32_t
values -- in particular, lengths derived from strings that can be very long -- without checking for overflow. Lines 9360, 9370, and 9320, plus every instance of extraSpaceNeeded += <something>
in ExtraSpaceNeededForAttrEncoding()
and AppendEncodedCharacters()
are vulnerable. (The following code is from FIREFOX_122_0_RELEASE
):
9326: static uint32_t ExtraSpaceNeededForAttrEncoding(const nsAString& aValue) {
9327: const char16_t* c = aValue.BeginReading();
9328: const char16_t* end = aValue.EndReading();
9329:
9330: uint32_t extraSpaceNeeded = 0;
9331: while (c < end) {
9332: switch (*c) {
9333: case '"':
9334: extraSpaceNeeded += ArrayLength(""") - 2;
9335: break;
... (similar for other escaped chars)
9344: }
9345: ++c;
9346: }
9347:
9348: return extraSpaceNeeded;
9349: }
9351: static void AppendEncodedAttributeValue(const nsAttrValue& aValue,
9352: StringBuilder& aBuilder) {
9353: if (nsAtom* atom = aValue.GetStoredAtom()) {
9354: nsDependentAtomString atomStr(atom);
9355: uint32_t space = ExtraSpaceNeededForAttrEncoding(atomStr);
9356: if (!space) {
...
9358: } else {
9359: aBuilder.AppendWithAttrEncode(nsString(atomStr),
9360: atomStr.Length() + space);
9361: }
9362: return;
9363: }
...
9366: nsString str;
9367: aValue.ToString(str);
9368: uint32_t space = ExtraSpaceNeededForAttrEncoding(str);
9369: if (space) {
9370: aBuilder.AppendWithAttrEncode(std::move(str), str.Length() + space);
9371: } else {
...
9373: }
9374: }
9271: static void AppendEncodedCharacters(const nsTextFragment* aText,
9272: StringBuilder& aBuilder) {
9273: uint32_t extraSpaceNeeded = 0;
9274: uint32_t len = aText->GetLength();
9275: if (aText->Is2b()) {
9276: const char16_t* data = aText->Get2b();
9277: for (uint32_t i = 0; i < len; ++i) {
9278: const char16_t c = data[i];
9279: switch (c) {
...
9289: case 0x00A0:
9290: extraSpaceNeeded += ArrayLength(" ") - 2;
9291: break;
9292: default:
9293: break;
9294: }
9295: }
9296: } else {
... (similar for 1-byte characters)
9317: }
9318:
9319: if (extraSpaceNeeded) {
9320: aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded);
...
9323: }
9324: }
Use the attached POC this way:
- Unzip
ffbug_2260_11.zip
, yieldingffbug_2260_11.htm
andffbug_2260_11_worker.js
. - Put these files on a webserver (I used a local webserver at
127.0.0.1
). - Start FF and attach a debugger to it.
- Set a BP on
AppendEncodedAttributeValue()
line 9369. - Load
ffbug_2260_11.htm
in FF. - When the BP fires, examine
space
and notice that it's0xd5552000
. - Examine
str.Length()
and notice that it's0x2aaae040
, which, when added tospace
using 32-bit math, yields the overflowed quantity 0x40. - Step into
StringBuilder::AppendWithAttrEncode()
and notice that it adds aUnit
having this length, also adding the same quantity toStringBuilder::mLength
, which is the total length of the string thatStringBuilder::ToString
will attempt to allocate later. - Set a BP on
StringBuilder::ToString
lines 9125 (output buffer allocation) and 9145 (theUnit::Type::StringWithEncode
case) and proceed. - When the buffer-allocation BP fires, notice that
mLength
is tiny (probably0x50
); this is because of the overflow that occurred in step 7. (The small additional string length of0x10
is for the other elements that surround the gigantic string in the POC). - Proceed. When the
StringWithEncode
BP fires, step intoEncodeAttrString()
and watch it write the attacker-provided data (and the escaped strings) far beyond the end of the output buffer allocated in step 10 (in my test, the buffer appeared to havemCapacity
==0x7b
). - Proceed. FF may crash copying the long string contents into the heap, or a different thread may malfunction or crash as it uses the data that
EncodeAttrString()
sprayed over the heap.
The POC uses a worker thread to illustrate what an attacker might do to stir the pot so that the heap-spraying is effective. In limited tests with an unoptimized x64 build without _DEBUG
, only the ToString()
thread crashes about 70% of the time, with both the ToString()
thread and one or more other threads crashing most of the remainder. To see multiple crashes, proceed after the debugger pops up with the first thread's crash. Frequent crash locations are in arena_t::ArenaRunRegAlloc()
on mask = aRun->mRegionsMask[i];
in WorkletThread::IsOnWorkletThread()
on return ccjscx && ccjscx->GetAsWorkletJSContext();
, and in GetCurrentThreadWorkerPrivate()
on WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext();
Reporter | ||
Updated•1 year ago
|
Reporter | ||
Comment 1•1 year ago
|
||
Updated•1 year ago
|
Assignee | ||
Updated•1 year ago
|
Assignee | ||
Comment 2•1 year ago
|
||
I guess I'll make this csectype-intoverflow, as that's the underlying problem here, though it is interesting that I think this won't show up on UBSan because the overflow is on an unsigned int, so it is defined behavior.
Assignee | ||
Updated•1 year ago
|
Assignee | ||
Comment 3•1 year ago
|
||
The overflow issue in the string builder being used here was fixed in bug 1512758.
Reporter | ||
Comment 4•1 year ago
•
|
||
Not exactly. That overflow was due to StringBuilder::mLength
overflowing, which was indeed fixed (by turning it into a CheckedInt
). The problem here is that that various len + extraSpaceNeeded
computations can overflow, as can the computations of extraSpaceNeeded
itself. That's why this bug still exists in FF 122.0, as opposed to getting fixed in ~66.0.
Assignee | ||
Comment 5•1 year ago
|
||
Sorry, I didn't mean the issue you reported here was fixed. I meant that a similar integer overflow in StringBuilder itself was fixed by that other bug.
Assignee | ||
Comment 6•1 year ago
|
||
When I ran the test case locally on an unmodified debug build, I hit this assertion which looks like a failed bounds check:
Assertion failure: mPosition + len <= mHandle.Length(), at dom/base/nsContentUtils.cpp:9015
#01: (anonymous namespace)::StringBuilder::ToString(nsTSubstring<char16_t>&) (dom/base/nsContentUtils.cpp:9169)
#02: nsContentUtils::SerializeNodeToMarkup(nsINode*, bool, nsTSubstring<char16_t>&) (obj-dbg.noindex/dist/include/mozilla/dom/NodeInfo.h:0)
I considered whether we should make this a release assert, but it looks like the whole point of bug 1488697 which added this bulk writer mechanism is to eliminate redundant checks, so I'll leave it alone.
Hopefully doing CheckedInt everywhere won't slow things down too much. We could avoid the checks in the inner loops of AppendEncodedCharacters and ExtraSpaceNeededForAttrEncoding by using an unchecked version if the worst case scenario (eg 5 * len
) won't overflow, but hopefully that's not necessary. I can try checking the microbenchmark linked to in bug 1488697, though it is so old I don't know how relevant it will be now.
Assignee | ||
Comment 7•1 year ago
|
||
Reporter | ||
Comment 8•1 year ago
|
||
(In reply to Andrew McCreight [:mccr8] from comment #6)
When I ran the test case locally on an unmodified debug build, I hit this assertion which looks like a failed bounds check:
Assertion failure: mPosition + len <= mHandle.Length(), at dom/base/nsContentUtils.cpp:9015
#01: (anonymous namespace)::StringBuilder::ToString(nsTSubstring<char16_t>&) (dom/base/nsContentUtils.cpp:9169)
#02: nsContentUtils::SerializeNodeToMarkup(nsINode*, bool, nsTSubstring<char16_t>&) (obj-dbg.noindex/dist/include/mozilla/dom/NodeInfo.h:0)I considered whether we should make this a release assert, but it looks like the whole point of bug 1488697 which added this bulk writer mechanism is to eliminate redundant checks, so I'll leave it alone.
That would reduce this bug to a DoS bug, but those are undesireable too.
Assignee | ||
Comment 9•1 year ago
|
||
Eh, if we crashes in that situation it wouldn't be too terrible. Your test case doesn't feel like something people are going to hit normally.
My patch is a fairly severe performance regression. When running https://bug-81214-attachments.webkit.org/attachment.cgi?id=132034 locally in a MacOS non-PGO opt build without the patch, I get around 58ms for body.innerHTML and body.outerHTML (about what I see on Nightly in my regular browsing session). With my patch, it is more like 76ms for both. I'll assume that's not okay and try to do something clever.
Assignee | ||
Comment 10•1 year ago
|
||
If I revert the CheckedInt changes for extraSpaceNeeded
, that seems to fix the performance regression entirely, as you might expect. I'll see if I can come up with something tomorrow that bypasses the checking in the usual case.
Reporter | ||
Comment 11•1 year ago
|
||
(In reply to Andrew McCreight [:mccr8] from comment #9)
My patch is a fairly severe performance regression. When running https://bug-81214-attachments.webkit.org/attachment.cgi?id=132034 locally in a MacOS non-PGO opt build without the patch, I get around 58ms for body.innerHTML and body.outerHTML (about what I see on Nightly in my regular browsing session). With my patch, it is more like 76ms for both. I'll assume that's not okay and try to do something clever.
That really surprises me. It maybe indicates that CheckedInt
could use some performance work.
Assignee | ||
Comment 12•1 year ago
|
||
That is the only case that uses it. This might save a bit of space
and avoids some useless stores. Idea by peterv.
Updated•1 year ago
|
Assignee | ||
Comment 13•1 year ago
|
||
Comment on attachment 9381124 [details]
Bug 1880692, part 2 - Use an estimate for encoded characters.
Security Approval Request
- How easily could an exploit be constructed based on the patch?: It is probably easy to figure out how to get a buffer overflow if you look at the patch. (Technically this is a performance improvement so we could potentially land this in a cover bug with that excuse, but the addition of CheckedInt is probably a red flag if somebody was taking a close look at this patch.)
- Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
- Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: all
- If not all supported branches, which bug introduced the flaw?: None
- Do you have backports for the affected branches?: No
- If not, how different, hard to create, and risky will they be?: It looks like this code hasn't really changed since 114 (bug 1830986), so it shouldn't be an issue.
- How likely is this patch to cause regressions; how much testing does it need?: I think the main risk here is that this code is quite performance sensitive. The entire reason we have this odd code that led to a buffer overflow in the first place is due to performance tuning. My first patch slowed down a microbenchmark by a lot. The new patch speeds us up on the microbenchmark, but it does so by increasing memory usage a little bit. Hopefully that increase is not enough to matter.
- Is the patch ready to land after security approval is given?: Yes
- Is Android affected?: Yes
Assignee | ||
Updated•1 year ago
|
Comment 14•1 year ago
|
||
Comment on attachment 9381857 [details]
Bug 1880692, part 1 - Make StringBuilder::Unit::mLength specific to literals.
Approved to land and uplift
Updated•1 year ago
|
Comment 15•1 year ago
|
||
Updated•1 year ago
|
![]() |
||
Comment 16•1 year ago
|
||
https://hg.mozilla.org/mozilla-central/rev/866922425c5e
https://hg.mozilla.org/mozilla-central/rev/9669232a5702
Comment 17•1 year ago
|
||
The patch landed in nightly and beta is affected.
:mccr8, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- If no, please set
status-firefox124
towontfix
.
For more information, please visit BugBot documentation.
Assignee | ||
Comment 18•1 year ago
|
||
I'll put in an uplift request on Monday. I think we're still early in the cycle and a few more days might let any performance issues shake out.
Assignee | ||
Comment 19•1 year ago
|
||
Here's a simplified version of the steps to reproduce from comment 0. Basically I removed all of the debugger steps, which are not necessary for verification in my experience. (I did not actually go through the debugger steps myself, though the description was useful for understanding the issue.
- Download the attachment
ffbug_2260_11.zip
and unzip it in a new directory, yieldingffbug_2260_11.htm
andffbug_2260_11_worker.js
. Note thatffbug_2260_11.htm
is around 683MB. - Start a webserver hosting these files. On MacOS, I did this by running
python3 -m http.server
in the directory I put the files from the previous step. - Start Firefox.
- Load
ffbug_2260_11.htm
in Firefox. With the way I set up the webserver, I was able to do this by visitinghttp://localhost:8000/ffbug_2260_11.htm
.
Expected behavior: the page loads, but nothing happens, resulting in a blank tab.
Actual behavior (unpatched): the tab crashes. This shouldn't take more than a few seconds.
Assignee | ||
Comment 20•1 year ago
|
||
Comment on attachment 9381857 [details]
Bug 1880692, part 1 - Make StringBuilder::Unit::mLength specific to literals.
Beta/Release Uplift Approval Request
- User impact if declined: sec-high
- Is this code covered by automated tests?: Yes
- Has the fix been verified in Nightly?: No
- Needs manual test from QE?: Yes
- If yes, steps to reproduce: See comment 19. It might be hard to verify on lower memory configurations, like 32-bit and Android, but the code is platform independent so I think it is okay to skip those if it is too difficult.
- List of other uplifts needed: none
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky): This makes us use slightly more memory in some situations, but I don't think it'll matter in any realistic scenario.
- String changes made/needed: none
- Is Android affected?: Yes
ESR Uplift Approval Request
- If this is not a sec:{high,crit} bug, please state case for ESR consideration: sec-high
- User impact if declined: sec-high
- Fix Landed on Version: 125
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky): This makes us use slightly more memory in some situations, but I don't think it'll matter in any realistic scenario.
Assignee | ||
Updated•1 year ago
|
Assignee | ||
Comment 21•1 year ago
|
||
Comment on attachment 9381124 [details]
Bug 1880692, part 2 - Use an estimate for encoded characters.
Beta/Release Uplift Approval Request
- User impact if declined: (I got an error when trying to fill out the uplift request for beta before. See all of my previous answers.)
- Is this code covered by automated tests?: Yes
- Has the fix been verified in Nightly?: No
- Needs manual test from QE?: Yes
- If yes, steps to reproduce: see above
- List of other uplifts needed: None
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky): see above
- String changes made/needed: none
- Is Android affected?: Yes
Assignee | ||
Comment 22•1 year ago
|
||
Would you mind putting together a version of your test case that dynamically constructs the attack string instead of including it inline? It would be nice to have a test case for this in-tree, but a 700MB test case is obviously a bit hard to deal with. Thanks.
Reporter | ||
Comment 23•1 year ago
|
||
(In reply to Andrew McCreight [:mccr8] from comment #22)
Would you mind putting together a version of your test case that dynamically constructs the attack string instead of including it inline? It would be nice to have a test case for this in-tree, but a 700MB test case is obviously a bit hard to deal with. Thanks.
Try the new attachments ffbug_2060_20.htm and ffbug_2260_20_worker.js. This POC crashes other threads even more frequently than the previous version, probably because there's more JS running.
Reporter | ||
Comment 24•1 year ago
|
||
Reporter | ||
Comment 25•1 year ago
|
||
Updated•1 year ago
|
Updated•1 year ago
|
Comment 26•1 year ago
•
|
||
Using old Nightly from 2024-02-20 and the steps provided in comment 19 I was able to reproduce the crash (https://crash-stats.mozilla.org/report/index/4b6ebcd8-6dcf-436c-bd2d-774b50240228).
Using latest Nightly 125.0a1 from today I was not able to get the crash anymore and the expected result from comment 19 is met "Expected behavior: the page loads, but nothing happens, resulting in a blank tab.". Testing was done on macOS 13.6, Windows 10 64bit and Ubuntu 22.04.
Comment 27•1 year ago
|
||
Comment on attachment 9381857 [details]
Bug 1880692, part 1 - Make StringBuilder::Unit::mLength specific to literals.
Approved for 124.0b6
Updated•1 year ago
|
Comment 28•1 year ago
|
||
Updated•1 year ago
|
Comment 29•1 year ago
|
||
(In reply to Bogdan Maris, Desktop QA from comment #26)
Using old Nightly from 2024-02-20 and the steps provided in comment 19 I was able to reproduce the crash (https://crash-stats.mozilla.org/report/index/4b6ebcd8-6dcf-436c-bd2d-774b50240228).
Using latest Nightly 125.0a1 from today I was not able to get the crash anymore and the expected result from comment 19 is met "Expected behavior: the page loads, but nothing happens, resulting in a blank tab.". Testing was done on macOS 13.6, Windows 10 64bit and Ubuntu 22.04.
Same thing using the latest Beta 124 build from treeherder on the same OSs. Not marking as verified until ESR gets the fix as well when the time comes.
Comment 30•1 year ago
|
||
Comment on attachment 9381124 [details]
Bug 1880692, part 2 - Use an estimate for encoded characters.
Approved for 115.9esr.
Updated•1 year ago
|
Comment 31•1 year ago
|
||
uplift |
Updated•1 year ago
|
Comment 32•1 year ago
|
||
Also verified using the latest Firefox 115 ESR build from treeherder across platforms (Windows 10 64bit, macOS 13.6 and Ubuntu 22.04) that this is fixed.
Updated•1 year ago
|
Updated•1 year ago
|
Comment 33•1 year ago
|
||
Updated•1 year ago
|
Updated•9 months ago
|
Updated•5 months ago
|
Description
•