Crash in [@ js::ErrorObject::init]
Categories
(Core :: JavaScript Engine, defect, P1)
Tracking
()
Tracking | Status | |
---|---|---|
firefox-esr68 | --- | unaffected |
firefox72 | --- | unaffected |
firefox73 | + | fixed |
firefox74 | --- | fixed |
People
(Reporter: philipp, Assigned: mccr8)
References
(Blocks 1 open bug, Regression)
Details
(Keywords: crash, regression)
Crash Data
Attachments
(1 file, 1 obsolete file)
47 bytes,
text/x-phabricator-request
|
RyanVM
:
approval-mozilla-beta+
|
Details | Review |
This bug is for crash report bp-a0e45964-058a-496d-b04b-15eea0200112.
Top 10 frames of crashing thread:
0 xul.dll static js::ErrorObject::init js/src/vm/ErrorObject.cpp:477
1 xul.dll static js::ErrorObject::create js/src/vm/ErrorObject.cpp:530
2 xul.dll JS::CreateError js/src/jsexn.cpp:703
3 xul.dll mozilla::dom::ClonedErrorHolder::ToErrorValue dom/ipc/ClonedErrorHolder.cpp:286
4 xul.dll static mozilla::dom::ClonedErrorHolder::ReadStructuredClone dom/ipc/ClonedErrorHolder.cpp:225
5 xul.dll mozilla::dom::`anonymous namespace'::StructuredCloneCallbacksRead dom/base/StructuredCloneHolder.cpp:55
6 xul.dll JSStructuredCloneReader::startRead js/src/vm/StructuredClone.cpp:2626
7 xul.dll ReadStructuredClone js/src/vm/StructuredClone.cpp:650
8 xul.dll mozilla::dom::StructuredCloneHolder::ReadFromBuffer dom/base/StructuredCloneHolder.cpp:316
9 xul.dll mozilla::dom::ipc::StructuredCloneData::Read dom/ipc/StructuredCloneData.cpp:101
this crash signature is increasing during the 73.0b cycle - perhaps due to the changes in bug 1588839?
Updated•5 years ago
|
Comment 1•5 years ago
|
||
Andrew, can you help find someone to investigate this new crash on 73?
Comment 2•5 years ago
|
||
Philipp's guess of the relation to bug 1588839 seems plausible. Kris, can you investigate?
Assignee | ||
Comment 3•5 years ago
|
||
It looks like we're crashing when deserializing a JSWindow actor. I looked at a half dozen crashes and they were all crashing on this specific line:
obj->initReservedSlot(FILENAME_SLOT, StringValue(fileName));
Based on prior experience with error messages and IPC, my wild guess would be this has to do with trying to send extremely long strings as a part of error messages, but I'm not sure how that would manifest specifically as a crash on this line. (In many places we've had to truncate strings that are part of error messages sent across IPC, because we were hitting OOM crashes.)
![]() |
||
Comment 4•5 years ago
|
||
That HandleString fileName
is just passed through by calls until we get to ClonedErrorHolder::ToErrorValue
, where it's inited like so:
JS::Rooted<JSString*> filename(aCx);
JS::Rooted<JSString*> message(aCx);
if (!ToJSString(aCx, mFilename, &filename) ||
!ToJSString(aCx, mMessage, &message)) {
return false;
}
ToJSString
is a custom thing defined in that file which will happily produce invalid things:
static bool ToJSString(JSContext* aCx, const nsACString& aStr,
JS::MutableHandle<JSString*> aJSString) {
if (aStr.IsVoid()) {
aJSString.set(nullptr);
return true;
}
...
so if aStr.IsVoid()
is true, we will end up with null in the Rooted<JSString*>
and when we try to StringValue()
it we will have an invalid JS::Value
. In a debug build you will have failed assert by now (e.g. the js::gc::IsCellPointerValid(str)
assert in JS::Value::setString
, but in an opt build we press on to the initReservedSlot
call, and eventually land at HeapSlot::post
. That does:
gc::Cell* cell = this->value.toGCThing();
if (cell->storeBuffer()) {
The toGCThing()
call is just casts and bitmasks, so will return 0. Then Cell::storeBuffer
will call Cell::chunk
, which also returns 0. Then it will try to do chunk->trailer.storeBuffer
and read that value.
offsetof(Chunk, trailer) == 1048552
and offsetof(ChunkTrailer, storeBuffer) == 8
. So I would expect crashes to happen around address 1048552 + 8 = 0xffff0
if we're ending up here. That is, in fact, what most of the crashreports show.
![]() |
||
Comment 5•5 years ago
|
||
Now what's unclear to me is whether JS::CreateError
should handle a null filename (like it handles a null message, if you dig down into things, at the level of js::ErrorObject::init
) or not. Jason, any opinions?
If it should, then we should fix that. If it should not, we need to figure out what to do in ClonedErrorHolder::ToErrorValue
if mFilename
is void. Which is its default state (see ClonedErrorHolder::ClonedErrorHolder
) until either ClonedErrorHolder::Init
happens with a non-null err->filename
or we do a ReadStructuredClone
.
Now in this case, we have ClonedErrorHolder::ReadStructuredClone
on the stack, so presumably the thing we serialized on the other end had a null filename for some reason... Presumably because it was initialized from a JSErrorReport
with a null filename?
Comment 6•5 years ago
|
||
Yeah, as far as I can see, the only way this can happen (short of data corruption) is if we serialize an error report with a null filename.
Assignee | ||
Comment 7•5 years ago
|
||
(In reply to Boris Zbarsky [:bzbarsky, bz on IRC] from comment #5)
Now what's unclear to me is whether
JS::CreateError
should handle a null filename (like it handles a null message, if you dig down into things, at the level ofjs::ErrorObject::init
) or not. Jason, any opinions?
FWIW, I see a number of places that null check JSErrorBase::filename in this list: https://searchfox.org/mozilla-central/search?q=symbol:F_%3CT_JSErrorBase%3E_filename&redirect=false
Comment 8•5 years ago
|
||
This bug isn't assigned and we're very-quickly running out of time to address this bug during this cycle with the All Hands next week and RC week after that. Is anybody actively working on this?
Assignee | ||
Comment 9•5 years ago
|
||
I can fix this. I was waiting on Jason to answer the needinfo, but I can just ask him to review a patch and see what he thinks.
Assignee | ||
Comment 10•5 years ago
|
||
I did figure out a way to hack up the code that causes a crash, following Boris's detailed breakdown of what the problem is. You just add err->filename = nullptr;
to ClonedErrorHolder::Init() right before it null checks err->filename
. This makes us send an error across IPC that has a null filename, and then we crash. I'm not sure how exactly to trigger that condition, though I did find bug 1610606. Given that there could be other issues lurking, plus the fact that it seems like a bad idea to crash if the child sends us bad data, I think it makes sense to just handle a null filename directly.
Assignee | ||
Comment 11•5 years ago
|
||
Oh, and after making that code change, you can run the test dom/ipc/tests/JSWindowActor/browser_sendQuery.js to trigger the crash. (I didn't check if some other thing would make it crash.)
Assignee | ||
Comment 12•5 years ago
|
||
If you serialize and then deserialize a JSErrorReport with a null filename
field, then we end up passing a null filename to JS::CreateError, which ends
up crashing. Perhaps CreateError should be robust against a null filename,
but the fact that whatever latent null filename issue wasn't causing crashes
until this serialization was added suggests it might be reasonable to deal
with it inside ClonedErrorHolder.
This patch causes a null string to instead become an empty string. We could do
this on the sending side, but by dealing with it on the receiving side we get
the bonus of keeping a malicious sender from crashing us.
Assignee | ||
Updated•5 years ago
|
Assignee | ||
Updated•5 years ago
|
Updated•5 years ago
|
Assignee | ||
Comment 13•5 years ago
|
||
Boris would prefer this be fixed on the JS side, so I'll move it back.
Assignee | ||
Comment 14•5 years ago
|
||
I tried to modify js::ErrorObject::init so that it does for .fileName what is being done for .message, but I started hitting various weird internal JS invariants like Assertion failure: obj->inDictionaryMode() || parent->hasMissingSlot() || child.slot() == parent->maybeSlot() + 1 || (parent->maybeSlot() + 1 < JSSLOT_FREE(obj->getClass()) && child.slot() == JSSLOT_FREE(obj->getClass()))
so I think somebody who is more familiar with the JS engine will have to look at this if we're going that route, so I'll re-needinfo Steven.
Assignee | ||
Comment 15•5 years ago
|
||
![]() |
||
Comment 16•5 years ago
|
||
Those asserts are from the change to js::ErrorObject::assignInitialShape
. That change needs to be undone; the property needs to exist. In particular, the issue is that the slot there is being added out of order, which is what the assert being hit is about.
Now what I don't know is why some of the callsites are setSlotWithType
and some are initReservedSlot
: I'd have to do some code-sleuthing to figure that out.
Comment 17•5 years ago
|
||
Jason, it looks like Andrew needs your help after all, could you provide some input or point me to who on the SM team could help.
Comment 18•5 years ago
•
|
||
(In reply to Boris Zbarsky [:bzbarsky, bz on IRC] from comment #5)
Now what's unclear to me is whether
JS::CreateError
should handle a null filename (like it handles a null message, if you dig down into things, at the level ofjs::ErrorObject::init
) or not. Jason, any opinions?
It should not.
There are two problems to fix here:
- something is producing invalid serialized data somehow
- deserialization must either accept or reject all serialized data, not crash
To fix problem 2, we should probably take mccr8's first patch. A more principled patch would treat that condition as invalid serialized input and refuse to create an Error.
On to problem 1:
If it should, then we should fix that. If it should not, we need to figure out what to do in
ClonedErrorHolder::ToErrorValue
ifmFilename
is void. Which is its default state (seeClonedErrorHolder::ClonedErrorHolder
) until eitherClonedErrorHolder::Init
happens with a non-nullerr->filename
or we do aReadStructuredClone
.Now in this case, we have
ClonedErrorHolder::ReadStructuredClone
on the stack, so presumably the thing we serialized on the other end had a null filename for some reason... Presumably because it was initialized from aJSErrorReport
with a null filename?
We could add a release assertion on initialization and try to catch this closer to the source.
Comment 19•5 years ago
|
||
Another way to proceed on problem 1 is to change JSErrorReport to require a non-null filename in its constructor. Currently the constructor leaves the filename
field null, an invalid state. It's up to users to populate it (like this).
Assignee | ||
Comment 20•5 years ago
|
||
I did find one place (bug 1610606) that could result in a null filename field, by code inspection. But of course there could be other places.
Updated•5 years ago
|
Assignee | ||
Updated•5 years ago
|
Assignee | ||
Comment 21•5 years ago
|
||
(In reply to Jason Orendorff [:jorendorff] from comment #18)
To fix problem 2, we should probably take mccr8's first patch. A more principled patch would treat that condition as invalid serialized input and refuse to create an Error.
I think we can't really handle this more nicely, because the exception is being sent in the middle of some kind of glob of JS, and as far as I can see any kind of error during deserialization results in a crash.
Boris, are you okay with the patch as it is, so we can fix the crash for beta, and then I can work on a larger patch to actually enforce that filename is non-null?
Updated•5 years ago
|
![]() |
||
Comment 22•5 years ago
|
||
Yeah, we can do that. Stamped. Thank you for looking at this!
![]() |
||
Updated•5 years ago
|
Comment 23•5 years ago
|
||
Comment 24•5 years ago
|
||
bugherder |
Comment 25•5 years ago
|
||
Andrew, could you request an uplift to beta please? Thanks
Assignee | ||
Comment 26•5 years ago
|
||
Comment on attachment 9122183 [details]
Bug 1608760 - Make ClonedErrorHolder::ToErrorValue() deal with a null filename.
Beta/Release Uplift Approval Request
- User impact if declined: crashes
- Is this code covered by automated tests?: Yes
- Has the fix been verified in Nightly?: No
- Needs manual test from QE?: No
- If yes, steps to reproduce:
- List of other uplifts needed: None
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky): The crash happens when we try to send a bad value from one process to another. We don't know how the bad value is created. The patch papers it over by detecting the bad value and fixing it. The behavior should be unchanged in other conditions.
- String changes made/needed: none
Comment 27•5 years ago
|
||
Comment on attachment 9122183 [details]
Bug 1608760 - Make ClonedErrorHolder::ToErrorValue() deal with a null filename.
Wallpapers over a crash in Beta73. Approved for 73.0b10.
Comment 28•5 years ago
|
||
bugherder uplift |
Description
•