• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/renderer/extensions/safe_builtins.h"
6 
7 #include "base/logging.h"
8 #include "base/stl_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/renderer/extensions/chrome_v8_context.h"
11 
12 namespace extensions {
13 
14 namespace {
15 
16 const char kClassName[] = "extensions::SafeBuiltins";
17 
18 // Documentation for makeCallback in the JavaScript, out here to reduce the
19 // (very small) amount of effort that the v8 parser needs to do:
20 //
21 // Returns a new object with every function on |obj| configured to call()\n"
22 // itself with the given arguments.\n"
23 // E.g. given\n"
24 //    var result = makeCallable(Function.prototype)\n"
25 // |result| will be a object including 'bind' such that\n"
26 //    result.bind(foo, 1, 2, 3);\n"
27 // is equivalent to Function.prototype.bind.call(foo, 1, 2, 3), and so on.\n"
28 // This is a convenient way to save functions that user scripts may clobber.\n"
29 const char kScript[] =
30     "(function() {\n"
31     "'use strict';\n"
32     "native function Apply();\n"
33     "native function Save();\n"
34     "\n"
35     "// Used in the callback implementation, could potentially be clobbered.\n"
36     "function makeCallable(obj, target, isStatic, propertyNames) {\n"
37     "  propertyNames.forEach(function(propertyName) {\n"
38     "    var property = obj[propertyName];\n"
39     "    target[propertyName] = function() {\n"
40     "      var recv = obj;\n"
41     "      var firstArgIndex = 0;\n"
42     "      if (!isStatic) {\n"
43     "        if (arguments.length == 0)\n"
44     "          throw 'There must be at least one argument, the recevier';\n"
45     "        recv = arguments[0];\n"
46     "        firstArgIndex = 1;\n"
47     "      }\n"
48     "      return Apply(\n"
49     "          property, recv, arguments, firstArgIndex, arguments.length);\n"
50     "    };\n"
51     "  });\n"
52     "}\n"
53     "\n"
54     "function saveBuiltin(builtin, protoPropertyNames, staticPropertyNames) {\n"
55     "  var safe = function() {\n"
56     "    throw 'Safe objects cannot be called nor constructed. ' +\n"
57     "          'Use $Foo.self() or new $Foo.self() instead.';\n"
58     "  };\n"
59     "  safe.self = builtin;\n"
60     "  makeCallable(builtin.prototype, safe, false, protoPropertyNames);\n"
61     "  if (staticPropertyNames)\n"
62     "    makeCallable(builtin, safe, true, staticPropertyNames);\n"
63     "  Save(builtin.name, safe);\n"
64     "}\n"
65     "\n"
66     "// Save only what is needed to make tests that override builtins pass.\n"
67     "saveBuiltin(Object,\n"
68     "            ['hasOwnProperty'],\n"
69     "            ['getPrototypeOf', 'keys']);\n"
70     "saveBuiltin(Function,\n"
71     "            ['apply', 'bind', 'call']);\n"
72     "saveBuiltin(Array,\n"
73     "            ['concat', 'forEach', 'indexOf', 'join', 'push', 'slice',\n"
74     "             'splice', 'map', 'filter']);\n"
75     "saveBuiltin(String,\n"
76     "            ['slice', 'split']);\n"
77     "saveBuiltin(RegExp,\n"
78     "            ['test']);\n"
79     "\n"
80     "// JSON is trickier because extensions can override toJSON in\n"
81     "// incompatible ways, and we need to prevent that.\n"
82     "var builtinTypes = [\n"
83     "  Object, Function, Array, String, Boolean, Number, Date, RegExp\n"
84     "];\n"
85     "var builtinToJSONs = builtinTypes.map(function(t) {\n"
86     "  return t.toJSON;\n"
87     "});\n"
88     "var builtinArray = Array;\n"
89     "var builtinJSONStringify = JSON.stringify;\n"
90     "Save('JSON', {\n"
91     "  parse: JSON.parse,\n"
92     "  stringify: function(obj) {\n"
93     "    var savedToJSONs = new builtinArray(builtinTypes.length);\n"
94     "    try {\n"
95     "      for (var i = 0; i < builtinTypes.length; ++i) {\n"
96     "        try {\n"
97     "          if (builtinTypes[i].prototype.toJSON !==\n"
98     "              builtinToJSONs[i]) {\n"
99     "            savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n"
100     "            builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n"
101     "          }\n"
102     "        } catch (e) {}\n"
103     "      }\n"
104     "    } catch (e) {}\n"
105     "    try {\n"
106     "      return builtinJSONStringify(obj);\n"
107     "    } finally {\n"
108     "      for (var i = 0; i < builtinTypes.length; ++i) {\n"
109     "        try {\n"
110     "          if (i in savedToJSONs)\n"
111     "            builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n"
112     "        } catch (e) {}\n"
113     "      }\n"
114     "    }\n"
115     "  }\n"
116     "});\n"
117     "\n"
118     "}());\n";
119 
MakeKey(const char * name,v8::Isolate * isolate)120 v8::Local<v8::String> MakeKey(const char* name, v8::Isolate* isolate) {
121   return v8::String::NewFromUtf8(
122       isolate, base::StringPrintf("%s::%s", kClassName, name).c_str());
123 }
124 
SaveImpl(const char * name,v8::Local<v8::Value> value,v8::Local<v8::Context> context)125 void SaveImpl(const char* name,
126               v8::Local<v8::Value> value,
127               v8::Local<v8::Context> context) {
128   CHECK(!value.IsEmpty() && value->IsObject()) << name;
129   context->Global()
130       ->SetHiddenValue(MakeKey(name, context->GetIsolate()), value);
131 }
132 
Load(const char * name,v8::Handle<v8::Context> context)133 v8::Local<v8::Object> Load(const char* name, v8::Handle<v8::Context> context) {
134   v8::Local<v8::Value> value =
135       context->Global()->GetHiddenValue(MakeKey(name, context->GetIsolate()));
136   CHECK(!value.IsEmpty() && value->IsObject()) << name;
137   return value->ToObject();
138 }
139 
140 class ExtensionImpl : public v8::Extension {
141  public:
ExtensionImpl()142   ExtensionImpl() : v8::Extension(kClassName, kScript) {}
143 
144  private:
GetNativeFunctionTemplate(v8::Isolate * isolate,v8::Handle<v8::String> name)145   virtual v8::Handle<v8::FunctionTemplate> GetNativeFunctionTemplate(
146       v8::Isolate* isolate,
147       v8::Handle<v8::String> name) OVERRIDE {
148     if (name->Equals(v8::String::NewFromUtf8(isolate, "Apply")))
149       return v8::FunctionTemplate::New(isolate, Apply);
150     if (name->Equals(v8::String::NewFromUtf8(isolate, "Save")))
151       return v8::FunctionTemplate::New(isolate, Save);
152     NOTREACHED() << *v8::String::Utf8Value(name);
153     return v8::Handle<v8::FunctionTemplate>();
154   }
155 
Apply(const v8::FunctionCallbackInfo<v8::Value> & info)156   static void Apply(const v8::FunctionCallbackInfo<v8::Value>& info) {
157     CHECK(info.Length() == 5 &&
158           info[0]->IsFunction() &&  // function
159           // info[1] could be an object or a string
160           info[2]->IsObject() &&    // args
161           info[3]->IsInt32() &&     // first_arg_index
162           info[4]->IsInt32());      // args_length
163     v8::Local<v8::Function> function = info[0].As<v8::Function>();
164     v8::Local<v8::Object> recv;
165     if (info[1]->IsObject()) {
166       recv = info[1]->ToObject();
167     } else if (info[1]->IsString()) {
168       recv = v8::StringObject::New(info[1]->ToString())->ToObject();
169     } else {
170       info.GetIsolate()->ThrowException(
171           v8::Exception::TypeError(v8::String::NewFromUtf8(
172               info.GetIsolate(),
173               "The first argument is the receiver and must be an object")));
174       return;
175     }
176     v8::Local<v8::Object> args = info[2]->ToObject();
177     int first_arg_index = static_cast<int>(info[3]->ToInt32()->Value());
178     int args_length = static_cast<int>(info[4]->ToInt32()->Value());
179 
180     int argc = args_length - first_arg_index;
181     scoped_ptr<v8::Local<v8::Value>[]> argv(new v8::Local<v8::Value>[argc]);
182     for (int i = 0; i < argc; ++i) {
183       CHECK(args->Has(i + first_arg_index));
184       argv[i] = args->Get(i + first_arg_index);
185     }
186 
187     v8::Local<v8::Value> return_value = function->Call(recv, argc, argv.get());
188     if (!return_value.IsEmpty())
189       info.GetReturnValue().Set(return_value);
190   }
191 
Save(const v8::FunctionCallbackInfo<v8::Value> & info)192   static void Save(const v8::FunctionCallbackInfo<v8::Value>& info) {
193     CHECK(info.Length() == 2 &&
194           info[0]->IsString() &&
195           info[1]->IsObject());
196     SaveImpl(*v8::String::Utf8Value(info[0]),
197              info[1],
198              info.GetIsolate()->GetCallingContext());
199   }
200 };
201 
202 }  // namespace
203 
204 // static
CreateV8Extension()205 v8::Extension* SafeBuiltins::CreateV8Extension() {
206   return new ExtensionImpl();
207 }
208 
SafeBuiltins(ChromeV8Context * context)209 SafeBuiltins::SafeBuiltins(ChromeV8Context* context) : context_(context) {}
210 
~SafeBuiltins()211 SafeBuiltins::~SafeBuiltins() {}
212 
GetArray() const213 v8::Local<v8::Object> SafeBuiltins::GetArray() const {
214   return Load("Array", context_->v8_context());
215 }
216 
GetFunction() const217 v8::Local<v8::Object> SafeBuiltins::GetFunction() const {
218   return Load("Function", context_->v8_context());
219 }
220 
GetJSON() const221 v8::Local<v8::Object> SafeBuiltins::GetJSON() const {
222   return Load("JSON", context_->v8_context());
223 }
224 
GetObjekt() const225 v8::Local<v8::Object> SafeBuiltins::GetObjekt() const {
226   return Load("Object", context_->v8_context());
227 }
228 
GetRegExp() const229 v8::Local<v8::Object> SafeBuiltins::GetRegExp() const {
230   return Load("RegExp", context_->v8_context());
231 }
232 
GetString() const233 v8::Local<v8::Object> SafeBuiltins::GetString() const {
234   return Load("String", context_->v8_context());
235 }
236 
237 } //  namespace extensions
238