diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index edbd84152a..42267dcb6c 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -330,7 +330,8 @@ WebIDL::ExceptionOr> Document::create_and_initialize(Type type DOM::DocumentLoadTimingInfo load_timing_info; // AD-HOC: The response object no longer has an associated timing info object. For now, we use response's non-standard response time property, // which represents the time that the time that the response object was created. - load_timing_info.navigation_start_time = navigation_params.response->response_time().nanoseconds() / 1e6; + auto response_creation_time = navigation_params.response->response_time().nanoseconds() / 1e6; + load_timing_info.navigation_start_time = HighResolutionTime::coarsen_time(response_creation_time, HTML::relevant_settings_object(*window).cross_origin_isolated_capability() == HTML::CanUseCrossOriginIsolatedAPIs::Yes); // 9. Let document be a new Document, with // type: type diff --git a/Libraries/LibWeb/HighResolutionTime/TimeOrigin.cpp b/Libraries/LibWeb/HighResolutionTime/TimeOrigin.cpp index c5ef9e9ca4..bf0e729191 100644 --- a/Libraries/LibWeb/HighResolutionTime/TimeOrigin.cpp +++ b/Libraries/LibWeb/HighResolutionTime/TimeOrigin.cpp @@ -5,6 +5,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -46,8 +47,19 @@ DOMHighResTimeStamp get_time_origin_timestamp(JS::Object const& global) // https://w3c.github.io/hr-time/#dfn-coarsen-time DOMHighResTimeStamp coarsen_time(DOMHighResTimeStamp timestamp, bool cross_origin_isolated_capability) { - // FIXME: Implement this. - (void)cross_origin_isolated_capability; + // 1. Let time resolution be 100 microseconds, or a higher implementation-defined value. + auto time_resolution_milliseconds = 0.1; + + // 2. If crossOriginIsolatedCapability is true, set time resolution to be 5 microseconds, or a higher implementation-defined value. + if (cross_origin_isolated_capability) + time_resolution_milliseconds = 0.005; + + // 3. In an implementation-defined manner, coarsen and potentially jitter timestamp such that its resolution will not exceed time resolution + timestamp = floor(timestamp / time_resolution_milliseconds) * time_resolution_milliseconds; + + // FIXME: Applying jitter to the coarsened timestamp here may decrease our susceptibility to timing attacks. + + // 4. Return timestamp as a moment return timestamp; } diff --git a/Tests/LibWeb/Text/expected/wpt-import/dom/events/Event-timestamp-safe-resolution.txt b/Tests/LibWeb/Text/expected/wpt-import/dom/events/Event-timestamp-safe-resolution.txt index e313229042..acd74bf53c 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/dom/events/Event-timestamp-safe-resolution.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/dom/events/Event-timestamp-safe-resolution.txt @@ -2,5 +2,5 @@ Harness status: OK Found 1 tests -1 Fail -Fail Event timestamp should not have a resolution better than 5 microseconds \ No newline at end of file +1 Pass +Pass Event timestamp should not have a resolution better than 5 microseconds \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/hr-time/timing-attack.txt b/Tests/LibWeb/Text/expected/wpt-import/hr-time/timing-attack.txt new file mode 100644 index 0000000000..c10c9a21cc --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/hr-time/timing-attack.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass The recommended minimum resolution of the Performance interface has been set to 100 microseconds for cross-origin isolated contexts. \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/PerformanceObserver/PerformanceObserver_basic.html b/Tests/LibWeb/Text/input/PerformanceObserver/PerformanceObserver_basic.html index a35b890342..db9001a9cf 100644 --- a/Tests/LibWeb/Text/input/PerformanceObserver/PerformanceObserver_basic.html +++ b/Tests/LibWeb/Text/input/PerformanceObserver/PerformanceObserver_basic.html @@ -5,6 +5,14 @@ bufferedMessages.push(message); } + function synchronousWaitMicroseconds(microseconds) { + var start = performance.now() * 1000, + now = start; + while (now - start < microseconds) { + now = performance.now() * 1000; + } + } + const globalObserver = new PerformanceObserver((list, observer) => { printlnBuffered(`observer === globalObserver: ${observer === globalObserver}`); printlnBuffered( @@ -53,7 +61,11 @@ globalObserver.observe({ entryTypes: ["measure", "mark"] }); const startMark = performance.mark("start"); + // The resolution of the clock used by the Performance interface is 100 microseconds, so we wait twice that time + // between calls to ensure they are ordered as we expect. + synchronousWaitMicroseconds(200); const endMark = performance.mark("end"); + synchronousWaitMicroseconds(200); const measureMark = performance.measure("measure", "start", "end"); function printCatchedException(func) { diff --git a/Tests/LibWeb/Text/input/wpt-import/hr-time/resources/timing-attack.js b/Tests/LibWeb/Text/input/wpt-import/hr-time/resources/timing-attack.js new file mode 100644 index 0000000000..f1fc786903 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/hr-time/resources/timing-attack.js @@ -0,0 +1,42 @@ +function run_test(isolated) { + let resolution = 100; + if (isolated) { + resolution = 5; + } + test(function() { + function check_resolutions(times, length) { + const end = length - 2; + + // we compare each value with the following ones + for (let i = 0; i < end; i++) { + const h1 = times[i]; + for (let j = i+1; j < end; j++) { + const h2 = times[j]; + const diff = h2 - h1; + assert_true((diff === 0) || ((diff * 1000) >= resolution), + "Differences smaller than ' + resolution + ' microseconds: " + diff); + } + } + return true; + } + + const times = new Array(10); + let index = 0; + let hrt1, hrt2, hrt; + assert_equals(self.crossOriginIsolated, isolated, "Document cross-origin isolated value matches"); + + // rapid firing of performance.now + hrt1 = performance.now(); + hrt2 = performance.now(); + times[index++] = hrt1; + times[index++] = hrt2; + + // ensure that we get performance.now() to return a different value + do { + hrt = performance.now(); + times[index++] = hrt; + } while ((hrt - hrt1) === 0); + + assert_true(check_resolutions(times, index), 'Difference should be at least ' + resolution + ' microseconds.'); + }, 'The recommended minimum resolution of the Performance interface has been set to ' + resolution + ' microseconds for cross-origin isolated contexts.'); +} diff --git a/Tests/LibWeb/Text/input/wpt-import/hr-time/timing-attack.html b/Tests/LibWeb/Text/input/wpt-import/hr-time/timing-attack.html new file mode 100644 index 0000000000..1db71f6558 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/hr-time/timing-attack.html @@ -0,0 +1,22 @@ + + + + +window.performance.now should not enable timing attacks + + + + + + + + +

Description

+

The recommended minimum resolution of the Performance interface should be set to 100 microseconds for non-isolated contexts.

+ +
+ + +