Commit c6e23216 authored by Takashi Toyoshima's avatar Takashi Toyoshima Committed by Commit Bot

OOR-CORS: each content:// should be assumed as an opaque origin

On Android Chrome, each content:// should be assumed as an opaque
origin, and should not allow CORS-enabled requests among content://
URLs. Also content:// can not load legacy worker scripts from
content:// URLs as the mode "same-origin" is not permitted too.

TEST=./out/a/bin/run_chrome_public_test_apk -A Feature=CORS

Bug: 1092449
Change-Id: I83d15f4c1e2f2d88e219032952a7da78f470c16a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2247920
Commit-Queue: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#779596}
parent e5ec9055
......@@ -34,6 +34,7 @@ import org.chromium.net.test.EmbeddedTestServer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
/** Test suite for different Android URL schemes. */
@RunWith(ChromeJUnit4ClassRunner.class)
......@@ -140,6 +141,85 @@ public class UrlSchemeTest {
});
}
private String loadContentUrlToMakeCorsToContent(final String api, final String mode)
throws Throwable {
final String resource = "content_url_make_cors_to_content.html";
final String imageUrl = createContentUrl("google.png");
mActivityTestRule.loadUrl(createContentUrl(resource) + "?api=" + api + "&mode=" + mode
+ "&url=" + URLEncoder.encode(imageUrl));
// Make sure the CORS request fail in the page.
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
return !mActivityTestRule.getActivity().getActivityTab().getTitle().equals(
"running");
}
});
// Make sure that content provider was asked to provide the content.
ensureResourceRequestCountInContentProviderNotLessThan(resource, 1);
return mActivityTestRule.getActivity().getActivityTab().getTitle();
}
@Test
@MediumTest
@Feature({"CORS"})
public void testContentUrlCanNotMakeXhrRequestToContentUrl() throws Throwable {
// The XMLHttpRequest can carry content:// URLs, but CORS requests for content:// are not
// permitted.
Assert.assertEquals("error", loadContentUrlToMakeCorsToContent("xhr", ""));
}
@Test
@MediumTest
@Feature({"CORS"})
public void testContentUrlCanNotMakeFetchCorsRequestToContentUrl() throws Throwable {
// The Fetch API does not support content:// scheme.
Assert.assertEquals("error", loadContentUrlToMakeCorsToContent("fetch", "cors"));
}
@Test
@MediumTest
@Feature({"CORS"})
public void testContentUrlCanNotMakeFetchSameOriginRequestToContentUrl() throws Throwable {
// The Fetch API does not support content:// scheme.
Assert.assertEquals("error", loadContentUrlToMakeCorsToContent("fetch", "same-origin"));
}
@Test
@MediumTest
@Feature({"CORS"})
public void testContentUrlCanNotMakeFetchNoCorsRequestToContentUrl() throws Throwable {
// The Fetch API does not support content:// scheme.
Assert.assertEquals("error", loadContentUrlToMakeCorsToContent("fetch", "no-cors"));
}
@Test
@MediumTest
@Feature({"CORS"})
public void testContentUrlToLoadWorkerFromContent() throws Throwable {
final String resource = "content_url_load_content_worker.html";
mActivityTestRule.loadUrl(createContentUrl(resource));
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
return !mActivityTestRule.getActivity().getActivityTab().getTitle().equals(
"running");
}
});
// Make sure that content provider was asked to provide the content.
ensureResourceRequestCountInContentProviderNotLessThan(resource, 1);
Assert.assertEquals(
"exception", mActivityTestRule.getActivity().getActivityTab().getTitle());
}
/**
* Test that a content URL is *ALLOWED* to access an image provided by a content URL.
*/
......
<!doctype html>
<!-- can not be in a sub-directory as TestContentProvider does not support it -->
<html>
<head><title>running</title></head>
<body><script>
try {
const worker = new Worker('./worker.js');
worker.onerror = e => document.title = 'error';
worker.onmessage = e => document.title = e.data;
worker.postMessage([]);
} catch (e) {
document.title = 'exception'
}
</script></body>
</html>
<!doctype html>
<!-- can not be in a sub-directory as TestContentProvider does not support it -->
<html>
<head><title>running</title></head>
<body><script>
window.onload = function () {
const params = new URL(document.location).searchParams;
const api = params.get('api');
if (api == "xhr") {
const xhr = new XMLHttpRequest();
xhr.open('get', params.get('url'), true);
xhr.onload = e => document.title = 'load';
xhr.onerror = e => document.title = 'error';
xhr.onabort = e => document.title = 'abort';
xhr.send();
} else if (api == "fetch") {
fetch(params.get('url'), { 'mode': params.get('mode') }).then(
e => document.title = 'load',
e => document.title = 'error');
} else {
document.title = "unknown api"
}
};
</script></body>
</html>
// Copyright 2020 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.
// This file is accessed via content:// URL and can not be in a sub-directory
// as TestContentProvider does not support it.
onmessage = function(e) {
postMessage('load');
}
......@@ -20,6 +20,7 @@
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/file_url_loader.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe.h"
......@@ -28,6 +29,7 @@
#include "net/base/net_errors.h"
#include "net/http/http_byte_range.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
......@@ -126,8 +128,28 @@ class ContentURLLoader : public network::mojom::URLLoader {
const network::ResourceRequest& request,
mojo::PendingReceiver<network::mojom::URLLoader> loader,
mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote) {
bool disable_web_security =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebSecurity);
network::mojom::FetchResponseType response_type =
network::cors::CalculateResponseType(request.mode,
disable_web_security);
// Don't allow content:// requests with kSameOrigin or kCors* unless the
// web security is turned off.
if ((!disable_web_security &&
request.mode == network::mojom::RequestMode::kSameOrigin) ||
response_type == network::mojom::FetchResponseType::kCors) {
mojo::Remote<network::mojom::URLLoaderClient>(std::move(client_remote))
->OnComplete(
network::URLLoaderCompletionStatus(network::CorsErrorStatus(
network::mojom::CorsError::kCorsDisabledScheme)));
return;
}
auto head = network::mojom::URLResponseHead::New();
head->request_start = head->response_start = base::TimeTicks::Now();
head->response_type = response_type;
receiver_.Bind(std::move(loader));
receiver_.set_disconnect_handler(base::BindOnce(
&ContentURLLoader::OnMojoDisconnect, base::Unretained(this)));
......
......@@ -149,22 +149,6 @@ MojoResult ConvertNetErrorToMojoResult(net::Error net_error) {
}
}
network::mojom::FetchResponseType CalculateResponseType(
network::mojom::RequestMode mode,
bool is_allowed_access) {
// Though file:// is out of web standards, let's roughly follow the step 5 of
// https://fetch.spec.whatwg.org/#main-fetch.
if (is_allowed_access || mode == network::mojom::RequestMode::kNavigate ||
mode == network::mojom::RequestMode::kSameOrigin) {
return network::mojom::FetchResponseType::kBasic;
} else if (mode == network::mojom::RequestMode::kNoCors) {
return network::mojom::FetchResponseType::kOpaque;
} else {
DCHECK(network::cors::IsCorsEnabledRequestMode(mode)) << mode;
return network::mojom::FetchResponseType::kCors;
}
}
class FileURLDirectoryLoader
: public network::mojom::URLLoader,
public net::DirectoryLister::DirectoryListerDelegate {
......@@ -841,7 +825,7 @@ void FileURLLoaderFactory::CreateLoaderAndStart(
// check that takes --allow-file-access-from-files into account.
// CORS is not available for the file scheme, but can be exceptionally
// permitted by the access lists.
bool is_allowed_access =
bool is_request_considered_same_origin =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebSecurity) ||
(request.request_initiator &&
......@@ -853,7 +837,8 @@ void FileURLLoaderFactory::CreateLoaderAndStart(
network::cors::OriginAccessList::AccessState::kAllowed)));
network::mojom::FetchResponseType response_type =
CalculateResponseType(request.mode, is_allowed_access);
network::cors::CalculateResponseType(request.mode,
is_request_considered_same_origin);
CreateLoaderAndStartInternal(request, response_type, std::move(loader),
std::move(client));
......
......@@ -624,6 +624,21 @@ bool CalculateCredentialsFlag(mojom::CredentialsMode credentials_mode,
}
}
mojom::FetchResponseType CalculateResponseType(
mojom::RequestMode mode,
bool is_request_considered_same_origin) {
if (is_request_considered_same_origin ||
mode == network::mojom::RequestMode::kNavigate ||
mode == network::mojom::RequestMode::kSameOrigin) {
return network::mojom::FetchResponseType::kBasic;
} else if (mode == network::mojom::RequestMode::kNoCors) {
return network::mojom::FetchResponseType::kOpaque;
} else {
DCHECK(network::cors::IsCorsEnabledRequestMode(mode)) << mode;
return network::mojom::FetchResponseType::kCors;
}
}
} // namespace cors
} // namespace network
......@@ -168,6 +168,19 @@ COMPONENT_EXPORT(NETWORK_CPP)
bool CalculateCredentialsFlag(mojom::CredentialsMode credentials_mode,
mojom::FetchResponseType response_tainting);
// TODO(toyoshim): Consider finding a more organized way to ensure adopting CORS
// checks against all URLLoaderFactory and URLLoader inheritances.
// Calculates mojom::FetchResponseType for non HTTP/HTTPS schemes those are out
// of web standards. This adopts a simplified step 5 of
// https://fetch.spec.whatwg.org/#main-fetch. |mode| is one of the
// network::ResourceRequest to provide a CORS mode for the request.
// |is_request_considered_same_origin| specifies if the request has a special
// permission to bypass CORS checks.
COMPONENT_EXPORT(NETWORK_CPP)
mojom::FetchResponseType CalculateResponseType(
mojom::RequestMode mode,
bool is_request_considered_same_origin);
} // namespace cors
} // namespace network
......
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