| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/url_loader_factory_manager.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/containers/flat_set.h" |
| #include "base/feature_list.h" |
| #include "base/hash/sha1.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/no_destructor.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_split.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/process_map.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/cors_util.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_set.h" |
| #include "extensions/common/manifest_handlers/content_scripts_handler.h" |
| #include "extensions/common/switches.h" |
| #include "extensions/common/user_script.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/url_loader_factory.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "url/scheme_host_port.h" |
| #include "url/url_constants.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| bool ShouldAllowlistAlsoApplyToOorCors() { |
| return base::FeatureList::IsEnabled( |
| network::features::kCorbAllowlistAlsoAppliesToOorCors); |
| } |
| |
| enum class FactoryUser { |
| kContentScript, |
| kExtensionProcess, |
| }; |
| |
| // The allowlist contains HashedExtensionId of extensions discovered via |
| // Extensions.CrossOriginFetchFromContentScript2 Rappor data. When the data was |
| // gathered, these extensions relied on making cross-origin requests from |
| // content scripts (which requires relaxing CORB and CORS). Going forward, |
| // these extensions should migrate to making those requests from elsewhere (e.g. |
| // from a background page, or in the future from extension service workers) at |
| // which point they can be removed from the allowlist. |
| // |
| // Migration plan for extension developers is described at |
| // https://chromium.org/Home/chromium-security/extension-content-script-fetches |
| const char* kHardcodedPartOfAllowlist[] = { |
| "039F93DD1DF836F1D4E2084C1BEFDB46A854A9D1", |
| "03E5D80A49C309F7B55ED6BD2B0EDEB38021ED4E", |
| "072D729E856B1F2C9894AEEC3A5DF65E519D6BEE", |
| "07333481B7B8D7F57A9BA64FB98CF86EA87455FC", |
| "086E69ED9071DCB20C93A081A68360963AB09385", |
| "0872D01871BC7F7428E075D656874AD43580B575", |
| "09386608C84745F0A05AC8C97165D8D76AC83771", |
| "0A8E468BCEAA5626207AB77C431C31C1F2A8F76A", |
| "0C011D916B15E5451E1B84BD14397B8EC98F455B", |
| "0CB16BAEE070B7617E9188B387C44964FB705D79", |
| "0D6C5E12B5D03639257D4C83AE1C27ECF1419C98", |
| "0EAEA2FDEE025D95B3ABB37014EFF5A98AC4BEAE", |
| "0FCD1282065485458E630683F098F591B24C406D", |
| "109A37B889E7C8AEA7B0103559C3EB6AF73B7832", |
| "11AF49711EE340C366CC820C98E3CA2AD7D7DE07", |
| "16A81AEA09A67B03F7AEA5B957D24A4095E764BE", |
| "1AB9CC404876117F49135E67BAD813F935AAE9BA", |
| "1B9251EF3EDD5A2B2872B168406F36FB18C72F37", |
| "1CDDF7436E5F891E1D5E37164F7EB992AECA0E2D", |
| "1DB115A4344B58C0B7CC1595662E86C9E92C0848", |
| "1DF1ED7172DC5B9FF337909ED5C32DED74084825", |
| "1E37F1A19C1C528E616637B105CFC4838ECF52B4", |
| "24B24CBA3D4B395503F9B0D6286E55E0FDDE4D69", |
| "260871EABEDE6F2D07D0D9450096093EDAFCBF34", |
| "28A9EDFD65BC27B5048E516AA7165339F5ACBB30", |
| "2A661509BCE9F8384B00CFC96807597D71DFE94C", |
| "2AA94E2D3F4DA33F0D3BCF5DD48F69B8BDB26F52", |
| "2E2D8A405430172AB15ADCC09740F3EEE990D605", |
| "30E95BD31B11118CE488EB4FC5FF7135E0C59425", |
| "31E6100DC7B4EAB4ABF6CA2A4E191D3945D3C731", |
| "3230014EA01150A27C1887B700E019B27A6DBA08", |
| "34FB670464B5F16EF5ABD6CD53E26030E97C26B3", |
| "360D8A88545D0C21CBDE2B55EA9E4E4285282B4C", |
| "3787567233ED6BACC4FC05387BE30E03434CE4CC", |
| "37AC33A3A46D271CCE57DD6CB3FACE6B01F5A347", |
| "38B4D1CC339580F506BC86D4027A49721AFB4BB9", |
| "395D84F94DA287C0E4DBAF9ACE478B9710C0029F", |
| "3A2E664CA697C622EA4CFA40373B3E641C01713B", |
| "3BC834B48C2C13765147FBAD710F792F026378D8", |
| "3CD98763C80D86E00CB1C4CAA56CEA8F3B0BA4F1", |
| "3EB17C39F8B6B28FAF34E2406E57A76013A2E066", |
| "3FDD3DB17F3B686F5A05204700ABA13DF20AE957", |
| "41536C3554CD9458EB2F05F1B58CF84BB7BF83BC", |
| "42C96DF87C997828C185DAD247F469940DAFC2CD", |
| "43865F8D555C201FE452A5A40702EA96240E749C", |
| "44943FADD66932EF56EE3D856A9FAAD4A8AF0FD9", |
| "4633828B42F673E0E62475059526DC2C24091690", |
| "47ADBB376050C083FFC54CC28CD3D1F54BF0BFED", |
| "4884C48FB64665BD86509BA1F2D22D75D61ACBF9", |
| "4913450195D177430217CAB64B356DC6F6B0F483", |
| "49FFDF2212E50090963E33159DBF853B5C475340", |
| "4A15AD9F12FB35BEEA6FF1807AC782A1DF979387", |
| "4A4E0B370B65C9EC65C8A615B1B9FB55A1B3E1FD", |
| "4A8EEEB4754A3E87C3A177CB31474A30967CB3C8", |
| "4E4167EDA0CFF22F261C0655E979B9474BF67C04", |
| "5053323D1F7B6EEC97A77A350DB6D0D8E51CD0AC", |
| "505F2C1E723731B2C8C9182FEAA73D00525B2048", |
| "505F3697087BFD3F290F42D029CE67F1C793B2DA", |
| "50DDD8734521B61564FCE273F8E60547F88BBCBE", |
| "52865B2087D0ABCD195A83DFD4BD041A3B4EBC34", |
| "52C94AC7680C3A03CCB6EA31445DD42BD0D5CA8E", |
| "537D26CC4EC6819A6CAAE8B8B5957F5ECFA7A44B", |
| "5741A650C3AAA935154C57681310805B5FC121C3", |
| "58BCF05A42C8ECED4E6D76F51E2E1A64AC4F7E7C", |
| "5B6DC4EEFEDF06CB2BF439E4607A5BAF6E45CCA1", |
| "5E052881B4847F68CFC8ED1A00C341FC14009C1E", |
| "5F0C47BC039BEDC1B29B68918F75370C292076A6", |
| "61E581B10D83C0AEF8366BB64C18C6615884B3D2", |
| "628FDD7CB49D46CF545AB9D46B3188802104F973", |
| "6357533CAFFB94A9EA5268ED110079E15561E469", |
| "65C20C06ED10E6F39EED527AC736D87B0390DE70", |
| "67528F9B47BE454EC46809C33D24F2C199BE408D", |
| "68F43E671577CF03AE901A5780DC07879331A3A9", |
| "6A113D4E2F96997D9BA4B391B90ED51058B37EFF", |
| "6AE81EF3B13B15080A2DDB23A205A24C65CCC10B", |
| "6D3A671DABC1F87910A1AA67EE85C02095D35624", |
| "6E49449D56D031B87835CC767734AF5A064E1A13", |
| "6FD56FE5B3831B17ED5301EE2EF949CEE5BFA871", |
| "71351EAA5C16350EC5A86C23D7A288317309E53D", |
| "71CB78C3334D5122E7F23C8525AD24100CDE7D4A", |
| "71EE66C0F71CD89BEE340F8568A44101D4C3A9A7", |
| "7246C8B523C1023D028327EA0D228787A8F72C97", |
| "7527942941BFF13D66B46E7A2A56FDBA873FB9E6", |
| "77D83E0A4157A0E77B51AD60BAB69A346CD4FEA3", |
| "7879DB88205D880B64D55E51B9726E1D12F7261F", |
| "7B179178FD3246EF29212093F1025A11C0F127ED", |
| "7BFE588B209A15260DE12777B4BBB738DE98FE6C", |
| "7C9DEE7EABBF6C722DC7C1B86460F0507E5AA561", |
| "808FA9BB3CD501D7801D1CD6D5A3DBA088FDD46F", |
| "81FD24AF95679B900370DB857CE2EACADBE50A9B", |
| "82FDBBF79F3517C3946BD89EAAF90C46DFDA4681", |
| "83431421F759AE7A3BDAC00A4959D13095C65805", |
| "834BD6E8E9F59D388DBB264453EB08A5DE45ED03", |
| "83B6C75264D5D2F81FDEFD681EDD2076DD8F0B9B", |
| "88C372CE52E21560C17BFD52556E60D694E12CAC", |
| "88F5F459139892C0F5DF3022676726BB3F01FB5C", |
| "896C60AC2F7F03B4F582027DD2107F3BB67DF69D", |
| "89C9B32115F19A18E9BE4906DC59F24A934CB9F0", |
| "89F40D84C0C72C6B02B320716E877FB1671218E9", |
| "8A0634388BCBB6D073E1C97B14C024396ED32D12", |
| "8CDD303D6C341D9CAB16713C3CD7587B90A7A84A", |
| "8CE6227B4E53DF42FF93B24F49D15EDE31E97E79", |
| "905C225238FB337834F193DF48550D721445B438", |
| "9233B7F0B98FCC181ED43AFAF58056C4EDCED162", |
| "92F2B155490417F0797849C81292E3986EBE6811", |
| "934B8F5753A3E5A276FC7D0EE5E575B335A0CC76", |
| "93934B0B87347437699EB62A8921F59F40C36D7A", |
| "93BBF911E8871F6FCC8170448FD2DF5B9EF233E5", |
| "95E78675D2DB61DC688586CD7A24202A260907A4", |
| "973E35633030AD27DABEC99609424A61386C7309", |
| "9784343657207FE88A629E8EAA7A4A19C7C8CBE0", |
| "97E04C5632954E778306CAC40B3F95C470B463B6", |
| "999BD8D1929F9ABB817E9368480D93BAB2A0983D", |
| "99E06C364BBB2D1F82A9D20BC1645BF21E478259", |
| "9C6A186F8D3C5FD0CC8DCF49682FA726BD8A7705", |
| "A04F08A772F1C83B7A14ED29788ACA4F000BBE05", |
| "A059797AECB77D24DEB248C3413D99B0D3BF9A8C", |
| "A07DA0EDB967D027E3B220208AD085FDC44C3231", |
| "A3660FA31A0DBF07C9F80D5342FF215DBC962719", |
| "A42FE007E0651EAC159EE1B393586AFFFE065DC2", |
| "A6057397EDC4E6CF25BC3A142F866ACC653B1CF1", |
| "A733063124AC9E1E6E1E331FFBAAE32D81AC2581", |
| "A8FB3967ADE404B77AC3FB5815A399C0660C3C63", |
| "A9A4B26C2387BA2A5861C790B0FF39F230427AC8", |
| "AA3DE48E23B2465B21F5D33E993FD959F611DD10", |
| "ADD14F4517B9A87D4E841369417E5BDB5FDFF263", |
| "AE063CF9FF5D718AD6F1CF242FABAC39B57ADEBA", |
| "AEEDAC793F184240CFB800DA73EE6321E5145102", |
| "AF0965B74237AFF383C981C05178732C9A05A140", |
| "B3CF6C01796E8D03378FAA77AF507E27BB847E9D", |
| "B4782AE831D849EFCC2AF4BE2012816EDDF8D908", |
| "B6903E9A5A8CC5D74A688DA0A67AFA2B0944F605", |
| "BF5224FB246A6B67EA986EFF77A43F6C1BCA9672", |
| "C0A30989F3717CE5B1B2FE462797951EA6D3922A", |
| "C4A81852B9ACE6CE02DAB58BB77BDA0AD75716EC", |
| "C5539F4EBECABA792CC40D03A56144AAD3BF9D19", |
| "C86D546CA47034163C12DC2C912910C3A12C3B07", |
| "C940F83135D9612865F4A44391DDDFE3B7BE1393", |
| "CA89BD35059845F2DB4B4398FD339B9F210E9337", |
| "CC32A0FD1D88B403308EACBE4DE3CA5AC54B93EB", |
| "CC93FDEE1B0440FAD87F17E287C606205B87E6AD", |
| "CD8AF9C47DDE6327F8D9A3EFA81F34C6B6C26EBB", |
| "CF40F6289951CBFA3B83B792EFA774E2EA06E4C0", |
| "D347F78F32567E90BC32D9C16B085254EA269590", |
| "D572BE31227F6D0BE95B9430BE2D5F21D7D9CF9A", |
| "D7C3879A8898618E3A23B0E6BFB6A38D01606246", |
| "DC39837AC518B832FCB2D2DC1CE8BA148F54758E", |
| "DC88B4C9E547F3E321B3E64CCDBD4B698116D2F4", |
| "DDA21167F058A65D878DF84C3CF3FCC60B053E80", |
| "E134BC4A0FF6C59CE42CC76BA6B2D6F5DC648EC4", |
| "E14510DB95CB9E60B4C8CB10B0AC9DE837B5D7D7", |
| "E178D4F4D6617C0B880C36F192DA3B18422C5064", |
| "E61DC62BF2F1D7CAEEF93E84A5EE5F6D2CFDBC79", |
| "E6B12430B6166B31BE20E13941C22569EA75B0F2", |
| "E7036E906DBFB77C46EDDEB003A72C0B5CC9BE7F", |
| "E8309B92AACFEB64D39F5466EF3BDB6B63A7248B", |
| "E873675B8E754F1B1F1222CB921EA14B4335179D", |
| "EAD0BFA4ED66D9A2112A4C9A0AA25329FF980AC6", |
| "EC24668224116D19FF1A5FFAA61238B88773982C", |
| "EC4A841BD03C8E5202043165188A9E060BF703A3", |
| "EE4BE5F23D2E59E4713958465941EFB4A18166B7", |
| "EE711E704D4A365C4644EE4637076C81DF454EA6", |
| "F1ACA279F460440E47078D91FE372212DD9B8709", |
| "F273C23C616F5C56E8EDBAE24B21F5D408936A0D", |
| "F566B33D62CE21415AF5B3F3FD8762B7454B8874", |
| "F59AB261280AB3AE9826D9359507838B90B07431", |
| "F608282162AD48CE45D5BC2F6F467B56E88EBFA4", |
| "F73F9EF0207603992CA3C00A7A0CB223D5571B3F", |
| "F9287A33E15038F2591F23E6E9C486717C7202DD", |
| "FCC2DC6574A3CA28ED77195926C67F612292C5C3", |
| "FEE3DC8C722657A4A5B0F72CA48CF950DC956148", |
| "FF0DA4BD87A88469B10709B99E79D4B0E11C0CA6", |
| "FF8257C73304BA655E10F324C962504BA6691DF2", |
| }; |
| |
| constexpr size_t kHashedExtensionIdLength = base::kSHA1Length * 2; |
| bool IsValidHashedExtensionId(const std::string& hash) { |
| bool correct_chars = std::all_of(hash.begin(), hash.end(), [](char c) { |
| return ('A' <= c && c <= 'F') || ('0' <= c && c <= '9'); |
| }); |
| bool correct_length = (kHashedExtensionIdLength == hash.length()); |
| return correct_chars && correct_length; |
| } |
| |
| std::vector<std::string> CreateExtensionAllowlist() { |
| std::vector<std::string> allowlist; |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kForceEmptyCorbAllowlist)) { |
| return allowlist; |
| } |
| |
| // Append extensions from the hardcoded allowlist. |
| allowlist.reserve(base::size(kHardcodedPartOfAllowlist)); |
| for (const char* hash : kHardcodedPartOfAllowlist) { |
| DCHECK(IsValidHashedExtensionId(hash)); // It also validates the length. |
| allowlist.push_back(std::string(hash, kHashedExtensionIdLength)); |
| } |
| |
| // Append extensions from the field trial param. |
| std::string field_trial_arg = base::GetFieldTrialParamValueByFeature( |
| network::features::kCorbAllowlistAlsoAppliesToOorCors, |
| network::features::kCorbAllowlistAlsoAppliesToOorCorsParamName); |
| field_trial_arg = base::ToUpperASCII(field_trial_arg); |
| std::vector<std::string> field_trial_allowlist = base::SplitString( |
| field_trial_arg, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| base::EraseIf(field_trial_allowlist, [](const std::string& hash) { |
| // Filter out invalid data from |field_trial_allowlist|. |
| if (IsValidHashedExtensionId(hash)) |
| return false; // Don't remove. |
| |
| LOG(ERROR) << "Invalid extension hash: " << hash; |
| return true; // Remove. |
| }); |
| std::move(field_trial_allowlist.begin(), field_trial_allowlist.end(), |
| std::back_inserter(allowlist)); |
| |
| return allowlist; |
| } |
| |
| // Returns a set of HashedExtensionId of extensions that depend on relaxed CORB |
| // or CORS behavior in their content scripts. |
| base::flat_set<std::string>& GetExtensionsAllowlist() { |
| static base::NoDestructor<base::flat_set<std::string>> s_allowlist([] { |
| base::flat_set<std::string> result(CreateExtensionAllowlist()); |
| result.shrink_to_fit(); |
| return result; |
| }()); |
| return *s_allowlist; |
| } |
| |
| bool DoContentScriptsDependOnRelaxedCorbOrCors(const Extension& extension) { |
| // Content scripts injected by Chrome Apps (e.g. into <webview> tag) need to |
| // run with relaxed CORB. |
| if (extension.is_platform_app()) |
| return true; |
| |
| // Content scripts in manifest v2 might be allowlisted to depend on relaxed |
| // CORB and/or CORS. |
| if (extension.manifest_version() <= 2) { |
| const std::string& hash = extension.hashed_id().value(); |
| DCHECK(IsValidHashedExtensionId(hash)); |
| return base::Contains(GetExtensionsAllowlist(), hash); |
| } |
| |
| // Safe fallback for future extension manifest versions. |
| return false; |
| } |
| |
| bool DoExtensionPermissionsCoverHttpOrHttpsOrigins(const Extension& extension) { |
| // TODO(lukasza): https://crbug.com/1016904: Return false if the |extension|'s |
| // permissions do not actually cover http or https origins. For now we |
| // conservatively return true so that *all* extensions get relaxed CORS/CORB |
| // treatment. |
| return true; |
| } |
| |
| // Returns whether the default URLLoaderFactoryParams::is_corb_enabled should be |
| // overridden and changed to false. |
| bool ShouldDisableCorb(const Extension& extension, FactoryUser factory_user) { |
| if (!DoExtensionPermissionsCoverHttpOrHttpsOrigins(extension)) |
| return false; |
| |
| switch (factory_user) { |
| case FactoryUser::kContentScript: |
| return DoContentScriptsDependOnRelaxedCorbOrCors(extension); |
| case FactoryUser::kExtensionProcess: |
| return true; |
| } |
| } |
| |
| // Returns whether URLLoaderFactoryParams::ignore_isolated_world_origin should |
| // be overridden and changed to false. |
| bool ShouldInspectIsolatedWorldOrigin(const Extension& extension, |
| FactoryUser factory_user) { |
| if (!DoExtensionPermissionsCoverHttpOrHttpsOrigins(extension)) |
| return false; |
| |
| switch (factory_user) { |
| case FactoryUser::kContentScript: |
| // If |extensions_features::kCorbAllowlistAlsoAppliesToOorCors| is |
| // disabled, then go back to the legacy CORS behavior for all extensions. |
| if (!ShouldAllowlistAlsoApplyToOorCors()) |
| return true; |
| |
| // Otherwise, make an |extension|-specific decision. |
| return DoContentScriptsDependOnRelaxedCorbOrCors(extension); |
| case FactoryUser::kExtensionProcess: |
| return false; |
| } |
| } |
| |
| bool ShouldCreateSeparateFactoryForContentScripts(const Extension& extension) { |
| return ShouldDisableCorb(extension, FactoryUser::kContentScript) || |
| ShouldInspectIsolatedWorldOrigin(extension, |
| FactoryUser::kContentScript); |
| } |
| |
| void OverrideFactoryParams(const Extension& extension, |
| FactoryUser factory_user, |
| network::mojom::URLLoaderFactoryParams* params) { |
| if (ShouldDisableCorb(extension, factory_user)) { |
| // TODO(lukasza): https://crbug.com/1016904: Use more granular CORB |
| // enforcement based on the specific |extension|'s permissions reflected |
| // in the |factory_bound_access_patterns| above. |
| params->is_corb_enabled = false; |
| |
| // Setup factory bound allow list that overwrites per-profile common list to |
| // allow tab specific permissions only for this newly created factory. |
| // |
| // TODO(lukasza): Setting |factory_bound_access_patterns| together with |
| // |is_corb_enabled| seems accidental. |
| params->factory_bound_access_patterns = |
| network::mojom::CorsOriginAccessPatterns::New(); |
| params->factory_bound_access_patterns->source_origin = |
| url::Origin::Create(extension.url()); |
| params->factory_bound_access_patterns->allow_patterns = |
| CreateCorsOriginAccessAllowList( |
| extension, |
| PermissionsData::EffectiveHostPermissionsMode::kIncludeTabSpecific); |
| params->factory_bound_access_patterns->block_patterns = |
| CreateCorsOriginAccessBlockList(extension); |
| } |
| |
| if (ShouldInspectIsolatedWorldOrigin(extension, factory_user)) |
| params->ignore_isolated_world_origin = false; |
| |
| // TODO(lukasza): Do not override |unsafe_non_webby_initiator| unless |
| // DoExtensionPermissionsCoverHttpOrHttpsOrigins(extension). |
| if (factory_user == FactoryUser::kExtensionProcess) |
| params->unsafe_non_webby_initiator = true; |
| } |
| |
| void MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory( |
| content::RenderFrameHost* frame, |
| std::vector<url::Origin> request_initiators, |
| bool push_to_renderer_now) { |
| DCHECK(!request_initiators.empty()); |
| frame->MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory( |
| std::move(request_initiators), push_to_renderer_now); |
| } |
| |
| // If |match_about_blank| is true, then traverses parent/opener chain until the |
| // first non-about-scheme document and returns its url. Otherwise, simply |
| // returns |document_url|. |
| // |
| // This function approximates ScriptContext::GetEffectiveDocumentURL from the |
| // renderer side. Unlike the renderer version of this code (in |
| // ScriptContext::GetEffectiveDocumentURL) the code below doesn't consider |
| // whether security origin of |frame| can access |next_candidate|. This is |
| // okay, because our only caller (DoesContentScriptMatchNavigatingFrame) expects |
| // false positives. |
| GURL GetEffectiveDocumentURL(content::RenderFrameHost* frame, |
| const GURL& document_url, |
| bool match_about_blank) { |
| base::flat_set<content::RenderFrameHost*> already_visited_frames; |
| |
| // Common scenario. If |match_about_blank| is false (as is the case in most |
| // extensions), or if the frame is not an about:-page, just return |
| // |document_url| (supposedly the URL of the frame). |
| if (!match_about_blank || !document_url.SchemeIs(url::kAboutScheme)) |
| return document_url; |
| |
| // Non-sandboxed about:blank and about:srcdoc pages inherit their security |
| // origin from their parent frame/window. So, traverse the frame/window |
| // hierarchy to find the closest non-about:-page and return its URL. |
| content::RenderFrameHost* found_frame = frame; |
| do { |
| DCHECK(found_frame); |
| already_visited_frames.insert(found_frame); |
| |
| // The loop should only execute (and consider the parent chain) if the |
| // currently considered frame has about: scheme. |
| DCHECK(match_about_blank); |
| DCHECK( |
| ((found_frame == frame) && document_url.SchemeIs(url::kAboutScheme)) || |
| (found_frame->GetLastCommittedURL().SchemeIs(url::kAboutScheme))); |
| |
| // Attempt to find |next_candidate| - either a parent of opener of |
| // |found_frame|. |
| content::RenderFrameHost* next_candidate = found_frame->GetParent(); |
| if (!next_candidate) { |
| next_candidate = |
| content::WebContents::FromRenderFrameHost(found_frame)->GetOpener(); |
| } |
| if (!next_candidate || |
| base::Contains(already_visited_frames, next_candidate)) { |
| break; |
| } |
| |
| found_frame = next_candidate; |
| } while (found_frame->GetLastCommittedURL().SchemeIs(url::kAboutScheme)); |
| |
| if (found_frame == frame) |
| return document_url; // Not committed yet at ReadyToCommitNavigation time. |
| return found_frame->GetLastCommittedURL(); |
| } |
| |
| // If |user_script| will inject JavaScript content script into the target of |
| // |navigation|, then DoesContentScriptMatchNavigatingFrame returns true. |
| // Otherwise it may return either true or false. Note that this function |
| // ignores CSS content scripts. |
| // |
| // This function approximates a subset of checks from |
| // UserScriptSet::GetInjectionForScript (which runs in the renderer process). |
| // Unlike the renderer version, the code below doesn't consider ability to |
| // create an injection host or the results of ScriptInjector::CanExecuteOnFrame. |
| // Additionally the |effective_url| calculations are also only an approximation. |
| // This is okay, because we may return either true even if no content scripts |
| // would be injected (i.e. it is okay to create a special URLLoaderFactory when |
| // in reality the content script won't be injected and won't need the factory). |
| bool DoesContentScriptMatchNavigatingFrame( |
| const UserScript& user_script, |
| content::RenderFrameHost* navigating_frame, |
| const GURL& navigation_target) { |
| // A special URLLoaderFactory is only needed for Javascript content scripts |
| // (and is never needed for CSS-only injections). |
| if (user_script.js_scripts().empty()) |
| return false; |
| |
| GURL effective_url = GetEffectiveDocumentURL( |
| navigating_frame, navigation_target, user_script.match_about_blank()); |
| bool is_subframe = navigating_frame->GetParent(); |
| return user_script.MatchesDocument(effective_url, is_subframe); |
| } |
| |
| } // namespace |
| |
| // static |
| bool URLLoaderFactoryManager::DoContentScriptsMatchNavigatingFrame( |
| const Extension& extension, |
| content::RenderFrameHost* navigating_frame, |
| const GURL& navigation_target) { |
| const UserScriptList& list = |
| ContentScriptsInfo::GetContentScripts(&extension); |
| return std::any_of(list.begin(), list.end(), |
| [navigating_frame, navigation_target]( |
| const std::unique_ptr<UserScript>& script) { |
| return DoesContentScriptMatchNavigatingFrame( |
| *script, navigating_frame, navigation_target); |
| }); |
| } |
| |
| // static |
| void URLLoaderFactoryManager::ReadyToCommitNavigation( |
| content::NavigationHandle* navigation) { |
| content::RenderFrameHost* frame = navigation->GetRenderFrameHost(); |
| const GURL& url = navigation->GetURL(); |
| |
| std::vector<url::Origin> initiators_requiring_separate_factory; |
| const ExtensionRegistry* registry = |
| ExtensionRegistry::Get(frame->GetProcess()->GetBrowserContext()); |
| DCHECK(registry); // ReadyToCommitNavigation shouldn't run during shutdown. |
| for (const auto& it : registry->enabled_extensions()) { |
| const Extension& extension = *it; |
| if (!DoContentScriptsMatchNavigatingFrame(extension, frame, url)) |
| continue; |
| |
| if (!ShouldCreateSeparateFactoryForContentScripts(extension)) |
| continue; |
| |
| initiators_requiring_separate_factory.push_back( |
| url::Origin::Create(extension.url())); |
| } |
| |
| if (!initiators_requiring_separate_factory.empty()) { |
| // At ReadyToCommitNavigation time there is no need to trigger an explicit |
| // push of URLLoaderFactoryBundle to the renderer - it is sufficient if the |
| // factories are pushed slightly later - during the commit. |
| constexpr bool kPushToRendererNow = false; |
| |
| MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory( |
| frame, std::move(initiators_requiring_separate_factory), |
| kPushToRendererNow); |
| } |
| } |
| |
| // static |
| void URLLoaderFactoryManager::WillExecuteCode(content::RenderFrameHost* frame, |
| const HostID& host_id) { |
| if (host_id.type() != HostID::EXTENSIONS) |
| return; |
| |
| const ExtensionRegistry* registry = |
| ExtensionRegistry::Get(frame->GetProcess()->GetBrowserContext()); |
| DCHECK(registry); // WillExecuteCode shouldn't happen during shutdown. |
| const Extension* extension = |
| registry->enabled_extensions().GetByID(host_id.id()); |
| DCHECK(extension); // Guaranteed by the caller - see the doc comment. |
| |
| if (!ShouldCreateSeparateFactoryForContentScripts(*extension)) |
| return; |
| |
| // When WillExecuteCode runs, the frame already received the initial |
| // URLLoaderFactoryBundle - therefore we need to request a separate push |
| // below. This doesn't race with the ExtensionMsg_ExecuteCode message, |
| // because the URLLoaderFactoryBundle is sent to the renderer over |
| // content.mojom.FrameNavigationControl interface which is associated with the |
| // legacy IPC pipe (raciness will be introduced if that ever changes). |
| constexpr bool kPushToRendererNow = true; |
| |
| MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory( |
| frame, {url::Origin::Create(extension->url())}, kPushToRendererNow); |
| } |
| |
| // static |
| void URLLoaderFactoryManager::OverrideURLLoaderFactoryParams( |
| content::BrowserContext* browser_context, |
| const url::Origin& origin, |
| bool is_for_isolated_world, |
| network::mojom::URLLoaderFactoryParams* factory_params) { |
| const ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context); |
| DCHECK(registry); // CreateFactory shouldn't happen during shutdown. |
| |
| // Opaque origins normally don't inherit security properties of their |
| // precursor origins, but here opaque origins (e.g. think data: URIs) created |
| // by an extension should inherit CORS/CORB treatment of the extension. |
| url::SchemeHostPort precursor_origin = |
| origin.GetTupleOrPrecursorTupleIfOpaque(); |
| |
| // Don't change factory params for something that is not an extension. |
| if (precursor_origin.scheme() != kExtensionScheme) |
| return; |
| |
| // Find the |extension| associated with |initiator_origin|. |
| const Extension* extension = |
| registry->enabled_extensions().GetByID(precursor_origin.host()); |
| if (!extension) { |
| // This may happen if an extension gets disabled between the time |
| // RenderFrameHost::MarkIsolatedWorldAsRequiringSeparateURLLoaderFactory is |
| // called and the time |
| // ContentBrowserClient::OverrideURLLoaderFactoryParams is called. |
| return; |
| } |
| |
| // Identify and set |factory_params| that need to be overridden. |
| FactoryUser factory_user = is_for_isolated_world |
| ? FactoryUser::kContentScript |
| : FactoryUser::kExtensionProcess; |
| OverrideFactoryParams(*extension, factory_user, factory_params); |
| } |
| |
| // static |
| void URLLoaderFactoryManager::AddExtensionToAllowlistForTesting( |
| const Extension& extension) { |
| GetExtensionsAllowlist().insert(extension.hashed_id().value()); |
| } |
| |
| // static |
| void URLLoaderFactoryManager::RemoveExtensionFromAllowlistForTesting( |
| const Extension& extension) { |
| GetExtensionsAllowlist().erase(extension.hashed_id().value()); |
| } |
| |
| } // namespace extensions |