Commit 60459f54 authored by Kunihiko Sakamoto's avatar Kunihiko Sakamoto Committed by Commit Bot

WebBundles: Require nosniff header in webbundle responses

After this patch, Web Bundles response from network without
"X-Content-Type-Options: nosniff" header will cause
net::ERR_INVALID_WEB_BUNDLE error.

Spec: https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html#name-serving-constraints

Bug: 1018640
Change-Id: I32632ee547ab32ea6646d0150dcc9493c374c263
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1992944Reviewed-by: default avatarTsuyoshi Horo <horo@chromium.org>
Commit-Queue: Kunihiko Sakamoto <ksakamoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#731849}
parent 9671e688
......@@ -42,15 +42,6 @@ namespace {
constexpr char kLoadResultHistogram[] = "SignedExchange.LoadResult2";
constexpr char kPrefetchLoadResultHistogram[] =
"SignedExchange.Prefetch.LoadResult2";
constexpr char kContentTypeOptionsHeaderName[] = "x-content-type-options";
constexpr char kNoSniffHeaderValue[] = "nosniff";
bool HasNoSniffHeader(const network::mojom::URLResponseHead& response) {
std::string content_type_options;
response.headers->EnumerateHeader(nullptr, kContentTypeOptionsHeaderName,
&content_type_options);
return base::LowerCaseEqualsASCII(content_type_options, kNoSniffHeaderValue);
}
SignedExchangeHandlerFactory* g_signed_exchange_factory_for_testing_ = nullptr;
......@@ -163,7 +154,8 @@ void SignedExchangeLoader::OnStartLoadingResponseBody(
signed_exchange_handler_ = std::make_unique<SignedExchangeHandler>(
IsOriginSecure(outer_request_.url),
HasNoSniffHeader(*outer_response_head_), content_type_,
signed_exchange_utils::HasNoSniffHeader(*outer_response_head_),
content_type_,
std::make_unique<network::DataPipeToSourceStream>(
std::move(response_body)),
base::BindOnce(&SignedExchangeLoader::OnHTTPExchangeFound,
......
......@@ -28,6 +28,8 @@ namespace content {
namespace signed_exchange_utils {
namespace {
constexpr char kContentTypeOptionsHeaderName[] = "x-content-type-options";
constexpr char kNoSniffHeaderValue[] = "nosniff";
base::Optional<base::Time> g_verification_time_for_testing;
} // namespace
......@@ -80,6 +82,13 @@ bool ShouldHandleAsSignedHTTPExchange(
return true;
}
bool HasNoSniffHeader(const network::mojom::URLResponseHead& response) {
std::string content_type_options;
response.headers->EnumerateHeader(nullptr, kContentTypeOptionsHeaderName,
&content_type_options);
return base::LowerCaseEqualsASCII(content_type_options, kNoSniffHeaderValue);
}
base::Optional<SignedExchangeVersion> GetSignedExchangeVersion(
const std::string& content_type) {
// https://wicg.github.io/webpackage/loading.html#signed-exchange-version
......
......@@ -59,6 +59,9 @@ bool ShouldHandleAsSignedHTTPExchange(
const GURL& request_url,
const network::mojom::URLResponseHead& head);
// Returns true if |response| has "X-Content-Type-Options: nosniff" header.
bool HasNoSniffHeader(const network::mojom::URLResponseHead& response);
// Extracts the signed exchange version [1] from |content_type|, and converts it
// to SignedExchanveVersion. Returns nullopt if the mime type is not a variant
// of application/signed-exchange. Returns SignedExchangeVersion::kUnknown if an
......
......@@ -16,6 +16,7 @@
#include "base/task/post_task.h"
#include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/loader/single_request_url_loader_factory.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/browser/web_package/web_bundle_handle_tracker.h"
#include "content/browser/web_package/web_bundle_navigation_info.h"
#include "content/browser/web_package/web_bundle_reader.h"
......@@ -46,6 +47,9 @@ using DoneCallback = base::OnceCallback<void(
const GURL& target_inner_url,
std::unique_ptr<WebBundleURLLoaderFactory> url_loader_factory)>;
constexpr char kNoSniffErrorMessage[] =
"Web Bundle response must have \"X-Content-Type-Options: nosniff\" header.";
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("web_bundle_start_url_loader",
R"(
......@@ -463,6 +467,12 @@ class InterceptorForNetwork final : public NavigationLoaderInterceptor {
}
*client_receiver = forwarding_client_.BindNewPipeAndPassReceiver();
if (!signed_exchange_utils::HasNoSniffHeader(**response_head)) {
CompleteWithInvalidWebBundleError(std::move(forwarding_client_),
frame_tree_node_id_,
kNoSniffErrorMessage);
return true;
}
auto source = WebBundleSource::MaybeCreateFromNetworkUrl(request.url);
if (!source) {
CompleteWithInvalidWebBundleError(
......@@ -1041,6 +1051,12 @@ class InterceptorForHistoryNavigationFromNetwork final
"Unexpected content type.");
return true;
}
if (!signed_exchange_utils::HasNoSniffHeader(**response_head)) {
CompleteWithInvalidWebBundleError(std::move(forwarding_client_),
frame_tree_node_id_,
kNoSniffErrorMessage);
return true;
}
uint64_t length_hint =
(*response_head)->content_length > 0
? static_cast<uint64_t>((*response_head)->content_length)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment