File: navigation_policy_container_builder.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (399 lines) | stat: -rw-r--r-- 13,813 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/navigation_policy_container_builder.h"

#include <utility>

#include "content/browser/renderer_host/frame_navigation_entry.h"
#include "content/browser/renderer_host/navigation_state_keep_alive.h"
#include "content/browser/renderer_host/policy_container_host.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/navigation_handle.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "services/network/public/mojom/content_security_policy.mojom-forward.h"
#include "services/network/public/mojom/ip_address_space.mojom.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom.h"

namespace content {
namespace {

// Returns a copy of |parent|'s policies, or nullopt if |parent| is nullptr.
std::unique_ptr<PolicyContainerPolicies> GetParentPolicies(
    RenderFrameHostImpl* parent) {
  if (!parent) {
    return nullptr;
  }

  return parent->policy_container_host()->policies().ClonePtr();
}

// Returns a copy of the navigation initiator's policies, if any.
//
// Must only be called on the browser's UI thread.
std::unique_ptr<PolicyContainerPolicies> GetInitiatorPolicies(
    const blink::LocalFrameToken* frame_token,
    int initiator_process_id,
    StoragePartitionImpl* storage_partition) {
  if (!frame_token) {
    return nullptr;
  }

  PolicyContainerHost* initiator_policy_container_host =
      RenderFrameHostImpl::GetPolicyContainerHost(
          frame_token, initiator_process_id, storage_partition);

  DCHECK(initiator_policy_container_host);
  if (!initiator_policy_container_host) {
    // Guard against wrong tokens being passed accidentally.
    return nullptr;
  }

  return initiator_policy_container_host->policies().ClonePtr();
}

// Returns a copy of the given history |entry|'s policies, if any.
std::unique_ptr<PolicyContainerPolicies> GetHistoryPolicies(
    const FrameNavigationEntry* entry) {
  if (!entry) {
    return nullptr;
  }

  const PolicyContainerPolicies* policies = entry->policy_container_policies();
  if (!policies) {
    return nullptr;
  }

  return policies->ClonePtr();
}

}  // namespace

NavigationPolicyContainerBuilder::NavigationPolicyContainerBuilder(
    RenderFrameHostImpl* parent,
    const blink::LocalFrameToken* initiator_frame_token,
    int initiator_process_id,
    StoragePartition* storage_partition,
    const FrameNavigationEntry* history_entry)
    : parent_policies_(GetParentPolicies(parent)),
      initiator_policies_(GetInitiatorPolicies(
          initiator_frame_token,
          initiator_process_id,
          static_cast<StoragePartitionImpl*>(storage_partition))),
      history_policies_(GetHistoryPolicies(history_entry)) {}

NavigationPolicyContainerBuilder::~NavigationPolicyContainerBuilder() = default;

const PolicyContainerPolicies*
NavigationPolicyContainerBuilder::InitiatorPolicies() const {
  return initiator_policies_.get();
}

const PolicyContainerPolicies*
NavigationPolicyContainerBuilder::ParentPolicies() const {
  return parent_policies_.get();
}

const PolicyContainerPolicies*
NavigationPolicyContainerBuilder::HistoryPolicies() const {
  return history_policies_.get();
}

void NavigationPolicyContainerBuilder::SetIPAddressSpace(
    network::mojom::IPAddressSpace address_space) {
  DCHECK(!HasComputedPolicies());
  delivered_policies_.ip_address_space = address_space;
}

void NavigationPolicyContainerBuilder::SetIsOriginPotentiallyTrustworthy(
    bool value) {
  DCHECK(!HasComputedPolicies());
  delivered_policies_.is_web_secure_context = value;
}

void NavigationPolicyContainerBuilder::SetCrossOriginIsolationEnabledByDIP() {
  DCHECK(HasComputedPolicies());
  host_->SetCrossOriginIsolationEnabledByDIP();
}

void NavigationPolicyContainerBuilder::AddContentSecurityPolicy(
    network::mojom::ContentSecurityPolicyPtr policy) {
  DCHECK(!HasComputedPolicies());
  DCHECK(policy);

  delivered_policies_.content_security_policies.push_back(std::move(policy));
}

void NavigationPolicyContainerBuilder::AddContentSecurityPolicies(
    std::vector<network::mojom::ContentSecurityPolicyPtr> policies) {
  DCHECK(!HasComputedPolicies());

  delivered_policies_.AddContentSecurityPolicies(std::move(policies));
}

void NavigationPolicyContainerBuilder::SetCrossOriginOpenerPolicy(
    network::CrossOriginOpenerPolicy coop) {
  DCHECK(!HasComputedPolicies());

  delivered_policies_.cross_origin_opener_policy = coop;
}

void NavigationPolicyContainerBuilder::SetCrossOriginEmbedderPolicy(
    network::CrossOriginEmbedderPolicy coep) {
  DCHECK(!HasComputedPolicies());

  delivered_policies_.cross_origin_embedder_policy = coep;
}

void NavigationPolicyContainerBuilder::SetDocumentIsolationPolicy(
    const network::DocumentIsolationPolicy& dip) {
  DCHECK(!HasComputedPolicies());

  delivered_policies_.document_isolation_policy = dip;
}

void NavigationPolicyContainerBuilder::SetIntegrityPolicy(
    network::IntegrityPolicy ip) {
  DCHECK(!HasComputedPolicies());

  delivered_policies_.integrity_policy = std::move(ip);
}

void NavigationPolicyContainerBuilder::SetIntegrityPolicyReportOnly(
    network::IntegrityPolicy ip) {
  DCHECK(!HasComputedPolicies());

  delivered_policies_.integrity_policy_report_only = std::move(ip);
}

const PolicyContainerPolicies&
NavigationPolicyContainerBuilder::DeliveredPoliciesForTesting() const {
  DCHECK(!HasComputedPolicies());

  return delivered_policies_;
}

void NavigationPolicyContainerBuilder::ComputePoliciesForError() {
  // The decision to commit an error page can happen after receiving the
  // response for a regular document. It overrides any previous attempt to
  // |ComputePolicies()|.
  host_ = nullptr;

  DCHECK(!HasComputedPolicies());

  // TODO(crbug.com/40747546): We should enforce strict policies on error
  // pages.
  PolicyContainerPolicies policies;

  // We commit error pages with the same address space as the underlying page,
  // so that auto-reloading error pages does not show up as a private network
  // request (from the unknown/public address space to private). See also
  // crbug.com/1180140.
  policies.ip_address_space = delivered_policies_.ip_address_space;

  SetFinalPolicies(std::move(policies));

  DCHECK(HasComputedPolicies());
}

void NavigationPolicyContainerBuilder::ComputeIsWebSecureContext() {
  DCHECK(!HasComputedPolicies());

  if (!parent_policies_) {
    // No parent. Only the trustworthiness of the origin matters.
    return;
  }

  // The child can only be a secure context if the parent is too.
  delivered_policies_.is_web_secure_context &=
      parent_policies_->is_web_secure_context;
}

void NavigationPolicyContainerBuilder::ComputeSandboxFlags(
    bool is_inside_mhtml,
    network::mojom::WebSandboxFlags frame_sandbox_flags,
    PolicyContainerPolicies& policies) {
  DCHECK(!HasComputedPolicies());

  auto sandbox_flags_to_commit = frame_sandbox_flags;

  // The document can also restrict sandbox further, via its CSP.
  for (const auto& csp : policies.content_security_policies) {
    sandbox_flags_to_commit |= csp->sandbox;
  }

  // The URL of a document loaded from a MHTML archive is controlled by the
  // Content-Location header. This can be set to an arbitrary URL. This is
  // potentially dangerous. For this reason we force the document to be
  // sandboxed, providing exceptions only for creating new windows. This
  // includes disallowing javascript and using an opaque origin.
  if (is_inside_mhtml) {
    network::mojom::WebSandboxFlags allowed_flags =
        network::mojom::WebSandboxFlags::kPopups |
        network::mojom::WebSandboxFlags::kPropagatesToAuxiliaryBrowsingContexts;

    // Allow JS to execute in saved MHTML documents, since certain constructs
    // like custom elements, require additional JS to support. This is believed
    // to be safe because:
    // - MHTML serialization generally tries to drop script, though this is on
    //   a best-effort basis
    // - a MHTML document and all its descendant frames are sandboxed without
    //   the allow-same-origin flag, so even though an MHTML archive can claim
    //   to contain resources from arbitrary URLs, each frame will have a
    //   unique opaque origin, which should limit any potential damage.
    if (base::FeatureList::IsEnabled(blink::features::kMHTML_Improvements)) {
      allowed_flags |= network::mojom::WebSandboxFlags::kScripts;
    }
    sandbox_flags_to_commit |= ~allowed_flags;
  }

  policies.sandbox_flags = sandbox_flags_to_commit;
}

void NavigationPolicyContainerBuilder::IncorporateDeliveredPoliciesForLocalURL(
    PolicyContainerPolicies& policies) {
  // Delivered content security policies must be appended.
  policies.AddContentSecurityPolicies(
      mojo::Clone(delivered_policies_.content_security_policies));

  // The delivered IP address space (if any) overrides the IP address space.
  if (delivered_policies_.ip_address_space !=
      network::mojom::IPAddressSpace::kUnknown) {
    policies.ip_address_space = delivered_policies_.ip_address_space;
  }
}

PolicyContainerPolicies
NavigationPolicyContainerBuilder::ComputeInheritedPolicies(const GURL& url) {
  DCHECK(url.SchemeIsLocal()) << url << " should not inherit policies";

  if (url.IsAboutSrcdoc()) {
    DCHECK(parent_policies_)
        << "About:srcdoc documents should always have a parent frame.";
    return parent_policies_->Clone();
  }

  if (initiator_policies_) {
    return initiator_policies_->Clone();
  }

  return PolicyContainerPolicies();
}

PolicyContainerPolicies NavigationPolicyContainerBuilder::ComputeFinalPolicies(
    NavigationHandle* navigation_handle,
    bool is_inside_mhtml,
    network::mojom::WebSandboxFlags frame_sandbox_flags,
    bool is_credentialless) {
  PolicyContainerPolicies policies;

  // Policies are either inherited from another document for local scheme, or
  // directly set from the delivered response.
  const GURL& url = navigation_handle->GetURL();
  if (!url.SchemeIsLocal()) {
    policies = delivered_policies_.Clone();
  } else if (history_policies_) {
    // For a local scheme, history policies should not incorporate delivered
    // ones as this may lead to duplication of some policies already stored in
    // history. For example, consider the following HTML:
    //    <iframe src="about:blank" csp="something">
    // This will store CSP: something in history. The next time we have a
    // history navigation we will have CSP: something twice.
    policies = history_policies_->Clone();
  } else {
    policies = ComputeInheritedPolicies(url);
    IncorporateDeliveredPoliciesForLocalURL(policies);

    // TODO(crbug.com/40053796): Persist the policy container for URLs with
    // local schemes so this override is not needed.
    std::optional<network::CrossOriginEmbedderPolicy>
        override_cross_origin_embedder_policy =
            GetContentClient()
                ->browser()
                ->MaybeOverrideLocalURLCrossOriginEmbedderPolicy(
                    navigation_handle);
    if (override_cross_origin_embedder_policy) {
      policies.cross_origin_embedder_policy =
          override_cross_origin_embedder_policy.value();
    }
  }

  // `can_navigate_top_without_user_gesture` is inherited from the parent.
  // Later in `NavigationRequest::CommitNavigation()` it will either be made
  // less strict for same-origin navigations, or stricter for cross-origin
  // navigations that do not explicitly allow top-level navigation without user
  // gesture.
  policies.can_navigate_top_without_user_gesture =
      parent_policies_ ? parent_policies_->can_navigate_top_without_user_gesture
                       : true;

  ComputeSandboxFlags(is_inside_mhtml, frame_sandbox_flags, policies);
  policies.is_credentialless = is_credentialless;
  return policies;
}

void NavigationPolicyContainerBuilder::ComputePolicies(
    NavigationHandle* navigation_handle,
    bool is_inside_mhtml,
    network::mojom::WebSandboxFlags frame_sandbox_flags,
    bool is_credentialless) {
  DCHECK(!HasComputedPolicies());
  ComputeIsWebSecureContext();
  SetFinalPolicies(ComputeFinalPolicies(navigation_handle, is_inside_mhtml,
                                        frame_sandbox_flags,
                                        is_credentialless));
}

bool NavigationPolicyContainerBuilder::HasComputedPolicies() const {
  return host_ != nullptr;
}

void NavigationPolicyContainerBuilder::SetAllowTopNavigationWithoutUserGesture(
    bool allow_top) {
  host_->SetCanNavigateTopWithoutUserGesture(allow_top);
}

void NavigationPolicyContainerBuilder::SetFinalPolicies(
    PolicyContainerPolicies policies) {
  DCHECK(!HasComputedPolicies());

  host_ = base::MakeRefCounted<PolicyContainerHost>(std::move(policies));
}

const PolicyContainerPolicies& NavigationPolicyContainerBuilder::FinalPolicies()
    const {
  DCHECK(HasComputedPolicies());

  return host_->policies();
}

blink::mojom::PolicyContainerPtr
NavigationPolicyContainerBuilder::CreatePolicyContainerForBlink() {
  DCHECK(HasComputedPolicies());

  return host_->CreatePolicyContainerForBlink();
}

scoped_refptr<PolicyContainerHost>
NavigationPolicyContainerBuilder::GetPolicyContainerHost() {
  DCHECK(HasComputedPolicies());
  CHECK(host_);

  return host_;
}

scoped_refptr<PolicyContainerHost>
NavigationPolicyContainerBuilder::TakePolicyContainerHost() && {
  DCHECK(HasComputedPolicies());

  return std::move(host_);
}

void NavigationPolicyContainerBuilder::ResetForCrossDocumentRestart() {
  host_ = nullptr;
  delivered_policies_ = PolicyContainerPolicies();
}

}  // namespace content