mirror of
https://github.com/servo/servo.git
synced 2025-08-02 20:20:14 +01:00
Support CSP report-only header (#36623)
This turned out to be a full rabbit hole. The new header is parsed in the new `parse_csp_list_from_metadata` which sets `disposition` to `report. I was testing this with `script-src-report-only-policy-works-with-external-hash-policy.html` which was blocking the script incorrectly. Turns out that there were multiple bugs in the CSP library, as well as a missing check in `fetch` to report violations. Additionally, in several locations we were manually reporting csp violations, instead of the new `global.report_csp_violations`. As a result of that, they would double report, since the report-only header would be appended as a policy and now would report twice. Now, all callsides use `global.report_csp_violations`. As a nice side-effect, I added the code to set source file information, since that was already present for the `eval` check, but nowhere else. Part of #36437 Requires servo/rust-content-security-policy#5 --------- Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com> Signed-off-by: Tim van der Lippe <TimvdLippe@users.noreply.github.com>
This commit is contained in:
parent
4ff45f86b9
commit
baa18e18af
23 changed files with 113 additions and 208 deletions
|
@ -2422,7 +2422,8 @@ impl GlobalScope {
|
|||
headers: &Option<Serde<HeaderMap>>,
|
||||
) -> Option<CspList> {
|
||||
// TODO: Implement step 1 (local scheme special case)
|
||||
let mut csp = headers.as_ref()?.get_all("content-security-policy").iter();
|
||||
let headers = headers.as_ref()?;
|
||||
let mut csp = headers.get_all("content-security-policy").iter();
|
||||
// This silently ignores the CSP if it contains invalid Unicode.
|
||||
// We should probably report an error somewhere.
|
||||
let c = csp.next().and_then(|c| c.to_str().ok())?;
|
||||
|
@ -2435,6 +2436,19 @@ impl GlobalScope {
|
|||
PolicyDisposition::Enforce,
|
||||
));
|
||||
}
|
||||
let csp_report = headers
|
||||
.get_all("content-security-policy-report-only")
|
||||
.iter();
|
||||
// This silently ignores the CSP if it contains invalid Unicode.
|
||||
// We should probably report an error somewhere.
|
||||
for c in csp_report {
|
||||
let c = c.to_str().ok()?;
|
||||
csp_list.append(CspList::parse(
|
||||
c,
|
||||
PolicySource::Header,
|
||||
PolicyDisposition::Report,
|
||||
));
|
||||
}
|
||||
Some(csp_list)
|
||||
}
|
||||
|
||||
|
@ -2822,36 +2836,16 @@ impl GlobalScope {
|
|||
}))
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub(crate) fn is_js_evaluation_allowed(&self, cx: SafeJSContext) -> bool {
|
||||
pub(crate) fn is_js_evaluation_allowed(&self, source: &str) -> bool {
|
||||
let Some(csp_list) = self.get_csp_list() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default();
|
||||
let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed;
|
||||
let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source);
|
||||
|
||||
if !is_js_evaluation_allowed {
|
||||
// FIXME: Don't fire event if `script-src` and `default-src`
|
||||
// were not passed.
|
||||
for policy in csp_list.0 {
|
||||
let report = CSPViolationReportBuilder::default()
|
||||
.resource("eval".to_owned())
|
||||
.effective_directive("script-src".to_owned())
|
||||
.report_only(policy.disposition == PolicyDisposition::Report)
|
||||
.source_file(scripted_caller.filename.clone())
|
||||
.line_number(scripted_caller.line)
|
||||
.column_number(scripted_caller.col)
|
||||
.build(self);
|
||||
let task = CSPViolationReportTask::new(self, report);
|
||||
self.report_csp_violations(violations);
|
||||
|
||||
self.task_manager()
|
||||
.dom_manipulation_task_source()
|
||||
.queue(task);
|
||||
}
|
||||
}
|
||||
|
||||
is_js_evaluation_allowed
|
||||
is_js_evaluation_allowed == CheckResult::Allowed
|
||||
}
|
||||
|
||||
pub(crate) fn create_image_bitmap(
|
||||
|
@ -3464,10 +3458,13 @@ impl GlobalScope {
|
|||
unreachable!();
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
|
||||
let scripted_caller =
|
||||
unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default();
|
||||
for violation in violations {
|
||||
let (sample, resource) = match violation.resource {
|
||||
ViolationResource::Inline { .. } => (None, "inline".to_owned()),
|
||||
ViolationResource::Inline { sample } => (sample, "inline".to_owned()),
|
||||
ViolationResource::Url(url) => (None, url.into()),
|
||||
ViolationResource::TrustedTypePolicy { sample } => {
|
||||
(Some(sample), "trusted-types-policy".to_owned())
|
||||
|
@ -3475,6 +3472,8 @@ impl GlobalScope {
|
|||
ViolationResource::TrustedTypeSink { sample } => {
|
||||
(Some(sample), "trusted-types-sink".to_owned())
|
||||
},
|
||||
ViolationResource::Eval { sample } => (sample, "eval".to_owned()),
|
||||
ViolationResource::WasmEval => (None, "wasm-eval".to_owned()),
|
||||
};
|
||||
let report = CSPViolationReportBuilder::default()
|
||||
.resource(resource)
|
||||
|
@ -3482,6 +3481,9 @@ impl GlobalScope {
|
|||
.effective_directive(violation.directive.name)
|
||||
.original_policy(violation.policy.to_string())
|
||||
.report_only(violation.policy.disposition == PolicyDisposition::Report)
|
||||
.source_file(scripted_caller.filename.clone())
|
||||
.line_number(scripted_caller.line)
|
||||
.column_number(scripted_caller.col + 1)
|
||||
.build(self);
|
||||
let task = CSPViolationReportTask::new(self, report);
|
||||
self.task_manager()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue