Bug 1754004 - Part 10: Clone PostData when writing into & reading from SessionHistoryInfo, r=smaug

Previously the SessionHistoryInfo would hold onto and hand out the
original nsIInputStream objects which were provided by the
nsDocShellLoadState and used to create the underlying channel. This
could cause issues in edge cases, as input streams when serialized over
IPC have their logical owner transferred to the IPC layer so that it can
copy the data to the peer process.

This patch changes the logic to instead clone the input stream to and
from the history info. This means that the history info has its own
instance of the stream type and interacting with it shouldn't interfere
with other consumers of the post data stream.

The behaviour for non-SHIP session history is not changed, as it doesn't
serialize the relevant streams over IPC in the same way, and is on track to be
removed.

Differential Revision: https://phabricator.services.mozilla.com/D141047
This commit is contained in:
Nika Layzell
2022-05-02 20:44:26 +00:00
parent f09977df50
commit 9ee6d342c4
5 changed files with 57 additions and 23 deletions

View File

@@ -3508,7 +3508,7 @@ bool BrowsingContext::IsPopupAllowed() {
bool BrowsingContext::ShouldAddEntryForRefresh( bool BrowsingContext::ShouldAddEntryForRefresh(
nsIURI* aCurrentURI, const SessionHistoryInfo& aInfo) { nsIURI* aCurrentURI, const SessionHistoryInfo& aInfo) {
return ShouldAddEntryForRefresh(aCurrentURI, aInfo.GetURI(), return ShouldAddEntryForRefresh(aCurrentURI, aInfo.GetURI(),
aInfo.GetPostData()); aInfo.HasPostData());
} }
/* static */ /* static */

View File

@@ -475,7 +475,7 @@ class CanonicalBrowsingContext final : public BrowsingContext {
bool ShouldAddEntryForRefresh(const SessionHistoryEntry* aEntry) { bool ShouldAddEntryForRefresh(const SessionHistoryEntry* aEntry) {
return ShouldAddEntryForRefresh(aEntry->Info().GetURI(), return ShouldAddEntryForRefresh(aEntry->Info().GetURI(),
aEntry->Info().GetPostData()); aEntry->Info().HasPostData());
} }
bool ShouldAddEntryForRefresh(nsIURI* aNewURI, bool aHasPostData) { bool ShouldAddEntryForRefresh(nsIURI* aNewURI, bool aHasPostData) {
nsCOMPtr<nsIURI> currentURI = GetCurrentURI(); nsCOMPtr<nsIURI> currentURI = GetCurrentURI();

View File

@@ -8800,19 +8800,6 @@ bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
} }
} }
#ifdef DEBUG
if (aState.mHistoryNavBetweenSameDoc) {
nsCOMPtr<nsIInputStream> currentPostData;
if (mozilla::SessionHistoryInParent()) {
currentPostData = mActiveEntry->GetPostData();
} else {
currentPostData = mOSHE->GetPostData();
}
NS_ASSERTION(currentPostData == aLoadState->PostDataStream(),
"Different POST data for entries for the same page?");
}
#endif
// A same document navigation happens when we navigate between two SHEntries // A same document navigation happens when we navigate between two SHEntries
// for the same document. We do a same document navigation under two // for the same document. We do a same document navigation under two
// circumstances. Either // circumstances. Either
@@ -10041,6 +10028,10 @@ nsIPrincipal* nsDocShell::GetInheritedPrincipal(
// we really need to have a content type associated with this stream!! // we really need to have a content type associated with this stream!!
postChannel->SetUploadStream(aLoadState->PostDataStream(), ""_ns, -1); postChannel->SetUploadStream(aLoadState->PostDataStream(), ""_ns, -1);
// Ownership of the stream has transferred to the channel, clear our
// reference.
aLoadState->SetPostDataStream(nullptr);
} }
/* If there is a valid postdata *and* it is a History Load, /* If there is a valid postdata *and* it is a History Load,

View File

@@ -9,10 +9,13 @@
#include "nsDocShell.h" #include "nsDocShell.h"
#include "nsDocShellLoadState.h" #include "nsDocShellLoadState.h"
#include "nsFrameLoader.h" #include "nsFrameLoader.h"
#include "nsIFormPOSTActionChannel.h"
#include "nsIHttpChannel.h" #include "nsIHttpChannel.h"
#include "nsIUploadChannel2.h"
#include "nsIXULRuntime.h" #include "nsIXULRuntime.h"
#include "nsSHEntryShared.h" #include "nsSHEntryShared.h"
#include "nsSHistory.h" #include "nsSHistory.h"
#include "nsStreamUtils.h"
#include "nsStructuredCloneContainer.h" #include "nsStructuredCloneContainer.h"
#include "nsXULAppAPI.h" #include "nsXULAppAPI.h"
#include "mozilla/PresState.h" #include "mozilla/PresState.h"
@@ -42,7 +45,6 @@ SessionHistoryInfo::SessionHistoryInfo(nsDocShellLoadState* aLoadState,
: mURI(aLoadState->URI()), : mURI(aLoadState->URI()),
mOriginalURI(aLoadState->OriginalURI()), mOriginalURI(aLoadState->OriginalURI()),
mResultPrincipalURI(aLoadState->ResultPrincipalURI()), mResultPrincipalURI(aLoadState->ResultPrincipalURI()),
mPostData(aLoadState->PostDataStream()),
mLoadType(aLoadState->LoadType()), mLoadType(aLoadState->LoadType()),
mSrcdocData(aLoadState->SrcdocData().IsVoid() mSrcdocData(aLoadState->SrcdocData().IsVoid()
? Nothing() ? Nothing()
@@ -56,6 +58,15 @@ SessionHistoryInfo::SessionHistoryInfo(nsDocShellLoadState* aLoadState,
aLoadState->PartitionedPrincipalToInherit(), aLoadState->Csp(), aLoadState->PartitionedPrincipalToInherit(), aLoadState->Csp(),
/* FIXME Is this correct? */ /* FIXME Is this correct? */
aLoadState->TypeHint())) { aLoadState->TypeHint())) {
// Pull the upload stream off of the channel instead of the load state, as
// ownership has already been transferred from the load state to the channel.
if (nsCOMPtr<nsIUploadChannel2> postChannel = do_QueryInterface(aChannel)) {
int64_t contentLength;
MOZ_ALWAYS_SUCCEEDS(postChannel->CloneUploadStream(
&contentLength, getter_AddRefs(mPostData)));
MOZ_ASSERT_IF(mPostData, NS_InputStreamIsCloneable(mPostData));
}
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) { if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
mReferrerInfo = httpChannel->GetReferrerInfo(); mReferrerInfo = httpChannel->GetReferrerInfo();
} }
@@ -163,6 +174,24 @@ void SessionHistoryInfo::MaybeUpdateTitleFromURI() {
} }
} }
already_AddRefed<nsIInputStream> SessionHistoryInfo::GetPostData() const {
// Return a clone of our post data stream. Our caller will either be
// transferring this stream to a different SessionHistoryInfo, or passing it
// off to necko/another process which will consume it, and we want to preserve
// our local instance.
nsCOMPtr<nsIInputStream> postData;
if (mPostData) {
MOZ_ALWAYS_SUCCEEDS(
NS_CloneInputStream(mPostData, getter_AddRefs(postData)));
}
return postData.forget();
}
void SessionHistoryInfo::SetPostData(nsIInputStream* aPostData) {
MOZ_ASSERT_IF(aPostData, NS_InputStreamIsCloneable(aPostData));
mPostData = aPostData;
}
uint64_t SessionHistoryInfo::SharedId() const { uint64_t SessionHistoryInfo::SharedId() const {
return mSharedState.Get()->mId; return mSharedState.Get()->mId;
} }
@@ -217,7 +246,8 @@ void SessionHistoryInfo::FillLoadInfo(nsDocShellLoadState& aLoadState) const {
aLoadState.SetOriginalURI(mOriginalURI); aLoadState.SetOriginalURI(mOriginalURI);
aLoadState.SetMaybeResultPrincipalURI(Some(mResultPrincipalURI)); aLoadState.SetMaybeResultPrincipalURI(Some(mResultPrincipalURI));
aLoadState.SetLoadReplace(mLoadReplace); aLoadState.SetLoadReplace(mLoadReplace);
aLoadState.SetPostDataStream(mPostData); nsCOMPtr<nsIInputStream> postData = GetPostData();
aLoadState.SetPostDataStream(postData);
aLoadState.SetReferrerInfo(mReferrerInfo); aLoadState.SetReferrerInfo(mReferrerInfo);
aLoadState.SetTypeHint(mSharedState.Get()->mContentType); aLoadState.SetTypeHint(mSharedState.Get()->mContentType);
@@ -653,14 +683,13 @@ SessionHistoryEntry::SetRefreshURIList(nsIMutableArray* aRefreshURIList) {
NS_IMETHODIMP NS_IMETHODIMP
SessionHistoryEntry::GetPostData(nsIInputStream** aPostData) { SessionHistoryEntry::GetPostData(nsIInputStream** aPostData) {
nsCOMPtr<nsIInputStream> postData = mInfo->mPostData; *aPostData = mInfo->GetPostData().take();
postData.forget(aPostData);
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
SessionHistoryEntry::SetPostData(nsIInputStream* aPostData) { SessionHistoryEntry::SetPostData(nsIInputStream* aPostData) {
mInfo->mPostData = aPostData; mInfo->SetPostData(aPostData);
return NS_OK; return NS_OK;
} }
@@ -1467,6 +1496,8 @@ namespace ipc {
void IPDLParamTraits<dom::SessionHistoryInfo>::Write( void IPDLParamTraits<dom::SessionHistoryInfo>::Write(
IPC::MessageWriter* aWriter, IProtocol* aActor, IPC::MessageWriter* aWriter, IProtocol* aActor,
const dom::SessionHistoryInfo& aParam) { const dom::SessionHistoryInfo& aParam) {
nsCOMPtr<nsIInputStream> postData = aParam.GetPostData();
Maybe<Tuple<uint32_t, dom::ClonedMessageData>> stateData; Maybe<Tuple<uint32_t, dom::ClonedMessageData>> stateData;
if (aParam.mStateData) { if (aParam.mStateData) {
stateData.emplace(); stateData.emplace();
@@ -1497,7 +1528,7 @@ void IPDLParamTraits<dom::SessionHistoryInfo>::Write(
WriteIPDLParam(aWriter, aActor, aParam.mReferrerInfo); WriteIPDLParam(aWriter, aActor, aParam.mReferrerInfo);
WriteIPDLParam(aWriter, aActor, aParam.mTitle); WriteIPDLParam(aWriter, aActor, aParam.mTitle);
WriteIPDLParam(aWriter, aActor, aParam.mName); WriteIPDLParam(aWriter, aActor, aParam.mName);
WriteIPDLParam(aWriter, aActor, aParam.mPostData); WriteIPDLParam(aWriter, aActor, postData);
WriteIPDLParam(aWriter, aActor, aParam.mLoadType); WriteIPDLParam(aWriter, aActor, aParam.mLoadType);
WriteIPDLParam(aWriter, aActor, aParam.mScrollPositionX); WriteIPDLParam(aWriter, aActor, aParam.mScrollPositionX);
WriteIPDLParam(aWriter, aActor, aParam.mScrollPositionY); WriteIPDLParam(aWriter, aActor, aParam.mScrollPositionY);
@@ -1570,6 +1601,17 @@ bool IPDLParamTraits<dom::SessionHistoryInfo>::Read(
return false; return false;
} }
// We should always see a cloneable input stream passed to SessionHistoryInfo.
// This is because it will be cloneable when first read in the parent process
// from the nsHttpChannel (which forces streams to be cloneable), and future
// streams in content will be wrapped in
// nsMIMEInputStream(RemoteLazyInputStream) which is also cloneable.
if (aResult->mPostData && !NS_InputStreamIsCloneable(aResult->mPostData)) {
aActor->FatalError(
"Unexpected non-cloneable postData for SessionHistoryInfo");
return false;
}
dom::SHEntrySharedParentState* sharedState = nullptr; dom::SHEntrySharedParentState* sharedState = nullptr;
if (XRE_IsParentProcess()) { if (XRE_IsParentProcess()) {
sharedState = dom::SHEntrySharedParentState::Lookup(sharedId); sharedState = dom::SHEntrySharedParentState::Lookup(sharedId);

View File

@@ -71,8 +71,9 @@ class SessionHistoryInfo {
mResultPrincipalURI = aResultPrincipalURI; mResultPrincipalURI = aResultPrincipalURI;
} }
nsIInputStream* GetPostData() const { return mPostData; } bool HasPostData() const { return mPostData; }
void SetPostData(nsIInputStream* aPostData) { mPostData = aPostData; } already_AddRefed<nsIInputStream> GetPostData() const;
void SetPostData(nsIInputStream* aPostData);
void GetScrollPosition(int32_t* aScrollPositionX, int32_t* aScrollPositionY) { void GetScrollPosition(int32_t* aScrollPositionX, int32_t* aScrollPositionY) {
*aScrollPositionX = mScrollPositionX; *aScrollPositionX = mScrollPositionX;