blob: 1092bff45fd16dff69e3d3ca014e3163446c7d10 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/loader/AllowedByNosniff.h"
#include "core/dom/ExecutionContext.h"
#include "core/frame/UseCounter.h"
#include "core/inspector/ConsoleMessage.h"
#include "platform/loader/fetch/ResourceResponse.h"
#include "platform/network/http_names.h"
#include "platform/network/mime/MIMETypeRegistry.h"
#include "platform/weborigin/SecurityOrigin.h"
namespace blink {
namespace {
// In addition to makeing an allowed/not-allowed decision,
// AllowedByNosniff::MimeTypeAsScript reports common usage patterns to support
// future decisions about which types can be safely be disallowed. Below
// is a number of constants about which use counters to report.
const WebFeature kApplicationFeatures[2] = {
WebFeature::kCrossOriginApplicationScript,
WebFeature::kSameOriginApplicationScript};
const WebFeature kTextFeatures[2] = {WebFeature::kCrossOriginTextScript,
WebFeature::kSameOriginTextScript};
const WebFeature kApplicationOctetStreamFeatures[2][2] = {
{WebFeature::kCrossOriginApplicationOctetStream,
WebFeature::kCrossOriginWorkerApplicationOctetStream},
{WebFeature::kSameOriginApplicationOctetStream,
WebFeature::kSameOriginWorkerApplicationOctetStream}};
const WebFeature kApplicationXmlFeatures[2][2] = {
{WebFeature::kCrossOriginApplicationXml,
WebFeature::kCrossOriginWorkerApplicationXml},
{WebFeature::kSameOriginApplicationXml,
WebFeature::kSameOriginWorkerApplicationXml}};
const WebFeature kTextHtmlFeatures[2][2] = {
{WebFeature::kCrossOriginTextHtml, WebFeature::kCrossOriginWorkerTextHtml},
{WebFeature::kSameOriginTextHtml, WebFeature::kSameOriginWorkerTextHtml}};
const WebFeature kTextPlainFeatures[2][2] = {
{WebFeature::kCrossOriginTextPlain,
WebFeature::kCrossOriginWorkerTextPlain},
{WebFeature::kSameOriginTextPlain, WebFeature::kSameOriginWorkerTextPlain}};
const WebFeature kTextXmlFeatures[2][2] = {
{WebFeature::kCrossOriginTextXml, WebFeature::kCrossOriginWorkerTextXml},
{WebFeature::kSameOriginTextXml, WebFeature::kSameOriginWorkerTextXml}};
} // namespace
bool AllowedByNosniff::MimeTypeAsScript(ExecutionContext* execution_context,
const ResourceResponse& response) {
String mime_type = response.HttpContentType();
// Allowed by nosniff?
if (!(ParseContentTypeOptionsHeader(response.HttpHeaderField(
HTTPNames::X_Content_Type_Options)) != kContentTypeOptionsNosniff ||
MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type))) {
execution_context->AddConsoleMessage(ConsoleMessage::Create(
kSecurityMessageSource, kErrorMessageLevel,
"Refused to execute script from '" + response.Url().ElidedString() +
"' because its MIME type ('" + mime_type +
"') is not executable, and "
"strict MIME type checking is "
"enabled."));
return false;
}
// TODO(mkwst): Remove the UseCoutner measurements below, once we verify
// that other vendors are following along with these restrictions..
// Check for certain non-executable MIME types.
// See:
// https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type
if (mime_type.StartsWithIgnoringASCIICase("image/") ||
mime_type.StartsWithIgnoringASCIICase("text/csv") ||
mime_type.StartsWithIgnoringASCIICase("audio/") ||
mime_type.StartsWithIgnoringASCIICase("video/")) {
execution_context->AddConsoleMessage(ConsoleMessage::Create(
kSecurityMessageSource, kErrorMessageLevel,
"Refused to execute script from '" + response.Url().ElidedString() +
"' because its MIME type ('" + mime_type +
"') is not executable."));
if (mime_type.StartsWithIgnoringASCIICase("image/")) {
UseCounter::Count(execution_context,
WebFeature::kBlockedSniffingImageToScript);
} else if (mime_type.StartsWithIgnoringASCIICase("audio/")) {
UseCounter::Count(execution_context,
WebFeature::kBlockedSniffingAudioToScript);
} else if (mime_type.StartsWithIgnoringASCIICase("video/")) {
UseCounter::Count(execution_context,
WebFeature::kBlockedSniffingVideoToScript);
} else if (mime_type.StartsWithIgnoringASCIICase("text/csv")) {
UseCounter::Count(execution_context,
WebFeature::kBlockedSniffingCSVToScript);
}
return false;
}
// Beyond this point, we accept the given MIME type. Since we also accept
// some legacy types, We want to log some of them using UseCounter to
// support deprecation decisions.
if (MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type))
return true;
if (mime_type.StartsWithIgnoringASCIICase("text/") &&
MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(
mime_type.Substring(5))) {
return true;
}
bool same_origin =
execution_context->GetSecurityOrigin()->CanRequest(response.Url());
bool worker_global_scope = execution_context->IsWorkerGlobalScope();
// These record usages for two MIME types (without subtypes), per same/cross
// origin.
if (mime_type.StartsWithIgnoringASCIICase("application/")) {
UseCounter::Count(execution_context, kApplicationFeatures[same_origin]);
} else if (mime_type.StartsWithIgnoringASCIICase("text/")) {
UseCounter::Count(execution_context, kTextFeatures[same_origin]);
}
// These record usages for several MIME types/subtypes, per same/cross origin,
// and per worker/non-worker context.
if (mime_type.StartsWithIgnoringASCIICase("application/octet-stream")) {
UseCounter::Count(
execution_context,
kApplicationOctetStreamFeatures[same_origin][worker_global_scope]);
} else if (mime_type.StartsWithIgnoringASCIICase("application/xml")) {
UseCounter::Count(
execution_context,
kApplicationXmlFeatures[same_origin][worker_global_scope]);
} else if (mime_type.StartsWithIgnoringASCIICase("text/html")) {
UseCounter::Count(execution_context,
kTextHtmlFeatures[same_origin][worker_global_scope]);
} else if (mime_type.StartsWithIgnoringASCIICase("text/plain")) {
UseCounter::Count(execution_context,
kTextPlainFeatures[same_origin][worker_global_scope]);
} else if (mime_type.StartsWithIgnoringCase("text/xml")) {
UseCounter::Count(execution_context,
kTextXmlFeatures[same_origin][worker_global_scope]);
}
return true;
}
} // namespace blink