Commit a6679963 authored by Kurt Horimoto's avatar Kurt Horimoto Committed by Commit Bot

[iOS] Create SafeBrowsingUnsafeResourceContainer

This tab helper is used to store copies of UnsafeResources that are
detected by the URL checks kicked off by the SafeBrowsingTabHelper.
It will be populated with UnsafeResources by the UrlCheckerDelegateImpl
before calling the SafeBrowsingTabHelper::UrlCheckerClient's completion
callback.  When the WebClient is preparing the error page for the
unsafe navigation, it can pop the UnsafeResource copy and use it to
populate the WebUI.

For unsafe resources in the main frame, a copy is stored directly in
the tab helper, as the associated pending NavigationItem may be deleted
due to the navigation cancellation.

For unsafe subresources, a copy is stored in the NavigationItem's user
data.  When the SafeBrowsingTabHelper detects an unsafe subresource, it
will kick off a reload of the NavigationItem and use the presence of
the unsafe subresource in the stack to cancel the main frame navigation
and display the error page.

Bug: 1064803
Change-Id: I96c99e0d5ca23443400a493023edb9f13a4fd393
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2166128Reviewed-by: default avatarRohit Rao <rohitrao@chromium.org>
Reviewed-by: default avatarAli Juma <ajuma@chromium.org>
Commit-Queue: Kurt Horimoto <kkhorimoto@chromium.org>
Auto-Submit: Kurt Horimoto <kkhorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#763455}
parent 9186956f
......@@ -14,6 +14,8 @@ source_set("safe_browsing") {
"safe_browsing_service_impl.mm",
"safe_browsing_tab_helper.h",
"safe_browsing_tab_helper.mm",
"safe_browsing_unsafe_resource_container.h",
"safe_browsing_unsafe_resource_container.mm",
"safe_browsing_url_allow_list.h",
"safe_browsing_url_allow_list.mm",
"url_checker_delegate_impl.h",
......@@ -68,6 +70,7 @@ source_set("unit_tests") {
testonly = true
sources = [
"safe_browsing_service_unittest.mm",
"safe_browsing_unsafe_resource_container_unittest.mm",
"safe_browsing_url_allow_list_unittest.mm",
]
......
// 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.
#ifndef IOS_CHROME_BROWSER_SAFE_BROWSING_SAFE_BROWSING_UNSAFE_RESOURCE_CONTAINER_H_
#define IOS_CHROME_BROWSER_SAFE_BROWSING_SAFE_BROWSING_UNSAFE_RESOURCE_CONTAINER_H_
#include "components/security_interstitials/core/unsafe_resource.h"
#import "ios/web/public/web_state_user_data.h"
#include "url/gurl.h"
namespace web {
class NavigationItem;
}
// Helper object that holds UnsafeResources for a WebState. UnsafeResources are
// copied and added when a navigation are detected to be unsafe, then released
// to populate the error page shown to the user after a navigation fails.
class SafeBrowsingUnsafeResourceContainer
: public web::WebStateUserData<SafeBrowsingUnsafeResourceContainer> {
public:
// SafeBrowsingUnsafeResourceContainer is move-only.
SafeBrowsingUnsafeResourceContainer(
SafeBrowsingUnsafeResourceContainer&& other);
SafeBrowsingUnsafeResourceContainer& operator=(
SafeBrowsingUnsafeResourceContainer&& other);
~SafeBrowsingUnsafeResourceContainer() override;
// Stores a copy of |resource|. Only one UnsafeResource can be stored at a
// time.
void StoreUnsafeResource(
const security_interstitials::UnsafeResource& resource);
// Whether the container has an unsafe resource for the main frame.
bool HasMainFrameUnsafeResource() const;
// Returns the main frame UnsafeResource, transferring ownership to the
// caller. Returns null if there is no main frame unsafe resource.
std::unique_ptr<security_interstitials::UnsafeResource>
ReleaseMainFrameUnsafeResource();
// Whether the container has an unsafe resource for a subframe for |item|.
// |item| must be non-null.
bool HasSubFrameUnsafeResource(web::NavigationItem* item) const;
// Returns the sub frame UnsafeResource for |item|, transferring ownership to
// the caller. Returns null if |item| has no sub frame unsafe resources.
// |item| must be non-null.
std::unique_ptr<security_interstitials::UnsafeResource>
ReleaseSubFrameUnsafeResource(web::NavigationItem* item);
private:
explicit SafeBrowsingUnsafeResourceContainer(web::WebState* web_state);
friend class web::WebStateUserData<SafeBrowsingUnsafeResourceContainer>;
WEB_STATE_USER_DATA_KEY_DECL();
// The WebState whose unsafe resources are managed by this container.
web::WebState* web_state_ = nullptr;
// The UnsafeResource for the main frame navigation.
std::unique_ptr<security_interstitials::UnsafeResource>
main_frame_unsafe_resource_;
};
#endif // IOS_CHROME_BROWSER_SAFE_BROWSING_SAFE_BROWSING_UNSAFE_RESOURCE_CONTAINER_H_
// 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.
#import "ios/chrome/browser/safe_browsing/safe_browsing_unsafe_resource_container.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using security_interstitials::UnsafeResource;
#pragma mark - UnsafeSubresourceContainer
namespace {
// Helper object storing unsafe subresources for a NavigationItem.
class UnsafeSubresourceContainer : public base::SupportsUserData::Data {
public:
~UnsafeSubresourceContainer() override = default;
// Lazily instantiates and returns the UnsafeSubresourceContainer for |item|.
static UnsafeSubresourceContainer* FromNavigationItem(
web::NavigationItem* item) {
DCHECK(item);
static void* kUserDataKey = &kUserDataKey;
UnsafeSubresourceContainer* stack =
static_cast<UnsafeSubresourceContainer*>(
item->GetUserData(kUserDataKey));
if (!stack) {
auto new_stack = base::WrapUnique(new UnsafeSubresourceContainer());
stack = new_stack.get();
item->SetUserData(kUserDataKey, std::move(new_stack));
}
return stack;
}
// Whether the container is holding an unsafe resource.
bool HasUnsafeResource() const { return unsafe_subresource_.get(); }
// Stores a copy of |subresource| in the container.
void StoreUnsafeSubresource(std::unique_ptr<UnsafeResource> subresource) {
unsafe_subresource_ = std::move(subresource);
}
// Returns the unsafe subresource, transferring ownership to the caller.
// Returns null if thre is no subresource in the container.
std::unique_ptr<UnsafeResource> ReleaseUnsafeSubresource() {
return std::move(unsafe_subresource_);
}
private:
UnsafeSubresourceContainer() = default;
std::unique_ptr<security_interstitials::UnsafeResource> unsafe_subresource_;
};
}
#pragma mark - SafeBrowsingUnsafeResourceContainer
WEB_STATE_USER_DATA_KEY_IMPL(SafeBrowsingUnsafeResourceContainer)
SafeBrowsingUnsafeResourceContainer::SafeBrowsingUnsafeResourceContainer(
web::WebState* web_state)
: web_state_(web_state) {}
SafeBrowsingUnsafeResourceContainer::SafeBrowsingUnsafeResourceContainer(
SafeBrowsingUnsafeResourceContainer&& other) = default;
SafeBrowsingUnsafeResourceContainer&
SafeBrowsingUnsafeResourceContainer::operator=(
SafeBrowsingUnsafeResourceContainer&& other) = default;
SafeBrowsingUnsafeResourceContainer::~SafeBrowsingUnsafeResourceContainer() =
default;
void SafeBrowsingUnsafeResourceContainer::StoreUnsafeResource(
const UnsafeResource& resource) {
DCHECK(!resource.web_state_getter.is_null() &&
resource.web_state_getter.Run() == web_state_);
// Store a copy of the resource, but reset the callback to prevent misuse.
std::unique_ptr<UnsafeResource> resource_copy =
std::make_unique<UnsafeResource>(resource);
resource_copy->callback = UnsafeResource::UrlCheckCallback();
if (resource.resource_type == safe_browsing::ResourceType::kMainFrame) {
// For main frame navigations, the copy is stored in
// |main_frame_unsafe_resource_| it corresponds with the pending
// NavigationItem, which is discarded when the navigation is cancelled.
DCHECK(!HasMainFrameUnsafeResource());
main_frame_unsafe_resource_ = std::move(resource_copy);
} else {
// Unsafe subresources are caused by loads triggered by the committed main
// frame navigation. These are associated with the NavigationItem stack so
// that they persist past reloads.
web::NavigationItem* item =
web_state_->GetNavigationManager()->GetLastCommittedItem();
DCHECK(!HasSubFrameUnsafeResource(item));
UnsafeSubresourceContainer::FromNavigationItem(item)
->StoreUnsafeSubresource(std::move(resource_copy));
}
}
bool SafeBrowsingUnsafeResourceContainer::HasMainFrameUnsafeResource() const {
return main_frame_unsafe_resource_.get();
}
std::unique_ptr<UnsafeResource>
SafeBrowsingUnsafeResourceContainer::ReleaseMainFrameUnsafeResource() {
return std::move(main_frame_unsafe_resource_);
}
bool SafeBrowsingUnsafeResourceContainer::HasSubFrameUnsafeResource(
web::NavigationItem* item) const {
return UnsafeSubresourceContainer::FromNavigationItem(item)
->HasUnsafeResource();
}
std::unique_ptr<security_interstitials::UnsafeResource>
SafeBrowsingUnsafeResourceContainer::ReleaseSubFrameUnsafeResource(
web::NavigationItem* item) {
return UnsafeSubresourceContainer::FromNavigationItem(item)
->ReleaseUnsafeSubresource();
}
// 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.
#import "ios/chrome/browser/safe_browsing/safe_browsing_unsafe_resource_container.h"
#include "base/bind.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/test/fakes/test_navigation_manager.h"
#import "ios/web/public/test/fakes/test_web_state.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using security_interstitials::UnsafeResource;
// Test fixture for SafeBrowsingUnsafeResourceContainer.
class SafeBrowsingUnsafeResourceContainerTest : public PlatformTest {
public:
SafeBrowsingUnsafeResourceContainerTest()
: item_(web::NavigationItem::Create()) {
std::unique_ptr<web::TestNavigationManager> navigation_manager =
std::make_unique<web::TestNavigationManager>();
navigation_manager->SetLastCommittedItem(item_.get());
web_state_.SetNavigationManager(std::move(navigation_manager));
SafeBrowsingUnsafeResourceContainer::CreateForWebState(&web_state_);
}
UnsafeResource MakeUnsafeResource(bool is_main_frame) {
UnsafeResource resource;
resource.url = GURL("http://www.chromium.test");
resource.callback =
base::BindRepeating([](bool proceed, bool showed_interstitial) {});
resource.resource_type = is_main_frame
? safe_browsing::ResourceType::kMainFrame
: safe_browsing::ResourceType::kSubFrame;
resource.web_state_getter = web_state_.CreateDefaultGetter();
return resource;
}
SafeBrowsingUnsafeResourceContainer* stack() {
return SafeBrowsingUnsafeResourceContainer::FromWebState(&web_state_);
}
protected:
std::unique_ptr<web::NavigationItem> item_;
web::TestWebState web_state_;
};
// Tests that main frame resources are correctly stored in and released from the
// container.
TEST_F(SafeBrowsingUnsafeResourceContainerTest, MainFrameResource) {
UnsafeResource resource = MakeUnsafeResource(/*is_main_frame=*/true);
// The container should not have any unsafe main frame resources initially.
EXPECT_FALSE(stack()->HasMainFrameUnsafeResource());
// Store |resource| in the container.
stack()->StoreUnsafeResource(resource);
EXPECT_TRUE(stack()->HasMainFrameUnsafeResource());
// Release the resource and check that it matches and that the callback has
// been removed.
std::unique_ptr<UnsafeResource> popped_resource =
stack()->ReleaseMainFrameUnsafeResource();
EXPECT_EQ(resource.url, popped_resource->url);
EXPECT_TRUE(popped_resource->callback.is_null());
// Verify that the main frame resource was removed from the container.
EXPECT_FALSE(stack()->HasMainFrameUnsafeResource());
EXPECT_FALSE(stack()->ReleaseMainFrameUnsafeResource());
}
// Tests that subresources are correctly stored in and released from the
// container.
TEST_F(SafeBrowsingUnsafeResourceContainerTest, SubFrameResource) {
UnsafeResource resource = MakeUnsafeResource(/*is_main_frame=*/false);
// The container should not have any unsafe sub frame resources initially.
EXPECT_FALSE(stack()->HasSubFrameUnsafeResource(item_.get()));
// Store |resource| in the container.
stack()->StoreUnsafeResource(resource);
EXPECT_TRUE(stack()->HasSubFrameUnsafeResource(item_.get()));
// Release the resource and check that it matches and that the callback has
// been removed.
std::unique_ptr<UnsafeResource> popped_resource =
stack()->ReleaseSubFrameUnsafeResource(item_.get());
EXPECT_EQ(resource.url, popped_resource->url);
EXPECT_TRUE(popped_resource->callback.is_null());
// Verify that the sub frame resource was removed from the container.
EXPECT_FALSE(stack()->HasSubFrameUnsafeResource(item_.get()));
EXPECT_FALSE(stack()->ReleaseSubFrameUnsafeResource(item_.get()));
}
......@@ -47,6 +47,7 @@
#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
#import "ios/chrome/browser/reading_list/reading_list_web_state_observer.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_tab_helper.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_unsafe_resource_container.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_url_allow_list.h"
#import "ios/chrome/browser/search_engines/search_engine_tab_helper.h"
#import "ios/chrome/browser/sessions/ios_chrome_session_tab_helper.h"
......@@ -125,9 +126,11 @@ void AttachTabHelpers(web::WebState* web_state, bool for_prerender) {
safe_browsing::kSafeBrowsingAvailableOnIOS)) {
SafeBrowsingTabHelper::CreateForWebState(web_state);
if (!for_prerender) {
// Unsafe navigations will never be allowed for prerender WebStates, so
// the allow list does not need to be created.
// Unsafe navigations will never be allowed for prerender WebStates, and
// error pages will never be shown, so the allow list and unsafe resource
// container do not need to be created.
SafeBrowsingUrlAllowList::CreateForWebState(web_state);
SafeBrowsingUnsafeResourceContainer::CreateForWebState(web_state);
}
}
......
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