`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 followed by 4GB of the repeated string `"` 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: 1. Unzip `ffbug_2260.zip`, yielding `ffbug_2260.htm`. 2. Put `ffbug_2260.htm` on a webserver, or in a local file. 3. Start FF and attach a debugger to it. 4. Set a BP on `AppendEncodedAttributeValue()` line 9369. 5. Load `ffbug_2260.htm` in FF. 6. When the BP fires, examine `space` and notice that it's `0xd5555507`. 7. Examine `str.Length()` and notice that it's `0x2aaaaaf9`, which, when added to `space` using 32-bit math, yields the overflowed quantity 0. 8. Step into `StringBuilder::AppendWithAttrEncode()` and notice that it adds a `Unit` having this length. 9. Set a BP on `StringBuilder::ToString` lines 9125 (output buffer allocation) and 9145 (the `Unit::Type::StringWithEncode` case) and proceed. 10. When the buffer-allocation BP fires, notice that `mLength` is tiny; this is because of the overflow that occurred in step 7. (The small additional string length of `0x10` is for the other elements that surround the gigantic string in the POC). 11. Proceed. When the `StringWithEncode` BP fires, step into `EncodeAttrString()` and watch it write far beyond the end of the output buffer allocated in step 10. 12. Proceed. FF may crash copying the escaped string contents into the heap, or a different thread may malfunction or crash as it uses the data that `EncodeAttrString()` sprayed over the heap.
Bug 1880692 Comment 0 Edit History
Note: The actual edited comment in the bug view page will always show the original commenter’s name and original timestamp.
`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 followed by 4GB of the repeated string `"` or one of a few other escape-strings. 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: 1. Unzip `ffbug_2260.zip`, yielding `ffbug_2260.htm`. 2. Put `ffbug_2260.htm` on a webserver, or in a local file. 3. Start FF and attach a debugger to it. 4. Set a BP on `AppendEncodedAttributeValue()` line 9369. 5. Load `ffbug_2260.htm` in FF. 6. When the BP fires, examine `space` and notice that it's `0xd5555507`. 7. Examine `str.Length()` and notice that it's `0x2aaaaaf9`, which, when added to `space` using 32-bit math, yields the overflowed quantity 0. 8. Step into `StringBuilder::AppendWithAttrEncode()` and notice that it adds a `Unit` having this length. 9. Set a BP on `StringBuilder::ToString` lines 9125 (output buffer allocation) and 9145 (the `Unit::Type::StringWithEncode` case) and proceed. 10. When the buffer-allocation BP fires, notice that `mLength` is tiny; this is because of the overflow that occurred in step 7. (The small additional string length of `0x10` is for the other elements that surround the gigantic string in the POC). 11. Proceed. When the `StringWithEncode` BP fires, step into `EncodeAttrString()` and watch it write far beyond the end of the output buffer allocated in step 10. 12. Proceed. FF may crash copying the escaped string contents into the heap, or a different thread may malfunction or crash as it uses the data that `EncodeAttrString()` sprayed over the heap.
`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: 1. Unzip `ffbug_2260_11.zip`, yielding `ffbug_2260_11.htm` and `ffbug_2260_11_worker.js` . 2. Put these files on a webserver (I used a local webserver at `127.0.0.1`). 3. Start FF and attach a debugger to it. 4. Set a BP on `AppendEncodedAttributeValue()` line 9369. 5. Load `ffbug_2260_11.htm` in FF. 6. When the BP fires, examine `space` and notice that it's `0xd5552000`. 7. Examine `str.Length()` and notice that it's `0x2aaae040`, which, when added to `space` using 32-bit math, yields the overflowed quantity 0x40. 8. Step into `StringBuilder::AppendWithAttrEncode()` and notice that it adds a `Unit` having this length, also adding the same quantity to `StringBuilder::mLength`, which is the total length of the string that `StringBuilder::ToString` will attempt to allocate later. 9. Set a BP on `StringBuilder::ToString` lines 9125 (output buffer allocation) and 9145 (the `Unit::Type::StringWithEncode` case) and proceed. 10. When the buffer-allocation BP fires, notice that `mLength` is tiny (probably `0x50`); this is because of the overflow that occurred in step 7. (The small additional string length of `0x10` is for the other elements that surround the gigantic string in the POC). 11. Proceed. When the `StringWithEncode` BP fires, step into `EncodeAttrString()` 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 appear to have `mCapacity` == `0x7b`). 12. 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`, usually both the `ToString()` thread and one or more other threads crash; to see multiple crashes, proceed after the debugger pops up with the first thread's crash).
`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: 1. Unzip `ffbug_2260_11.zip`, yielding `ffbug_2260_11.htm` and `ffbug_2260_11_worker.js` . 2. Put these files on a webserver (I used a local webserver at `127.0.0.1`). 3. Start FF and attach a debugger to it. 4. Set a BP on `AppendEncodedAttributeValue()` line 9369. 5. Load `ffbug_2260_11.htm` in FF. 6. When the BP fires, examine `space` and notice that it's `0xd5552000`. 7. Examine `str.Length()` and notice that it's `0x2aaae040`, which, when added to `space` using 32-bit math, yields the overflowed quantity 0x40. 8. Step into `StringBuilder::AppendWithAttrEncode()` and notice that it adds a `Unit` having this length, also adding the same quantity to `StringBuilder::mLength`, which is the total length of the string that `StringBuilder::ToString` will attempt to allocate later. 9. Set a BP on `StringBuilder::ToString` lines 9125 (output buffer allocation) and 9145 (the `Unit::Type::StringWithEncode` case) and proceed. 10. When the buffer-allocation BP fires, notice that `mLength` is tiny (probably `0x50`); this is because of the overflow that occurred in step 7. (The small additional string length of `0x10` is for the other elements that surround the gigantic string in the POC). 11. Proceed. When the `StringWithEncode` BP fires, step into `EncodeAttrString()` 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 appear to have `mCapacity` == `0x7b`). 12. 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 only crashes about 50% 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();`.
`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: 1. Unzip `ffbug_2260_11.zip`, yielding `ffbug_2260_11.htm` and `ffbug_2260_11_worker.js` . 2. Put these files on a webserver (I used a local webserver at `127.0.0.1`). 3. Start FF and attach a debugger to it. 4. Set a BP on `AppendEncodedAttributeValue()` line 9369. 5. Load `ffbug_2260_11.htm` in FF. 6. When the BP fires, examine `space` and notice that it's `0xd5552000`. 7. Examine `str.Length()` and notice that it's `0x2aaae040`, which, when added to `space` using 32-bit math, yields the overflowed quantity 0x40. 8. Step into `StringBuilder::AppendWithAttrEncode()` and notice that it adds a `Unit` having this length, also adding the same quantity to `StringBuilder::mLength`, which is the total length of the string that `StringBuilder::ToString` will attempt to allocate later. 9. Set a BP on `StringBuilder::ToString` lines 9125 (output buffer allocation) and 9145 (the `Unit::Type::StringWithEncode` case) and proceed. 10. When the buffer-allocation BP fires, notice that `mLength` is tiny (probably `0x50`); this is because of the overflow that occurred in step 7. (The small additional string length of `0x10` is for the other elements that surround the gigantic string in the POC). 11. Proceed. When the `StringWithEncode` BP fires, step into `EncodeAttrString()` 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 appear to have `mCapacity` == `0x7b`). 12. 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 50% 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();`.
`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: 1. Unzip `ffbug_2260_11.zip`, yielding `ffbug_2260_11.htm` and `ffbug_2260_11_worker.js` . 2. Put these files on a webserver (I used a local webserver at `127.0.0.1`). 3. Start FF and attach a debugger to it. 4. Set a BP on `AppendEncodedAttributeValue()` line 9369. 5. Load `ffbug_2260_11.htm` in FF. 6. When the BP fires, examine `space` and notice that it's `0xd5552000`. 7. Examine `str.Length()` and notice that it's `0x2aaae040`, which, when added to `space` using 32-bit math, yields the overflowed quantity 0x40. 8. Step into `StringBuilder::AppendWithAttrEncode()` and notice that it adds a `Unit` having this length, also adding the same quantity to `StringBuilder::mLength`, which is the total length of the string that `StringBuilder::ToString` will attempt to allocate later. 9. Set a BP on `StringBuilder::ToString` lines 9125 (output buffer allocation) and 9145 (the `Unit::Type::StringWithEncode` case) and proceed. 10. When the buffer-allocation BP fires, notice that `mLength` is tiny (probably `0x50`); this is because of the overflow that occurred in step 7. (The small additional string length of `0x10` is for the other elements that surround the gigantic string in the POC). 11. Proceed. When the `StringWithEncode` BP fires, step into `EncodeAttrString()` 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 appear to have `mCapacity` == `0x7b`). 12. 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();`
`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: 1. Unzip `ffbug_2260_11.zip`, yielding `ffbug_2260_11.htm` and `ffbug_2260_11_worker.js` . 2. Put these files on a webserver (I used a local webserver at `127.0.0.1`). 3. Start FF and attach a debugger to it. 4. Set a BP on `AppendEncodedAttributeValue()` line 9369. 5. Load `ffbug_2260_11.htm` in FF. 6. When the BP fires, examine `space` and notice that it's `0xd5552000`. 7. Examine `str.Length()` and notice that it's `0x2aaae040`, which, when added to `space` using 32-bit math, yields the overflowed quantity 0x40. 8. Step into `StringBuilder::AppendWithAttrEncode()` and notice that it adds a `Unit` having this length, also adding the same quantity to `StringBuilder::mLength`, which is the total length of the string that `StringBuilder::ToString` will attempt to allocate later. 9. Set a BP on `StringBuilder::ToString` lines 9125 (output buffer allocation) and 9145 (the `Unit::Type::StringWithEncode` case) and proceed. 10. When the buffer-allocation BP fires, notice that `mLength` is tiny (probably `0x50`); this is because of the overflow that occurred in step 7. (The small additional string length of `0x10` is for the other elements that surround the gigantic string in the POC). 11. Proceed. When the `StringWithEncode` BP fires, step into `EncodeAttrString()` 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 have `mCapacity` == `0x7b`). 12. 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();`