Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: utilityProcess.postMessage crash with invalid transferrable #41504

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

codebytere
Copy link
Member

Description of Change

Refs #37585

Fixes a potential crash in utilityProcess.postMessage when calling with an invalid transferable. From the original PR:

When passing an (unsupported) ArrayBuffer, for example, this happened because we try to unwrap everything in the transferrables list and upstream's WrapperInfo logic doesn't take into account that an object might have the correct number of internal fields but not be a WrappableBase.

This applies here as well.

This PR also removes a check in message_port.cc for at least one argument being passed. The type of message is any per spec, and thus null or undefined is acceptable. As it was, if null or undefined were passed the error wouldn't be thrown regardless, as those values can be converted to v8::Value.

Checklist

Release Notes

Notes: Fixes a potential crash in utilityProcess.postMessage when calling with an invalid transferable.

@codebytere codebytere added semver/patch backwards-compatible bug fixes target/28-x-y PR should also be added to the "28-x-y" branch. target/29-x-y PR should also be added to the "29-x-y" branch. target/30-x-y PR should also be added to the "30-x-y" branch. labels Mar 4, 2024
@electron-cation electron-cation bot added the new-pr 🌱 PR opened in the last 24 hours label Mar 4, 2024
@codebytere codebytere force-pushed the utility-process-wrapper-check branch from 1277859 to b737cdc Compare March 4, 2024 09:24
shell/browser/api/electron_api_utility_process.cc Outdated Show resolved Hide resolved
shell/browser/api/electron_api_utility_process.cc Outdated Show resolved Hide resolved
shell/browser/api/message_port.cc Outdated Show resolved Hide resolved
shell/browser/api/electron_api_utility_process.cc Outdated Show resolved Hide resolved
@codebytere codebytere force-pushed the utility-process-wrapper-check branch from b737cdc to a8afd88 Compare March 4, 2024 20:05
@electron-cation electron-cation bot removed the new-pr 🌱 PR opened in the last 24 hours label Mar 5, 2024
@codebytere codebytere force-pushed the utility-process-wrapper-check branch from a8afd88 to e5765e6 Compare March 7, 2024 09:47
@codebytere codebytere requested a review from nornagon March 7, 2024 09:47
@codebytere codebytere changed the title fix: utilityProcess.postMessage crash with invalid transferrable fix: utilityProcess.postMessage crash with invalid transferrable Mar 7, 2024
Comment on lines +12 to +25
bool IsValidWrappable(const v8::Local<v8::Value>& obj) {
v8::Local<v8::Object> port = v8::Local<v8::Object>::Cast(obj);

if (!port->IsObject())
return false;

if (port->InternalFieldCount() != gin::kNumberOfInternalFields)
return false;

const auto* info = static_cast<gin::WrapperInfo*>(
port->GetAlignedPointerFromInternalField(gin::kWrapperInfoIndex));

return info && info->embedder == gin::kEmbedderNativeGin;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't see how this differs from gin's impl...

here's gin:

void* FromV8Impl(v8::Isolate* isolate, v8::Local<v8::Value> val,
                 WrapperInfo* wrapper_info) {
  if (!val->IsObject())
    return NULL;
  v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(val);
  WrapperInfo* info = WrapperInfo::From(obj);

  // If this fails, the object is not managed by Gin. It is either a normal JS
  // object that's not wrapping any external C++ object, or it is wrapping some
  // C++ object, but that object isn't managed by Gin (maybe Blink).
  if (!info)
    return NULL;

  // If this fails, the object is managed by Gin, but it's not wrapping an
  // instance of the C++ class associated with wrapper_info.
  if (info != wrapper_info)
    return NULL;

  return obj->GetAlignedPointerFromInternalField(kEncodedValueIndex);
}

// ...

WrapperInfo* WrapperInfo::From(v8::Local<v8::Object> object) {
  if (object->InternalFieldCount() != kNumberOfInternalFields)
    return NULL;
  WrapperInfo* info = static_cast<WrapperInfo*>(
      object->GetAlignedPointerFromInternalField(kWrapperInfoIndex));
  return info->embedder == kEmbedderNativeGin ? info : NULL;
}
  1. Both check if the value is an Object,
  2. Both check that object->InternalFieldCount() == kNumberOfInternalFields,
  3. Both fetch the kWrapperInfoIndex'th internal field and cast it to WrapperInfo*
  4. Our impl checks that this value isn't NULL, where gin's doesn't. I suppose this is the one difference?
  5. Both check that info->embedder == kEmbedderNativeGin,
  6. gin checks that the info is the right one for the class being unwrapped, where ours doesn't. This seems like a bug in ours and we should check this.

Is (4) the only difference? If so it seems reasonable to upstream a change that adds a null check there. If that's what you mean when you say that's already been tried and rejected, is there a crrev link?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nornagon yes and 4 is what causes the crash. Here's the CL: https://chromium-review.googlesource.com/c/chromium/src/+/4335426

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, that's a bit confusing—Camillo said there that this check is unsound. That is, he's saying that an ArrayBuffer should never get to this point, and that's what the embedder should be checking. Adding this check in our helper functions exposes us to the same unsound behavior, which is that info, even if it's not null, is not even guaranteed to be a pointer! Meaning that info && info->embedder could segfault, or return other random data. We haven't really handled this edge case, we've just essentially applied the patch that was rejected upstream that has the same bug :)

I think there is a way around this, which is to reorder (6) and (5). That is, instead of what gin currently does, which is:

  1. Get info out of the internal fields
  2. Check that info->embedder == kEmbedderNativeGin
  3. Check that info == wrapper_info

Instead, do (3) first and then (2). Or even, I think, if info == wrapper_info then we don't even need to check info->embedder really, since the pointer matches the one from the class we're good to go.

I think this would address Camillo's concern?

@github-actions github-actions bot added the target/31-x-y PR should also be added to the "31-x-y" branch. label Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/patch backwards-compatible bug fixes target/28-x-y PR should also be added to the "28-x-y" branch. target/29-x-y PR should also be added to the "29-x-y" branch. target/30-x-y PR should also be added to the "30-x-y" branch. target/31-x-y PR should also be added to the "31-x-y" branch.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants