tor-browser

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

SavedStacks.h (15192B)


      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 vm_SavedStacks_h
      8 #define vm_SavedStacks_h
      9 
     10 #include "mozilla/Attributes.h"
     11 #include "mozilla/FastBernoulliTrial.h"
     12 #include "mozilla/Maybe.h"
     13 
     14 #include "js/ColumnNumber.h"  // JS::TaggedColumnNumberOneOrigin
     15 #include "js/HashTable.h"
     16 #include "js/Stack.h"
     17 #include "vm/SavedFrame.h"
     18 
     19 namespace JS {
     20 enum class SavedFrameSelfHosted;
     21 }
     22 
     23 namespace js {
     24 
     25 class FrameIter;
     26 
     27 // # Saved Stacks
     28 //
     29 // The `SavedStacks` class provides a compact way to capture and save JS stacks
     30 // as `SavedFrame` `JSObject` subclasses. A single `SavedFrame` object
     31 // represents one frame that was on the stack, and has a strong reference to its
     32 // parent `SavedFrame` (the next youngest frame). This reference is null when
     33 // the `SavedFrame` object is the oldest frame that was on the stack.
     34 //
     35 // This comment documents implementation. For usage documentation, see the
     36 // `js/src/doc/SavedFrame/SavedFrame.md` file and relevant `SavedFrame`
     37 // functions in `js/src/jsapi.h`.
     38 //
     39 // ## Compact
     40 //
     41 // Older saved stack frame tails are shared via hash consing, to deduplicate
     42 // structurally identical data. `SavedStacks` contains a hash table of weakly
     43 // held `SavedFrame` objects, and when the owning compartment is swept, it
     44 // removes entries from this table that aren't held alive in any other way. When
     45 // saving new stacks, we use this table to find pre-existing `SavedFrame`
     46 // objects. If such an object is already extant, it is reused; otherwise a new
     47 // `SavedFrame` is allocated and inserted into the table.
     48 //
     49 //    Naive         |   Hash Consing
     50 //    --------------+------------------
     51 //    c -> b -> a   |   c -> b -> a
     52 //                  |        ^
     53 //    d -> b -> a   |   d ---|
     54 //                  |        |
     55 //    e -> b -> a   |   e ---'
     56 //
     57 // This technique is effective because of the nature of the events that trigger
     58 // capturing the stack. Currently, these events consist primarily of `JSObject`
     59 // allocation (when an observing `Debugger` has such tracking), `Promise`
     60 // settlement, and `Error` object creation. While these events may occur many
     61 // times, they tend to occur only at a few locations in the JS source. For
     62 // example, if we enable Object allocation tracking and run the esprima
     63 // JavaScript parser on its own JavaScript source, there are approximately 54700
     64 // total `Object` allocations, but just ~1400 unique JS stacks at allocation
     65 // time. There's only ~200 allocation sites if we capture only the youngest
     66 // stack frame.
     67 //
     68 // ## Security and Wrappers
     69 //
     70 // We save every frame on the stack, regardless of whether the `SavedStack`'s
     71 // compartment's principals subsume the frame's compartment's principals or
     72 // not. This gives us maximum flexibility down the line when accessing and
     73 // presenting captured stacks, but at the price of some complication involved in
     74 // preventing the leakage of privileged stack frames to unprivileged callers.
     75 //
     76 // When a `SavedFrame` method or accessor is called, we compare the caller's
     77 // compartment's principals to each `SavedFrame`'s captured principals. We avoid
     78 // using the usual `CallNonGenericMethod` and `nativeCall` machinery which
     79 // enters the `SavedFrame` object's compartment before we can check these
     80 // principals, because we need access to the original caller's compartment's
     81 // principals (unlike other `CallNonGenericMethod` users) to determine what view
     82 // of the stack to present. Instead, we take a similar approach to that used by
     83 // DOM methods, and manually unwrap wrappers until we get the underlying
     84 // `SavedFrame` object, find the first `SavedFrame` in its stack whose captured
     85 // principals are subsumed by the caller's principals, access the reserved slots
     86 // we care about, and then rewrap return values as necessary.
     87 //
     88 // Consider the following diagram:
     89 //
     90 //                                              Content Compartment
     91 //                                    +---------------------------------------+
     92 //                                    |                                       |
     93 //                                    |           +------------------------+  |
     94 //      Chrome Compartment            |           |                        |  |
     95 //    +--------------------+          |           | SavedFrame C (content) |  |
     96 //    |                    |          |           |                        |  |
     97 //    |                  +--------------+         +------------------------+  |
     98 //    |                  |              |                    ^                |
     99 //    |     var x -----> | Xray Wrapper |-----.              |                |
    100 //    |                  |              |     |              |                |
    101 //    |                  +--------------+     |   +------------------------+  |
    102 //    |                    |          |       |   |                        |  |
    103 //    |                  +--------------+     |   | SavedFrame B (content) |  |
    104 //    |                  |              |     |   |                        |  |
    105 //    |     var y -----> | CCW (waived) |--.  |   +------------------------+  |
    106 //    |                  |              |  |  |              ^                |
    107 //    |                  +--------------+  |  |              |                |
    108 //    |                    |          |    |  |              |                |
    109 //    +--------------------+          |    |  |   +------------------------+  |
    110 //                                    |    |  '-> |                        |  |
    111 //                                    |    |      | SavedFrame A (chrome)  |  |
    112 //                                    |    '----> |                        |  |
    113 //                                    |           +------------------------+  |
    114 //                                    |                      ^                |
    115 //                                    |                      |                |
    116 //                                    |           var z -----'                |
    117 //                                    |                                       |
    118 //                                    +---------------------------------------+
    119 //
    120 // CCW is a plain cross-compartment wrapper, yielded by waiving Xray vision. A
    121 // is the youngest `SavedFrame` and represents a frame that was from the chrome
    122 // compartment, while B and C are from frames from the content compartment. C is
    123 // the oldest frame.
    124 //
    125 // Note that it is always safe to waive an Xray around a SavedFrame object,
    126 // because SavedFrame objects and the SavedFrame prototype are always frozen you
    127 // will never run untrusted code.
    128 //
    129 // Depending on who the caller is, the view of the stack will be different, and
    130 // is summarized in the table below.
    131 //
    132 //    Var  | View
    133 //    -----+------------
    134 //    x    | A -> B -> C
    135 //    y, z | B -> C
    136 //
    137 // In the case of x, the `SavedFrame` accessors are called with an Xray wrapper
    138 // around the `SavedFrame` object as the `this` value, and the chrome
    139 // compartment as the cx's current principals. Because the chrome compartment's
    140 // principals subsume both itself and the content compartment's principals, x
    141 // has the complete view of the stack.
    142 //
    143 // In the case of y, the cross-compartment machinery automatically enters the
    144 // content compartment, and calls the `SavedFrame` accessors with the wrapped
    145 // `SavedFrame` object as the `this` value. Because the cx's current compartment
    146 // is the content compartment, and the content compartment's principals do not
    147 // subsume the chrome compartment's principals, it can only see the B and C
    148 // frames.
    149 //
    150 // In the case of z, the `SavedFrame` accessors are called with the `SavedFrame`
    151 // object in the `this` value, and the content compartment as the cx's current
    152 // compartment. Similar to the case of y, only the B and C frames are exposed
    153 // because the cx's current compartment's principals do not subsume A's captured
    154 // principals.
    155 
    156 class SavedStacks {
    157  friend class SavedFrame;
    158  friend bool JS::ubi::ConstructSavedFrameStackSlow(
    159      JSContext* cx, JS::ubi::StackFrame& ubiFrame,
    160      MutableHandleObject outSavedFrameStack);
    161 
    162 public:
    163  SavedStacks()
    164      : bernoulliSeeded(false),
    165        bernoulli(1.0, 0x59fdad7f6b4cc573, 0x91adf38db96a9354),
    166        creatingSavedFrame(false) {}
    167 
    168  [[nodiscard]] bool saveCurrentStack(
    169      JSContext* cx, MutableHandle<SavedFrame*> frame,
    170      JS::StackCapture&& capture = JS::StackCapture(JS::AllFrames()),
    171      HandleObject startAt = nullptr);
    172  [[nodiscard]] bool copyAsyncStack(
    173      JSContext* cx, HandleObject asyncStack, HandleString asyncCause,
    174      MutableHandle<SavedFrame*> adoptedStack,
    175      const mozilla::Maybe<size_t>& maxFrameCount);
    176  void traceWeak(JSTracer* trc);
    177  void trace(JSTracer* trc);
    178  uint32_t count();
    179  void clear();
    180  void chooseSamplingProbability(JS::Realm* realm);
    181 
    182  // Set the sampling random number generator's state to |state0| and
    183  // |state1|. One or the other must be non-zero. See the comments for
    184  // mozilla::non_crypto::XorShift128PlusRNG::setState for details.
    185  void setRNGState(uint64_t state0, uint64_t state1) {
    186    bernoulli.setRandomState(state0, state1);
    187  }
    188 
    189  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
    190 
    191  // An alloction metadata builder that marks cells with the JavaScript stack
    192  // at which they were allocated.
    193  struct MetadataBuilder : public AllocationMetadataBuilder {
    194    MetadataBuilder() = default;
    195    virtual JSObject* build(JSContext* cx, HandleObject obj,
    196                            AutoEnterOOMUnsafeRegion& oomUnsafe) const override;
    197  };
    198 
    199  static const MetadataBuilder metadataBuilder;
    200 
    201 private:
    202  SavedFrame::Set frames;
    203  bool bernoulliSeeded;
    204  mozilla::FastBernoulliTrial bernoulli;
    205  bool creatingSavedFrame;
    206 
    207  // Similar to mozilla::ReentrancyGuard, but instead of asserting against
    208  // reentrancy, just change the behavior of SavedStacks::saveCurrentStack to
    209  // return a nullptr SavedFrame.
    210  struct MOZ_RAII AutoReentrancyGuard {
    211    SavedStacks& stacks;
    212 
    213    explicit AutoReentrancyGuard(SavedStacks& stacks) : stacks(stacks) {
    214      stacks.creatingSavedFrame = true;
    215    }
    216 
    217    ~AutoReentrancyGuard() { stacks.creatingSavedFrame = false; }
    218  };
    219 
    220  [[nodiscard]] bool insertFrames(JSContext* cx,
    221                                  MutableHandle<SavedFrame*> frame,
    222                                  JS::StackCapture&& capture,
    223                                  HandleObject startAt);
    224  [[nodiscard]] bool adoptAsyncStack(
    225      JSContext* cx, MutableHandle<SavedFrame*> asyncStack,
    226      Handle<JSAtom*> asyncCause, const mozilla::Maybe<size_t>& maxFrameCount);
    227  [[nodiscard]] bool checkForEvalInFramePrev(
    228      JSContext* cx, MutableHandle<SavedFrame::Lookup> lookup);
    229  SavedFrame* getOrCreateSavedFrame(JSContext* cx,
    230                                    Handle<SavedFrame::Lookup> lookup);
    231  SavedFrame* createFrameFromLookup(JSContext* cx,
    232                                    Handle<SavedFrame::Lookup> lookup);
    233  void setSamplingProbability(double probability);
    234 
    235  // Cache for memoizing PCToLineNumber lookups.
    236 
    237  struct PCKey {
    238    PCKey(JSScript* script, jsbytecode* pc) : script(script), pc(pc) {}
    239 
    240    WeakHeapPtr<JSScript*> script;
    241    jsbytecode* pc;
    242 
    243    void trace(JSTracer* trc) { /* PCKey is weak. */ }
    244    bool traceWeak(JSTracer* trc) {
    245      return TraceWeakEdge(trc, &script, "traceWeak");
    246    }
    247  };
    248 
    249 public:
    250  struct LocationValue {
    251    LocationValue() : source(nullptr), sourceId(0), line(0) {}
    252    LocationValue(JSAtom* source, uint32_t sourceId, size_t line,
    253                  JS::TaggedColumnNumberOneOrigin column)
    254        : source(source), sourceId(sourceId), line(line), column(column) {}
    255 
    256    void trace(JSTracer* trc) {
    257      TraceNullableEdge(trc, &source, "SavedStacks::LocationValue::source");
    258    }
    259 
    260    bool traceWeak(JSTracer* trc) {
    261      MOZ_ASSERT(source);
    262      // TODO: Bug 1501334: IsAboutToBeFinalized doesn't work for atoms.
    263      // Otherwise we should assert TraceWeakEdge always returns true;
    264      return TraceWeakEdge(trc, &source, "traceWeak");
    265    }
    266 
    267    WeakHeapPtr<JSAtom*> source;
    268    uint32_t sourceId;
    269 
    270    // Line number (1-origin).
    271    size_t line;
    272 
    273    // Column number in UTF-16 code units.
    274    JS::TaggedColumnNumberOneOrigin column;
    275  };
    276 
    277 private:
    278  struct PCLocationHasher : public DefaultHasher<PCKey> {
    279    using ScriptPtrHasher = DefaultHasher<JSScript*>;
    280    using BytecodePtrHasher = DefaultHasher<jsbytecode*>;
    281 
    282    static HashNumber hash(const PCKey& key) {
    283      return mozilla::AddToHash(ScriptPtrHasher::hash(key.script),
    284                                BytecodePtrHasher::hash(key.pc));
    285    }
    286 
    287    static bool match(const PCKey& l, const PCKey& k) {
    288      return ScriptPtrHasher::match(l.script, k.script) &&
    289             BytecodePtrHasher::match(l.pc, k.pc);
    290    }
    291  };
    292 
    293  // We eagerly Atomize the script source stored in LocationValue because
    294  // wasm does not always have a JSScript and the source might not be
    295  // available when we need it later. However, since the JSScript does not
    296  // actually hold this atom, we have to trace it strongly to keep it alive.
    297  // Thus, it takes two GC passes to fully clean up this table: the first GC
    298  // removes the dead script; the second will clear out the source atom since
    299  // it is no longer held by the table.
    300  using PCLocationMap =
    301      GCHashMap<PCKey, LocationValue, PCLocationHasher, SystemAllocPolicy>;
    302  PCLocationMap pcLocationMap;
    303 
    304  [[nodiscard]] bool getLocation(JSContext* cx, const FrameIter& iter,
    305                                 MutableHandle<LocationValue> locationp);
    306 };
    307 
    308 template <typename Wrapper>
    309 struct WrappedPtrOperations<SavedStacks::LocationValue, Wrapper> {
    310  JSAtom* source() const { return loc().source; }
    311  uint32_t sourceId() const { return loc().sourceId; }
    312  size_t line() const { return loc().line; }
    313  JS::TaggedColumnNumberOneOrigin column() const { return loc().column; }
    314 
    315 private:
    316  const SavedStacks::LocationValue& loc() const {
    317    return static_cast<const Wrapper*>(this)->get();
    318  }
    319 };
    320 
    321 template <typename Wrapper>
    322 struct MutableWrappedPtrOperations<SavedStacks::LocationValue, Wrapper>
    323    : public WrappedPtrOperations<SavedStacks::LocationValue, Wrapper> {
    324  void setSource(JSAtom* v) { loc().source = v; }
    325  void setSourceId(uint32_t v) { loc().sourceId = v; }
    326  void setLine(size_t v) { loc().line = v; }
    327  void setColumn(JS::TaggedColumnNumberOneOrigin v) { loc().column = v; }
    328 
    329 private:
    330  SavedStacks::LocationValue& loc() {
    331    return static_cast<Wrapper*>(this)->get();
    332  }
    333 };
    334 
    335 JS::UniqueChars BuildUTF8StackString(JSContext* cx, JSPrincipals* principals,
    336                                     HandleObject stack);
    337 
    338 js::SavedFrame* UnwrapSavedFrame(JSContext* cx, JSPrincipals* principals,
    339                                 HandleObject obj,
    340                                 JS::SavedFrameSelfHosted selfHosted,
    341                                 bool& skippedAsync);
    342 
    343 } /* namespace js */
    344 
    345 #endif /* vm_SavedStacks_h */