Verified Commit 05f91c29 authored by David Parks's avatar David Parks Committed by ma1
Browse files

Bug 2057113 - Part 1: Reject re-entrant native Windows clipboard mutations. a=diannaS DONTBUILD

Native clipboard writes can spin a nested event loop or pump the native message
queue. A clipboard write dispatched during that period re-enters the platform's
backend and mutates state the suspended commit is still using.  This exposed
a case where the clipboard cache gets out-of-sync due to re-entrant SetData.
The older write would win the cache but the native clipboard holds the newer
write's contents.  Re-entrant requests are now rejected (neither interleaved
nor queued).

Original Revision: https://phabricator.services.mozilla.com/D326255

Differential Revision: https://phabricator.services.mozilla.com/D328681
parent d8047c80
Loading
Loading
Loading
Loading
+27 −0
Changes for widget/nsBaseClipboard.cpp: 27 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#include "nsBaseClipboard.h"

#include "ContentAnalysis.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Components.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
@@ -376,6 +377,17 @@ NS_IMETHODIMP nsBaseClipboard::SetData(
    }
  }

  // A clipboard write may still be on the stack, having spun a nested event
  // loop or pumped the native message queue while rendering its data.
  // Overwriting the native clipboard now would free state that the other
  // commit is still using.  We fail the new request now rather than clearing
  // the clipboard and queueing the incoming clipboard write.  This only covers
  // the step of a write that can nest an event loop.
  if (mMutatingNativeClipboard) {
    MOZ_CLIPBOARD_LOG("%s: rejecting re-entrant write.", __FUNCTION__);
    return NS_ERROR_IN_PROGRESS;
  }

  const auto& clipboardCache = mCaches[aWhichClipboard];
  MOZ_ASSERT(clipboardCache);
  if (aTransferable == clipboardCache->GetTransferable() &&
@@ -391,7 +403,11 @@ NS_IMETHODIMP nsBaseClipboard::SetData(
    mIgnoreEmptyNotification = true;
    // Reject existing pending asyncSetData request if any.
    RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
    {
      mozilla::AutoRestore<bool> mutating(mMutatingNativeClipboard);
      mMutatingNativeClipboard = true;
      rv = SetNativeClipboardData(aTransferable, aWhichClipboard);
    }
    mIgnoreEmptyNotification = false;
  }
  if (NS_FAILED(rv)) {
@@ -745,7 +761,18 @@ NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(ClipboardType aWhichClipboard) {
    return NS_ERROR_FAILURE;
  }

  if (mMutatingNativeClipboard) {
    // We are in the middle of a clipboard operation.  Don't cancel/empty.
    // See SetData.
    MOZ_CLIPBOARD_LOG("%s: rejecting re-entrant empty.", __FUNCTION__);
    return NS_ERROR_IN_PROGRESS;
  }

  {
    mozilla::AutoRestore<bool> mutating(mMutatingNativeClipboard);
    mMutatingNativeClipboard = true;
    EmptyNativeClipboardData(aWhichClipboard);
  }

  const auto& clipboardCache = mCaches[aWhichClipboard];
  MOZ_ASSERT(clipboardCache);
+3 −0
Changes for widget/nsBaseClipboard.h: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -264,6 +264,9 @@ class nsBaseClipboard : public nsIClipboard {
      mCaches;
  const mozilla::dom::ClipboardCapabilities mClipboardCaps;
  bool mIgnoreEmptyNotification = false;

  // True when SetNativeClipboardData or EmptyNativeClipboardData is running.
  bool mMutatingNativeClipboard = false;
};

#endif  // nsBaseClipboard_h__