Bug 1812192 - Store schemeless address bar loads in loadinfo and upgrade to https with fallback r=necko-reviewers,webidl,smaug,freddyb,kershaw

Depends on D179794

Differential Revision: https://phabricator.services.mozilla.com/D179795
This commit is contained in:
Frederik Braun 2023-10-10 08:43:00 +00:00
parent 9301710e8e
commit aed55ecb55
18 changed files with 162 additions and 30 deletions

View File

@ -228,6 +228,12 @@ var gIdentityHandler = {
"identity-popup-securityView"
));
},
get _identityPopupHttpsOnlyMode() {
delete this._identityPopupHttpsOnlyMode;
return (this._identityPopupHttpsOnlyMode = document.getElementById(
"identity-popup-security-httpsonlymode"
));
},
get _identityPopupHttpsOnlyModeMenuList() {
delete this._identityPopupHttpsOnlyModeMenuList;
return (this._identityPopupHttpsOnlyModeMenuList = document.getElementById(
@ -352,6 +358,15 @@ var gIdentityHandler = {
);
return this._httpsFirstModeEnabledPBM;
},
get _schemelessHttpsFirstModeEnabled() {
delete this._schemelessHttpsFirstModeEnabled;
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_schemelessHttpsFirstModeEnabled",
"dom.security.https_first_schemeless"
);
return this._schemelessHttpsFirstModeEnabled;
},
_isHttpsOnlyModeActive(isWindowPrivate) {
return (
@ -366,6 +381,12 @@ var gIdentityHandler = {
(isWindowPrivate && this._httpsFirstModeEnabledPBM))
);
},
_isSchemelessHttpsFirstModeActive(isWindowPrivate) {
return (
!this._isHttpsFirstModeActive(isWindowPrivate) &&
this._schemelessHttpsFirstModeEnabled
);
},
/**
* Handles clicks on the "Clear Cookies and Site Data" button.
@ -1017,12 +1038,23 @@ var gIdentityHandler = {
const isHttpsFirstModeActive = this._isHttpsFirstModeActive(
privateBrowsingWindow
);
const isSchemelessHttpsFirstModeActive =
this._isSchemelessHttpsFirstModeActive(privateBrowsingWindow);
let httpsOnlyStatus = "";
if (isHttpsFirstModeActive || isHttpsOnlyModeActive) {
if (
isHttpsFirstModeActive ||
isHttpsOnlyModeActive ||
isSchemelessHttpsFirstModeActive
) {
// Note: value and permission association is laid out
// in _getHttpsOnlyPermission
let value = this._getHttpsOnlyPermission();
// We do not want to display the exception ui for schemeless
// HTTPS-First, but we still want the "Upgraded to HTTPS" label.
this._identityPopupHttpsOnlyMode.hidden =
isSchemelessHttpsFirstModeActive;
this._identityPopupHttpsOnlyModeMenuListOffItem.hidden =
privateBrowsingWindow && value != 1;

View File

@ -6055,7 +6055,8 @@ already_AddRefed<nsIURI> nsDocShell::AttemptURIFixup(
nsIChannel* aChannel, nsresult aStatus,
const mozilla::Maybe<nsCString>& aOriginalURIString, uint32_t aLoadType,
bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing,
bool aNotifyKeywordSearchLoading, nsIInputStream** aNewPostData) {
bool aNotifyKeywordSearchLoading, nsIInputStream** aNewPostData,
bool* outWasSchemelessInput) {
if (aStatus != NS_ERROR_UNKNOWN_HOST && aStatus != NS_ERROR_NET_RESET &&
aStatus != NS_ERROR_CONNECTION_REFUSED &&
aStatus !=
@ -6149,6 +6150,7 @@ already_AddRefed<nsIURI> nsDocShell::AttemptURIFixup(
}
if (info) {
info->GetPreferredURI(getter_AddRefs(newURI));
info->GetWasSchemelessInput(outWasSchemelessInput);
if (newURI) {
info->GetKeywordAsSent(keywordAsSent);
info->GetKeywordProviderName(keywordProviderName);

View File

@ -450,7 +450,8 @@ class nsDocShell final : public nsDocLoader,
const mozilla::Maybe<nsCString>& aOriginalURIString, uint32_t aLoadType,
bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing,
bool aNotifyKeywordSearchLoading = false,
nsIInputStream** aNewPostData = nullptr);
nsIInputStream** aNewPostData = nullptr,
bool* outWasSchemelessInput = nullptr);
static already_AddRefed<nsIURI> MaybeFixBadCertDomainErrorURI(
nsIChannel* aChannel, nsIURI* aUrl);

View File

@ -89,6 +89,7 @@ nsDocShellLoadState::nsDocShellLoadState(
mTriggeringWindowId = aLoadState.TriggeringWindowId();
mTriggeringStorageAccess = aLoadState.TriggeringStorageAccess();
mTriggeringRemoteType = aLoadState.TriggeringRemoteType();
mWasSchemelessInput = aLoadState.WasSchemelessInput();
mCsp = aLoadState.Csp();
mOriginalURIString = aLoadState.OriginalURIString();
mCancelContentJSEpoch = aLoadState.CancelContentJSEpoch();
@ -192,7 +193,8 @@ nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther)
mWasCreatedRemotely(aOther.mWasCreatedRemotely),
mUnstrippedURI(aOther.mUnstrippedURI),
mRemoteTypeOverride(aOther.mRemoteTypeOverride),
mTriggeringRemoteType(aOther.mTriggeringRemoteType) {
mTriggeringRemoteType(aOther.mTriggeringRemoteType),
mWasSchemelessInput(aOther.mWasSchemelessInput) {
MOZ_DIAGNOSTIC_ASSERT(
XRE_IsParentProcess(),
"Cloning a nsDocShellLoadState with the same load identifier is only "
@ -236,7 +238,8 @@ nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier)
mWasCreatedRemotely(false),
mTriggeringRemoteType(XRE_IsContentProcess()
? ContentChild::GetSingleton()->GetRemoteType()
: NOT_REMOTE_TYPE) {
: NOT_REMOTE_TYPE),
mWasSchemelessInput(false) {
MOZ_ASSERT(aURI, "Cannot create a LoadState with a null URI!");
}
@ -478,6 +481,8 @@ nsresult nsDocShellLoadState::CreateFromLoadURIOptions(
aLoadURIOptions.mRemoteTypeOverride.Value());
}
loadState->SetWasSchemelessInput(aLoadURIOptions.mWasSchemelessInput);
loadState.forget(aResult);
return NS_OK;
}
@ -1282,6 +1287,7 @@ DocShellLoadStateInit nsDocShellLoadState::Serialize(
loadState.TriggeringWindowId() = mTriggeringWindowId;
loadState.TriggeringStorageAccess() = mTriggeringStorageAccess;
loadState.TriggeringRemoteType() = mTriggeringRemoteType;
loadState.WasSchemelessInput() = mWasSchemelessInput;
loadState.Csp() = mCsp;
loadState.OriginalURIString() = mOriginalURIString;
loadState.CancelContentJSEpoch() = mCancelContentJSEpoch;

View File

@ -327,6 +327,12 @@ class nsDocShellLoadState final {
mRemoteTypeOverride = mozilla::Some(aRemoteTypeOverride);
}
void SetWasSchemelessInput(bool aWasSchemelessInput) {
mWasSchemelessInput = aWasSchemelessInput;
}
bool GetWasSchemelessInput() { return mWasSchemelessInput; }
// Determine the remote type of the process which should be considered
// responsible for this load for the purposes of security checks.
//
@ -594,6 +600,9 @@ class nsDocShellLoadState final {
// Remote type of the process which originally requested the load.
nsCString mTriggeringRemoteType;
// if the to-be-loaded address had it protocol added through a fixup
bool mWasSchemelessInput = false;
};
#endif /* nsDocShellLoadState_h__ */

View File

@ -108,4 +108,9 @@ dictionary LoadURIOptions {
* `remoteTypeOverride` and a `remoteTypeOverride` of `NOT_REMOTE_TYPE`.
*/
UTF8String? remoteTypeOverride;
/**
* Whether the search/URL term was without an explicit scheme.
*/
boolean wasSchemelessInput = false;
};

View File

@ -217,6 +217,7 @@ struct DocShellLoadStateInit
bool HasValidUserGestureActivation;
bool AllowFocusMove;
bool IsFromProcessingFrameAttributes;
bool WasSchemelessInput;
// Fields missing due to lack of need or serialization
// nsCOMPtr<nsIDocShell> mSourceDocShell;

View File

@ -154,6 +154,8 @@ HTTPSOnlyFailedDowngradeAgain = Upgrading insecure request “%S” failed. Down
# %1$S is the URL of the upgraded speculative TCP connection; %2$S is the upgraded scheme.
HTTPSOnlyUpgradeSpeculativeConnection = Upgrading insecure speculative TCP connection “%1$S” to use “%2$S”.
HTTPSFirstSchemeless = Upgrading URL loaded in the address bar without explicit protocol scheme to use HTTPS.
# LOCALIZATION NOTE: %S is the URL of the blocked request;
IframeSandboxBlockedDownload = Download of “%S” was blocked because the triggering iframe has the sandbox flag set.

View File

@ -819,6 +819,9 @@ static void DebugDoContentSecurityCheck(nsIChannel* aChannel,
MOZ_LOG(sCSMLog, LogLevel::Verbose,
(" allowDeprecatedSystemRequests: %s\n",
aLoadInfo->GetAllowDeprecatedSystemRequests() ? "true" : "false"));
MOZ_LOG(sCSMLog, LogLevel::Verbose,
(" wasSchemeless: %s\n",
aLoadInfo->GetWasSchemelessInput() ? "true" : "false"));
// Log CSPrequestPrincipal
nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadInfo->GetCsp();
@ -841,7 +844,9 @@ static void DebugDoContentSecurityCheck(nsIChannel* aChannel,
// Security Flags
MOZ_LOG(sCSMLog, LogLevel::Verbose, (" securityFlags:"));
LogSecurityFlags(aLoadInfo->GetSecurityFlags());
// HTTPS-Only
LogHTTPSOnlyInfo(aLoadInfo);
MOZ_LOG(sCSMLog, LogLevel::Debug, ("\n#DebugDoContentSecurityCheck End\n"));
}
}

View File

@ -76,8 +76,10 @@ void nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(
// if neither HTTPS-Only nor HTTPS-First mode is enabled, then there is
// nothing to do here.
if (!IsHttpsOnlyModeEnabled(isPrivateWin) &&
!IsHttpsFirstModeEnabled(isPrivateWin)) {
if ((!IsHttpsOnlyModeEnabled(isPrivateWin) &&
!IsHttpsFirstModeEnabled(isPrivateWin)) &&
!(loadInfo->GetWasSchemelessInput() &&
mozilla::StaticPrefs::dom_security_https_first_schemeless())) {
return;
}
@ -118,7 +120,9 @@ void nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(
// all http connections to be https and overrules HTTPS-First. In case
// HTTPS-First is enabled, but HTTPS-Only is not enabled, we might return
// early if attempting to send a background request to a non standard port.
if (IsHttpsFirstModeEnabled(isPrivateWin) &&
if ((IsHttpsFirstModeEnabled(isPrivateWin) ||
(loadInfo->GetWasSchemelessInput() &&
mozilla::StaticPrefs::dom_security_https_first_schemeless())) &&
!IsHttpsOnlyModeEnabled(isPrivateWin)) {
int32_t port = 0;
nsresult rv = channelURI->GetPort(&port);
@ -368,7 +372,9 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
nsILoadInfo* aLoadInfo) {
// 1. Check if HTTPS-First Mode is enabled
bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
if (!IsHttpsFirstModeEnabled(isPrivateWin)) {
if (!IsHttpsFirstModeEnabled(isPrivateWin) &&
!(aLoadInfo->GetWasSchemelessInput() &&
mozilla::StaticPrefs::dom_security_https_first_schemeless())) {
return false;
}
@ -427,19 +433,31 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
// We can upgrade the request - let's log to the console and set the status
// so we know that we upgraded the request.
nsAutoCString scheme;
aURI->GetScheme(scheme);
scheme.AppendLiteral("s");
NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
NS_ConvertUTF8toUTF16 reportScheme(scheme);
if (aLoadInfo->GetWasSchemelessInput() &&
mozilla::StaticPrefs::dom_security_https_first_schemeless()) {
nsAutoCString urlCString;
aURI->GetSpec(urlCString);
NS_ConvertUTF8toUTF16 urlString(urlCString);
bool isSpeculative = contentType == ExtContentPolicy::TYPE_SPECULATIVE;
AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
nsHTTPSOnlyUtils::LogLocalizedString(
isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection"
: "HTTPSOnlyUpgradeRequest",
params, nsIScriptError::warningFlag, aLoadInfo, aURI, true);
AutoTArray<nsString, 1> params = {urlString};
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSFirstSchemeless", params,
nsIScriptError::warningFlag, aLoadInfo,
aURI, true);
} else {
nsAutoCString scheme;
aURI->GetScheme(scheme);
scheme.AppendLiteral("s");
NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
NS_ConvertUTF8toUTF16 reportScheme(scheme);
bool isSpeculative = contentType == ExtContentPolicy::TYPE_SPECULATIVE;
AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
nsHTTPSOnlyUtils::LogLocalizedString(
isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection"
: "HTTPSOnlyUpgradeRequest",
params, nsIScriptError::warningFlag, aLoadInfo, aURI, true);
}
// Set flag so we know that we upgraded the request
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST;
aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
@ -622,7 +640,10 @@ void nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(
bool isPrivateWin = loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
bool isHttpsOnly = IsHttpsOnlyModeEnabled(isPrivateWin);
bool isHttpsFirst = IsHttpsFirstModeEnabled(isPrivateWin);
if (!isHttpsOnly && !isHttpsFirst) {
bool isSchemelessHttpsFirst =
(loadInfo->GetWasSchemelessInput() &&
mozilla::StaticPrefs::dom_security_https_first_schemeless());
if (!isHttpsOnly && !isHttpsFirst && !isSchemelessHttpsFirst) {
return;
}

View File

@ -567,9 +567,10 @@ nsresult LoadInfoToLoadInfoArgs(nsILoadInfo* aLoadInfo,
aLoadInfo->GetIsFormSubmission(), aLoadInfo->GetSendCSPViolationEvents(),
aLoadInfo->GetOriginAttributes(), redirectChainIncludingInternalRedirects,
redirectChain, aLoadInfo->GetHasInjectedCookieForCookieBannerHandling(),
ipcClientInfo, ipcReservedClientInfo, ipcInitialClientInfo, ipcController,
aLoadInfo->CorsUnsafeHeaders(), aLoadInfo->GetForcePreflight(),
aLoadInfo->GetIsPreflight(), aLoadInfo->GetLoadTriggeredFromExternal(),
aLoadInfo->GetWasSchemelessInput(), ipcClientInfo, ipcReservedClientInfo,
ipcInitialClientInfo, ipcController, aLoadInfo->CorsUnsafeHeaders(),
aLoadInfo->GetForcePreflight(), aLoadInfo->GetIsPreflight(),
aLoadInfo->GetLoadTriggeredFromExternal(),
aLoadInfo->GetServiceWorkerTaintingSynthesized(),
aLoadInfo->GetDocumentHasUserInteracted(),
aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(),
@ -868,7 +869,8 @@ nsresult LoadInfoArgsToLoadInfo(const LoadInfoArgs& loadInfoArgs,
loadInfoArgs.loadingEmbedderPolicy(),
loadInfoArgs.originTrialCoepCredentiallessEnabledForTopLevel(),
loadInfoArgs.unstrippedURI(), interceptionInfo,
loadInfoArgs.hasInjectedCookieForCookieBannerHandling());
loadInfoArgs.hasInjectedCookieForCookieBannerHandling(),
loadInfoArgs.wasSchemelessInput());
if (loadInfoArgs.isFromProcessingFrameAttributes()) {
loadInfo->SetIsFromProcessingFrameAttributes();

View File

@ -3704,6 +3704,14 @@
value: true
mirror: always
# If true, top-level requests that are initiated from the address
# bar and with an empty scheme get upgraded to HTTPS
# with a fallback
- name: dom.security.https_first_schemeless
type: RelaxedAtomicBool
value: false
mirror: always
- name: dom.security.unexpected_system_load_telemetry_enabled
type: bool
value: true

View File

@ -641,7 +641,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
mUnstrippedURI(rhs.mUnstrippedURI),
mInterceptionInfo(rhs.mInterceptionInfo),
mHasInjectedCookieForCookieBannerHandling(
rhs.mHasInjectedCookieForCookieBannerHandling) {}
rhs.mHasInjectedCookieForCookieBannerHandling),
mWasSchemelessInput(rhs.mWasSchemelessInput) {}
LoadInfo::LoadInfo(
nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
@ -685,7 +686,7 @@ LoadInfo::LoadInfo(
nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy,
bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel,
nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo,
bool aHasInjectedCookieForCookieBannerHandling)
bool aHasInjectedCookieForCookieBannerHandling, bool aWasSchemelessInput)
: mLoadingPrincipal(aLoadingPrincipal),
mTriggeringPrincipal(aTriggeringPrincipal),
mPrincipalToInherit(aPrincipalToInherit),
@ -761,7 +762,8 @@ LoadInfo::LoadInfo(
mUnstrippedURI(aUnstrippedURI),
mInterceptionInfo(aInterceptionInfo),
mHasInjectedCookieForCookieBannerHandling(
aHasInjectedCookieForCookieBannerHandling) {
aHasInjectedCookieForCookieBannerHandling),
mWasSchemelessInput(aWasSchemelessInput) {
// Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
MOZ_ASSERT(mLoadingPrincipal ||
aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
@ -2321,4 +2323,16 @@ LoadInfo::SetHasInjectedCookieForCookieBannerHandling(
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::GetWasSchemelessInput(bool* aWasSchemelessInput) {
*aWasSchemelessInput = mWasSchemelessInput;
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) {
mWasSchemelessInput = aWasSchemelessInput;
return NS_OK;
}
} // namespace mozilla::net

View File

@ -245,7 +245,7 @@ class LoadInfo final : public nsILoadInfo {
nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy,
bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel,
nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo,
bool aHasInjectedCookieForCookieBannerHandling);
bool aHasInjectedCookieForCookieBannerHandling, bool aWasSchemelessInput);
LoadInfo(const LoadInfo& rhs);
NS_IMETHOD GetRedirects(JSContext* aCx,
@ -387,6 +387,7 @@ class LoadInfo final : public nsILoadInfo {
nsCOMPtr<nsIInterceptionInfo> mInterceptionInfo;
bool mHasInjectedCookieForCookieBannerHandling = false;
bool mWasSchemelessInput = false;
};
// This is exposed solely for testing purposes and should not be used outside of

View File

@ -845,5 +845,15 @@ TRRLoadInfo::SetHasInjectedCookieForCookieBannerHandling(
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TRRLoadInfo::GetWasSchemelessInput(bool* aWasSchemelessInput) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TRRLoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) {
return NS_ERROR_NOT_IMPLEMENTED;
}
} // namespace net
} // namespace mozilla

View File

@ -1505,4 +1505,9 @@ interface nsILoadInfo : nsISupports
* handle a cookie banner. This is only done for top-level requests.
*/
[infallible] attribute boolean hasInjectedCookieForCookieBannerHandling;
/**
* Whether the load has gone through the URL bar, where the fixup had to add * the protocol scheme.
*/
[infallible] attribute boolean wasSchemelessInput;
};

View File

@ -159,6 +159,8 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
}
loadInfo->SetWasSchemelessInput(aLoadState->GetWasSchemelessInput());
loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess());
@ -2343,11 +2345,13 @@ bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) {
}
nsCOMPtr<nsIInputStream> newPostData;
bool wasSchemelessInput = false;
nsCOMPtr<nsIURI> newURI = nsDocShell::AttemptURIFixup(
mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(),
mLoadStateInternalLoadFlags &
nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData));
bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData),
&wasSchemelessInput);
// Since aStatus will be NS_OK for 4xx and 5xx error codes we
// have to check each request which was upgraded by https-first.
@ -2380,6 +2384,9 @@ bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) {
loadState->SetPostDataStream(newPostData);
// Record whether the protocol was added through a fixup.
loadState->SetWasSchemelessInput(wasSchemelessInput);
if (isHTTPSFirstFixup) {
// We have to exempt the load from HTTPS-First to prevent a
// upgrade-downgrade loop.

View File

@ -133,6 +133,7 @@ struct LoadInfoArgs
RedirectHistoryEntryInfo[] redirectChainIncludingInternalRedirects;
RedirectHistoryEntryInfo[] redirectChain;
bool hasInjectedCookieForCookieBannerHandling;
bool wasSchemelessInput;
/**
* ClientInfo structure representing the window or worker that triggered