LibURL: Use a nonce to distinguish opaque origins

Opaque origins are meant to be unique in terms of equality from
one another. Since this uniqueness needs to be across processes,
use a nonce to implement the uniqueness check.
This commit is contained in:
Shannon Booth 2025-06-17 14:49:37 +12:00 committed by Tim Ledbetter
commit 38765fd617
Notes: github-actions[bot] 2025-06-25 15:48:27 +00:00
7 changed files with 77 additions and 24 deletions

View file

@ -105,8 +105,10 @@ template<>
ErrorOr<URL::Origin> decode(Decoder& decoder)
{
auto is_opaque = TRY(decoder.decode<bool>());
if (is_opaque)
return URL::Origin::create_opaque();
if (is_opaque) {
auto nonce = TRY(decoder.decode<URL::Origin::Nonce>());
return URL::Origin { nonce };
}
auto scheme = TRY(decoder.decode<Optional<String>>());
auto host = TRY(decoder.decode<URL::Host>());

View file

@ -114,6 +114,7 @@ ErrorOr<void> encode(Encoder& encoder, URL::Origin const& origin)
{
if (origin.is_opaque()) {
TRY(encoder.encode(true));
TRY(encoder.encode(origin.nonce()));
} else {
TRY(encoder.encode(false));
TRY(encoder.encode(origin.scheme()));

View file

@ -20,5 +20,5 @@ set(SOURCES
)
serenity_lib(LibURL url)
target_link_libraries(LibURL PRIVATE LibUnicode LibTextCodec LibRegex)
target_link_libraries(LibURL PRIVATE LibUnicode LibTextCodec LibRegex LibCrypto)
target_compile_definitions(LibURL PRIVATE ENABLE_PUBLIC_SUFFIX=$<BOOL:${ENABLE_PUBLIC_SUFFIX_DOWNLOAD}>)

View file

@ -1,19 +1,19 @@
/*
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCrypto/SecureRandom.h>
#include <LibURL/Origin.h>
#include <LibURL/Parser.h>
#include <LibURL/Site.h>
namespace URL {
// FIXME: This should be generating a unique origin identifer that can be used for equality checks.
Origin Origin::create_opaque()
{
return Origin {};
return Origin { Crypto::get_secure_random<Nonce>() };
}
// https://html.spec.whatwg.org/multipage/browsers.html#same-site
@ -66,8 +66,14 @@ namespace AK {
unsigned Traits<URL::Origin>::hash(URL::Origin const& origin)
{
if (origin.is_opaque())
return 0;
if (origin.is_opaque()) {
auto const& nonce = origin.nonce();
// Random data, so the first u32 is as good as hashing the entire thing.
return (static_cast<u32>(nonce[0]) << 24)
| (static_cast<u32>(nonce[1]) << 16)
| (static_cast<u32>(nonce[2]) << 8)
| (static_cast<u32>(nonce[3]));
}
unsigned hash = origin.scheme().value_or(String {}).hash();

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -15,8 +16,17 @@ namespace URL {
class Origin {
public:
using Nonce = Array<u8, 16>;
explicit Origin(Nonce nonce)
: m_state(move(nonce))
{
}
static Origin create_opaque();
Origin(Optional<String> const& scheme, Host const& host, Optional<u16> port)
: m_state(State {
: m_state(Tuple {
.scheme = scheme,
.host = host,
.port = move(port),
@ -24,22 +34,21 @@ public:
{
}
static Origin create_opaque();
// https://html.spec.whatwg.org/multipage/origin.html#concept-origin-opaque
bool is_opaque() const { return !m_state.has_value(); }
bool is_opaque() const { return m_state.has<Nonce>(); }
Optional<String> const& scheme() const { return m_state->scheme; }
Host const& host() const { return m_state->host; }
Optional<u16> port() const { return m_state->port; }
Optional<String> const& scheme() const { return m_state.get<Tuple>().scheme; }
Host const& host() const { return m_state.get<Tuple>().host; }
Optional<u16> port() const { return m_state.get<Tuple>().port; }
Nonce const& nonce() const { return m_state.get<Nonce>(); }
// https://html.spec.whatwg.org/multipage/origin.html#same-origin
bool is_same_origin(Origin const& other) const
{
// 1. If A and B are the same opaque origin, then return true.
// FIXME: What about opaque origins that are not equal to one another?
if (is_opaque() && other.is_opaque())
return true;
return nonce() == other.nonce();
// 2. If A and B are both tuple origins and their schemes, hosts, and port are identical, then return true.
if (!is_opaque() && !other.is_opaque()
@ -57,9 +66,8 @@ public:
bool is_same_origin_domain(Origin const& other) const
{
// 1. If A and B are the same opaque origin, then return true.
// FIXME: What about opaque origins that are not equal to one another?
if (is_opaque() && other.is_opaque())
return true;
return nonce() == other.nonce();
// 2. If A and B are both tuple origins, run these substeps:
if (!is_opaque() && !other.is_opaque()) {
@ -94,20 +102,19 @@ public:
// FIXME: 2. If origin's domain is non-null, then return origin's domain.
// 3. Return origin's host.
return m_state->host;
return m_state.get<Tuple>().host;
}
bool operator==(Origin const& other) const { return is_same_origin(other); }
private:
Origin() = default;
struct State {
struct Tuple {
Optional<String> scheme;
Host host;
Optional<u16> port;
};
Optional<State> m_state;
Variant<Tuple, Nonce> m_state;
};
}

View file

@ -0,0 +1 @@
Can't access property 'thing' on cross-origin object

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<body>
<iframe id="iframeOuter"></iframe>
<script>
asyncTest(async done => {
const iframeOuter = document.getElementById('iframeOuter');
window.addEventListener('message', (event) => {
println(event.data);
done();
});
// Data URLs result in the iframes having an opaque origin, resulting in cross origin access.
const iframeOuterContent = `
<iframe id="iframeInner" src="data:text/html,<p>Iframe 1 content</p>" style="width: 300px; height: 100px;"></iframe>
<script>
const iframeInner = document.getElementById('iframeInner');
iframeInner.onload = () => {
try {
iframeInner.contentWindow.parent.frames[0].thing;
} catch (e) {
window.top.postMessage(e.message, '*');
}
}
<\/script>
`;
iframeOuter.src = 'data:text/html,' + encodeURIComponent(iframeOuterContent);
})
</script>
</body>