blob: b4efd5720aa6b1bd2e60d9a0483629d01579c298 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/mojo_binder_policy_applier.h"
#include <string_view>
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_set.h"
#include "content/public/browser/mojo_binder_policy_map.h"
#include "mojo/public/cpp/bindings/message.h"
namespace {
// TODO(crbug.com/40196368): It is not sustainable to maintain a list.
// An ideal solution should:
// 1. Show a pre-submit warning if a frame-scoped interface is specified with
// kDefer but declares synchronous methods.
// 2. When an interface that can make sync IPC is registered with BinderMap,
// change its policy to kCancel by default.
// 3. Bind these receivers to a generic implementation, and terminate the
// execution context if it receives a synchronous message.
// Stores the list of interface names that declare sync methods.
constexpr auto kSyncMethodInterfaces = base::MakeFixedFlatSet<std::string_view>(
{"blink.mojom.NotificationService"});
} // namespace
namespace content {
MojoBinderPolicyApplier::MojoBinderPolicyApplier(
const MojoBinderPolicyMapImpl* policy_map,
base::OnceCallback<void(const std::string& interface_name)> cancel_callback)
: policy_map_(*policy_map), cancel_callback_(std::move(cancel_callback)) {}
MojoBinderPolicyApplier::~MojoBinderPolicyApplier() = default;
// static
std::unique_ptr<MojoBinderPolicyApplier>
MojoBinderPolicyApplier::CreateForSameOriginPrerendering(
base::OnceCallback<void(const std::string& interface_name)>
cancel_callback) {
return std::make_unique<MojoBinderPolicyApplier>(
MojoBinderPolicyMapImpl::GetInstanceForSameOriginPrerendering(),
std::move(cancel_callback));
}
// static
std::unique_ptr<MojoBinderPolicyApplier>
MojoBinderPolicyApplier::CreateForPreview(
base::OnceCallback<void(const std::string& interface_name)>
cancel_callback) {
return std::make_unique<MojoBinderPolicyApplier>(
MojoBinderPolicyMapImpl::GetInstanceForPreview(),
std::move(cancel_callback));
}
void MojoBinderPolicyApplier::ApplyPolicyToNonAssociatedBinder(
const std::string& interface_name,
base::OnceClosure binder_callback) {
if (mode_ == Mode::kGrantAll) {
std::move(binder_callback).Run();
return;
}
const MojoBinderNonAssociatedPolicy policy =
GetNonAssociatedMojoBinderPolicy(interface_name);
// Run in the kPrepareToGrantAll mode before the renderer sends back a
// DidCommitActivation. In this mode, MojoBinderPolicyApplier loosens
// policies, but still defers binders to ensure that the renderer does not
// receive unexpected messages before CommitActivation arrives.
if (mode_ == Mode::kPrepareToGrantAll) {
switch (policy) {
case MojoBinderNonAssociatedPolicy::kGrant:
// Grant these two kinds of interfaces because:
// - kCancel and kUnexpected interfaces may have sync methods, so grant
// them to avoid deadlocks.
// - Renderer might request these interfaces during the prerenderingchange
// event, because from the page's point of view it is no longer
// prerendering.
case MojoBinderNonAssociatedPolicy::kCancel:
case MojoBinderNonAssociatedPolicy::kUnexpected:
std::move(binder_callback).Run();
break;
case MojoBinderNonAssociatedPolicy::kDefer:
if (base::Contains(kSyncMethodInterfaces, interface_name)) {
std::move(binder_callback).Run();
} else {
deferred_binders_.push_back(std::move(binder_callback));
}
break;
}
return;
}
DCHECK_EQ(mode_, Mode::kEnforce);
switch (policy) {
case MojoBinderNonAssociatedPolicy::kGrant:
std::move(binder_callback).Run();
break;
case MojoBinderNonAssociatedPolicy::kCancel:
if (cancel_callback_) {
std::move(cancel_callback_).Run(interface_name);
}
break;
case MojoBinderNonAssociatedPolicy::kDefer:
if (base::Contains(kSyncMethodInterfaces, interface_name)) {
deferred_sync_binders_.push_back(std::move(binder_callback));
} else {
deferred_binders_.push_back(std::move(binder_callback));
}
break;
case MojoBinderNonAssociatedPolicy::kUnexpected:
mojo::ReportBadMessage("MBPA_BAD_INTERFACE: " + interface_name);
if (cancel_callback_) {
std::move(cancel_callback_).Run(interface_name);
}
break;
}
}
bool MojoBinderPolicyApplier::ApplyPolicyToAssociatedBinder(
const std::string& interface_name) {
MojoBinderAssociatedPolicy policy = MojoBinderAssociatedPolicy::kCancel;
switch (mode_) {
// Always allow binders to run.
case Mode::kGrantAll:
case Mode::kPrepareToGrantAll:
return true;
case Mode::kEnforce:
policy = policy_map_->GetAssociatedMojoBinderPolicy(
interface_name, MojoBinderAssociatedPolicy::kCancel);
if (policy != MojoBinderAssociatedPolicy::kGrant) {
if (cancel_callback_)
std::move(cancel_callback_).Run(interface_name);
return false;
}
}
return true;
}
void MojoBinderPolicyApplier::PrepareToGrantAll() {
DCHECK_EQ(mode_, Mode::kEnforce);
// The remote side would think its status has changed after the browser
// executes this method, so it is safe to send some synchronous method, so the
// browser side should make the IPC pipeline ready.
for (auto& deferred_binder : deferred_sync_binders_) {
std::move(deferred_binder).Run();
}
deferred_sync_binders_.clear();
mode_ = Mode::kPrepareToGrantAll;
}
void MojoBinderPolicyApplier::GrantAll() {
DCHECK_NE(mode_, Mode::kGrantAll);
// Check that we are in a Mojo message dispatch, since the deferred binders
// might call mojo::ReportBadMessage().
//
// TODO(https://crbug.com/1217977): Give the deferred_binders_ a
// BadMessageCallback and forbid them from using mojo::ReportBadMessage()
// directly. We are currently in the message stack of one of the PageBroadcast
// Mojo callbacks handled by RenderViewHost, so if a binder calls
// mojo::ReportBadMessage() it kills possibly the wrong renderer. Even if we
// only run the binders associated with the RVH for each message per-RVH,
// there are still subtle problems with running all these callbacks at once:
// for example, mojo::GetMessageCallback()/mojo::ReportBadMessage() can only
// be called once per message dispatch.
DCHECK(mojo::IsInMessageDispatch());
mode_ = Mode::kGrantAll;
// It's safe to iterate over `deferred_binders_` because no more callbacks
// will be added to it once `grant_all_` is true."
for (auto& deferred_binder : deferred_binders_)
std::move(deferred_binder).Run();
deferred_binders_.clear();
}
void MojoBinderPolicyApplier::DropDeferredBinders() {
deferred_binders_.clear();
}
MojoBinderNonAssociatedPolicy
MojoBinderPolicyApplier::GetNonAssociatedMojoBinderPolicy(
const std::string& interface_name) const {
return policy_map_->GetNonAssociatedMojoBinderPolicy(interface_name,
default_policy_);
}
} // namespace content