1 // Copyright 2014 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 "extensions/renderer/safe_builtins.h"
6
7 #include "base/logging.h"
8 #include "base/stl_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "extensions/renderer/script_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 " ['create', 'defineProperty', 'getOwnPropertyDescriptor',\n"
70 " 'getPrototypeOf', 'keys']);\n"
71 "saveBuiltin(Function,\n"
72 " ['apply', 'bind', 'call']);\n"
73 "saveBuiltin(Array,\n"
74 " ['concat', 'forEach', 'indexOf', 'join', 'push', 'slice',\n"
75 " 'splice', 'map', 'filter']);\n"
76 "saveBuiltin(String,\n"
77 " ['slice', 'split']);\n"
78 "saveBuiltin(RegExp,\n"
79 " ['test']);\n"
80 "\n"
81 "// JSON is trickier because extensions can override toJSON in\n"
82 "// incompatible ways, and we need to prevent that.\n"
83 "var builtinTypes = [\n"
84 " Object, Function, Array, String, Boolean, Number, Date, RegExp\n"
85 "];\n"
86 "var builtinToJSONs = builtinTypes.map(function(t) {\n"
87 " return t.toJSON;\n"
88 "});\n"
89 "var builtinArray = Array;\n"
90 "var builtinJSONStringify = JSON.stringify;\n"
91 "Save('JSON', {\n"
92 " parse: JSON.parse,\n"
93 " stringify: function(obj) {\n"
94 " var savedToJSONs = new builtinArray(builtinTypes.length);\n"
95 " try {\n"
96 " for (var i = 0; i < builtinTypes.length; ++i) {\n"
97 " try {\n"
98 " if (builtinTypes[i].prototype.toJSON !==\n"
99 " builtinToJSONs[i]) {\n"
100 " savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n"
101 " builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n"
102 " }\n"
103 " } catch (e) {}\n"
104 " }\n"
105 " } catch (e) {}\n"
106 " try {\n"
107 " return builtinJSONStringify(obj);\n"
108 " } finally {\n"
109 " for (var i = 0; i < builtinTypes.length; ++i) {\n"
110 " try {\n"
111 " if (i in savedToJSONs)\n"
112 " builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n"
113 " } catch (e) {}\n"
114 " }\n"
115 " }\n"
116 " }\n"
117 "});\n"
118 "\n"
119 "}());\n";
120
MakeKey(const char * name,v8::Isolate * isolate)121 v8::Local<v8::String> MakeKey(const char* name, v8::Isolate* isolate) {
122 return v8::String::NewFromUtf8(
123 isolate, base::StringPrintf("%s::%s", kClassName, name).c_str());
124 }
125
SaveImpl(const char * name,v8::Local<v8::Value> value,v8::Local<v8::Context> context)126 void SaveImpl(const char* name,
127 v8::Local<v8::Value> value,
128 v8::Local<v8::Context> context) {
129 CHECK(!value.IsEmpty() && value->IsObject()) << name;
130 context->Global()->SetHiddenValue(MakeKey(name, context->GetIsolate()),
131 value);
132 }
133
Load(const char * name,v8::Handle<v8::Context> context)134 v8::Local<v8::Object> Load(const char* name, v8::Handle<v8::Context> context) {
135 v8::Local<v8::Value> value =
136 context->Global()->GetHiddenValue(MakeKey(name, context->GetIsolate()));
137 CHECK(!value.IsEmpty() && value->IsObject()) << name;
138 return value->ToObject();
139 }
140
141 class ExtensionImpl : public v8::Extension {
142 public:
ExtensionImpl()143 ExtensionImpl() : v8::Extension(kClassName, kScript) {}
144
145 private:
GetNativeFunctionTemplate(v8::Isolate * isolate,v8::Handle<v8::String> name)146 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunctionTemplate(
147 v8::Isolate* isolate,
148 v8::Handle<v8::String> name) OVERRIDE {
149 if (name->Equals(v8::String::NewFromUtf8(isolate, "Apply")))
150 return v8::FunctionTemplate::New(isolate, Apply);
151 if (name->Equals(v8::String::NewFromUtf8(isolate, "Save")))
152 return v8::FunctionTemplate::New(isolate, Save);
153 NOTREACHED() << *v8::String::Utf8Value(name);
154 return v8::Handle<v8::FunctionTemplate>();
155 }
156
Apply(const v8::FunctionCallbackInfo<v8::Value> & info)157 static void Apply(const v8::FunctionCallbackInfo<v8::Value>& info) {
158 CHECK(info.Length() == 5 && 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 && info[0]->IsString() && info[1]->IsObject());
194 SaveImpl(*v8::String::Utf8Value(info[0]),
195 info[1],
196 info.GetIsolate()->GetCallingContext());
197 }
198 };
199
200 } // namespace
201
202 // static
CreateV8Extension()203 v8::Extension* SafeBuiltins::CreateV8Extension() { return new ExtensionImpl(); }
204
SafeBuiltins(ScriptContext * context)205 SafeBuiltins::SafeBuiltins(ScriptContext* context) : context_(context) {}
206
~SafeBuiltins()207 SafeBuiltins::~SafeBuiltins() {}
208
GetArray() const209 v8::Local<v8::Object> SafeBuiltins::GetArray() const {
210 return Load("Array", context_->v8_context());
211 }
212
GetFunction() const213 v8::Local<v8::Object> SafeBuiltins::GetFunction() const {
214 return Load("Function", context_->v8_context());
215 }
216
GetJSON() const217 v8::Local<v8::Object> SafeBuiltins::GetJSON() const {
218 return Load("JSON", context_->v8_context());
219 }
220
GetObjekt() const221 v8::Local<v8::Object> SafeBuiltins::GetObjekt() const {
222 return Load("Object", context_->v8_context());
223 }
224
GetRegExp() const225 v8::Local<v8::Object> SafeBuiltins::GetRegExp() const {
226 return Load("RegExp", context_->v8_context());
227 }
228
GetString() const229 v8::Local<v8::Object> SafeBuiltins::GetString() const {
230 return Load("String", context_->v8_context());
231 }
232
233 } // namespace extensions
234