#include <optional>
#include "base/debug/crash_logging.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/types/optional_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/api/messaging/channel_endpoint.h"
#include "extensions/browser/api/messaging/extension_message_port.h"
#include "extensions/browser/api/messaging/message_service.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/script_injection_tracker.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/trace_util.h"
#include "ipc/constants.mojom.h"
#include "url/origin_debug.h"
using content::BrowserThread;
using content::RenderProcessHost;
using perfetto::protos::pbzero::ChromeTrackEvent;
namespace extensions {
namespace {
bool IsPortContextSandboxed(RenderProcessHost& process,
const PortContext& context) {
if (!context.is_for_render_frame()) {
return false;
}
content::RenderFrameHost* frame = content::RenderFrameHost::FromID(
process.GetDeprecatedID(), context.frame->routing_id);
if (!frame) {
DUMP_WILL_BE_NOTREACHED();
return false;
}
if (!frame->HasPolicyContainerHost()) {
SCOPED_CRASH_KEY_NUMBER("EMF_INVALID_RFH", "lifecycle_state",
static_cast<size_t>(frame->GetLifecycleState()));
DUMP_WILL_BE_NOTREACHED();
return false;
}
return frame->IsSandboxed(network::mojom::WebSandboxFlags::kOrigin);
}
bool IsValidMessagingSource(RenderProcessHost& process,
const MessagingEndpoint& source_endpoint,
const PortContext& source_context) {
switch (source_endpoint.type) {
case MessagingEndpoint::Type::kNativeApp:
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_CHANNEL_SOURCE_TYPE);
return false;
case MessagingEndpoint::Type::kExtension:
if (!source_endpoint.extension_id.has_value()) {
if (!base::FeatureList::IsEnabled(
extensions_features::kCheckingNoExtensionIdInExtensionIpcs)) {
base::UmaHistogramSparse(
"Stability.BadMessageTerminated.Extensions",
bad_message::EMF_NO_EXTENSION_ID_FOR_EXTENSION_SOURCE);
return true;
}
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_NO_EXTENSION_ID_FOR_EXTENSION_SOURCE);
return false;
}
if (!util::CanRendererHostExtensionOrigin(
process.GetDeprecatedID(), source_endpoint.extension_id.value(),
IsPortContextSandboxed(process, source_context))) {
bad_message::ReceivedBadMessage(
&process,
bad_message::EMF_INVALID_EXTENSION_ID_FOR_EXTENSION_SOURCE);
return false;
}
return true;
case MessagingEndpoint::Type::kContentScript: {
if (!source_endpoint.extension_id) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_CONTENT_SCRIPT);
return false;
}
bool is_content_script_expected =
ScriptInjectionTracker::DidProcessRunContentScriptFromExtension(
process, *source_endpoint.extension_id);
if (!is_content_script_expected) {
debug::ScopedScriptInjectionTrackerFailureCrashKeys tracker_keys(
*process.GetBrowserContext(), source_endpoint.extension_id.value());
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_CONTENT_SCRIPT);
return false;
}
return true;
}
case MessagingEndpoint::Type::kUserScript: {
if (!source_endpoint.extension_id) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_USER_SCRIPT);
return false;
}
bool is_user_script_expected =
ScriptInjectionTracker::DidProcessRunUserScriptFromExtension(
process, *source_endpoint.extension_id);
if (!is_user_script_expected) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_USER_SCRIPT);
return false;
}
return true;
}
case MessagingEndpoint::Type::kWebPage:
if (source_endpoint.extension_id) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_WEB_PAGE);
return false;
}
return true;
}
}
bool IsValidMessagingTarget(RenderProcessHost& process,
const MessagingEndpoint& source_endpoint,
const ExtensionId& target_id) {
switch (source_endpoint.type) {
case MessagingEndpoint::Type::kNativeApp:
case MessagingEndpoint::Type::kExtension:
case MessagingEndpoint::Type::kWebPage:
case MessagingEndpoint::Type::kContentScript:
return true;
case MessagingEndpoint::Type::kUserScript:
CHECK(source_endpoint.extension_id);
if (source_endpoint.extension_id != target_id) {
bad_message::ReceivedBadMessage(
&process,
bad_message::EMF_INVALID_EXTERNAL_EXTENSION_ID_FOR_USER_SCRIPT);
return false;
}
return true;
}
}
bool IsValidSourceContext(RenderProcessHost& process,
const PortContext& source_context) {
if (source_context.is_for_service_worker()) {
const PortContext::WorkerContext& worker_context =
source_context.worker.value();
if (!util::CanRendererHostExtensionOrigin(
process.GetDeprecatedID(), worker_context.extension_id,
IsPortContextSandboxed(process, source_context))) {
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_INVALID_EXTENSION_ID_FOR_WORKER_CONTEXT);
return false;
}
}
return true;
}
bool IsValidSourceUrl(content::RenderProcessHost& process,
const GURL& source_url,
const PortContext& source_context) {
if (source_url.is_empty()) {
return true;
}
url::Origin base_origin;
if (source_context.is_for_render_frame()) {
content::RenderFrameHost* frame = content::RenderFrameHost::FromID(
process.GetDeprecatedID(), source_context.frame->routing_id);
if (!frame) {
return false;
}
if (frame->GetLastCommittedURL() == source_url) {
return true;
}
base_origin = frame->GetLastCommittedOrigin();
} else if (source_context.is_for_service_worker()) {
if (!IsValidSourceContext(process, source_context)) {
return false;
}
base_origin = Extension::CreateOriginFromExtensionId(
source_context.worker->extension_id);
} else {
DCHECK(source_context.is_for_native_host());
bad_message::ReceivedBadMessage(
&process,
bad_message::EMF_INVALID_OPEN_CHANNEL_TO_EXTENSION_FROM_NATIVE_HOST);
return false;
}
url::Origin source_url_origin = url::Origin::Resolve(source_url, base_origin);
if (IsPortContextSandboxed(process, source_context)) {
source_url_origin = source_url_origin.DeriveNewOpaqueOrigin();
}
auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
if (!policy->HostsOrigin(process.GetDeprecatedID(), source_url_origin)) {
SCOPED_CRASH_KEY_STRING256(
"EMF_INVALID_SOURCE_URL", "base_origin",
base_origin.GetDebugString(false ));
bad_message::ReceivedBadMessage(&process,
bad_message::EMF_INVALID_SOURCE_URL);
return false;
}
return true;
}
base::debug::CrashKeyString* GetTargetIdCrashKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"ExternalConnectionInfo-target_id", base::debug::CrashKeySize::Size64);
return crash_key;
}
base::debug::CrashKeyString* GetSourceOriginCrashKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"ExternalConnectionInfo-source_origin",
base::debug::CrashKeySize::Size256);
return crash_key;
}
base::debug::CrashKeyString* GetSourceUrlCrashKey() {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"ExternalConnectionInfo-source_url", base::debug::CrashKeySize::Size256);
return crash_key;
}
class ScopedExternalConnectionInfoCrashKeys {
public:
explicit ScopedExternalConnectionInfoCrashKeys(
const MessageService::ExternalConnectionInfo& info)
: target_id_(GetTargetIdCrashKey(), info.target_id),
source_endpoint_(info.source_endpoint),
source_origin_(GetSourceOriginCrashKey(),
base::OptionalToPtr(info.source_origin)),
source_url_(GetSourceUrlCrashKey(),
info.source_url.possibly_invalid_spec()) {}
~ScopedExternalConnectionInfoCrashKeys() = default;
ScopedExternalConnectionInfoCrashKeys(
const ScopedExternalConnectionInfoCrashKeys&) = delete;
ScopedExternalConnectionInfoCrashKeys& operator=(
const ScopedExternalConnectionInfoCrashKeys&) = delete;
private:
base::debug::ScopedCrashKeyString target_id_;
extensions::debug::ScopedMessagingEndpointCrashKeys source_endpoint_;
url::debug::ScopedOriginCrashKey source_origin_;
base::debug::ScopedCrashKeyString source_url_;
};
std::optional<ExtensionId> ValidateSourceContextAndExtractExtensionId(
content::RenderProcessHost& process,
const PortContext& source_context) {
if (!IsValidSourceContext(process, source_context)) {
return std::nullopt;
}
if (source_context.is_for_service_worker()) {
return source_context.worker->extension_id;
}
if (source_context.is_for_render_frame()) {
content::RenderFrameHost* frame = content::RenderFrameHost::FromID(
process.GetDeprecatedID(), source_context.frame->routing_id);
if (!frame) {
return std::nullopt;
}
const url::Origin& origin = frame->GetLastCommittedOrigin();
const url::SchemeHostPort& scheme_host_port =
origin.GetTupleOrPrecursorTupleIfOpaque();
if (scheme_host_port.scheme() != kExtensionScheme
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
&& scheme_host_port.scheme() != kArkwebExtensionScheme
#endif
) {
SCOPED_CRASH_KEY_STRING256(
"EMF_NON_EXTENSION_SENDER_FRAME", "origin",
origin.GetDebugString(false ));
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_NON_EXTENSION_SENDER_FRAME);
return std::nullopt;
}
return scheme_host_port.host();
}
DCHECK(source_context.is_for_native_host());
bad_message::ReceivedBadMessage(
&process, bad_message::EMF_NON_EXTENSION_SENDER_NATIVE_HOST);
return std::nullopt;
}
}
void MessageService::OpenChannelToExtension(
const ChannelEndpoint& source,
const PortId& source_port_id,
const ExternalConnectionInfo& info,
mojom::ChannelType channel_type,
const std::string& channel_name,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
port_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process =
content::RenderProcessHost::FromID(source.render_process_id());
if (!process) {
return;
}
ScopedExternalConnectionInfoCrashKeys info_crash_keys(info);
debug::ScopedPortContextCrashKeys port_context_crash_keys(
source.port_context());
if (!IsValidMessagingSource(*process, info.source_endpoint,
source.port_context()) ||
!IsValidMessagingTarget(*process, info.source_endpoint, info.target_id) ||
!IsValidSourceUrl(*process, info.source_url, source.port_context()) ||
!IsValidSourceContext(*process, source.port_context())) {
return;
}
std::unique_ptr<MessagePort> opener_port =
ExtensionMessagePort::CreateForEndpoint(
weak_factory_.GetWeakPtr(), source_port_id,
info.source_endpoint.extension_id ? *info.source_endpoint.extension_id
: ExtensionId(),
source, std::move(port), std::move(port_host));
OpenChannelToExtension(source, source_port_id, info.source_endpoint,
std::move(opener_port), info.target_id,
info.source_url, channel_type, channel_name);
}
void MessageService::OpenChannelToNativeApp(
const ChannelEndpoint& source,
const PortId& source_port_id,
const std::string& native_app_name,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
port_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process =
content::RenderProcessHost::FromID(source.render_process_id());
if (!process) {
return;
}
debug::ScopedPortContextCrashKeys port_context_crash_keys(
source.port_context());
if (!IsValidSourceContext(*process, source.port_context())) {
return;
}
OpenChannelToNativeAppImpl(source, source_port_id, native_app_name,
std::move(port), std::move(port_host));
}
void MessageService::OpenChannelToTab(
const ChannelEndpoint& source,
const PortId& source_port_id,
int tab_id,
int frame_id,
const std::string& document_id,
mojom::ChannelType channel_type,
const std::string& channel_name,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
port_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process =
content::RenderProcessHost::FromID(source.render_process_id());
if (!process) {
return;
}
std::optional<ExtensionId> extension_id =
ValidateSourceContextAndExtractExtensionId(*process,
source.port_context());
if (!extension_id) {
return;
}
OpenChannelToTabImpl(source, source_port_id, tab_id, frame_id, document_id,
*extension_id, channel_type, channel_name,
std::move(port), std::move(port_host));
}
void MessageService::OpenPort(RenderProcessHost* process,
const PortId& port_id,
const PortContext& source) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsValidSourceContext(*process, source)) {
return;
}
OpenPortImpl(port_id, process->GetDeprecatedID(), source);
}
void MessageService::ClosePort(RenderProcessHost* process,
const PortId& port_id,
const PortContext& port_context,
bool force_close) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!port_context.is_for_render_frame() &&
!port_context.is_for_service_worker()) {
bad_message::ReceivedBadMessage(process,
bad_message::EMF_INVALID_PORT_CONTEXT);
return;
}
if (!IsValidSourceContext(*process, port_context)) {
return;
}
int routing_id = port_context.frame ? port_context.frame->routing_id
: IPC::mojom::kRoutingIdNone;
int worker_thread_id =
port_context.worker ? port_context.worker->thread_id : kMainThreadId;
ClosePortImpl(port_id, process->GetDeprecatedID(), routing_id,
worker_thread_id, force_close, std::string());
}
void MessageService::NotifyResponsePending(RenderProcessHost* process,
const PortId& port_id,
const PortContext& port_context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsValidSourceContext(*process, port_context)) {
return;
}
NotifyResponsePending(port_id);
}
}