Skip to content

Commit

Permalink
[Permissions Policy Wildcards] (1) Add function to detect subdomain m…
Browse files Browse the repository at this point in the history
…atches

This CL focuses just on the wildcard subdomain matching function itself
as it's a dangerous point of failure and needs deep review.
Design doc:
w3c/webappsec-permissions-policy#482

This CL is part of a series:
(1) Add function to detect subdomain matches
(2) Use OriginWithPossibleWildcards in policy
(3) Parse wildcard subdomain matches in policy
(4) Add WPTs

Bug: 1345994
Change-Id: I42b4513038e91db0883d3e9afa77c7ebed57e3fb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3910955
Auto-Submit: Ari Chivukula <arichiv@chromium.org>
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Reviewed-by: Yoav Weiss <yoavweiss@chromium.org>
Reviewed-by: Ian Clelland <iclelland@chromium.org>
Commit-Queue: Ari Chivukula <arichiv@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1052043}
NOKEYCHECK=True
GitOrigin-RevId: 4a12e8c0da01f98fa1a0b073100c7cb34b6f6515
  • Loading branch information
arichiv authored and Copybara-Service committed Sep 28, 2022
1 parent 300a0a5 commit 82cd402
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 1 deletion.
2 changes: 2 additions & 0 deletions blink/common/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ source_set("common") {
"peerconnection/webrtc_ip_handling_policy.cc",
"permissions/permission_utils.cc",
"permissions_policy/document_policy.cc",
"permissions_policy/origin_with_possible_wildcards.cc",
"permissions_policy/permissions_policy.cc",
"permissions_policy/permissions_policy_declaration.cc",
"permissions_policy/permissions_policy_mojom_traits.cc",
Expand Down Expand Up @@ -376,6 +377,7 @@ source_set("common_unittests_sources") {
"page/content_to_visible_time_reporter_unittest.cc",
"page_state/page_state_serialization_unittest.cc",
"permissions_policy/document_policy_unittest.cc",
"permissions_policy/origin_with_possible_wildcards_unittest.cc",
"permissions_policy/permissions_policy_declaration_unittest.cc",
"permissions_policy/permissions_policy_unittest.cc",
"permissions_policy/policy_value_unittest.cc",
Expand Down
4 changes: 4 additions & 0 deletions blink/common/features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1625,5 +1625,9 @@ BASE_FEATURE(kFastPathPaintPropertyUpdates,
"FastPathPaintPropertyUpdates",
base::FEATURE_ENABLED_BY_DEFAULT);

BASE_FEATURE(kWildcardSubdomainsInPermissionsPolicy,
"WildcardSubdomainsInPermissionsPolicy",
base::FEATURE_DISABLED_BY_DEFAULT);

} // namespace features
} // namespace blink
90 changes: 90 additions & 0 deletions blink/common/permissions_policy/origin_with_possible_wildcards.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h"

#include "base/feature_list.h"
#include "third_party/blink/public/common/features.h"
#include "url/origin.h"

namespace blink {

OriginWithPossibleWildcards::OriginWithPossibleWildcards() = default;

OriginWithPossibleWildcards::OriginWithPossibleWildcards(
const url::Origin& origin,
bool has_subdomain_wildcard)
: origin(origin), has_subdomain_wildcard(has_subdomain_wildcard) {
// Origins that do have wildcards cannot be opaque.
DCHECK(!origin.opaque() || !has_subdomain_wildcard);
}

OriginWithPossibleWildcards::OriginWithPossibleWildcards(
const OriginWithPossibleWildcards& rhs) = default;

OriginWithPossibleWildcards& OriginWithPossibleWildcards::operator=(
const OriginWithPossibleWildcards& rhs) = default;

OriginWithPossibleWildcards::~OriginWithPossibleWildcards() = default;

bool OriginWithPossibleWildcards::DoesMatchOrigin(
const url::Origin& match_origin) const {
// TODO(crbug.com/1345994): Merge logic with IsSubdomainOfHost where possible.
if (has_subdomain_wildcard) {
// Only try to match at all if wildcard matching is enabled.
if (!base::FeatureList::IsEnabled(
features::kWildcardSubdomainsInPermissionsPolicy)) {
return false;
}
// This function won't match https://*.foo.com with https://foo.com.
if (origin == match_origin) {
return false;
}
const auto& tested_host = match_origin.host();
const auto& policy_host = origin.host();
// The tested host must be at least 2 char longer than the policy host
// to be a subdomain of it.
if (tested_host.length() < (policy_host.length() + 2)) {
return false;
}
// The tested host must end with the policy host.
if (!base::EndsWith(tested_host, policy_host)) {
return false;
}
// The tested host without the policy host must end with a ".".
if (tested_host[tested_host.length() - policy_host.length() - 1] != '.') {
return false;
}
// If anything but the host doesn't match they can't match.
if (origin != url::Origin::CreateFromNormalizedTuple(match_origin.scheme(),
policy_host,
match_origin.port())) {
return false;
}
return true;
} else {
// If there is no wildcard test normal match.
return origin == match_origin;
}
}

bool operator==(const OriginWithPossibleWildcards& lhs,
const OriginWithPossibleWildcards& rhs) {
return std::tie(lhs.origin, lhs.has_subdomain_wildcard) ==
std::tie(rhs.origin, rhs.has_subdomain_wildcard);
}

bool operator!=(const OriginWithPossibleWildcards& lhs,
const OriginWithPossibleWildcards& rhs) {
return std::tie(lhs.origin, lhs.has_subdomain_wildcard) !=
std::tie(rhs.origin, rhs.has_subdomain_wildcard);
}

bool operator<(const OriginWithPossibleWildcards& lhs,
const OriginWithPossibleWildcards& rhs) {
return std::tie(lhs.origin, lhs.has_subdomain_wildcard) <
std::tie(rhs.origin, rhs.has_subdomain_wildcard);
}

} // namespace blink
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h"

#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace blink {

class OriginWithPossibleWildcardsTest : public testing::TestWithParam<bool> {
public:
void SetUp() override {
scoped_feature_list_.InitWithFeatureState(
features::kWildcardSubdomainsInPermissionsPolicy,
HasWildcardSubdomainsInPermissionsPolicy());
}

bool HasWildcardSubdomainsInPermissionsPolicy() { return GetParam(); }

private:
base::test::ScopedFeatureList scoped_feature_list_;
};

INSTANTIATE_TEST_SUITE_P(All, OriginWithPossibleWildcardsTest, testing::Bool());

TEST_P(OriginWithPossibleWildcardsTest, DoesMatchOrigin) {
// Tuple of {origin to test, origin in policy, w/ wildcard, result,
// description}.
const auto& values = {
std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
url::Origin::Create(GURL("https://foo.com")), false, true,
"Same origin, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
url::Origin::Create(GURL("http://foo.com")), false, false,
"Different scheme, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
url::Origin::Create(GURL("https://foo.com:443")), false,
true, "Ignore default port, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://bar.foo.com")),
url::Origin::Create(GURL("https://foo.com")), false,
false, "Subdomain matches, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
url::Origin::Create(GURL("https://bar.foo.com")), false,
false, "Different subdomain, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
url::Origin(), false, false,
"Origin to opaque, no wildcard"),
std::make_tuple(url::Origin(),
url::Origin::Create(GURL("https://foo.com")), false,
false, "Opaque to origin, no wildcard"),
std::make_tuple(url::Origin(), url::Origin(), false, false,
"Opaque to opaque, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("file:///test")),
url::Origin::Create(GURL("file:///test")), false, true,
"File, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://192.168.1.1")),
url::Origin::Create(GURL("http://192.168.1.1")), false,
true, "Same IPv4, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://192.168.1.1")),
url::Origin::Create(GURL("http://192.168.1.2")), false,
false, "Different IPv4, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://[2001:db8::1]")),
url::Origin::Create(GURL("http://[2001:db8::1]")), false,
true, "Same IPv6, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://[2001:db8::1]")),
url::Origin::Create(GURL("http://[2001:db8::2]")), false,
false, "Different IPv6, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
url::Origin::Create(GURL("https://foo.com")), true, false,
"Same origin, w/ wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://bar.foo.com")),
url::Origin::Create(GURL("https://foo.com")), true,
HasWildcardSubdomainsInPermissionsPolicy(),
"Subdomain matches, w/ wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://bar.foo.com")),
url::Origin::Create(GURL("https://foo.com")), true, false,
"Different scheme, w/ wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://baz.bar.foo.com")),
url::Origin::Create(GURL("https://foo.com")), true,
HasWildcardSubdomainsInPermissionsPolicy(),
"Sub-subdomain matches, w/ wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
url::Origin::Create(GURL("https://bar.foo.com")), true,
false, "Subdomain doesn't match, w/ wildcard"),
std::make_tuple(url::Origin::Create(GURL("https://bar.foo.com")),
url::Origin::Create(GURL("https://foo.com:443")), true,
HasWildcardSubdomainsInPermissionsPolicy(),
"Ignore default port, w/ wildcard"),
std::make_tuple(url::Origin(),
url::Origin::Create(GURL("https://foo.com")), true, false,
"Opaque to origin, w/ wildcard"),
std::make_tuple(url::Origin::Create(GURL("file:///test")),
url::Origin::Create(GURL("file:///test")), true, false,
"File, w/ wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://192.168.1.1")),
url::Origin::Create(GURL("http://192.168.1.1")), true,
false, "Same IPv4, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://192.168.1.1")),
url::Origin::Create(GURL("http://192.168.1.2")), true,
false, "Different IPv4, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://[2001:db8::1]")),
url::Origin::Create(GURL("http://[2001:db8::1]")), true,
false, "Same IPv6, no wildcard"),
std::make_tuple(url::Origin::Create(GURL("http://[2001:db8::1]")),
url::Origin::Create(GURL("http://[2001:db8::2]")), true,
false, "Different IPv6, no wildcard")};
for (const auto& value : values) {
SCOPED_TRACE(std::get<4>(value));
EXPECT_EQ(std::get<3>(value), OriginWithPossibleWildcards(
std::get<1>(value), std::get<2>(value))
.DoesMatchOrigin(std::get<0>(value)));
}
}

TEST_P(OriginWithPossibleWildcardsTest, Constructors) {
OriginWithPossibleWildcards a;
OriginWithPossibleWildcards b(url::Origin(), false);
OriginWithPossibleWildcards c(b);
OriginWithPossibleWildcards d = c;
EXPECT_NE(a, b);
EXPECT_EQ(b, c);
EXPECT_EQ(c, d);
}

TEST_P(OriginWithPossibleWildcardsTest, Opaque) {
OriginWithPossibleWildcards opaque_without_wildcards(url::Origin(), false);
EXPECT_DCHECK_DEATH(
OriginWithPossibleWildcards opaque_with_wildcards(url::Origin(), true));
}

} // namespace blink
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@

namespace mojo {

bool StructTraits<blink::mojom::OriginWithPossibleWildcardsDataView,
blink::OriginWithPossibleWildcards>::
Read(blink::mojom::OriginWithPossibleWildcardsDataView in,
blink::OriginWithPossibleWildcards* out) {
out->has_subdomain_wildcard = in.has_subdomain_wildcard();
return in.ReadOrigin(&out->origin);
}

bool StructTraits<blink::mojom::ParsedPermissionsPolicyDeclarationDataView,
blink::ParsedPermissionsPolicyDeclaration>::
Read(blink::mojom::ParsedPermissionsPolicyDeclarationDataView in,
Expand Down
19 changes: 19 additions & 0 deletions blink/common/permissions_policy/permissions_policy_mojom_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,31 @@
#include "mojo/public/cpp/bindings/enum_traits.h"
#include "third_party/blink/common/permissions_policy/policy_value_mojom_traits.h"
#include "third_party/blink/public/common/common_export.h"
#include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-shared.h"
#include "url/mojom/origin_mojom_traits.h"

namespace mojo {

template <>
class BLINK_COMMON_EXPORT
StructTraits<blink::mojom::OriginWithPossibleWildcardsDataView,
blink::OriginWithPossibleWildcards> {
public:
static const url::Origin& origin(const blink::OriginWithPossibleWildcards&
origin_with_possible_wildcards) {
return origin_with_possible_wildcards.origin;
}
static bool has_subdomain_wildcard(const blink::OriginWithPossibleWildcards&
origin_with_possible_wildcards) {
return origin_with_possible_wildcards.has_subdomain_wildcard;
}

static bool Read(blink::mojom::OriginWithPossibleWildcardsDataView in,
blink::OriginWithPossibleWildcards* out);
};

template <>
class BLINK_COMMON_EXPORT
StructTraits<blink::mojom::ParsedPermissionsPolicyDeclarationDataView,
Expand Down
4 changes: 4 additions & 0 deletions blink/public/common/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,10 @@ BLINK_COMMON_EXPORT extern const base::FeatureParam<bool>
// applied directly instead of using the property tree builder.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kFastPathPaintPropertyUpdates);

// If enabled, wildcard subdomains are supported in permissions policies.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
kWildcardSubdomainsInPermissionsPolicy);

} // namespace features
} // namespace blink

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_PERMISSIONS_POLICY_ORIGIN_WITH_POSSIBLE_WILDCARDS_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_PERMISSIONS_POLICY_ORIGIN_WITH_POSSIBLE_WILDCARDS_H_

#include "third_party/blink/public/common/common_export.h"
#include "url/origin.h"

namespace blink {

// This struct can represent an origin like https://foo.com/ or like
// https://*.foo.com/. The wildcard can only represent a subdomain.
// Note that https://*.foo.com/ matches domains like https://example.foo.com/
// or https://test.example.foo.com/ but does not match https://foo.com/.
// Origins that do have wildcards cannot be opaque.
struct BLINK_COMMON_EXPORT OriginWithPossibleWildcards {
OriginWithPossibleWildcards();
OriginWithPossibleWildcards(const url::Origin& origin,
bool has_subdomain_wildcard);
OriginWithPossibleWildcards(const OriginWithPossibleWildcards& rhs);
OriginWithPossibleWildcards& operator=(
const OriginWithPossibleWildcards& rhs);
~OriginWithPossibleWildcards();

// If there is no subdomain wildcard, this function returns true if the
// origins match.
// For example: https://foo.com/ matches <https://foo.com/, false> but
// https://bar.foo.com/ does not match <https://foo.com/, false>.
//
// If there is a subdomain wildcard, this function returns
// true if and only if the first origin is a subdomain of the second.
// For example: https://bar.foo.com/ matches <https://foo.com/, true> but
// https://foo.com/ does not match <https://foo.com/, true>.
//
// For more details on use see:
// https://github.com/w3c/webappsec-permissions-policy/pull/482
bool DoesMatchOrigin(const url::Origin& match_origin) const;

url::Origin origin;
bool has_subdomain_wildcard{false};
};

bool BLINK_COMMON_EXPORT operator==(const OriginWithPossibleWildcards& lhs,
const OriginWithPossibleWildcards& rhs);
bool BLINK_COMMON_EXPORT operator!=(const OriginWithPossibleWildcards& lhs,
const OriginWithPossibleWildcards& rhs);
bool BLINK_COMMON_EXPORT operator<(const OriginWithPossibleWildcards& lhs,
const OriginWithPossibleWildcards& rhs);

} // namespace blink

#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_PERMISSIONS_POLICY_ORIGIN_WITH_POSSIBLE_WILDCARDS_H_
9 changes: 8 additions & 1 deletion blink/public/mojom/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -351,12 +351,19 @@ mojom("mojom_platform") {
},
{
types = [
{
mojom = "blink.mojom.OriginWithPossibleWildcards"
cpp = "::blink::OriginWithPossibleWildcards"
},
{
mojom = "blink.mojom.ParsedPermissionsPolicyDeclaration"
cpp = "::blink::ParsedPermissionsPolicyDeclaration"
},
]
traits_headers = [ "//third_party/blink/public/common/permissions_policy/permissions_policy_declaration.h" ]
traits_headers = [
"//third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h",
"//third_party/blink/public/common/permissions_policy/permissions_policy_declaration.h",
]
traits_private_headers = [ "//third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.h" ]
},
{
Expand Down

0 comments on commit 82cd402

Please sign in to comment.