Skip to content

Commit 7ba7013

Browse files
VergeAchromium-wpt-export-bot
authored andcommitted
[Connection-Allowlist] Implement CSP-style restrictions for WebRTC.
Previously, Connection Allowlist blocked WebRTC by throwing an error in the RTCPeerConnection constructor (crrev.com/c/7676321). However, this diverges from how CSP handles WebRTC blocking, which could result in churn for developers. This CL changes the renderer-side WebRTC blocking behavior to match what has been specified for CSP. In particular: * Filter all ICE servers to avoid surfacing local candidates * Prevent adding of remote ICE candidates (this mimics the current state of the WebRTC + CSP integration) * Immediately fire an event to change to “failed” connection state(s) after construction * Calling restartIce() doesn’t change this outcome. These four changes should produce the same result declared in the CSP spec: https://w3c.github.io/webappsec-csp/#directive-webrtc Web Platform Tests have been updated to match this new behavior, and even pass the existing, unmodified CSP test logic. Bug: 492439214 Fixed: 492439214 Change-Id: Ib3b6261d0d19fdb3121ddca4f7d26905a0935fce Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8132041 Commit-Queue: Andrew Verge <averge@chromium.org> Reviewed-by: Guido Urdaneta <guidou@chromium.org> Reviewed-by: Noam Rosenthal <nrosenthal@google.com> Cr-Commit-Position: refs/heads/main@{#1669728}
1 parent cd0e4e4 commit 7ba7013

7 files changed

Lines changed: 158 additions & 56 deletions

connection-allowlist/tentative/webrtc-allow.sub.window.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
// META: script=/common/get-host-info.sub.js
2+
// META: script=/content-security-policy/webrtc/webrtc.js
23
//
34
// The following tests assume the policy `Connection-Allowlist:
45
// (response-origin);webrtc=allow` has been set.
56
promise_test(async (t) => {
6-
try {
7-
const configuration = {};
8-
const peerConnection = new RTCPeerConnection(configuration);
9-
} catch (err) {
10-
assert_unreached('RTCPeerConnection construction should succeed');
11-
}
7+
assert_equals(await tryConnect(), 'allowed');
128
}, 'Test that webrtc=allow Connection-Allowlist param is respected.');
139

1410
promise_test(async (t) => {

connection-allowlist/tentative/webrtc-block-default.sub.window.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
// META: script=/common/get-host-info.sub.js
2+
// META: script=/content-security-policy/webrtc/webrtc.js
23
//
34
// The following tests assume the policy `Connection-Allowlist:
4-
// (response-origin)` has been set.
5+
// (response-origin)` has been set. WebRTC should be blocked by default.
56
promise_test(async (t) => {
6-
try {
7-
const configuration = {};
8-
const peerConnection = new RTCPeerConnection(configuration);
9-
assert_unreached('RTCPeerConnection construction should fail.')
10-
} catch (err) {
11-
assert_equals(err.name, 'NotAllowedError');
12-
}
13-
}, 'Test that setting Connection-Allowlist blocks WebRTC by default.');
7+
assert_equals(await tryConnect(), 'blocked');
8+
}, 'Test that default Connection-Allowlist WebRTC blocking is respected.');
149

1510
promise_test(async (t) => {
1611
return fetch('/common/blank.html');
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// META: script=/common/get-host-info.sub.js
2+
// META: script=/content-security-policy/webrtc/webrtc.js
3+
//
4+
// The following tests assume the policy `Connection-Allowlist:
5+
// (response-origin);webrtc=block` has been set.
6+
//
7+
// Other Connection-Allowlist tests for WebRTC are mostly interested in higher-
8+
// level outcomes: whether the RTCPeerConnection transitions to a failure state,
9+
// and whether violation reports are sent. These tests observe more specific
10+
// script interactions with the RTCPeerConnection object if WebRTC has been
11+
// blocked.
12+
async function createBlockedPeerConnection() {
13+
// Copied from https://webrtc.org/getting-started/peer-connections.
14+
const configuration = {
15+
'iceServers': [{'urls': 'stun:stun.example.com:19302'}]
16+
};
17+
const pc1 = new RTCPeerConnection(configuration);
18+
const pc2 = new RTCPeerConnection(configuration);
19+
20+
// Returns a promise which resolves to a boolean which is true
21+
// if and only if pc.iceConnectionState settles in the "failed"
22+
// state, and never transitions to any state other than "new"
23+
// or "failed."
24+
const pcFailed =
25+
(pc) => {
26+
return new Promise((resolve, _reject) => {
27+
pc.oniceconnectionstatechange = (e) => {
28+
resolve(pc.iceConnectionState == 'failed');
29+
};
30+
});
31+
}
32+
33+
let pc1Failed = pcFailed(pc1);
34+
let pc2Failed = pcFailed(pc2);
35+
36+
// Creating a data channel is necessary to induce negotiation:
37+
const channel = pc1.createDataChannel('test');
38+
39+
// Usual webrtc signaling dance:
40+
pc1.onicecandidate = ({candidate}) => pc2.addIceCandidate(candidate);
41+
pc2.onicecandidate = ({candidate}) => pc1.addIceCandidate(candidate);
42+
const offer = await pc1.createOffer();
43+
await pc1.setLocalDescription(offer);
44+
await pc2.setRemoteDescription(pc1.localDescription);
45+
const answer = await pc2.createAnswer();
46+
await pc2.setLocalDescription(answer);
47+
await pc1.setRemoteDescription(pc2.localDescription);
48+
49+
const failed1 = await pc1Failed;
50+
const failed2 = await pc2Failed;
51+
assert_true(failed1);
52+
assert_true(failed2);
53+
return pc1;
54+
};
55+
56+
promise_test(async (t) => {
57+
let pc = await createBlockedPeerConnection();
58+
59+
// Even though we passed an ICE server into the RTCPeerConnection's
60+
// constructor, it should have been filtered out before any requests could be
61+
// made to it.
62+
assert_equals(pc.getConfiguration().iceServers.length, 0);
63+
}, 'ICE servers are not present when WebRTC is blocked.');
64+
65+
promise_test(async (t) => {
66+
let pc = await createBlockedPeerConnection();
67+
68+
// Adding an ICE candidate to the connection should return an empty promise.
69+
let candidate = new RTCIceCandidate({
70+
sdpMid: 'video',
71+
sdpMLineIndex: 1,
72+
usernameFragment: 'test',
73+
relayProtocol: 'udp',
74+
url: 'stun:stun.example.org'
75+
});
76+
let candidateResult = await pc.addIceCandidate(candidate);
77+
assert_equals(candidateResult, undefined);
78+
}, 'Adding a candidate returns undefined when WebRTC is blocked.');
79+
80+
promise_test(async (t) => {
81+
let pc = await createBlockedPeerConnection();
82+
83+
let iceStatePromise = new Promise((resolve, _reject) => {
84+
pc.oniceconnectionstatechange = (e) => {
85+
resolve('Ice state changed.');
86+
};
87+
});
88+
pc.restartIce();
89+
90+
let result = await Promise.race(
91+
[new Promise(r => t.step_timeout(r, 2000)), iceStatePromise]);
92+
assert_equals(result, undefined);
93+
}, 'restartIce() has no observable effect when WebRTC is blocked.');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Connection-Allowlist: (response-origin);webrtc=block

connection-allowlist/tentative/webrtc-block.sub.window.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
// META: script=/common/get-host-info.sub.js
2+
// META: script=/content-security-policy/webrtc/webrtc.js
23
//
34
// The following tests assume the policy `Connection-Allowlist:
45
// (response-origin);webrtc=block` has been set.
56
promise_test(async (t) => {
6-
try {
7-
// Copied from https://webrtc.org/getting-started/peer-connections.
8-
const configuration = {
9-
'iceServers': [{'urls': 'stun:stun.example.com:19302'}]
10-
};
11-
const peerConnection = new RTCPeerConnection(configuration);
12-
assert_unreached('RTCPeerConnection construction should fail.')
13-
} catch (err) {
14-
assert_equals(err.name, 'NotAllowedError');
15-
}
7+
assert_equals(await tryConnect(), 'blocked');
168
}, 'Test that webrtc=block Connection-Allowlist param is respected.');
179

1810
promise_test(async (t) => {

connection-allowlist/tentative/webrtc-queuereport-enforce.https.sub.window.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
// META: script=/common/get-host-info.sub.js
22
// META: script=/reporting/resources/report-helper.js
3+
// META: script=/content-security-policy/webrtc/webrtc.js
34
//
45
// The following tests assume the policy `Connection-Allowlist:
5-
// (response-origin); webrtc=block; report-to=endpoint` has been set.
6+
// (response-origin); webrtc=block; report-to=endpoint` has been set. There
7+
// should be 2 reports sent, 1 for each RTCPeerConnection created in
8+
// tryConnect().
69
promise_test(async (t) => {
7-
let observer = new ReportingObserver(() => {});
8-
observer.observe();
9-
10-
try {
11-
const configuration = {};
12-
const peerConnection = new RTCPeerConnection(configuration);
13-
assert_unreached(
14-
'In enforce mode, RTCPeerConnection creation should throw.');
15-
} catch (err) {
16-
assert_equals(err.name, 'NotAllowedError')
17-
}
18-
observer.disconnect();
10+
let local_reports = [];
11+
let report_promise = new Promise((resolve) => {
12+
let observer = new ReportingObserver((reports) => {
13+
local_reports = local_reports.concat(reports);
14+
if (local_reports.length >= 2) {
15+
observer.disconnect();
16+
resolve();
17+
}
18+
});
19+
observer.observe();
20+
})
21+
assert_equals(await tryConnect(), 'blocked');
1922

2023
// Check ReportingObserver receipt of the report.
21-
const local_reports = observer.takeRecords();
22-
assert_equals(local_reports.length, 1);
24+
await report_promise;
25+
26+
assert_equals(local_reports.length, 2);
27+
// Convert Report objects to JSON before comparing them.
28+
assert_object_equals(local_reports[0].toJSON(), local_reports[1].toJSON());
2329
assert_equals(local_reports[0]['type'], 'connection-allowlist');
2430
assert_equals(local_reports[0]['url'], location.href);
2531

@@ -36,7 +42,13 @@ promise_test(async (t) => {
3642
const id = 'ea5269e0-d728-4173-87a5-da9e6624f6be';
3743
await wait(5000);
3844
const remote_reports = await pollReports(endpoint, id);
39-
assert_equals(remote_reports.length, 1);
45+
assert_equals(remote_reports.length, 2);
46+
47+
// Normalize the "age" property so we can compare the rest of the reports for
48+
// equality.
49+
remote_reports[0]['age'] = 0;
50+
remote_reports[1]['age'] = 0;
51+
assert_object_equals(remote_reports[0], remote_reports[1]);
4052
assert_equals(remote_reports[0]['type'], 'connection-allowlist');
4153
assert_equals(remote_reports[0]['url'], location.href);
4254

connection-allowlist/tentative/webrtc-queuereport-report-only.https.sub.window.js

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
// META: script=/common/get-host-info.sub.js
22
// META: script=/reporting/resources/report-helper.js
3+
// META: script=/content-security-policy/webrtc/webrtc.js
34
//
45
// The following tests assume the policy `Connection-Allowlist-Report-Only:
5-
// (response-origin); webrtc=block; report-to=endpoint` has been set.
6+
// (response-origin); webrtc=block; report-to=endpoint` has been set. There
7+
// should be 2 reports sent, 1 for each RTCPeerConnection created in
8+
// tryConnect().
69
promise_test(async (t) => {
7-
let observer = new ReportingObserver(() => {});
8-
observer.observe();
9-
10-
try {
11-
const configuration = {};
12-
const peerConnection = new RTCPeerConnection(configuration);
13-
} catch (err) {
14-
assert_unreached(
15-
'In report-only mode, RTCPeerConnection should be created successfully.');
16-
}
17-
observer.disconnect();
10+
let local_reports = [];
11+
let report_promise = new Promise((resolve) => {
12+
let observer = new ReportingObserver((reports) => {
13+
local_reports = local_reports.concat(reports);
14+
if (local_reports.length >= 2) {
15+
observer.disconnect();
16+
resolve();
17+
}
18+
});
19+
observer.observe();
20+
})
21+
assert_equals(await tryConnect(), 'allowed');
1822

1923
// Check ReportingObserver receipt of the report.
20-
const local_reports = observer.takeRecords();
21-
assert_equals(local_reports.length, 1);
24+
await report_promise;
25+
26+
assert_equals(local_reports.length, 2);
27+
// Convert Report objects to JSON before comparing them.
28+
assert_object_equals(local_reports[0].toJSON(), local_reports[1].toJSON());
2229
assert_equals(local_reports[0]['type'], 'connection-allowlist');
2330
assert_equals(local_reports[0]['url'], location.href);
2431

@@ -35,7 +42,13 @@ promise_test(async (t) => {
3542
const id = '593e9558-bbec-4f10-9cba-ecb85906246a';
3643
await wait(5000);
3744
const remote_reports = await pollReports(endpoint, id);
38-
assert_equals(remote_reports.length, 1);
45+
assert_equals(remote_reports.length, 2);
46+
47+
// Normalize the "age" property so we can compare the rest of the reports for
48+
// equality.
49+
remote_reports[0]['age'] = 0;
50+
remote_reports[1]['age'] = 0;
51+
assert_object_equals(remote_reports[0], remote_reports[1]);
3952
assert_equals(remote_reports[0]['type'], 'connection-allowlist');
4053
assert_equals(remote_reports[0]['url'], location.href);
4154

0 commit comments

Comments
 (0)