tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AutoReferenceChainGuard.h (6383B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_
      8 #define LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_
      9 
     10 #include "Element.h"
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/Attributes.h"
     13 #include "mozilla/Likely.h"
     14 #include "mozilla/dom/Document.h"
     15 #include "nsDebug.h"
     16 #include "nsIFrame.h"
     17 
     18 namespace mozilla {
     19 
     20 /**
     21 * This helper class helps us to protect against two related issues that can
     22 * occur in SVG content: reference loops, and reference chains that we deem to
     23 * be too long.
     24 *
     25 * Some SVG effects can reference another effect of the same type to produce
     26 * a chain of effects to be applied (e.g. clipPath), while in other cases it is
     27 * possible that while processing an effect of a certain type another effect
     28 * of the same type may be encountered indirectly (e.g. pattern).  In order to
     29 * avoid stack overflow crashes and performance issues we need to impose an
     30 * arbitrary limit on the length of the reference chains that SVG content may
     31 * try to create.  (Some SVG authoring tools have been known to create absurdly
     32 * long reference chains.  For example, bug 1253590 details a case where Adobe
     33 * Illustrator was used to created an SVG with a chain of 5000 clip paths which
     34 * could cause us to run out of stack space and crash.)
     35 *
     36 * This class is intended to be used with the nsIFrame's of SVG effects that
     37 * may involve reference chains.  To use it add a boolean member, something
     38 * like this:
     39 *
     40 *   // Flag used to indicate whether a methods that may reenter due to
     41 *   // following a reference to another instance is currently executing.
     42 *   bool mIsBeingProcessed;
     43 *
     44 * Make sure to initialize the member to false in the class' constructons.
     45 *
     46 * Then add the following to the top of any methods that may be reentered due
     47 * to following a reference:
     48 *
     49 *   static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
     50 *
     51 *   AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
     52 *                                         &sRefChainLengthCounter);
     53 *   if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
     54 *     return; // Break reference chain
     55 *   }
     56 *
     57 * Note that mIsBeingProcessed and sRefChainLengthCounter should never be used
     58 * by the frame except when it initialize them as indicated above.
     59 */
     60 class MOZ_RAII AutoReferenceChainGuard {
     61  static const int16_t sDefaultMaxChainLength = 10;  // arbitrary length
     62 
     63 public:
     64  static const int16_t noChain = -2;
     65 
     66  /**
     67   * @param aFrame The frame for an effect that may involve a reference chain.
     68   * @param aFrameInUse The member variable on aFrame that is used to indicate
     69   *   whether one of aFrame's methods that may involve following a reference
     70   *   to another effect of the same type is currently being executed.
     71   * @param aChainCounter A static variable in the method in which this class
     72   *   is instantiated that is used to keep track of how many times the method
     73   *   is reentered (and thus how long the a reference chain is).
     74   * @param aMaxChainLength The maximum number of links that are allowed in
     75   *   a reference chain.
     76   */
     77  AutoReferenceChainGuard(nsIFrame* aFrame, bool* aFrameInUse,
     78                          int16_t* aChainCounter,
     79                          int16_t aMaxChainLength = sDefaultMaxChainLength)
     80      : mFrame(aFrame),
     81        mFrameInUse(aFrameInUse),
     82        mChainCounter(aChainCounter),
     83        mMaxChainLength(aMaxChainLength),
     84        mBrokeReference(false) {
     85    MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter);
     86    MOZ_ASSERT(aMaxChainLength > 0);
     87    MOZ_ASSERT(*aChainCounter == noChain ||
     88               (*aChainCounter >= 0 && *aChainCounter < aMaxChainLength));
     89  }
     90 
     91  ~AutoReferenceChainGuard() {
     92    if (mBrokeReference) {
     93      // We didn't change mFrameInUse or mChainCounter
     94      return;
     95    }
     96 
     97    *mFrameInUse = false;
     98 
     99    // If we fail this assert then there were more destructor calls than
    100    // Reference() calls (a consumer forgot to to call Reference()), or else
    101    // someone messed with the variable pointed to by mChainCounter.
    102    MOZ_ASSERT(*mChainCounter < mMaxChainLength);
    103 
    104    (*mChainCounter)++;
    105 
    106    if (*mChainCounter == mMaxChainLength) {
    107      *mChainCounter = noChain;  // reset ready for use next time
    108    }
    109  }
    110 
    111  /**
    112   * Returns true on success (no reference loop/reference chain length is
    113   * within the specified limits), else returns false on failure (there is a
    114   * reference loop/the reference chain has exceeded the specified limits).
    115   * If it returns false then an error message will be reported to the DevTools
    116   * console (only once).
    117   */
    118  [[nodiscard]] bool Reference() {
    119    if (MOZ_UNLIKELY(*mFrameInUse)) {
    120      mBrokeReference = true;
    121      ReportErrorToConsole();
    122      return false;
    123    }
    124 
    125    if (*mChainCounter == noChain) {
    126      // Initialize - we start at aMaxChainLength and decrement towards zero.
    127      *mChainCounter = mMaxChainLength;
    128    } else {
    129      // If we fail this assertion then either a consumer failed to break a
    130      // reference loop/chain, or else they called Reference() more than once
    131      MOZ_ASSERT(*mChainCounter >= 0);
    132 
    133      if (MOZ_UNLIKELY(*mChainCounter < 1)) {
    134        mBrokeReference = true;
    135        ReportErrorToConsole();
    136        return false;
    137      }
    138    }
    139 
    140    // We only set these once we know we're returning true.
    141    *mFrameInUse = true;
    142    (*mChainCounter)--;
    143 
    144    return true;
    145  }
    146 
    147 private:
    148  void ReportErrorToConsole() {
    149    AutoTArray<nsString, 2> params;
    150    dom::Element* element = mFrame->GetContent()->AsElement();
    151    element->GetTagName(*params.AppendElement());
    152    element->GetId(*params.AppendElement());
    153    auto doc = mFrame->GetContent()->OwnerDoc();
    154    auto warning = *mFrameInUse ? dom::Document::eSVGRefLoop
    155                                : dom::Document::eSVGRefChainLengthExceeded;
    156    doc->WarnOnceAbout(warning, /* asError */ true, params);
    157  }
    158 
    159  nsIFrame* mFrame;
    160  bool* mFrameInUse;
    161  int16_t* mChainCounter;
    162  const int16_t mMaxChainLength;
    163  bool mBrokeReference;
    164 };
    165 
    166 }  // namespace mozilla
    167 
    168 #endif  // LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_