filter out .caller from other worlds
authordcarney@chromium.org <dcarney@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 19 May 2014 13:45:45 +0000 (13:45 +0000)
committerdcarney@chromium.org <dcarney@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 19 May 2014 13:45:45 +0000 (13:45 +0000)
R=verwaest@chromium.org

BUG=

Review URL: https://codereview.chromium.org/261103002

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@21366 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

include/v8.h
src/accessors.cc
src/api.cc
src/contexts.h
src/isolate.cc
test/mjsunit/cross-realm-filtering.js [new file with mode: 0644]

index 9843933..94b7fa1 100644 (file)
@@ -1201,6 +1201,7 @@ class V8_EXPORT StackTrace {
     kIsConstructor = 1 << 5,
     kScriptNameOrSourceURL = 1 << 6,
     kScriptId = 1 << 7,
+    kExposeFramesAcrossSecurityOrigins = 1 << 8,
     kOverview = kLineNumber | kColumnOffset | kScriptName | kFunctionName,
     kDetailed = kOverview | kIsEval | kIsConstructor | kScriptNameOrSourceURL
   };
index f219bed..6f2c01b 100644 (file)
@@ -1124,22 +1124,33 @@ Handle<AccessorInfo> Accessors::FunctionArgumentsInfo(
 //
 
 
+static inline bool AllowAccessToFunction(Context* current_context,
+                                         JSFunction* function) {
+  return current_context->HasSameSecurityTokenAs(function->context());
+}
+
+
 class FrameFunctionIterator {
  public:
   FrameFunctionIterator(Isolate* isolate, const DisallowHeapAllocation& promise)
-      : frame_iterator_(isolate),
+      : isolate_(isolate),
+        frame_iterator_(isolate),
         functions_(2),
         index_(0) {
     GetFunctions();
   }
   JSFunction* next() {
-    if (functions_.length() == 0) return NULL;
-    JSFunction* next_function = functions_[index_];
-    index_--;
-    if (index_ < 0) {
-      GetFunctions();
+    while (true) {
+      if (functions_.length() == 0) return NULL;
+      JSFunction* next_function = functions_[index_];
+      index_--;
+      if (index_ < 0) {
+        GetFunctions();
+      }
+      // Skip functions from other origins.
+      if (!AllowAccessToFunction(isolate_->context(), next_function)) continue;
+      return next_function;
     }
-    return next_function;
   }
 
   // Iterate through functions until the first occurence of 'function'.
@@ -1164,6 +1175,7 @@ class FrameFunctionIterator {
     frame_iterator_.Advance();
     index_ = functions_.length() - 1;
   }
+  Isolate* isolate_;
   JavaScriptFrameIterator frame_iterator_;
   List<JSFunction*> functions_;
   int index_;
@@ -1211,6 +1223,10 @@ MaybeHandle<JSFunction> FindCaller(Isolate* isolate,
   if (caller->shared()->strict_mode() == STRICT) {
     return MaybeHandle<JSFunction>();
   }
+  // Don't return caller from another security context.
+  if (!AllowAccessToFunction(isolate->context(), caller)) {
+    return MaybeHandle<JSFunction>();
+  }
   return Handle<JSFunction>(caller);
 }
 
index 7d4ea16..4b305b0 100644 (file)
@@ -2115,6 +2115,9 @@ Local<StackTrace> StackTrace::CurrentStackTrace(
     StackTraceOptions options) {
   i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
   ENTER_V8(i_isolate);
+  // TODO(dcarney): remove when ScriptDebugServer is fixed.
+  options = static_cast<StackTraceOptions>(
+      static_cast<int>(options) | kExposeFramesAcrossSecurityOrigins);
   i::Handle<i::JSArray> stackTrace =
       i_isolate->CaptureCurrentStackTrace(frame_limit, options);
   return Utils::StackTraceToLocal(stackTrace);
index 978e582..fd60a89 100644 (file)
@@ -449,6 +449,11 @@ class Context: public FixedArray {
     return map == map->GetHeap()->global_context_map();
   }
 
+  bool HasSameSecurityTokenAs(Context* that) {
+    return this->global_object()->native_context()->security_token() ==
+        that->global_object()->native_context()->security_token();
+  }
+
   // A native context holds a list of all functions with optimized code.
   void AddOptimizedFunction(JSFunction* function);
   void RemoveOptimizedFunction(JSFunction* function);
index 99af8fa..46f6d71 100644 (file)
@@ -388,13 +388,15 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
        iter.Advance()) {
     StackFrame* raw_frame = iter.frame();
     if (IsVisibleInStackTrace(raw_frame, *caller, &seen_caller)) {
-      frames_seen++;
       JavaScriptFrame* frame = JavaScriptFrame::cast(raw_frame);
       // Set initial size to the maximum inlining level + 1 for the outermost
       // function.
       List<FrameSummary> frames(FLAG_max_inlining_levels + 1);
       frame->Summarize(&frames);
       for (int i = frames.length() - 1; i >= 0; i--) {
+        Handle<JSFunction> fun = frames[i].function();
+        // Filter out frames from other security contexts.
+        if (!this->context()->HasSameSecurityTokenAs(fun->context())) continue;
         if (cursor + 4 > elements->length()) {
           int new_capacity = JSObject::NewElementsCapacity(elements->length());
           Handle<FixedArray> new_elements =
@@ -407,7 +409,6 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
         ASSERT(cursor + 4 <= elements->length());
 
         Handle<Object> recv = frames[i].receiver();
-        Handle<JSFunction> fun = frames[i].function();
         Handle<Code> code = frames[i].code();
         Handle<Smi> offset(Smi::FromInt(frames[i].offset()), this);
         // The stack trace API should not expose receivers and function
@@ -426,6 +427,7 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
         elements->set(cursor++, *code);
         elements->set(cursor++, *offset);
       }
+      frames_seen++;
     }
   }
   elements->set(0, Smi::FromInt(sloppy_frames));
@@ -480,10 +482,14 @@ Handle<JSArray> Isolate::CaptureCurrentStackTrace(
     List<FrameSummary> frames(FLAG_max_inlining_levels + 1);
     frame->Summarize(&frames);
     for (int i = frames.length() - 1; i >= 0 && frames_seen < limit; i--) {
+      Handle<JSFunction> fun = frames[i].function();
+      // Filter frames from other security contexts.
+      if (!(options & StackTrace::kExposeFramesAcrossSecurityOrigins) &&
+          !this->context()->HasSameSecurityTokenAs(fun->context())) continue;
+
       // Create a JSObject to hold the information for the StackFrame.
       Handle<JSObject> stack_frame = factory()->NewJSObject(object_function());
 
-      Handle<JSFunction> fun = frames[i].function();
       Handle<Script> script(Script::cast(fun->shared()->script()));
 
       if (options & StackTrace::kLineNumber) {
diff --git a/test/mjsunit/cross-realm-filtering.js b/test/mjsunit/cross-realm-filtering.js
new file mode 100644 (file)
index 0000000..9523e8c
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var realms = [Realm.current(), Realm.create()];
+
+// Check stack trace filtering across security contexts.
+var thrower_script =
+    "(function () { Realm.eval(Realm.current(), 'throw Error()') })";
+Realm.shared = {
+  thrower_0: Realm.eval(realms[0], thrower_script),
+  thrower_1: Realm.eval(realms[1], thrower_script),
+};
+
+var script = "                                                                 \
+  Error.prepareStackTrace = function(a, b) { return b; };                      \
+  try {                                                                        \
+    Realm.shared.thrower_0();                                                  \
+  } catch (e) {                                                                \
+    Realm.shared.error_0 = e.stack;                                            \
+  }                                                                            \
+  try {                                                                        \
+    Realm.shared.thrower_1();                                                  \
+  } catch (e) {                                                                \
+    Realm.shared.error_1 = e.stack;                                            \
+  }                                                                            \
+";
+
+function assertNotIn(thrower, error) {
+  for (var i = 0; i < error.length; i++) {
+    assertFalse(false === error[i].getFunction());
+  }
+}
+
+Realm.eval(realms[1], script);
+assertSame(3, Realm.shared.error_0.length);
+assertSame(4, Realm.shared.error_1.length);
+
+assertTrue(Realm.shared.thrower_1 === Realm.shared.error_1[2].getFunction());
+assertNotIn(Realm.shared.thrower_0, Realm.shared.error_0);
+assertNotIn(Realm.shared.thrower_0, Realm.shared.error_1);
+
+Realm.eval(realms[0], script);
+assertSame(5, Realm.shared.error_0.length);
+assertSame(4, Realm.shared.error_1.length);
+
+assertTrue(Realm.shared.thrower_0 === Realm.shared.error_0[2].getFunction());
+assertNotIn(Realm.shared.thrower_1, Realm.shared.error_0);
+assertNotIn(Realm.shared.thrower_1, Realm.shared.error_1);
+
+
+// Check .caller filtering across security contexts.
+var caller_script = "(function (f) { f(); })";
+Realm.shared = {
+  caller_0 : Realm.eval(realms[0], caller_script),
+  caller_1 : Realm.eval(realms[1], caller_script),
+}
+
+script = "                                                                     \
+  function f_0() { Realm.shared.result_0 = arguments.callee.caller; };         \
+  function f_1() { Realm.shared.result_1 = arguments.callee.caller; };         \
+  Realm.shared.caller_0(f_0);                                                  \
+  Realm.shared.caller_1(f_1);                                                  \
+";
+
+Realm.eval(realms[1], script);
+assertSame(null, Realm.shared.result_0);
+assertSame(Realm.shared.caller_1, Realm.shared.result_1);
+
+Realm.eval(realms[0], script);
+assertSame(Realm.shared.caller_0, Realm.shared.result_0);
+assertSame(null, Realm.shared.result_1);