blob: fc94db6e9b55323e7e1f160a39d325c316496e05 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/to_string.h"
// This file contains the ResponseAnalyzerTests (which test the response
// analyzer's behavior in several parameterized test scenarios) and at the end
// includes the CrossOriginReadBlockingTests, which are more typical unittests.
#include <initializer_list>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/test/task_environment.h"
#include "net/base/mime_sniffer.h"
#include "net/http/http_util.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_test_util.h"
#include "services/network/orb/orb_impl.h"
#include "services/network/orb/orb_mimetypes.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace network::orb {
namespace {
// CORB's verdict on a given scenario. kAllowBecauseOutOfData occurs when one of
// the sniffers still desires more data but the response has run out, or
// net::kMaxBytesToSniff has been reached.
enum class Verdict {
kAllow,
kBlock,
kAllowBecauseOutOfData,
};
constexpr int kVerdictPacketForHeadersBasedVerdict = -1;
constexpr int kVerdictPacketForInconclusiveSniffing = 99999;
// This struct is used to describe each test case in this file. It's passed as
// a test parameter to each TEST_P test.
struct TestScenario {
// Attributes to make test failure messages useful.
const char* description;
int source_line;
// Attributes of the HTTP Request.
const char* target_url;
const char* initiator_origin;
// Attributes of the HTTP response.
const char* response_headers;
const char* response_content_type;
MimeType canonical_mime_type;
// |packets| specifies the response data which may arrive over the course of
// several writes.
std::initializer_list<const char*> packets;
std::string data() const {
std::string data;
for (const char* packet : packets) {
data += packet;
}
return data;
}
// Whether the resource should seem sensitive (either through the CORS
// heuristic or the Cache heuristic). This is used for testing that CORB would
// have protected the resource, were it requested cross-origin.
bool resource_is_sensitive;
// Expected result.
Verdict verdict;
// The packet number during which the verdict is decided.
// kVerdictPacketForHeadersBasedVerdict means that the verdict can be decided
// before the first packet's data is available. |packets.size()| means that
// the verdict is decided during the end-of-stream call.
int verdict_packet;
};
inline std::ostream& operator<<(std::ostream& out, const MimeType& value) {
out << static_cast<int>(value);
return out;
}
// Stream operator to let GetParam() print a useful result if any tests fail.
::std::ostream& operator<<(::std::ostream& os, const TestScenario& scenario) {
std::string verdict;
switch (scenario.verdict) {
case Verdict::kAllow:
verdict = "Verdict::kAllow";
break;
case Verdict::kBlock:
verdict = "Verdict::kBlock";
break;
case Verdict::kAllowBecauseOutOfData:
verdict = "Verdict::kAllowBecauseOutOfData";
break;
}
std::string response_headers_formatted;
base::ReplaceChars(scenario.response_headers, "\n",
"\n ",
&response_headers_formatted);
std::string packets = "{";
for (std::string packet : scenario.packets) {
base::ReplaceChars(packet, "\\", "\\\\", &packet);
base::ReplaceChars(packet, "\"", "\\\"", &packet);
base::ReplaceChars(packet, "\n", "\\n", &packet);
base::ReplaceChars(packet, "\t", "\\t", &packet);
base::ReplaceChars(packet, "\r", "\\r", &packet);
if (packets.length() > 1)
packets += ", ";
packets += "\"";
packets += packet;
packets += "\"";
}
packets += "}";
return os << "\n description = " << scenario.description
<< "\n source_line = " << scenario.source_line
<< "\n target_url = " << scenario.target_url
<< "\n initiator_origin = " << scenario.initiator_origin
<< "\n response_headers = " << response_headers_formatted
<< "\n response_content_type = " << scenario.response_content_type
<< "\n canonical_mime_type = " << scenario.canonical_mime_type
<< "\n packets = " << packets
<< "\n resource_is_sensitive = "
<< base::ToString(scenario.resource_is_sensitive)
<< "\n verdict = " << verdict
<< "\n verdict_packet = " << scenario.verdict_packet;
}
// An HTML response with an HTML comment that's longer than the sniffing
// threshold. We don't sniff past net::kMaxBytesToSniff, so these are not
// protected
const char kHTMLWithTooLongComment[] =
"<!--.............................................................72 chars"
"................................................................144 chars"
"................................................................216 chars"
"................................................................288 chars"
"................................................................360 chars"
"................................................................432 chars"
"................................................................504 chars"
"................................................................576 chars"
"................................................................648 chars"
"................................................................720 chars"
"................................................................792 chars"
"................................................................864 chars"
"................................................................936 chars"
"...............................................................1008 chars"
"...............................................................1080 chars"
"--><html><head>";
// A set of test cases that verify CrossSiteDocumentResourceHandler correctly
// classifies network responses as allowed or blocked. These TestScenarios are
// passed to the TEST_P tests below as test parameters.
const TestScenario kScenarios[] = {
// Allowed responses (without sniffing):
{
"Allowed: Same-origin XHR to HTML",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: Same-origin JSON with parser breaker and HTML mime type",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{")]}',\n[true, true, false, \"user@chromium.org\"]"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: Same-origin JSON with parser breaker and JSON mime type",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/json", // response_content_type
MimeType::kJson, // canonical_mime_type
{")]}'\n[true, true, false, \"user@chromium.org\"]"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: Cross-site script without parser breaker",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: Cross-site XHR to HTML with CORS for origin",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: Cross-site XHR to XML with CORS for any",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Access-Control-Allow-Origin: *", // response_headers
"application/rss+xml", // response_content_type
MimeType::kXml, // canonical_mime_type
{"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: Cross-site XHR to JSON with CORS for null",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Access-Control-Allow-Origin: null", // response_headers
"text/json", // response_content_type
MimeType::kJson, // canonical_mime_type
{"{\"x\" : 3}"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
// This case won't be reached in practice today, because CORB is only
// used by certain URLLoaderFactories (e.g. in the NetworkService, when
// handling http(s) URLs) and is not used for ftp://... URLs.
"Blocked: Cross-site XHR to HTML over FTP",
__LINE__,
"ftp://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
{
// This case won't be reached in practice today, because CORB is only
// used by certain URLLoaderFactories (e.g. in the NetworkService, when
// handling http(s) URLs) and is not used for file://... URLs.
"Blocked: Cross-site XHR to HTML from file://",
__LINE__,
"file:///foo/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
{
// Blocked. (Simulating a behavior of a compromised renderer that only
// pretends to be hosting PDF).
"Blocked: Cross-site fetch HTML from Flash without CORS",
__LINE__,
"http://www.b.com/plugin.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: Cross-site fetch HTML from NaCl with CORS response",
__LINE__,
"http://www.b.com/plugin.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // first_chunk
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: JSON object + CORS with parser-breaker labeled as JavaScript",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Access-Control-Allow-Origin: *", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{")]}'\n[true, false]"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked: JSON object labeled as JavaScript with a no-sniff header",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"{ \"key\"", ": true }"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
1, // verdict_packet
},
{
"Allowed: Empty response with PNG mime type",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"image/png", // response_content_type
MimeType::kOthers, // canonical_mime_type
{}, // packets
false, // resource_is_sensitive
Verdict::kAllowBecauseOutOfData, // verdict
kVerdictPacketForInconclusiveSniffing, // verdict_packet
},
{
"Allowed: Empty response with PNG mime type and nosniff header",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"image/png", // response_content_type
MimeType::kOthers, // canonical_mime_type
{}, // packets
false, // resource_is_sensitive
Verdict::kAllowBecauseOutOfData, // verdict
kVerdictPacketForInconclusiveSniffing, // verdict_packet
},
// Allowed responses due to sniffing:
{
"Allowed: Cross-site script to JSONP labeled as HTML",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"foo({\"x\" : 3})"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: Cross-site script to JavaScript labeled as text",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{"var x = 3;"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: JSON-like JavaScript labeled as text",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{"{", " \n", "var x = 3;\n", "console.log('hello');"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
2, // verdict_packet
},
{
"Allowed: JSONP labeled as JSON",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/json", // response_content_type
MimeType::kJson, // canonical_mime_type
{"invoke({ \"key\": true });"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed (for now): JSON array literal labeled as text/plain",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{"[1, 2, {}, true, false, \"yay\"]"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: JSON array literal on which a function is called.",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{"[1, 2, {}, true, false, \"yay\"]", ".map(x => console.log(x))",
".map(x => console.log(x));"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: Cross-site XHR to nonsense labeled as XML",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"application/xml", // response_content_type
MimeType::kXml, // canonical_mime_type
{"Won't sniff as XML"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: Cross-site XHR to nonsense labeled as JSON",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/json", // response_content_type
MimeType::kJson, // canonical_mime_type
{"Won't sniff as JSON"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: Cross-site XHR to partial match for <HTML> tag",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<htm"}, // packets
false, // resource_is_sensitive
Verdict::kAllowBecauseOutOfData, // verdict
kVerdictPacketForInconclusiveSniffing, // verdict_packet
},
{
"Allowed: HTML tag appears only after net::kMaxBytesToSniff",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{kHTMLWithTooLongComment}, // packets
false, // resource_is_sensitive
Verdict::kAllowBecauseOutOfData, // verdict
kVerdictPacketForInconclusiveSniffing, // verdict_packet
},
{
"Allowed: Empty response with html mime type",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{}, // packets
false, // resource_is_sensitive
Verdict::kAllowBecauseOutOfData, // verdict
kVerdictPacketForInconclusiveSniffing, // verdict_packet
},
{
"Allowed: Same-origin XHR to a filesystem URI",
__LINE__,
"filesystem:http://www.a.com/file.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: Same-origin XHR to a blob URI",
__LINE__,
"blob:http://www.a.com/guid-goes-here", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// Blocked responses (without sniffing):
{
"Blocked: Cross-site XHR to nosniff HTML without CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked: nosniff + Content-Type: text/html; charset=utf-8",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"text/html; charset=utf-8", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked: Cross-site XHR to nosniff response without CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"Wouldn't sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked: Cross-origin, same-site XHR to nosniff HTML without CORS",
__LINE__,
"https://foo.site.com/resource.html", // target_url
"https://bar.site.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked: Cross-site JSON with parser breaker/html/nosniff",
__LINE__,
"http://a.com/resource.html", // target_url
"http://c.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{")]", "}'\n[true, true, false, \"user@chromium.org\"]"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
// This scenario is unusual, since there's no difference between
// a blocked response and a non-blocked response.
"Blocked(-ish?): Nosniff header + empty response",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// CORB only applies to `no-cors` responses.
{
"Allowed: CORB N/A for CORS requests: Cross-site XHR + same-site CORS",
// Note that initiator_origin is cross-origin, but same-site in relation
// to the CORS response (the Access-Control-Allow-Origin header).
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://foo.example.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Access-Control-Allow-Origin: http://example.com", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<hTmL><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: CORB N/A for CORS requests: Cross-site XHR with wrong CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Access-Control-Allow-Origin: http://example.com", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<hTmL><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Allowed: CORB N/A for CORS requests: JSON parser-breaker + wrong CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Access-Control-Allow-Origin: http://example.com\n"
"X-Content-Type-Options: nosniff", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{")]}'\n[true, false]"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
// Blocked responses due to sniffing:
{
"Blocked: Cross-site XHR to HTML without CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
{
"Blocked: Cross-site XHR to XML without CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"application/xml", // response_content_type
MimeType::kXml, // canonical_mime_type
{"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
{
"Blocked: Cross-site XHR to JSON without CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"application/json", // response_content_type
MimeType::kJson, // canonical_mime_type
{"{\"x\" : 3}"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
{
"Blocked: slow-arriving JSON labeled as text/plain",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{" ", "\t", "{", "\"x\" ", " ", ": 3}"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
5, // verdict_packet
},
{
"Blocked: slow-arriving xml labeled as text/plain",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{" ", "\t", "<", "?", "x", "m", "l", ">"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
6, // verdict_packet
},
{
"Blocked: slow-arriving html labeled as text/plain",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{" <!--", "\t -", "-", "->", "\n", "<", "s", "c", "r", "i", "p",
"t"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
11, // verdict_packet
},
{
"Blocked: slow-arriving html with commented-out xml tag",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{" <!--", " <?xml ", "-->\n", "<", "h", "e", "a", "d"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
7, // verdict_packet
},
{
"Blocked: Cross-site XHR to HTML labeled as text without CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
{
"Blocked: Cross-site <script> inclusion of HTML w/ DTD without CORS",
__LINE__,
"http://www.b.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<!doc", "type html><html itemscope=\"\" ",
"itemtype=\"http://schema.org/SearchResultsPage\" ",
"lang=\"en\"><head>"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
1, // verdict_packet
},
{
"Blocked: Cross-site fetch HTML from NaCl without CORS response",
__LINE__,
"http://www.b.com/plugin.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // first_chunk
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
{
"Blocked: Cross-site JSON with parser breaker and JSON mime type",
__LINE__,
"http://a.com/resource.html", // target_url
"http://c.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/json", // response_content_type
MimeType::kJson, // canonical_mime_type
{")]", "}'\n[true, true, false, \"user@chromium.org\"]"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
1, // verdict_packet
},
{
"Blocked: Cross-site JSON with parser breaker/nosniff/other mime type",
__LINE__,
"http://a.com/resource.html", // target_url
"http://c.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff", // response_headers
"application/octet-stream", // response_content_type
MimeType::kOthers, // canonical_mime_type
{")]", "}'\n[true, true, false, \"user@chromium.org\"]"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
1, // verdict_packet
},
{
"Blocked: Cross-site JSON with parser breaker and other mime type",
__LINE__,
"http://a.com/resource.html", // target_url
"http://c.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"for(;;)", ";[true, true, false, \"user@chromium.org\"]"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
1, // verdict_packet
},
{
// Test based on wpt/.../corb/.../css-with-json-parser-breaker.css
"Blocked: Cross-site CSS with parser breaker and text/css mime type",
__LINE__,
"http://a.com/resource.css", // target_url
"http://c.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/css", // response_content_type
MimeType::kOthers, // canonical_mime_type
{R"()]}'
{}
#header { color: red; } )"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
// Test based on http/tests/security/resources/xorigincss1.css
"Blocked: Cross-site HTML/CSS polyglot with text/css mime type",
__LINE__,
"http://a.com/resource.css", // target_url
"http://c.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/css", // response_content_type
MimeType::kOthers, // canonical_mime_type
{R"( <html>{}\n"
.id3 {
background-color: yellow;
}
</html> )"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked: Cross-site XHR to a filesystem URI",
__LINE__,
"filesystem:http://www.b.com/file.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
{
"Blocked: Cross-site XHR to a blob URI",
__LINE__,
"blob:http://www.b.com/guid-goes-here", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
// Range response. The product code doesn't currently look at the exact
// range specified, so we can get away with testing with arbitrary/random
// values.
{
"Blocked-by-ORB: Javascript 206",
__LINE__,
"http://www.b.com/script.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Content-Range: bytes 200-1000/67589", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"x = 1;"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
// Here the resources is allowed cross-origin from b.com to a.com
// because of the CORS header. However CORB still blocks c.com from
// accessing it (so the protection decision should be kBlock).
"Allowed: text/html 206 media with CORS",
__LINE__,
"http://www.b.com/movie.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Content-Range: bytes 200-1000/67589\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"simulated *middle*-of-html content"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked-by-ORB: text/plain 206 media",
__LINE__,
"http://www.b.com/movie.txt", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Content-Range: bytes 200-1000/67589", // response_headers
"text/plain", // response_content_type
MimeType::kPlain, // canonical_mime_type
{"movie content"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked: text/html 206 media",
__LINE__,
"http://www.b.com/movie.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Content-Range: bytes 200-1000/67589", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"these middle bytes are unsniffable"}, // packets
false, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Blocked-by-ORB: application/octet-stream 206 (middle)",
__LINE__,
"http://www.b.com/movie.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Content-Range: bytes 200-1000/67589", // response_headers
"application/octet-stream", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"these middle bytes are unsniffable"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: application/octet-stream 206 media - beginning of video",
__LINE__,
"http://www.b.com/movie.mp4", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Content-Range: bytes 0-800/67589", // response_headers
"application/octet-stream", // response_content_type
MimeType::kOthers, // canonical_mime_type
// Body of test response is based on:
// 1) net/base/mime_sniffer.cc
// 2) https://mimesniff.spec.whatwg.org/#signature-for-mp4
{"....ftypmp4...."}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: video/mp4 206 media - beginning of resource",
__LINE__,
"http://www.b.com/movie.mp4", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Content-Range: bytes 0-800/67589", // response_headers
"video/mp4", // response_content_type
MimeType::kOthers, // canonical_mime_type
// Body of test response is based on:
// 1) net/base/mime_sniffer.cc
// 2) https://mimesniff.spec.whatwg.org/#signature-for-mp4
{"MIME type means this doesn't have to sniff as video"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed: video/mp4 206 media - middle of resource",
__LINE__,
"http://www.b.com/movie.mp4", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Content-Range: bytes 200-1000/67589", // response_headers
"video/mp4", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"these middle bytes are unsniffable"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// Responses with no data.
{
"Allowed: same-origin 204 response with no data",
__LINE__,
"http://a.com/resource.html", // target_url
"http://a.com/", // initiator_origin
"HTTP/1.1 204 NO CONTENT", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{/* empty body doesn't sniff as html */}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Allowed after sniffing: cross-site 204 response with no data",
__LINE__,
"http://a.com/resource.html", // target_url
"http://b.com/", // initiator_origin
"HTTP/1.1 204 NO CONTENT", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{/* empty body doesn't sniff as html */}, // packets
false, // resource_is_sensitive
Verdict::kAllowBecauseOutOfData, // verdict
kVerdictPacketForInconclusiveSniffing, // verdict_packet
},
// Testing the CORB protection logging.
{
"Not Sensitive: script without CORS or Cache heuristic",
__LINE__,
"http://www.a.com/resource.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Not Sensitive: vary user-agent is present and should be ignored",
__LINE__,
"http://www.a.com/resource.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin, User-Agent", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Not Sensitive: cache-control no-store should be ignored",
__LINE__,
"http://www.a.com/resource.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin\n"
"Cache-Control: No-Store", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// Responses with the Access-Control-Allow-Origin header value other than *.
{
"Sensitive, Allowed: script with CORS heuristic and range header",
__LINE__,
"http://www.a.com/resource.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Vary: Origin\n"
"Access-Control-Allow-Origin: http://www.a.com/\n"
"Content-Range: bytes 200-1000/67589", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Sensitive, Blocked: html with CORS heuristic and no sniff",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin\n"
"X-Content-Type-Options: nosniff\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Sensitive, Blocked after sniffing: html with CORS heuristic",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Sensitive, Allowed after sniffing: javascript with CORS heuristic",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Sensitive slow-arriving JSON with CORS heurisitic. Only needs "
"sniffing for the CORP protection statistics.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/json", // response_content_type
MimeType::kJson, // canonical_mime_type
{" ", "\t", "{", "\"x\" ", " ", ": 3}"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
5, // verdict_packet
},
// Responses with Vary: Origin and Cache-Control: Private headers.
{
"Sensitive, Allowed: script with cache heuristic and range header",
__LINE__,
"http://www.a.com/resource.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Vary: Origin\n"
"Cache-Control: Private\n"
"Content-Range: bytes 200-1000/67589", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Sensitive, Allowed: script with cache heuristic and range "
"header. Has vary user agent + cache no store which should not "
"confuse the cache heuristic.",
__LINE__,
"http://www.a.com/resource.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Vary: Origin, User-Agent\n"
"Cache-Control: Private, No-Store\n"
"Content-Range: bytes 200-1000/67589", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Sensitive, Blocked: html with cache heuristic and no sniff",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Vary: Origin\n"
"Cache-Control: Private", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Sensitive, Blocked after sniffing: html with cache heuristic",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin\n"
"Cache-Control: Private", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Sensitive, Allowed after sniffing: javascript with cache heuristic",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin\n"
"Cache-Control: Private", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Sensitive slow-arriving JSON with cache heurisitic. Only needs "
"sniffing for the CORP protection statistics.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin\n"
"Cache-Control: Private", // response_headers
"text/json", // response_content_type
MimeType::kJson, // canonical_mime_type
{" ", "\t", "{", "\"x\" ", " ", ": 3}"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
5, // verdict_packet
},
// The next two tests together ensure that when CORB blocks and strips the
// cache/vary headers from a sensitive response the CORB protection logging
// still correctly identifies the response as sensitive and reports it.
//
// In this first test, the protection logging reports immediately (without
// sniffing). So we can (somewhat) safely assume the response is being
// correctly reported as sensitive. Thus this test ensures the testing
// infrastructure itself is also correctly idenitfying the response as
// sensitive.
{
"Sensitive cache heuristic, both CORB and the protection stats block",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.b.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Vary: Origin\n"
"Cache-Control: Private", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kBlock, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// Here the protection logging only reports after sniffing. Despite this,
// the resource should still be identified as sensitive.
{
"Sensitive cache heuristic, both CORB and the protection stats block",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.b.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Vary: Origin\n"
"Cache-Control: Private", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
// A test that makes sure we don't double log the CORB protection stats.
// Because the request is cross-origin + same-site and has a same-site CORP
// header, CORB needs to sniff. However CORB protection logging makes the
// request cross-site and so needs no sniffing. We don't want the protection
// logging to be triggered a second time after the sniffing.
{
"Sensitive, CORB and CORB protection stats need sniffing",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.foo.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Cross-Origin-Resource-Policy: same-site\n"
"Vary: Origin\n"
"Cache-Control: Private", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kBlock, // verdict
0, // verdict_packet
},
// Response with an unknown MIME type.
{
"Sensitive, Allowed: unknown MIME type with CORS heuristic and range "
"header",
__LINE__,
"http://www.a.com/resource.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"Vary: Origin\n"
"Content-Range: bytes 200-1000/67589\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"unknown/mime_type", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// Responses with the accept-ranges header.
{
"Sensitive response with an accept-ranges header.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Accept-Ranges: bytes\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Sensitive response with an accept-ranges header but value |none|.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Accept-Ranges: none\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Non-sensitive response with an accept-ranges header.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Accept-Ranges: bytes", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
false, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// Sensitive responses with the accept-ranges header, a protected MIME type
// and protection decision = kBlock.
{
"CORS-heuristic response with an accept-ranges header.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Accept-Ranges: bytes\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Cache-heuristic response with an accept-ranges header.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Accept-Ranges: bytes\n"
"Cache-Control: private\n"
"Vary: origin", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Cache + CORS heuristics, accept-ranges header says |none|.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Accept-Ranges: none\n"
"Cache-Control: private\n"
"Access-Control-Allow-Origin: http://www.a.com/\n"
"Vary: origin", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// Sensitive responses with the accept-ranges header, a protected MIME type
// and protection decision = kBlockedAfterSniffing. (These tests are
// identical to the previous 3, except they lack a nosniff header.)
{
"CORS-heuristic response with an accept-ranges header.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Accept-Ranges: bytes\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Cache-heuristic response with an accept-ranges header.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Accept-Ranges: bytes\n"
"Cache-Control: private\n"
"Vary: origin", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Cache + CORS heuristics, accept-ranges header says |none|.",
__LINE__,
"http://www.a.com/resource.html", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Accept-Ranges: none\n"
"Cache-Control: private\n"
"Access-Control-Allow-Origin: http://www.a.com/\n"
"Vary: origin", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
// A Sensitive response with the accept-ranges header and a protected MIME
// type but protection decision = kAllow (so the secondary accept-ranges
// stats should not be reported).
{
"Sensitive + accept-ranges header but protection decision = kAllow.",
__LINE__,
"http://www.a.com/resource.js", // target_url
"http://www.a.com/", // initiator_origin
"HTTP/1.1 206 OK\n"
"X-Content-Type-Options: nosniff\n"
"Accept-Ranges: bytes\n"
"Content-Range: bytes 200-1000/67589\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// Sensitive responses with no data.
{
"Sensitive, allowed after sniffing: same-origin 204 with no data",
__LINE__,
"http://a.com/resource.html", // target_url
"http://a.com/", // initiator_origin
"HTTP/1.1 204 NO CONTENT\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{/* empty body doesn't sniff as html */}, // packets
true, // resource_is_sensitive
Verdict::kAllowBecauseOutOfData, // verdict
kVerdictPacketForInconclusiveSniffing, // verdict_packet
},
// These responses confirm we are correctly reporting when a nosniff header
// is present. This should *only* be reported when a blocked, sensitive,
// protected mime-type response has a nosniff header.
//
// This first response satisfies all criteria except it does not have a
// nosniff header.
{
"Cache heuristic with no nosniff header",
__LINE__,
"http://a.com/resource.html", // target_url
"http://a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"Cache-Control: private\n"
"Vary: origin", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
// These next responses have nosniff headers but are missing one of the
// other criteria.
{
"Cache heuristic with nosniff header but not a protected type",
__LINE__,
"http://a.com/resource.js", // target_url
"http://a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Cache-Control: private\n"
"Vary: origin", // response_headers
"application/javascript", // response_content_type
MimeType::kOthers, // canonical_mime_type
{"var x=3;"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
0, // verdict_packet
},
{
"Cache heuristic with nosniff header and protection decision == kBlock",
__LINE__,
"http://a.com/resource.html", // target_url
"http://a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Access-Control-Allow-Origin: *\n"
"Cache-Control: private\n"
"Vary: origin", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
// These responses satisfies all criteria and should report its nosniff
// header.
{
"Nosniff header and satisfies the CORS heuristic",
__LINE__,
"http://a.com/resource.html", // target_url
"http://a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Access-Control-Allow-Origin: http://www.a.com/", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{"<html><head>this should sniff as HTML"}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
{
"Nosniff header and satisfies the Cache heuristic",
__LINE__,
"http://a.com/resource.html", // target_url
"http://a.com/", // initiator_origin
"HTTP/1.1 200 OK\n"
"X-Content-Type-Options: nosniff\n"
"Cache-Control: private\n"
"Vary: origin", // response_headers
"text/html", // response_content_type
MimeType::kHtml, // canonical_mime_type
{/* empty body doesn't sniff as html */}, // packets
true, // resource_is_sensitive
Verdict::kAllow, // verdict
kVerdictPacketForHeadersBasedVerdict, // verdict_packet
},
};
} // namespace
// Tests that verify ResponseAnalyzer correctly classifies network responses as
// allowed or blocked.
//
// The various test cases are passed as a list of TestScenario structs.
class ResponseAnalyzerTest : public testing::Test,
public testing::WithParamInterface<TestScenario> {
public:
ResponseAnalyzerTest()
: context_(net::CreateTestURLRequestContextBuilder()->Build()) {}
ResponseAnalyzerTest(const ResponseAnalyzerTest&) = delete;
ResponseAnalyzerTest& operator=(const ResponseAnalyzerTest&) = delete;
// Returns a ResourceResponse that matches the TestScenario's parameters.
mojom::URLResponseHeadPtr CreateResponse(
const std::string& response_content_type,
const std::string& raw_response_headers,
const std::string& initiator_origin) {
auto response = mojom::URLResponseHead::New();
std::string formatted_response_headers =
net::HttpUtil::AssembleRawHeaders(raw_response_headers);
scoped_refptr<net::HttpResponseHeaders> response_headers =
base::MakeRefCounted<net::HttpResponseHeaders>(
formatted_response_headers);
std::string charset;
bool had_charset = false;
response_headers->SetHeader("Content-Type", response_content_type);
net::HttpUtil::ParseContentType(response_content_type, &response->mime_type,
&charset, &had_charset, nullptr);
EXPECT_FALSE(response->mime_type.empty())
<< "Invalid MIME type defined in kScenarios.";
response->headers = response_headers;
return response;
}
// Take and run ResponseAnalyzer on the current scenario. Allow the analyzer
// to sniff the response body if needed and confirm it correctly decides to
// block or allow.
void RunAnalyzerOnScenario(const TestScenario& scenario,
const mojom::URLResponseHead& response,
std::unique_ptr<ResponseAnalyzer> analyzer,
bool verify_when_decision_is_made = true) {
// Initialize |request| from the parameters.
std::unique_ptr<net::URLRequest> request = context_->CreateRequest(
GURL(scenario.target_url), net::DEFAULT_PRIORITY, &delegate_,
TRAFFIC_ANNOTATION_FOR_TESTS);
request->set_initiator(
url::Origin::Create(GURL(scenario.initiator_origin)));
// Check if this is a CORS request.
auto request_mode =
response.headers->GetNormalizedHeader("access-control-allow-origin")
.value_or(std::string()) == ""
? mojom::RequestMode::kNoCors
: mojom::RequestMode::kCors;
// Initialize the `analyzer`.
//
// Note that the `analyzer` will be destructed when `analyzer` goes out of
// scope (the destructor may trigger logging of UMAs that some callers of
// RunAnalyzerOnScenario attempt to verify).
ResponseAnalyzer::Decision decision =
analyzer->Init(request->url(), request->initiator(), request_mode,
mojom::RequestDestination::kEmpty, response);
// This vector holds the packets to be delivered.
std::vector<const char*> packets_vector(scenario.packets);
packets_vector.push_back(
""); // End-of-stream is marked by an empty packet.
// If the |verdict_packet| == kVerdictPacketForHeadersBasedVerdict = -1,
// then the sniffing loop below will be skipped.
if (scenario.verdict_packet != kVerdictPacketForInconclusiveSniffing &&
scenario.verdict_packet != kVerdictPacketForHeadersBasedVerdict) {
EXPECT_LT(scenario.verdict_packet,
static_cast<int>(packets_vector.size()));
}
// Verify that the ResponseAnalyzer asks for sniffing if this is what the
// testcase expects.
if (verify_when_decision_is_made) {
bool expected_to_sniff =
scenario.verdict_packet != kVerdictPacketForHeadersBasedVerdict;
if (expected_to_sniff) {
EXPECT_EQ(decision, ResponseAnalyzer::Decision::kSniffMore);
} else {
// If we don't expect to sniff then ResponseAnalyzer should have already
// made a blockng decision based on the headers.
if (scenario.verdict == Verdict::kBlock) {
EXPECT_EQ(decision, ResponseAnalyzer::Decision::kBlock);
} else {
EXPECT_EQ(decision, ResponseAnalyzer::Decision::kAllow);
}
}
}
// Simulate the behaviour of the URLLoader by appending the packets into
// |data_buffer| and feeding this to |analyzer|.
bool run_out_of_data_to_sniff = false;
int actual_verdict_packet = kVerdictPacketForHeadersBasedVerdict;
if (decision == ResponseAnalyzer::Decision::kSniffMore) {
std::string data_buffer;
size_t data_offset = 0;
for (size_t packet_index = 0; packet_index < packets_vector.size();
packet_index++) {
SCOPED_TRACE(testing::Message()
<< "While delivering packet #" << packet_index);
// At each iteration of the loop we feed a new packet to |analyzer|,
// expecting to get a decision at the |verdict_packet|. Since we haven't
// given the next packet to |analyzer| yet at this point in the loop, it
// shouldn't have made a decision yet.
EXPECT_EQ(decision, ResponseAnalyzer::Decision::kSniffMore);
// Append the next packet of the response body. If appending the entire
// packet would exceed net::kMaxBytesToSniff we truncate the data.
size_t bytes_to_append = strlen(packets_vector[packet_index]);
if (data_offset + bytes_to_append > net::kMaxBytesToSniff)
bytes_to_append = net::kMaxBytesToSniff - data_offset;
data_buffer.append(packets_vector[packet_index], bytes_to_append);
// Hand |analyzer_| the data to sniff.
decision = analyzer->Sniff(data_buffer);
data_offset += bytes_to_append;
if (decision != ResponseAnalyzer::Decision::kSniffMore) {
actual_verdict_packet = packet_index;
break;
}
}
}
// Handle scenarios where no decision can be made before running out of data
// to sniff.
if (decision == ResponseAnalyzer::Decision::kSniffMore) {
actual_verdict_packet = kVerdictPacketForInconclusiveSniffing;
run_out_of_data_to_sniff = true;
decision = analyzer->HandleEndOfSniffableResponseBody();
// HandleEndOfSniffableResponseBody should never return kSniffMore.
EXPECT_NE(decision, ResponseAnalyzer::Decision::kSniffMore);
}
// Confirm the analyzer is blocking or allowing correctly (now that we have
// performed any needed sniffing).
if (verify_when_decision_is_made) {
EXPECT_EQ(scenario.verdict_packet, actual_verdict_packet);
}
if (scenario.verdict == Verdict::kBlock) {
EXPECT_EQ(decision, ResponseAnalyzer::Decision::kBlock);
} else {
EXPECT_EQ(decision, ResponseAnalyzer::Decision::kAllow);
if (verify_when_decision_is_made) {
// In this case either the |analyzer| has decided to allow the response,
// or run out of data and so the response will be allowed by default.
if (scenario.verdict == Verdict::kAllow) {
EXPECT_FALSE(run_out_of_data_to_sniff);
} else {
EXPECT_EQ(Verdict::kAllowBecauseOutOfData, scenario.verdict);
EXPECT_TRUE(run_out_of_data_to_sniff);
}
}
}
}
protected:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<net::URLRequestContext> context_;
net::TestDelegate delegate_;
}; // namespace network
TEST_P(ResponseAnalyzerTest, OpaqueResponseBlocking) {
TestScenario scenario = GetParam();
SCOPED_TRACE(testing::Message()
<< "\nScenario at " << __FILE__ << ":" << scenario.source_line);
// Unlike CORB, ORB blocks all 206 responses, unless there was an earlier
// request to the same URL and that earlier request was classified (based on
// the MIME type or sniffing) as an audio-or-video response.
std::string_view description = scenario.description;
if (description == "Blocked-by-ORB: text/plain 206 media" ||
description == "Blocked-by-ORB: Javascript 206" ||
description == "Blocked-by-ORB: application/octet-stream 206 (middle)") {
scenario.verdict = Verdict::kBlock;
scenario.verdict_packet = kVerdictPacketForHeadersBasedVerdict;
}
// Initialize |response| from the parameters and record if it looks sensitive
// or supports range requests. These values are saved because the analyzer
// will clear the response headers in the event it decides to block.
auto response =
CreateResponse(scenario.response_content_type, scenario.response_headers,
scenario.initiator_origin);
// ORB may make the final decision at a different time than CORB. This
// makes no difference from functional/observable behavior perspective
// and skipping this verification makes it easier to share testcases
// across ORB and CORB.
//
// TODO(lukasza): Eventually `verify_when_decision_is_made` for ORB (once
// CORB is removed at the latest, but possibly earlier than that).
constexpr bool kVerifyWhenDecisionIsMade = false;
PerFactoryState per_factory_state;
auto analyzer =
std::make_unique<OpaqueResponseBlockingAnalyzer>(&per_factory_state);
RunAnalyzerOnScenario(scenario, *response, std::move(analyzer),
kVerifyWhenDecisionIsMade);
}
INSTANTIATE_TEST_SUITE_P(All,
ResponseAnalyzerTest,
::testing::ValuesIn(kScenarios));
// =============================================================================
// The following individual tests check the behaviour of various methods in
// isolation.
// =============================================================================
mojom::URLResponseHeadPtr CreateResponse(std::string raw_headers) {
scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(raw_headers));
auto response = mojom::URLResponseHead::New();
response->headers = headers;
return response;
}
TEST(CrossOriginReadBlockingTest, OrbReportsIssuesOnARAResponse) {
struct {
std::string header;
bool expect_report;
} kTestCases[] = {
{/*header=*/"", /*expect_report=*/false},
{"Attribution-Reporting-Register-Source: {}", /*expect_report=*/true},
{"Attribution-Reporting-Register-Trigger: {}", /*expect_report=*/true},
{"Attribution-Reporting-Register-OS-Source: {}", /*expect_report=*/true},
{"Attribution-Reporting-Register-OS-Trigger: {}",
/*expect_report=*/true},
};
for (const auto& test_case : kTestCases) {
PerFactoryState per_factory_state;
auto analyzer =
std::make_unique<OpaqueResponseBlockingAnalyzer>(&per_factory_state);
auto ara_response = CreateResponse("HTTP/1.1 200 OK\n" + test_case.header);
// Mark the response as empty s.t. it would normally not report.
ara_response->content_length = 0;
analyzer->Init(GURL("https://b.test"),
url::Origin::Create(GURL("https://a.test")),
mojom::RequestMode::kNoCors,
mojom::RequestDestination::kEmpty, *ara_response);
EXPECT_EQ(analyzer->ShouldReportBlockedResponse(), test_case.expect_report);
}
}
} // namespace network::orb