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 receiver';\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 by the extension modules.\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 " ['indexOf', 'slice', 'split']);\n" 78 "saveBuiltin(RegExp,\n" 79 " ['test']);\n" 80 "saveBuiltin(Error,\n" 81 " [],\n" 82 " ['captureStackTrace']);\n" 83 "\n" 84 "// JSON is trickier because extensions can override toJSON in\n" 85 "// incompatible ways, and we need to prevent that.\n" 86 "var builtinTypes = [\n" 87 " Object, Function, Array, String, Boolean, Number, Date, RegExp\n" 88 "];\n" 89 "var builtinToJSONs = builtinTypes.map(function(t) {\n" 90 " return t.toJSON;\n" 91 "});\n" 92 "var builtinArray = Array;\n" 93 "var builtinJSONStringify = JSON.stringify;\n" 94 "Save('JSON', {\n" 95 " parse: JSON.parse,\n" 96 " stringify: function(obj) {\n" 97 " var savedToJSONs = new builtinArray(builtinTypes.length);\n" 98 " try {\n" 99 " for (var i = 0; i < builtinTypes.length; ++i) {\n" 100 " try {\n" 101 " if (builtinTypes[i].prototype.toJSON !==\n" 102 " builtinToJSONs[i]) {\n" 103 " savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n" 104 " builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n" 105 " }\n" 106 " } catch (e) {}\n" 107 " }\n" 108 " } catch (e) {}\n" 109 " try {\n" 110 " return builtinJSONStringify(obj);\n" 111 " } finally {\n" 112 " for (var i = 0; i < builtinTypes.length; ++i) {\n" 113 " try {\n" 114 " if (i in savedToJSONs)\n" 115 " builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n" 116 " } catch (e) {}\n" 117 " }\n" 118 " }\n" 119 " }\n" 120 "});\n" 121 "\n" 122 "}());\n"; 123 124 v8::Local<v8::String> MakeKey(const char* name, v8::Isolate* isolate) { 125 return v8::String::NewFromUtf8( 126 isolate, base::StringPrintf("%s::%s", kClassName, name).c_str()); 127 } 128 129 void SaveImpl(const char* name, 130 v8::Local<v8::Value> value, 131 v8::Local<v8::Context> context) { 132 CHECK(!value.IsEmpty() && value->IsObject()) << name; 133 context->Global()->SetHiddenValue(MakeKey(name, context->GetIsolate()), 134 value); 135 } 136 137 v8::Local<v8::Object> Load(const char* name, v8::Handle<v8::Context> context) { 138 v8::Local<v8::Value> value = 139 context->Global()->GetHiddenValue(MakeKey(name, context->GetIsolate())); 140 CHECK(!value.IsEmpty() && value->IsObject()) << name; 141 return value->ToObject(); 142 } 143 144 class ExtensionImpl : public v8::Extension { 145 public: 146 ExtensionImpl() : v8::Extension(kClassName, kScript) {} 147 148 private: 149 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunctionTemplate( 150 v8::Isolate* isolate, 151 v8::Handle<v8::String> name) OVERRIDE { 152 if (name->Equals(v8::String::NewFromUtf8(isolate, "Apply"))) 153 return v8::FunctionTemplate::New(isolate, Apply); 154 if (name->Equals(v8::String::NewFromUtf8(isolate, "Save"))) 155 return v8::FunctionTemplate::New(isolate, Save); 156 NOTREACHED() << *v8::String::Utf8Value(name); 157 return v8::Handle<v8::FunctionTemplate>(); 158 } 159 160 static void Apply(const v8::FunctionCallbackInfo<v8::Value>& info) { 161 CHECK(info.Length() == 5 && info[0]->IsFunction() && // function 162 // info[1] could be an object or a string 163 info[2]->IsObject() && // args 164 info[3]->IsInt32() && // first_arg_index 165 info[4]->IsInt32()); // args_length 166 v8::Local<v8::Function> function = info[0].As<v8::Function>(); 167 v8::Local<v8::Object> recv; 168 if (info[1]->IsObject()) { 169 recv = info[1]->ToObject(); 170 } else if (info[1]->IsString()) { 171 recv = v8::StringObject::New(info[1]->ToString())->ToObject(); 172 } else { 173 info.GetIsolate()->ThrowException( 174 v8::Exception::TypeError(v8::String::NewFromUtf8( 175 info.GetIsolate(), 176 "The first argument is the receiver and must be an object"))); 177 return; 178 } 179 v8::Local<v8::Object> args = info[2]->ToObject(); 180 int first_arg_index = static_cast<int>(info[3]->ToInt32()->Value()); 181 int args_length = static_cast<int>(info[4]->ToInt32()->Value()); 182 183 int argc = args_length - first_arg_index; 184 scoped_ptr<v8::Local<v8::Value> []> argv(new v8::Local<v8::Value>[argc]); 185 for (int i = 0; i < argc; ++i) { 186 CHECK(args->Has(i + first_arg_index)); 187 argv[i] = args->Get(i + first_arg_index); 188 } 189 190 v8::Local<v8::Value> return_value = function->Call(recv, argc, argv.get()); 191 if (!return_value.IsEmpty()) 192 info.GetReturnValue().Set(return_value); 193 } 194 195 static void Save(const v8::FunctionCallbackInfo<v8::Value>& info) { 196 CHECK(info.Length() == 2 && info[0]->IsString() && info[1]->IsObject()); 197 SaveImpl(*v8::String::Utf8Value(info[0]), 198 info[1], 199 info.GetIsolate()->GetCallingContext()); 200 } 201 }; 202 203 } // namespace 204 205 // static 206 v8::Extension* SafeBuiltins::CreateV8Extension() { return new ExtensionImpl(); } 207 208 SafeBuiltins::SafeBuiltins(ScriptContext* context) : context_(context) {} 209 210 SafeBuiltins::~SafeBuiltins() {} 211 212 v8::Local<v8::Object> SafeBuiltins::GetArray() const { 213 return Load("Array", context_->v8_context()); 214 } 215 216 v8::Local<v8::Object> SafeBuiltins::GetFunction() const { 217 return Load("Function", context_->v8_context()); 218 } 219 220 v8::Local<v8::Object> SafeBuiltins::GetJSON() const { 221 return Load("JSON", context_->v8_context()); 222 } 223 224 v8::Local<v8::Object> SafeBuiltins::GetObjekt() const { 225 return Load("Object", context_->v8_context()); 226 } 227 228 v8::Local<v8::Object> SafeBuiltins::GetRegExp() const { 229 return Load("RegExp", context_->v8_context()); 230 } 231 232 v8::Local<v8::Object> SafeBuiltins::GetString() const { 233 return Load("String", context_->v8_context()); 234 } 235 236 v8::Local<v8::Object> SafeBuiltins::GetError() const { 237 return Load("Error", context_->v8_context()); 238 } 239 240 } // namespace extensions 241