LibWeb: Implement basic high resolution time coarsening

Several interfaces that return a high resolution time require that
time to be coarsened, in order to prevent timing attacks. This
implementation simply reduces the resolution of the returned timestamp
to the minimum values given in the specification. Further work may be
needed to make our implementation more robust to the kind of attacks
that this mechanism is designed to prevent.
This commit is contained in:
Tim Ledbetter 2025-01-28 10:47:32 +00:00 committed by Alexander Kalenik
parent a5be7cb6fb
commit 39445d6dd6
Notes: github-actions[bot] 2025-01-30 17:39:14 +00:00
7 changed files with 100 additions and 5 deletions

View file

@ -330,7 +330,8 @@ WebIDL::ExceptionOr<GC::Ref<Document>> 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

View file

@ -5,6 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Math.h>
#include <AK/Time.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
@ -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;
}

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Event timestamp should not have a resolution better than 5 microseconds
1 Pass
Pass Event timestamp should not have a resolution better than 5 microseconds

View file

@ -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.

View file

@ -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) {

View file

@ -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.');
}

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>window.performance.now should not enable timing attacks</title>
<link rel="author" title="W3C" href="http://www.w3.org/" />
<link rel="help" href="http://w3c.github.io/hr-time/#privacy-security"/>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/timing-attack.js"></script>
<script>
run_test(/*isolated=*/false);
</script>
</head>
<body>
<h1>Description</h1>
<p>The recommended minimum resolution of the Performance interface should be set to 100 microseconds for non-isolated contexts.</p>
<div id="log"></div>
</body>
</html>