content_script_injection_url_getter.cc 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "extensions/common/content_script_injection_url_getter.h"
  5. #include "base/containers/contains.h"
  6. #include "base/containers/flat_set.h"
  7. #include "base/notreached.h"
  8. #include "base/trace_event/typed_macros.h"
  9. #include "url/scheme_host_port.h"
  10. namespace extensions {
  11. ContentScriptInjectionUrlGetter::FrameAdapter::~FrameAdapter() = default;
  12. // static
  13. GURL ContentScriptInjectionUrlGetter::Get(
  14. const FrameAdapter& frame,
  15. const GURL& document_url,
  16. MatchOriginAsFallbackBehavior match_origin_as_fallback,
  17. bool allow_inaccessible_parents) {
  18. // The following schemes are considered for opaque origins if the
  19. // `match_origin_as_fallback` behavior is to always match.
  20. // NOTE(devlin): This isn't an exhaustive list of schemes: some schemes may
  21. // be missing, or more schemes may be added in the future. Would it make
  22. // sense to turn this into a blocklist? Just doing this for all opaque
  23. // schemes *should* be safe, since We still have a permission check against
  24. // the precursor origin. This would only be a problem if an
  25. // extension-accessible precursor origin can create an opaque-origin frame
  26. // that *shouldn't* be accessible.
  27. static const char* const kAllowedSchemesToMatchOriginAsFallback[] = {
  28. url::kAboutScheme,
  29. url::kBlobScheme,
  30. url::kDataScheme,
  31. url::kFileSystemScheme,
  32. };
  33. // TODO(https://crbug.com/1212918): Consider reducing tracing instrumentation
  34. // in the main function bodu and in the lambda below (once the bug is
  35. // understood and fixed).
  36. auto should_consider_origin = [&document_url, match_origin_as_fallback]() {
  37. bool result = false;
  38. switch (match_origin_as_fallback) {
  39. case MatchOriginAsFallbackBehavior::kNever: {
  40. TRACE_EVENT_INSTANT("extensions",
  41. "ContentScriptInjectionUrlGetter::Get/"
  42. "should_consider_origin: origin-never");
  43. result = false;
  44. break;
  45. }
  46. case MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree: {
  47. TRACE_EVENT_INSTANT("extensions",
  48. "ContentScriptInjectionUrlGetter::Get/"
  49. "should_consider_origin: origin-climb");
  50. result = document_url.SchemeIs(url::kAboutScheme);
  51. break;
  52. }
  53. case MatchOriginAsFallbackBehavior::kAlways: {
  54. TRACE_EVENT_INSTANT("extensions",
  55. "ContentScriptInjectionUrlGetter::Get/"
  56. "should_consider_origin: origin-always");
  57. result = base::Contains(kAllowedSchemesToMatchOriginAsFallback,
  58. document_url.scheme());
  59. break;
  60. }
  61. }
  62. if (result) {
  63. TRACE_EVENT_INSTANT("extensions",
  64. "ContentScriptInjectionUrlGetter::Get/"
  65. "should_consider_origin=true");
  66. } else {
  67. TRACE_EVENT_INSTANT("extensions",
  68. "ContentScriptInjectionUrlGetter::Get/"
  69. "should_consider_origin=false");
  70. }
  71. return result;
  72. };
  73. // If we don't need to consider the origin, we're done.
  74. if (!should_consider_origin()) {
  75. TRACE_EVENT_INSTANT(
  76. "extensions", "ContentScriptInjectionUrlGetter::Get/!consider-origin");
  77. return document_url;
  78. }
  79. // Get the security origin for the `frame`. For about: frames, this is the
  80. // origin of that of the controlling frame - e.g., an about:blank frame on
  81. // https://example.com will have the security origin of https://example.com.
  82. // Other frames, like data: frames, will have an opaque origin. For these,
  83. // we can get the precursor origin.
  84. const url::Origin frame_origin = frame.GetOrigin();
  85. const url::SchemeHostPort& tuple_or_precursor_tuple =
  86. frame_origin.GetTupleOrPrecursorTupleIfOpaque();
  87. // When there's no valid tuple (which can happen in the case of e.g. a
  88. // browser-initiated navigation to an opaque URL), there's no origin to
  89. // fallback to. Bail.
  90. if (!tuple_or_precursor_tuple.IsValid()) {
  91. TRACE_EVENT_INSTANT("extensions",
  92. "ContentScriptInjectionUrlGetter::Get/invalid-tuple");
  93. return document_url;
  94. }
  95. const url::Origin origin_or_precursor_origin =
  96. url::Origin::Create(tuple_or_precursor_tuple.GetURL());
  97. if (!allow_inaccessible_parents &&
  98. !frame.CanAccess(origin_or_precursor_origin)) {
  99. // The `frame` can't access its precursor. Bail.
  100. TRACE_EVENT_INSTANT(
  101. "extensions",
  102. "ContentScriptInjectionUrlGetter::Get/no-precursor-access");
  103. return document_url;
  104. }
  105. // Note: Just because the frame origin can theoretically access its
  106. // precursor origin, there may be more restrictions in practice - such as
  107. // if the frame has the disallowdocumentaccess attribute. It's okay to
  108. // ignore this case for context classification because it's not meant as an
  109. // origin boundary (unlike e.g. a sandboxed frame).
  110. // Looks like the initiator origin is an appropriate fallback!
  111. if (match_origin_as_fallback == MatchOriginAsFallbackBehavior::kAlways) {
  112. // The easy case! We use the origin directly. We're done.
  113. TRACE_EVENT_INSTANT(
  114. "extensions",
  115. "ContentScriptInjectionUrlGetter::Get/origin-or-precursor");
  116. return origin_or_precursor_origin.GetURL();
  117. }
  118. DCHECK_EQ(MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree,
  119. match_origin_as_fallback);
  120. // Unfortunately, in this case, we have to climb the frame tree. This is for
  121. // match patterns that are associated with paths as well, not just origins.
  122. // For instance, if an extension wants to run on google.com/maps/* with
  123. // match_about_blank true, then it should run on about:-scheme frames created
  124. // by google.com/maps, but not about:-scheme frames created by google.com
  125. // (which is what the precursor tuple origin would be).
  126. // Traverse the frame/window hierarchy to find the closest non-about:-page
  127. // with the same origin as the precursor and return its URL.
  128. // TODO(https://crbug.com/1186321): This can return the incorrect result, e.g.
  129. // if a parent frame navigates a grandchild frame to about:blank.
  130. std::unique_ptr<FrameAdapter> parent = frame.Clone();
  131. GURL parent_url;
  132. base::flat_set<uintptr_t> already_visited_frame_ids;
  133. do {
  134. already_visited_frame_ids.insert(parent->GetId());
  135. parent = parent->GetLocalParentOrOpener();
  136. // We reached the end of the ancestral chain without finding a valid parent,
  137. // or found a remote web frame (in which case, it's a different origin).
  138. // Bail and use the original URL.
  139. if (!parent) {
  140. TRACE_EVENT_INSTANT(
  141. "extensions", "ContentScriptInjectionUrlGetter::Get/no-more-parents");
  142. return document_url;
  143. }
  144. // Avoid an infinite loop - see https://crbug.com/568432 and
  145. // https://crbug.com/883526.
  146. if (base::Contains(already_visited_frame_ids, parent->GetId())) {
  147. TRACE_EVENT_INSTANT("extensions",
  148. "ContentScriptInjectionUrlGetter::Get/infinite-loop");
  149. return document_url;
  150. }
  151. url::SchemeHostPort parent_tuple_or_precursor_tuple =
  152. url::Origin(parent->GetOrigin()).GetTupleOrPrecursorTupleIfOpaque();
  153. if (!parent_tuple_or_precursor_tuple.IsValid() ||
  154. parent_tuple_or_precursor_tuple != tuple_or_precursor_tuple) {
  155. // The parent has a different tuple origin than frame; this could happen
  156. // in edge cases where a parent navigates an iframe or popup of a child
  157. // frame at a different origin. [1] In this case, bail, since we can't
  158. // find a full URL (i.e., one including the path) with the same security
  159. // origin to use for the frame in question.
  160. // [1] Consider a frame tree like:
  161. // <html> <!--example.com-->
  162. // <iframe id="a" src="a.com">
  163. // <iframe id="b" src="b.com"></iframe>
  164. // </iframe>
  165. // </html>
  166. // Frame "a" is cross-origin from the top-level frame, and so the
  167. // example.com top-level frame can't directly access frame "b". However,
  168. // it can navigate it through
  169. // window.frames[0].frames[0].location.href = 'about:blank';
  170. // In that case, the precursor origin tuple origin of frame "b" would be
  171. // example.com, but the parent tuple origin is a.com.
  172. // Note that usually, this would have bailed earlier with a remote frame,
  173. // but it may not if we're at the process limit.
  174. TRACE_EVENT_INSTANT("extensions",
  175. "ContentScriptInjectionUrlGetter::Get/tuple-diff");
  176. return document_url;
  177. }
  178. // If we don't allow inaccessible parents, the security origin may still
  179. // be restricted if the author has prevented same-origin access via the
  180. // disallowdocumentaccess attribute on iframe.
  181. if (!allow_inaccessible_parents && !frame.CanAccess(*parent)) {
  182. // The frame can't access its precursor. Bail.
  183. TRACE_EVENT_INSTANT(
  184. "extensions",
  185. "ContentScriptInjectionUrlGetter::Get/no-parent-access");
  186. return document_url;
  187. }
  188. parent_url = parent->GetUrl();
  189. } while (parent_url.SchemeIs(url::kAboutScheme));
  190. DCHECK(!parent_url.is_empty());
  191. // We should know that the frame can access the parent document (unless we
  192. // explicitly allow it not to), since it has the same tuple origin as the
  193. // frame, and we checked the frame access above.
  194. TRACE_EVENT_INSTANT("extensions",
  195. "ContentScriptInjectionUrlGetter::Get/parent-url");
  196. DCHECK(allow_inaccessible_parents || frame.CanAccess(parent->GetOrigin()));
  197. return parent_url;
  198. }
  199. } // namespace extensions