blob: 3b49510b0b3e975309ed00f11c5f9521be445d42 [file] [log] [blame]
// Copyright 2019 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/renderer_host/ipc_utils.h"
#include <utility>
#include "content/browser/bad_message.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/common/frame.mojom.h"
#include "content/common/navigation_params_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/url_constants.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
namespace content {
namespace {
// Validates that |received_token| is non-null iff associated with a blob: URL.
bool VerifyBlobToken(
int process_id,
const mojo::PendingRemote<blink::mojom::BlobURLToken>& received_token,
const GURL& received_url) {
DCHECK_NE(ChildProcessHost::kInvalidUniqueID, process_id);
if (received_token.is_valid()) {
if (!received_url.SchemeIsBlob()) {
bad_message::ReceivedBadMessage(
process_id, bad_message::BLOB_URL_TOKEN_FOR_NON_BLOB_URL);
return false;
}
}
return true;
}
bool VerifyInitiatorOrigin(int process_id,
const url::Origin& initiator_origin) {
// TODO(acolwell, nasko): https://crbug.com/1029092: Ensure the precursor of
// opaque origins matches the origin lock. One known problematic case are
// reloads initiated from error pages - see the following
// RenderFrameHostManagerTest tests:
// 1. ErrorPageNavigationReload:
// - renderer origin lock = chrome-error://chromewebdata/
// - precursor of initiator origin = http://127.0.0.1:.../
// 2. ErrorPageNavigationReload_InSubframe_BlockedByClient
// - renderer origin lock = http://b.com:.../
// - precursor of initiator origin = http://c.com:.../
if (initiator_origin.opaque())
return true;
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
if (!policy->CanAccessDataForOrigin(process_id, initiator_origin)) {
bad_message::ReceivedBadMessage(process_id,
bad_message::INVALID_INITIATOR_ORIGIN);
return false;
}
return true;
}
} // namespace
bool VerifyDownloadUrlParams(SiteInstance* site_instance,
const blink::mojom::DownloadURLParams& params) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(site_instance);
RenderProcessHost* process = site_instance->GetProcess();
int process_id = process->GetID();
// Verifies |params.blob_url_token| is appropriately set.
if (!VerifyBlobToken(process_id, params.blob_url_token, params.url))
return false;
// Verify |params.initiator_origin|.
if (params.initiator_origin &&
!VerifyInitiatorOrigin(process_id, *params.initiator_origin))
return false;
// If |params.url| is not set, this must be a large data URL being passed
// through |params.data_url_blob|.
if (!params.url.is_valid() && !params.data_url_blob.is_valid())
return false;
// Verification succeeded.
return true;
}
bool VerifyOpenURLParams(RenderFrameHostImpl* current_rfh,
SiteInstance* site_instance,
const blink::mojom::OpenURLParamsPtr& params,
GURL* out_validated_url,
scoped_refptr<network::SharedURLLoaderFactory>*
out_blob_url_loader_factory) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(current_rfh);
DCHECK(site_instance);
DCHECK(out_validated_url);
DCHECK(out_blob_url_loader_factory);
RenderProcessHost* process = site_instance->GetProcess();
int process_id = process->GetID();
// Verify |params.url| and populate |out_validated_url|.
*out_validated_url = params->url;
process->FilterURL(false, out_validated_url);
// Verify |params.blob_url_token| and populate |out_blob_url_loader_factory|.
if (!VerifyBlobToken(process_id, params->blob_url_token, params->url))
return false;
if (params->blob_url_token.is_valid()) {
*out_blob_url_loader_factory =
ChromeBlobStorageContext::URLLoaderFactoryForToken(
site_instance->GetBrowserContext()->GetStoragePartition(
site_instance),
std::move(params->blob_url_token));
}
// Verify |params.post_body|.
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
if (!policy->CanReadRequestBody(site_instance, params->post_body)) {
bad_message::ReceivedBadMessage(process,
bad_message::ILLEGAL_UPLOAD_PARAMS);
return false;
}
// Verify |params.initiator_origin|.
if (!VerifyInitiatorOrigin(process_id, params->initiator_origin))
return false;
// Verify that the initiator frame can navigate `current_rfh`.
if (!VerifyNavigationInitiator(current_rfh, params->initiator_frame_token,
process_id)) {
return false;
}
// Verification succeeded.
return true;
}
bool VerifyBeginNavigationCommonParams(
SiteInstance* site_instance,
blink::mojom::CommonNavigationParams* common_params) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(site_instance);
DCHECK(common_params);
RenderProcessHost* process = site_instance->GetProcess();
int process_id = process->GetID();
// Verify (and possibly rewrite) |url|.
process->FilterURL(false, &common_params->url);
if (common_params->url.SchemeIs(kChromeErrorScheme)) {
mojo::ReportBadMessage("Renderer cannot request error page URLs directly");
return false;
}
// Verify |post_data|.
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
if (!policy->CanReadRequestBody(site_instance, common_params->post_data)) {
bad_message::ReceivedBadMessage(process,
bad_message::ILLEGAL_UPLOAD_PARAMS);
return false;
}
// Verify |transition| is webby.
if (!PageTransitionIsWebTriggerable(
ui::PageTransitionFromInt(common_params->transition))) {
bad_message::ReceivedBadMessage(
process, bad_message::RFHI_BEGIN_NAVIGATION_NON_WEBBY_TRANSITION);
return false;
}
// Verify |initiator_origin|.
if (!common_params->initiator_origin.has_value()) {
bad_message::ReceivedBadMessage(
process, bad_message::RFHI_BEGIN_NAVIGATION_MISSING_INITIATOR_ORIGIN);
return false;
}
if (!VerifyInitiatorOrigin(process_id,
common_params->initiator_origin.value())) {
return false;
}
// Verify |base_url_for_data_url|.
if (!common_params->base_url_for_data_url.is_empty()) {
// Kills the process. http://crbug.com/726142
bad_message::ReceivedBadMessage(
process, bad_message::RFH_BASE_URL_FOR_DATA_URL_SPECIFIED);
return false;
}
// Asynchronous (browser-controlled, but) renderer-initiated navigations can
// not be same-document. Allowing this incorrectly could have us try to
// navigate an existing document to a different site.
if (NavigationTypeUtils::IsSameDocument(common_params->navigation_type))
return false;
// Verification succeeded.
return true;
}
bool VerifyNavigationInitiator(
RenderFrameHostImpl* current_rfh,
const absl::optional<blink::LocalFrameToken>& initiator_frame_token,
int initiator_process_id) {
// Verify that a frame inside a fenced frame cannot navigate its ancestors,
// unless the frame being navigated is the outermost main frame.
if (current_rfh->IsOutermostMainFrame())
return true;
if (!initiator_frame_token)
return true;
RenderFrameHostImpl* initiator_render_frame_host =
RenderFrameHostImpl::FromFrameToken(initiator_process_id,
initiator_frame_token.value());
if (!initiator_render_frame_host)
return true;
// Verify that a frame cannot navigate a frame with a different fenced frame
// nonce, unless the navigating frame is a fenced frame root and its owner
// frame has the same fenced frame nonce as the initiator frame (e.g. in a
// A(A1,A2(FF)) setup, A, A1, and A2 are all allowed to navigate FF).
absl::optional<base::UnguessableToken> initiator_fenced_frame_nonce =
initiator_render_frame_host->frame_tree_node()->fenced_frame_nonce();
if (initiator_fenced_frame_nonce !=
current_rfh->frame_tree_node()->fenced_frame_nonce()) {
if (!current_rfh->IsFencedFrameRoot() ||
current_rfh->frame_tree_node()
->GetParentOrOuterDocument()
->frame_tree_node()
->fenced_frame_nonce() != initiator_fenced_frame_nonce) {
mojo::ReportBadMessage(
"The fenced frame nonces of initiator and current frame don't match, "
"nor is the current frame a fenced frame root whose owner frame has "
"the same fenced frame nonce as the initiator frame.");
return false;
}
}
if (!initiator_render_frame_host->IsNestedWithinFencedFrame())
return true;
FrameTreeNode* node = initiator_render_frame_host->frame_tree_node();
if (node == current_rfh->frame_tree_node())
return true;
while (node) {
node = node->parent() ? node->parent()->frame_tree_node() : nullptr;
if (node == current_rfh->frame_tree_node()) {
mojo::ReportBadMessage(
"A frame in a fenced frame tree cannot navigate an ancestor frame.");
return false;
}
}
return true;
}
} // namespace content