summaryrefslogtreecommitdiffstats
path: root/layout/svg/AutoReferenceChainGuard.h
diff options
context:
space:
mode:
Diffstat (limited to 'layout/svg/AutoReferenceChainGuard.h')
-rw-r--r--layout/svg/AutoReferenceChainGuard.h169
1 files changed, 169 insertions, 0 deletions
diff --git a/layout/svg/AutoReferenceChainGuard.h b/layout/svg/AutoReferenceChainGuard.h
new file mode 100644
index 0000000000..c25ba0ea70
--- /dev/null
+++ b/layout/svg/AutoReferenceChainGuard.h
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_
+#define LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_
+
+#include "Element.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ReentrancyGuard.h"
+#include "mozilla/Likely.h"
+#include "nsDebug.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFrame.h"
+
+namespace mozilla {
+
+/**
+ * This helper class helps us to protect against two related issues that can
+ * occur in SVG content: reference loops, and reference chains that we deem to
+ * be too long.
+ *
+ * Some SVG effects can reference another effect of the same type to produce
+ * a chain of effects to be applied (e.g. clipPath), while in other cases it is
+ * possible that while processing an effect of a certain type another effect
+ * of the same type may be encountered indirectly (e.g. pattern). In order to
+ * avoid stack overflow crashes and performance issues we need to impose an
+ * arbitrary limit on the length of the reference chains that SVG content may
+ * try to create. (Some SVG authoring tools have been known to create absurdly
+ * long reference chains. For example, bug 1253590 details a case where Adobe
+ * Illustrator was used to created an SVG with a chain of 5000 clip paths which
+ * could cause us to run out of stack space and crash.)
+ *
+ * This class is intended to be used with the nsIFrame's of SVG effects that
+ * may involve reference chains. To use it add a boolean member, something
+ * like this:
+ *
+ * // Flag used to indicate whether a methods that may reenter due to
+ * // following a reference to another instance is currently executing.
+ * bool mIsBeingProcessed;
+ *
+ * Make sure to initialize the member to false in the class' constructons.
+ *
+ * Then add the following to the top of any methods that may be reentered due
+ * to following a reference:
+ *
+ * static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ *
+ * AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
+ * &sRefChainLengthCounter);
+ * if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ * return; // Break reference chain
+ * }
+ *
+ * Note that mIsBeingProcessed and sRefChainLengthCounter should never be used
+ * by the frame except when it initialize them as indicated above.
+ */
+class MOZ_RAII AutoReferenceChainGuard {
+ static const int16_t sDefaultMaxChainLength = 10; // arbitrary length
+
+ public:
+ static const int16_t noChain = -2;
+
+ /**
+ * @param aFrame The frame for an effect that may involve a reference chain.
+ * @param aFrameInUse The member variable on aFrame that is used to indicate
+ * whether one of aFrame's methods that may involve following a reference
+ * to another effect of the same type is currently being executed.
+ * @param aChainCounter A static variable in the method in which this class
+ * is instantiated that is used to keep track of how many times the method
+ * is reentered (and thus how long the a reference chain is).
+ * @param aMaxChainLength The maximum number of links that are allowed in
+ * a reference chain.
+ */
+ AutoReferenceChainGuard(nsIFrame* aFrame, bool* aFrameInUse,
+ int16_t* aChainCounter,
+ int16_t aMaxChainLength = sDefaultMaxChainLength)
+ : mFrame(aFrame),
+ mFrameInUse(aFrameInUse),
+ mChainCounter(aChainCounter),
+ mMaxChainLength(aMaxChainLength),
+ mBrokeReference(false) {
+ MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter);
+ MOZ_ASSERT(aMaxChainLength > 0);
+ MOZ_ASSERT(*aChainCounter == noChain ||
+ (*aChainCounter >= 0 && *aChainCounter < aMaxChainLength));
+ }
+
+ ~AutoReferenceChainGuard() {
+ if (mBrokeReference) {
+ // We didn't change mFrameInUse or mChainCounter
+ return;
+ }
+
+ *mFrameInUse = false;
+
+ // If we fail this assert then there were more destructor calls than
+ // Reference() calls (a consumer forgot to to call Reference()), or else
+ // someone messed with the variable pointed to by mChainCounter.
+ MOZ_ASSERT(*mChainCounter < mMaxChainLength);
+
+ (*mChainCounter)++;
+
+ if (*mChainCounter == mMaxChainLength) {
+ *mChainCounter = noChain; // reset ready for use next time
+ }
+ }
+
+ /**
+ * Returns true on success (no reference loop/reference chain length is
+ * within the specified limits), else returns false on failure (there is a
+ * reference loop/the reference chain has exceeded the specified limits).
+ * If it returns false then an error message will be reported to the DevTools
+ * console (only once).
+ */
+ [[nodiscard]] bool Reference() {
+ if (MOZ_UNLIKELY(*mFrameInUse)) {
+ mBrokeReference = true;
+ ReportErrorToConsole();
+ return false;
+ }
+
+ if (*mChainCounter == noChain) {
+ // Initialize - we start at aMaxChainLength and decrement towards zero.
+ *mChainCounter = mMaxChainLength;
+ } else {
+ // If we fail this assertion then either a consumer failed to break a
+ // reference loop/chain, or else they called Reference() more than once
+ MOZ_ASSERT(*mChainCounter >= 0);
+
+ if (MOZ_UNLIKELY(*mChainCounter < 1)) {
+ mBrokeReference = true;
+ ReportErrorToConsole();
+ return false;
+ }
+ }
+
+ // We only set these once we know we're returning true.
+ *mFrameInUse = true;
+ (*mChainCounter)--;
+
+ return true;
+ }
+
+ private:
+ void ReportErrorToConsole() {
+ AutoTArray<nsString, 2> params;
+ dom::Element* element = mFrame->GetContent()->AsElement();
+ element->GetTagName(*params.AppendElement());
+ element->GetId(*params.AppendElement());
+ auto doc = mFrame->GetContent()->OwnerDoc();
+ auto warning = *mFrameInUse ? dom::Document::eSVGRefLoop
+ : dom::Document::eSVGRefChainLengthExceeded;
+ doc->WarnOnceAbout(warning, /* asError */ true, params);
+ }
+
+ nsIFrame* mFrame;
+ bool* mFrameInUse;
+ int16_t* mChainCounter;
+ const int16_t mMaxChainLength;
+ bool mBrokeReference;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_