Skip to content

Commit

Permalink
Update adblock-rust with CSP rule support
Browse files Browse the repository at this point in the history
  • Loading branch information
antonok-edm committed Mar 23, 2021
1 parent 5ca5db2 commit e475c29
Show file tree
Hide file tree
Showing 22 changed files with 432 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
69 changes: 69 additions & 0 deletions browser/net/brave_ad_block_tp_network_delegate_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "brave/common/network_constants.h"
#include "brave/common/url_constants.h"
#include "brave/components/brave_shields/browser/ad_block_service.h"
#include "brave/components/brave_shields/browser/ad_block_service_helper.h"
#include "brave/components/brave_shields/browser/brave_shields_util.h"
#include "brave/components/brave_shields/browser/brave_shields_web_contents_observer.h"
#include "brave/components/brave_shields/common/brave_shield_constants.h"
Expand Down Expand Up @@ -246,4 +247,72 @@ int OnBeforeURLRequest_AdBlockTPPreWork(const ResponseCallback& next_callback,
return net::ERR_IO_PENDING;
}

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 (ctx->resource_type == blink::mojom::ResourceType::kMainFrame ||
ctx->resource_type == blink::mojom::ResourceType::kSubFrame) {

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 (response_headers->GetNormalizedHeader("Content-Security-Policy",
&original_csp_string)) {
original_csp = base::Optional<std::string>(original_csp_string);
}

*override_response_headers =
new net::HttpResponseHeaders(response_headers->raw_headers());
(*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
7 changes: 7 additions & 0 deletions browser/net/brave_ad_block_tp_network_delegate_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ int OnBeforeURLRequest_AdBlockTPPreWork(
const ResponseCallback& next_callback,
std::shared_ptr<BraveRequestInfo> ctx);

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_TP_NETWORK_DELEGATE_HELPER_H_
4 changes: 4 additions & 0 deletions browser/net/brave_request_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ void BraveRequestHandler::SetupCallbacks() {
base::Bind(webtorrent::OnHeadersReceived_TorrentRedirectWork);
headers_received_callbacks_.push_back(headers_received_callback);
#endif

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: 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
14 changes: 14 additions & 0 deletions components/adblock_rust_ffi/src/wrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ void Engine::matches(const std::string& url,
}
}

std::string Engine::getCspDirectives(const std::string& url,
const std::string& host,
const std::string& tab_host,
bool is_third_party,
const std::string& resource_type) {
char* csp_raw = engine_get_csp_directives(raw, url.c_str(), host.c_str(),
tab_host.c_str(), is_third_party,
resource_type.c_str());
const std::string csp = std::string(csp_raw);

c_char_buffer_destroy(csp_raw);
return csp;
}

bool Engine::deserialize(const char* data, size_t data_size) {
return engine_deserialize(raw, data, data_size);
}
Expand Down
5 changes: 5 additions & 0 deletions components/adblock_rust_ffi/src/wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class ADBLOCK_EXPORT Engine {
bool* did_match_exception,
bool* did_match_important,
std::string* redirect);
std::string getCspDirectives(const std::string& url,
const std::string& host,
const std::string& tab_host,
bool is_third_party,
const std::string& resource_type);
bool deserialize(const char* data, size_t data_size);
void addTag(const std::string& tag);
void addResource(const std::string& key,
Expand Down
24 changes: 24 additions & 0 deletions components/brave_shields/browser/ad_block_base_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,30 @@ void AdBlockBaseService::ShouldStartRequest(
// << ", url.spec(): " << url.spec();
}

base::Optional<std::string> AdBlockBaseService::GetCspDirectives(
const GURL& url,
blink::mojom::ResourceType resource_type,
const std::string& tab_host) {
DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());

// Determine third-party here so the library doesn't need to figure it out.
// CreateFromNormalizedTuple is needed because SameDomainOrHost needs
// a URL or origin and not a string to a host name.
bool is_third_party = !SameDomainOrHost(
url,
url::Origin::CreateFromNormalizedTuple("https", tab_host.c_str(), 80),
INCLUDE_PRIVATE_REGISTRIES);
const std::string result = ad_block_client_->getCspDirectives(
url.spec(), url.host(), tab_host, is_third_party,
ResourceTypeToString(resource_type));

if (result.empty()) {
return base::nullopt;
} else {
return base::Optional<std::string>(result);
}
}

void AdBlockBaseService::EnableTag(const std::string& tag, bool enabled) {
if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
GetTaskRunner()->PostTask(
Expand Down
3 changes: 3 additions & 0 deletions components/brave_shields/browser/ad_block_base_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class AdBlockBaseService : public BaseBraveShieldsService {
bool* did_match_exception,
bool* did_match_important,
std::string* mock_data_url) override;
base::Optional<std::string> GetCspDirectives(const GURL& url,
blink::mojom::ResourceType resource_type,
const std::string& tab_host);
void AddResources(const std::string& resources);
void EnableTag(const std::string& tag, bool enabled);
bool TagExists(const std::string& tag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ void AdBlockRegionalServiceManager::ShouldStartRequest(
}
}

base::Optional<std::string> AdBlockRegionalServiceManager::GetCspDirectives(
const GURL& url,
blink::mojom::ResourceType resource_type,
const std::string& tab_host) {
base::Optional<std::string> csp_directives = base::nullopt;

for (const auto& regional_service : regional_services_) {
const auto directive = regional_service.second->GetCspDirectives(
url, resource_type, tab_host);
MergeCspDirectiveInto(directive, &csp_directives);
}

return csp_directives;
}

void AdBlockRegionalServiceManager::EnableTag(const std::string& tag,
bool enabled) {
base::AutoLock lock(regional_services_lock_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class AdBlockRegionalServiceManager {
bool* did_match_exception,
bool* did_match_important,
std::string* mock_data_url);
base::Optional<std::string> GetCspDirectives(const GURL& url,
blink::mojom::ResourceType resource_type,
const std::string& tab_host);
void EnableTag(const std::string& tag, bool enabled);
void AddResources(const std::string& resources);
void EnableFilterList(const std::string& uuid, bool enabled);
Expand Down
Loading

0 comments on commit e475c29

Please sign in to comment.