sdk icon indicating copy to clipboard operation
sdk copied to clipboard

Accessing contentWindow on cross domain iframes with static js interop doesn't work

Open jezell opened this issue 1 year ago • 6 comments
trafficstars

A security exception is thrown when trying to access the contentWindow property of a cross domain iframe using static interop:

frame.contentWindow!.postMessage(...)

Given the same setup using non static interop, the call succeeds:

(frame as html.IFrameElement).contentWindow!.postMessage(...)

It blows up here in the rtti:


      default:
        // The interceptors for native JavaScript types like bool, string, etc.
        // (excluding number and function, see above) are stored as a symbolized
        // property and can be accessed from the native value itself.
        classRef = JS('', '#[#]', obj, _extensionType);
        

With this message:


Uncaught DOMException: Failed to read a named property from 'Window': Blocked a frame with origin "http://localhost:8002" from accessing a cross-origin frame.
   at Object.getInterceptorForRti (http://localhost:8002/dart_sdk.js:12719:27)
   at dart_rti.Rti.new._isTestViaProperty (http://localhost:8002/dart_sdk.js:24534:28)
   at dart_rti.Rti.new._generalNullableIsTestImplementation (http://localhost:8002/dart_sdk.js:24525:24)
   at dart_rti.Rti.new._generalNullableAsCheckImplementation (http://localhost:8002/dart_sdk.js:24563:28)
   at Object.getProperty (http://localhost:8002/dart_sdk.js:71713:18)
   ...
   at internalCallback (http://localhost:8002/dart_sdk.js:37398:11)
   

It appears something about the rtti check is accessing a property that is locked down on cross domain frames when trying to get the Window object, but I'm unable to step deeper into the stack in the devtools to see exactly the property that is being checked.

jezell avatar Feb 16 '24 04:02 jezell

Item Details
Summary Static interop fails to access contentWindow on cross-domain iframes due to security exception.
Triage to area-dart-cli (medium confidence)

(what's this?)

dart-github-bot avatar Feb 16 '24 17:02 dart-github-bot

cc @nshahan @fishythefish - I believe this wouldn't affect the dart2js compiler due to differences in how we lookup the interceptor there, but it's worth checking nonetheless.

sigmundch avatar Feb 16 '24 17:02 sigmundch

Can repro similar behavior in JS trying to access properties that don't exist on a cross domain iframe.

var frame = document.createElement("iframe")
frame.src = "https://some_other_domain.com";
frame.contentWindow["property_that_does_not_exist"]; // throws

however

var frame = document.createElement("iframe")
frame.src = "https://some_other_domain.com";
frame.contentWindow["property_that_does_not_exist"] = 1;
frame.contentWindow["property_that_does_not_exist"]; // doesn't throw

So it is checking to see if it is a dart object or a js object, but that can't be done without an exception being thrown on the Window object since it is cross domain. Perhaps the an exception looking up the symbol could be handled and then the symbol set to null in the catch for a minimal performance penalty so it only happens first time around?

jezell avatar Feb 16 '24 17:02 jezell

@jezell Thanks for the helpful report!

@sigmundch I agree with your assessment. The code referenced here is only run in DDC. If dart2js has a problem finding the interceptor for cross origin values it will need to be addressed separately. Would dart2js allow for apps from different frames to pass Dart values between them or are they forced to do some type of conversion to a JavaScript interop value first?

If there isn't any Dart to Dart object passing allowed then in DDC it seems like the right thing to do here is to treat any cross origin value as a LegacyJavaScriptObject. We just need to find the right way to test for that as efficiently as possible.

nshahan avatar Feb 16 '24 18:02 nshahan

cc @rakudrama

precisely - I think treating it as JSObject makes sense.

Anything sent between separate apps (whether they are on the same page or separate iframes) will need to go through interop, and as a result is treated as JS values. Even if you send raw unboxed value through the interop boundary (bypassing some interop conversions), all RTI data is isolated, so it will be viewed as a JS object.

sigmundch avatar Feb 16 '24 18:02 sigmundch

Similar issue: https://github.com/dart-lang/sdk/issues/54443. It's possible we may come across other issues here even if this specific bug is fixed in the RTI.

srujzs avatar Feb 16 '24 19:02 srujzs

The Flutter sidebar in VS Code is failing with what seems like the same error:

https://github.com/Dart-Code/Dart-Code/issues/5049

The sidebar (part of Flutter DevTools) is hosted inside a sandboxed iframe in VS Code and trying to communicate with its parent using window.parent?.postMessage

If this isn't a simple fix, are there any reasonable workarounds we could apply to that code?

DanTup avatar Mar 27 '24 16:03 DanTup

@DanTup As a workaround, I believe you can cast to the old dart:html version which doesn't have the problem.

jezell avatar Mar 27 '24 17:03 jezell

That issue seems more like https://github.com/dart-lang/sdk/issues/54443 considering it's dart2js, but it's a similar issue around cross-domain frames. The error seems to arise from _generalNullableAsCheckImplementation, indicating there's a downcast with a nullable type.

Looking at the source I wonder if it's parent?.postMessage triggering this. Parker is using a workaround with getProperty to work around this. Maybe that can help here too, but I'll need to look at the JS code to determine which part is the issue. Is there a good way to repro this and get the sources?

srujzs avatar Mar 27 '24 19:03 srujzs

@srujzs you can repro this using VS Code and Flutter master (just try to load the Flutter Sidebar), but it's probably not very easy to debug that way (however I don't know if it can easily be reproduced outside of it because of the sandboxed iframe etc.).

You can click Help -> Toggle Developer Tools in VS Code to open the Chrome DevTools.

I'll see if getProperty helps.

DanTup avatar Mar 27 '24 19:03 DanTup

I tried all of these, but no change:

final parent = window.parent;
if (parent != null) {
  parent.postMessage(message.jsify(), targetOrigin.toJS);
}
(window.parent as JSObject?)
      ?.callMethod('postMessage'.toJS, message.jsify(), targetOrigin.toJS);
void postMessage(Object? message, String targetOrigin) {
  (window as JSObject)
      .getProperty<JSObject?>('parent'.toJS)
      ?.callMethod('postMessage'.toJS, message.jsify(), targetOrigin.toJS);
}
var parent = (window as JSObject).getProperty<JSObject?>('parent'.toJS);
if (parent != null) {
  parent.callMethod('postMessage'.toJS, message.jsify(), targetOrigin.toJS);
}

I don't know if any of them were exactly what you were suggesting though.

DanTup avatar Mar 27 '24 19:03 DanTup

Oh by the way, you can get at the source from your Flutter SDK checkout (be on master, not stable):

flutter\bin\cache\dart-sdk\bin\resources\devtools\main.dart.js

(let me know if I should move this to another issue since it sounds like it might not be the same)

For convenience, here's the bit of code I suspect you care about:

  A.DartToolingApiImpl_DartToolingApiImpl$postMessage_closure.prototype = {
    call$1(message) {
      var t1 = type$.nullable_JavaScriptObject._as(type$.JavaScriptObject._as(self.window).parent);
      if (t1 != null)
        A.callMethod(t1, "postMessage", [A.jsify(message), "*"], type$.void);
    },
    $signature: 7
  };

DanTup avatar Mar 27 '24 19:03 DanTup

(following up in https://github.com/flutter/devtools/pull/7476 - thanks for all the details!)

srujzs avatar Mar 27 '24 20:03 srujzs

This is still blocking us from being able to move to package:web.

jezell avatar Aug 08 '24 08:08 jezell

Can we bump priority of fixing this? cc @natebiggs

mraleph avatar Aug 08 '24 10:08 mraleph

Yeah, I don't see a usable workaround for this like we had with the Window wrapper in dart:html that wrapped dynamic. I'll work on seeing if we can get a wrapper like that in package:web next.

srujzs avatar Aug 08 '24 15:08 srujzs

@srujzs and I talked about this earlier. We've prioritized finding a solution here.

We want to make sure that it's an separate opt-in API for cases where a user knows they might be dealing with a x-domain object. Given the need for extra interceptor logic/a wrapper we want to make sure we don't degrade other interop use cases.

biggs0125 avatar Aug 08 '24 23:08 biggs0125