Skip to content

Commit

Permalink
Merge pull request #8281 from brave/csp-filter-rules
Browse files Browse the repository at this point in the history
Update adblock-rust with CSP rule support
  • Loading branch information
antonok-edm authored Apr 14, 2021
2 parents cda2a42 + 01b3e0a commit a27e101
Show file tree
Hide file tree
Showing 28 changed files with 503 additions and 7 deletions.
58 changes: 58 additions & 0 deletions browser/brave_shields/ad_block_service_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,64 @@ IN_PROC_BROWSER_TEST_F(AdBlockServiceTest, RedirectRulesAreRespected) {
EXPECT_EQ(browser()->profile()->GetPrefs()->GetUint64(kAdsBlocked), 1ULL);
}

// Verify that scripts violating a Content Security Policy from a `$csp` rule
// are not loaded.
IN_PROC_BROWSER_TEST_F(AdBlockServiceTest, CspRule) {
UpdateAdBlockInstanceWithRules(
"||example.com^$csp=script-src 'nonce-abcdef' 'unsafe-eval' 'self'");
EXPECT_EQ(browser()->profile()->GetPrefs()->GetUint64(kAdsBlocked), 0ULL);

const GURL url =
embedded_test_server()->GetURL("example.com", "/csp_rules.html");
ui_test_utils::NavigateToURL(browser(), url);
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();

auto res = EvalJs(contents, "await window.allLoaded");
ASSERT_EQ(true, EvalJs(contents, "!!window.loadedNonceScript"));
ASSERT_EQ(true, EvalJs(contents, "!!window.loadedEvalScript"));
ASSERT_EQ(true, EvalJs(contents, "!!window.loadedSamePartyScript"));
ASSERT_EQ(false, EvalJs(contents, "!!window.loadedThirdPartyScript"));
ASSERT_EQ(false, EvalJs(contents, "!!window.loadedUnsafeInlineScript"));
ASSERT_EQ(true, EvalJs(contents, "!!window.loadedDataImage"));

// Violations of injected CSP directives do not increment the Shields counter
EXPECT_EQ(browser()->profile()->GetPrefs()->GetUint64(kAdsBlocked), 0ULL);
}

// Verify that Content Security Policies from multiple `$csp` rules are
// combined.
//
// The policy resulting from two of the same kind of directive will be the
// union of both.
IN_PROC_BROWSER_TEST_F(AdBlockServiceTest, CspRuleMerging) {
UpdateAdBlockInstanceWithRules(
"||example.com^$csp=script-src 'nonce-abcdef' 'unsafe-eval' 'self'");
ASSERT_TRUE(g_brave_browser_process->ad_block_custom_filters_service()
->UpdateCustomFilters(
"||example.com^$csp=img-src 'none'\n"
"||sub.example.com^$csp=script-src 'nonce-abcdef' "
"'unsafe-eval' 'unsafe-inline'"));
EXPECT_EQ(browser()->profile()->GetPrefs()->GetUint64(kAdsBlocked), 0ULL);

const GURL url =
embedded_test_server()->GetURL("sub.example.com", "/csp_rules.html");
ui_test_utils::NavigateToURL(browser(), url);
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();

auto res = EvalJs(contents, "await window.allLoaded");
ASSERT_EQ(true, EvalJs(contents, "!!window.loadedNonceScript"));
ASSERT_EQ(true, EvalJs(contents, "!!window.loadedEvalScript"));
ASSERT_EQ(false, EvalJs(contents, "!!window.loadedSamePartyScript"));
ASSERT_EQ(false, EvalJs(contents, "!!window.loadedThirdPartyScript"));
ASSERT_EQ(false, EvalJs(contents, "!!window.loadedUnsafeInlineScript"));
ASSERT_EQ(false, EvalJs(contents, "!!window.loadedDataImage"));

// Violations of injected CSP directives do not increment the Shields counter
EXPECT_EQ(browser()->profile()->GetPrefs()->GetUint64(kAdsBlocked), 0ULL);
}

class CosmeticFilteringFlagDisabledTest : public AdBlockServiceTest {
public:
CosmeticFilteringFlagDisabledTest() {
Expand Down
3 changes: 3 additions & 0 deletions browser/net/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ source_set("net") {
check_includes = false
configs += [ "//brave/build/geolocation" ]
sources = [
"brave_ad_block_csp_network_delegate_helper.cc",
"brave_ad_block_csp_network_delegate_helper.h",
"brave_ad_block_tp_network_delegate_helper.cc",
"brave_ad_block_tp_network_delegate_helper.h",
"brave_block_safebrowsing_urls.cc",
Expand Down Expand Up @@ -51,6 +53,7 @@ source_set("net") {
"//brave/components/brave_component_updater/browser",
"//brave/components/brave_referrals/buildflags",
"//brave/components/brave_shields/browser",
"//brave/components/brave_shields/common",
"//brave/components/brave_webtorrent/browser/buildflags",
"//brave/components/ipfs/buildflags",
"//brave/extensions:common",
Expand Down
102 changes: 102 additions & 0 deletions browser/net/brave_ad_block_csp_network_delegate_helper.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "brave/browser/net/brave_ad_block_csp_network_delegate_helper.h"

#include <string>

#include "brave/browser/brave_browser_process_impl.h"
#include "brave/browser/net/url_context.h"
#include "brave/components/brave_shields/browser/ad_block_service.h"
#include "brave/components/brave_shields/browser/ad_block_service_helper.h"
#include "net/http/http_response_headers.h"
#include "url/gurl.h"

namespace brave {

base::Optional<std::string> GetCspDirectivesOnTaskRunner(
std::shared_ptr<BraveRequestInfo> ctx,
base::Optional<std::string> original_csp) {
std::string source_host;
if (ctx->initiator_url.is_valid()) {
source_host = ctx->initiator_url.host();
} else if (ctx->request_url.is_valid()) {
// Top-level document requests do not have a valid initiator URL, so we use
// the request URL as the initiator.
source_host = ctx->request_url.host();
} else {
return base::nullopt;
}

base::Optional<std::string> csp_directives =
g_brave_browser_process->ad_block_service()->GetCspDirectives(
ctx->request_url, ctx->resource_type, source_host);

brave_shields::MergeCspDirectiveInto(original_csp, &csp_directives);
return csp_directives;
}

void OnReceiveCspDirectives(
const ResponseCallback& next_callback,
std::shared_ptr<BraveRequestInfo> ctx,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
base::Optional<std::string> csp_directives) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

if (csp_directives) {
(*override_response_headers)
->AddHeader("Content-Security-Policy", *csp_directives);
}

next_callback.Run();
}

int OnHeadersReceived_AdBlockCspWork(
const net::HttpResponseHeaders* response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
GURL* allowed_unsafe_redirect_url,
const brave::ResponseCallback& next_callback,
std::shared_ptr<brave::BraveRequestInfo> ctx) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

if (!response_headers) {
return net::OK;
}

if (ctx->resource_type == blink::mojom::ResourceType::kMainFrame ||
ctx->resource_type == blink::mojom::ResourceType::kSubFrame) {
// If the override_response_headers have already been populated, we should
// use those directly. Otherwise, we populate them from the original
// headers.
if (!*override_response_headers) {
*override_response_headers =
new net::HttpResponseHeaders(response_headers->raw_headers());
}

scoped_refptr<base::SequencedTaskRunner> task_runner =
g_brave_browser_process->ad_block_service()->GetTaskRunner();

std::string original_csp_string;
base::Optional<std::string> original_csp = base::nullopt;
if ((*override_response_headers)
->GetNormalizedHeader("Content-Security-Policy",
&original_csp_string)) {
original_csp = base::Optional<std::string>(original_csp_string);
}

(*override_response_headers)->RemoveHeader("Content-Security-Policy");

task_runner->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&GetCspDirectivesOnTaskRunner, ctx, original_csp),
base::BindOnce(&OnReceiveCspDirectives, next_callback, ctx,
override_response_headers));
return net::ERR_IO_PENDING;
}

return net::OK;
}

} // namespace brave
31 changes: 31 additions & 0 deletions browser/net/brave_ad_block_csp_network_delegate_helper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef BRAVE_BROWSER_NET_BRAVE_AD_BLOCK_CSP_NETWORK_DELEGATE_HELPER_H_
#define BRAVE_BROWSER_NET_BRAVE_AD_BLOCK_CSP_NETWORK_DELEGATE_HELPER_H_

#include <memory>

#include "base/memory/scoped_refptr.h"
#include "brave/browser/net/url_context.h"

namespace net {
class HttpResponseHeaders;
} // namespace net

class GURL;

namespace brave {

int OnHeadersReceived_AdBlockCspWork(
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
GURL* allowed_unsafe_redirect_url,
const brave::ResponseCallback& next_callback,
std::shared_ptr<brave::BraveRequestInfo> ctx);

} // namespace brave

#endif // BRAVE_BROWSER_NET_BRAVE_AD_BLOCK_CSP_NETWORK_DELEGATE_HELPER_H_
9 changes: 9 additions & 0 deletions browser/net/brave_request_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "brave/browser/net/brave_ad_block_csp_network_delegate_helper.h"
#include "brave/browser/net/brave_ad_block_tp_network_delegate_helper.h"
#include "brave/browser/net/brave_common_static_redirect_network_delegate_helper.h"
#include "brave/browser/net/brave_httpse_network_delegate_helper.h"
Expand All @@ -21,6 +22,7 @@
#include "brave/common/pref_names.h"
#include "brave/components/brave_referrals/buildflags/buildflags.h"
#include "brave/components/brave_rewards/browser/buildflags/buildflags.h"
#include "brave/components/brave_shields/common/features.h"
#include "brave/components/brave_webtorrent/browser/buildflags/buildflags.h"
#include "brave/components/ipfs/buildflags/buildflags.h"
#include "chrome/browser/browser_process.h"
Expand Down Expand Up @@ -122,6 +124,13 @@ void BraveRequestHandler::SetupCallbacks() {
base::Bind(webtorrent::OnHeadersReceived_TorrentRedirectWork);
headers_received_callbacks_.push_back(headers_received_callback);
#endif

if (base::FeatureList::IsEnabled(
::brave_shields::features::kBraveAdblockCspRules)) {
brave::OnHeadersReceivedCallback headers_received_callback2 =
base::Bind(brave::OnHeadersReceived_AdBlockCspWork);
headers_received_callbacks_.push_back(headers_received_callback2);
}
}

void BraveRequestHandler::InitPrefChangeRegistrar() {
Expand Down
7 changes: 4 additions & 3 deletions build/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions chromium_src/chrome/browser/about_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

using brave_shields::features::kBraveAdblockCosmeticFiltering;
using brave_shields::features::kBraveAdblockCosmeticFilteringNative;
using brave_shields::features::kBraveAdblockCspRules;
using brave_shields::features::kBraveDomainBlock;
using brave_shields::features::kBraveExtensionNetworkBlocking;
using ntp_background_images::features::kBraveNTPBrandedWallpaper;
Expand Down Expand Up @@ -137,6 +138,10 @@ using ntp_background_images::features::kBraveNTPSuperReferralWallpaper;
flag_descriptions::kBraveAdblockCosmeticFilteringNativeDescription, \
kOsMac | kOsWin | kOsLinux, \
FEATURE_VALUE_TYPE(kBraveAdblockCosmeticFilteringNative)}, \
{"brave-adblock-csp-rules", \
flag_descriptions::kBraveAdblockCspRulesName, \
flag_descriptions::kBraveAdblockCspRulesDescription, kOsAll, \
FEATURE_VALUE_TYPE(kBraveAdblockCspRules)}, \
{"brave-domain-block", \
flag_descriptions::kBraveDomainBlockName, \
flag_descriptions::kBraveDomainBlockDescription, kOsAll, \
Expand Down
4 changes: 4 additions & 0 deletions chromium_src/chrome/browser/flag_descriptions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const char kBraveAdblockCosmeticFilteringNativeName[] =
"Use native implementation for cosmetic filtering";
const char kBraveAdblockCosmeticFilteringNativeDescription[] =
"Uses native implementation for cosmetic filtering instead of extension";
const char kBraveAdblockCspRulesName[] = "Enable support for CSP rules";
const char kBraveAdblockCspRulesDescription[] =
"Applies additional CSP rules to pages for which a $csp rule has been "
"loaded from a filter list";
const char kBraveDomainBlockName[] = "Enable domain blocking";
const char kBraveDomainBlockDescription[] =
"Enable support for blocking domains with an interstitial page";
Expand Down
2 changes: 2 additions & 0 deletions chromium_src/chrome/browser/flag_descriptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ extern const char kBraveAdblockCosmeticFilteringName[];
extern const char kBraveAdblockCosmeticFilteringNativeName[];
extern const char kBraveAdblockCosmeticFilteringDescription[];
extern const char kBraveAdblockCosmeticFilteringNativeDescription[];
extern const char kBraveAdblockCspRulesDescription[];
extern const char kBraveAdblockCspRulesName[];
extern const char kBraveDomainBlockName[];
extern const char kBraveDomainBlockDescription[];
extern const char kBraveExtensionNetworkBlockingName[];
Expand Down
5 changes: 3 additions & 2 deletions components/adblock_rust_ffi/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion components/adblock_rust_ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["Brian R. Bondy <netzen@gmail.com>"]
edition = "2018"

[dependencies]
adblock = { version = "~0.3.5", git = "https://github.com/brave/adblock-rust", rev = "732994bf57f4a5c7f3a2a7b873a97571737def42", default-features = false, features = ["full-regex-handling", "object-pooling"] }
adblock = { version = "~0.3.10", default-features = false, features = ["full-regex-handling", "object-pooling"] }
serde_json = "1.0"
libc = "0.2"

Expand Down
11 changes: 11 additions & 0 deletions components/adblock_rust_ffi/src/lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ void engine_match(struct C_Engine *engine,
bool *did_match_important,
char **redirect);

/**
* Returns any CSP directives that should be added to a subdocument or document request's response
* headers.
*/
char *engine_get_csp_directives(struct C_Engine *engine,
const char *url,
const char *host,
const char *tab_host,
bool third_party,
const char *resource_type);

/**
* Adds a tag to the engine for consideration
*/
Expand Down
32 changes: 32 additions & 0 deletions components/adblock_rust_ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,38 @@ pub unsafe extern "C" fn engine_match(
};
}

/// Returns any CSP directives that should be added to a subdocument or document request's response
/// headers.
#[no_mangle]
pub unsafe extern "C" fn engine_get_csp_directives(
engine: *mut Engine,
url: *const c_char,
host: *const c_char,
tab_host: *const c_char,
third_party: bool,
resource_type: *const c_char,
) -> *mut c_char {
let url = CStr::from_ptr(url).to_str().unwrap();
let host = CStr::from_ptr(host).to_str().unwrap();
let tab_host = CStr::from_ptr(tab_host).to_str().unwrap();
let resource_type = CStr::from_ptr(resource_type).to_str().unwrap();
assert!(!engine.is_null());
let engine = Box::leak(Box::from_raw(engine));
if let Some(directive) = engine.get_csp_directives(url, host, tab_host, resource_type, Some(third_party)) {
let ptr = CString::new(directive)
.expect("Error: CString::new()")
.into_raw();
std::mem::forget(ptr);
ptr
} else {
let ptr = CString::new("")
.expect("Error: CString::new()")
.into_raw();
std::mem::forget(ptr);
ptr
}
}

/// Adds a tag to the engine for consideration
#[no_mangle]
pub unsafe extern "C" fn engine_add_tag(engine: *mut Engine, tag: *const c_char) {
Expand Down
Loading

0 comments on commit a27e101

Please sign in to comment.