Commit 1b270ef5 authored by rafaelw@chromium.org's avatar rafaelw@chromium.org

Re-enable Object.observe and add enforcement for security invariants.

This patch reverts r21062 which disabled Object.observe and the relevant tests.

It also adds enforcement for the following three invariants:

1) No observer may receive a change record describing changes to an object which is in different security origin (context have differing security tokens)

2) No observer may receive a change record whose context's security token is different from that of the object described by the change.

3) Object.getNotifier will return null if the caller and the provided object are in differing security origins

Further, it ensures that the global object can never be observed nor a notifier retrieved for it.

Tests are included.
R=verwaest@chromium.org, rossberg
LOG=Y

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@21122 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent d76ad169
...@@ -3567,28 +3567,13 @@ Local<v8::Object> v8::Object::Clone() { ...@@ -3567,28 +3567,13 @@ Local<v8::Object> v8::Object::Clone() {
} }
static i::Context* GetCreationContext(i::JSObject* object) {
i::Object* constructor = object->map()->constructor();
i::JSFunction* function;
if (!constructor->IsJSFunction()) {
// Functions have null as a constructor,
// but any JSFunction knows its context immediately.
ASSERT(object->IsJSFunction());
function = i::JSFunction::cast(object);
} else {
function = i::JSFunction::cast(constructor);
}
return function->context()->native_context();
}
Local<v8::Context> v8::Object::CreationContext() { Local<v8::Context> v8::Object::CreationContext() {
i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate(); i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate();
ON_BAILOUT(isolate, ON_BAILOUT(isolate,
"v8::Object::CreationContext()", return Local<v8::Context>()); "v8::Object::CreationContext()", return Local<v8::Context>());
ENTER_V8(isolate); ENTER_V8(isolate);
i::Handle<i::JSObject> self = Utils::OpenHandle(this); i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Context* context = GetCreationContext(*self); i::Context* context = self->GetCreationContext();
return Utils::ToLocal(i::Handle<i::Context>(context)); return Utils::ToLocal(i::Handle<i::Context>(context));
} }
......
...@@ -77,6 +77,7 @@ var kMessages = { ...@@ -77,6 +77,7 @@ var kMessages = {
observe_perform_non_string: ["Invalid non-string changeType"], observe_perform_non_string: ["Invalid non-string changeType"],
observe_perform_non_function: ["Cannot perform non-function"], observe_perform_non_function: ["Cannot perform non-function"],
observe_notify_non_notifier: ["notify called on non-notifier object"], observe_notify_non_notifier: ["notify called on non-notifier object"],
observe_global_proxy: ["%0", " cannot be called on the global proxy object"],
not_typed_array: ["this is not a typed array."], not_typed_array: ["this is not a typed array."],
invalid_argument: ["invalid_argument"], invalid_argument: ["invalid_argument"],
data_view_not_array_buffer: ["First argument to DataView constructor must be an ArrayBuffer"], data_view_not_array_buffer: ["First argument to DataView constructor must be an ArrayBuffer"],
......
...@@ -355,6 +355,8 @@ function CallbackInfoNormalize(callback) { ...@@ -355,6 +355,8 @@ function CallbackInfoNormalize(callback) {
function ObjectObserve(object, callback, acceptList) { function ObjectObserve(object, callback, acceptList) {
if (!IS_SPEC_OBJECT(object)) if (!IS_SPEC_OBJECT(object))
throw MakeTypeError("observe_non_object", ["observe"]); throw MakeTypeError("observe_non_object", ["observe"]);
if (%IsJSGlobalProxy(object))
throw MakeTypeError("observe_global_proxy", ["observe"]);
if (!IS_SPEC_FUNCTION(callback)) if (!IS_SPEC_FUNCTION(callback))
throw MakeTypeError("observe_non_function", ["observe"]); throw MakeTypeError("observe_non_function", ["observe"]);
if (ObjectIsFrozen(callback)) if (ObjectIsFrozen(callback))
...@@ -370,6 +372,8 @@ function ObjectObserve(object, callback, acceptList) { ...@@ -370,6 +372,8 @@ function ObjectObserve(object, callback, acceptList) {
function ObjectUnobserve(object, callback) { function ObjectUnobserve(object, callback) {
if (!IS_SPEC_OBJECT(object)) if (!IS_SPEC_OBJECT(object))
throw MakeTypeError("observe_non_object", ["unobserve"]); throw MakeTypeError("observe_non_object", ["unobserve"]);
if (%IsJSGlobalProxy(object))
throw MakeTypeError("observe_global_proxy", ["unobserve"]);
if (!IS_SPEC_FUNCTION(callback)) if (!IS_SPEC_FUNCTION(callback))
throw MakeTypeError("observe_non_function", ["unobserve"]); throw MakeTypeError("observe_non_function", ["unobserve"]);
...@@ -392,19 +396,15 @@ function ArrayUnobserve(object, callback) { ...@@ -392,19 +396,15 @@ function ArrayUnobserve(object, callback) {
return ObjectUnobserve(object, callback); return ObjectUnobserve(object, callback);
} }
function ObserverEnqueueIfActive(observer, objectInfo, changeRecord, function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) {
needsAccessCheck) {
if (!ObserverIsActive(observer, objectInfo) || if (!ObserverIsActive(observer, objectInfo) ||
!TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) { !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) {
return; return;
} }
var callback = ObserverGetCallback(observer); var callback = ObserverGetCallback(observer);
if (needsAccessCheck && if (!%ObserverObjectAndRecordHaveSameOrigin(callback, changeRecord.object,
// Drop all splice records on the floor for access-checked objects changeRecord)) {
(changeRecord.type == 'splice' ||
!%IsAccessAllowedForObserver(
callback, changeRecord.object, changeRecord.name))) {
return; return;
} }
...@@ -433,22 +433,16 @@ function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { ...@@ -433,22 +433,16 @@ function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) {
} }
ObjectFreeze(newRecord); ObjectFreeze(newRecord);
ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord, ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord);
true /* skip access check */);
} }
function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord, function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord) {
skipAccessCheck) {
// TODO(rossberg): adjust once there is a story for symbols vs proxies. // TODO(rossberg): adjust once there is a story for symbols vs proxies.
if (IS_SYMBOL(changeRecord.name)) return; if (IS_SYMBOL(changeRecord.name)) return;
var needsAccessCheck = !skipAccessCheck &&
%IsAccessCheckNeeded(changeRecord.object);
if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
var observer = objectInfo.changeObservers; var observer = objectInfo.changeObservers;
ObserverEnqueueIfActive(observer, objectInfo, changeRecord, ObserverEnqueueIfActive(observer, objectInfo, changeRecord);
needsAccessCheck);
return; return;
} }
...@@ -456,8 +450,7 @@ function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord, ...@@ -456,8 +450,7 @@ function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord,
var observer = objectInfo.changeObservers[priority]; var observer = objectInfo.changeObservers[priority];
if (IS_NULL(observer)) if (IS_NULL(observer))
continue; continue;
ObserverEnqueueIfActive(observer, objectInfo, changeRecord, ObserverEnqueueIfActive(observer, objectInfo, changeRecord);
needsAccessCheck);
} }
} }
...@@ -558,9 +551,13 @@ function ObjectNotifierPerformChange(changeType, changeFn) { ...@@ -558,9 +551,13 @@ function ObjectNotifierPerformChange(changeType, changeFn) {
function ObjectGetNotifier(object) { function ObjectGetNotifier(object) {
if (!IS_SPEC_OBJECT(object)) if (!IS_SPEC_OBJECT(object))
throw MakeTypeError("observe_non_object", ["getNotifier"]); throw MakeTypeError("observe_non_object", ["getNotifier"]);
if (%IsJSGlobalProxy(object))
throw MakeTypeError("observe_global_proxy", ["getNotifier"]);
if (ObjectIsFrozen(object)) return null; if (ObjectIsFrozen(object)) return null;
if (!%ObjectWasCreatedInCurrentOrigin(object)) return null;
var objectInfo = ObjectInfoGetOrCreate(object); var objectInfo = ObjectInfoGetOrCreate(object);
return ObjectInfoGetNotifier(objectInfo); return ObjectInfoGetNotifier(objectInfo);
} }
...@@ -622,5 +619,4 @@ function SetupObjectObserve() { ...@@ -622,5 +619,4 @@ function SetupObjectObserve() {
)); ));
} }
// Disable Object.observe API for M35. SetupObjectObserve();
// SetupObjectObserve();
...@@ -1957,6 +1957,21 @@ MaybeHandle<Object> JSObject::AddProperty( ...@@ -1957,6 +1957,21 @@ MaybeHandle<Object> JSObject::AddProperty(
} }
Context* JSObject::GetCreationContext() {
Object* constructor = this->map()->constructor();
JSFunction* function;
if (!constructor->IsJSFunction()) {
// Functions have null as a constructor,
// but any JSFunction knows its context immediately.
function = JSFunction::cast(this);
} else {
function = JSFunction::cast(constructor);
}
return function->context()->native_context();
}
void JSObject::EnqueueChangeRecord(Handle<JSObject> object, void JSObject::EnqueueChangeRecord(Handle<JSObject> object,
const char* type_str, const char* type_str,
Handle<Name> name, Handle<Name> name,
......
...@@ -2654,6 +2654,8 @@ class JSObject: public JSReceiver { ...@@ -2654,6 +2654,8 @@ class JSObject: public JSReceiver {
static inline int SizeOf(Map* map, HeapObject* object); static inline int SizeOf(Map* map, HeapObject* object);
}; };
Context* GetCreationContext();
// Enqueue change record for Object.observe. May cause GC. // Enqueue change record for Object.observe. May cause GC.
static void EnqueueChangeRecord(Handle<JSObject> object, static void EnqueueChangeRecord(Handle<JSObject> object,
const char* type, const char* type,
......
...@@ -14872,11 +14872,11 @@ RUNTIME_FUNCTION(Runtime_HaveSameMap) { ...@@ -14872,11 +14872,11 @@ RUNTIME_FUNCTION(Runtime_HaveSameMap) {
} }
RUNTIME_FUNCTION(Runtime_IsAccessCheckNeeded) { RUNTIME_FUNCTION(Runtime_IsJSGlobalProxy) {
SealHandleScope shs(isolate); SealHandleScope shs(isolate);
ASSERT(args.length() == 1); ASSERT(args.length() == 1);
CONVERT_ARG_CHECKED(HeapObject, obj, 0); CONVERT_ARG_CHECKED(Object, obj, 0);
return isolate->heap()->ToBoolean(obj->IsAccessCheckNeeded()); return isolate->heap()->ToBoolean(obj->IsJSGlobalProxy());
} }
...@@ -14961,32 +14961,38 @@ RUNTIME_FUNCTION(Runtime_ObservationWeakMapCreate) { ...@@ -14961,32 +14961,38 @@ RUNTIME_FUNCTION(Runtime_ObservationWeakMapCreate) {
} }
RUNTIME_FUNCTION(Runtime_IsAccessAllowedForObserver) { static bool ContextsHaveSameOrigin(Handle<Context> context1,
Handle<Context> context2) {
return context1->security_token() == context2->security_token();
}
RUNTIME_FUNCTION(Runtime_ObserverObjectAndRecordHaveSameOrigin) {
HandleScope scope(isolate); HandleScope scope(isolate);
ASSERT(args.length() == 3); ASSERT(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(JSFunction, observer, 0); CONVERT_ARG_HANDLE_CHECKED(JSFunction, observer, 0);
CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 1); CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 1);
RUNTIME_ASSERT(object->map()->is_access_check_needed()); CONVERT_ARG_HANDLE_CHECKED(JSObject, record, 2);
CONVERT_ARG_HANDLE_CHECKED(Object, key, 2);
SaveContext save(isolate); Handle<Context> observer_context(observer->context()->native_context(),
isolate->set_context(observer->context()); isolate);
if (!isolate->MayNamedAccess( Handle<Context> object_context(object->GetCreationContext());
object, isolate->factory()->undefined_value(), v8::ACCESS_KEYS)) { Handle<Context> record_context(record->GetCreationContext());
return isolate->heap()->false_value();
} return isolate->heap()->ToBoolean(
bool access_allowed = false; ContextsHaveSameOrigin(object_context, observer_context) &&
uint32_t index = 0; ContextsHaveSameOrigin(object_context, record_context));
if (key->ToArrayIndex(&index) || }
(key->IsString() && String::cast(*key)->AsArrayIndex(&index))) {
access_allowed =
isolate->MayIndexedAccess(object, index, v8::ACCESS_GET) && RUNTIME_FUNCTION(Runtime_ObjectWasCreatedInCurrentOrigin) {
isolate->MayIndexedAccess(object, index, v8::ACCESS_HAS); HandleScope scope(isolate);
} else { ASSERT(args.length() == 1);
access_allowed = CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
isolate->MayNamedAccess(object, key, v8::ACCESS_GET) &&
isolate->MayNamedAccess(object, key, v8::ACCESS_HAS); Handle<Context> creation_context(object->GetCreationContext(), isolate);
} return isolate->heap()->ToBoolean(
return isolate->heap()->ToBoolean(access_allowed); ContextsHaveSameOrigin(creation_context, isolate->native_context()));
} }
......
...@@ -311,7 +311,8 @@ namespace internal { ...@@ -311,7 +311,8 @@ namespace internal {
F(SetIsObserved, 1, 1) \ F(SetIsObserved, 1, 1) \
F(GetObservationState, 0, 1) \ F(GetObservationState, 0, 1) \
F(ObservationWeakMapCreate, 0, 1) \ F(ObservationWeakMapCreate, 0, 1) \
F(IsAccessAllowedForObserver, 3, 1) \ F(ObserverObjectAndRecordHaveSameOrigin, 3, 1) \
F(ObjectWasCreatedInCurrentOrigin, 1, 1) \
\ \
/* Harmony typed arrays */ \ /* Harmony typed arrays */ \
F(ArrayBufferInitialize, 2, 1)\ F(ArrayBufferInitialize, 2, 1)\
...@@ -397,7 +398,7 @@ namespace internal { ...@@ -397,7 +398,7 @@ namespace internal {
F(HasFastProperties, 1, 1) \ F(HasFastProperties, 1, 1) \
F(TransitionElementsKind, 2, 1) \ F(TransitionElementsKind, 2, 1) \
F(HaveSameMap, 2, 1) \ F(HaveSameMap, 2, 1) \
F(IsAccessCheckNeeded, 1, 1) F(IsJSGlobalProxy, 1, 1)
#define RUNTIME_FUNCTION_LIST_DEBUGGER(F) \ #define RUNTIME_FUNCTION_LIST_DEBUGGER(F) \
......
...@@ -84,10 +84,6 @@ ...@@ -84,10 +84,6 @@
'test-api/Threading3': [PASS, ['mode == debug', SLOW]], 'test-api/Threading3': [PASS, ['mode == debug', SLOW]],
'test-api/Threading4': [PASS, ['mode == debug', SLOW]], 'test-api/Threading4': [PASS, ['mode == debug', SLOW]],
'test-strings/StringOOM*': [PASS, ['mode == debug', SKIP]], 'test-strings/StringOOM*': [PASS, ['mode == debug', SKIP]],
# Object.observe is disabled.
'test-object-observe/*': [SKIP],
'test-microtask-delivery/*': [SKIP],
}], # ALWAYS }], # ALWAYS
############################################################################## ##############################################################################
......
This diff is collapsed.
...@@ -113,6 +113,8 @@ Object.defineProperty(changeRecordWithAccessor, 'name', { ...@@ -113,6 +113,8 @@ Object.defineProperty(changeRecordWithAccessor, 'name', {
// Object.observe // Object.observe
assertThrows(function() { Object.observe("non-object", observer.callback); }, assertThrows(function() { Object.observe("non-object", observer.callback); },
TypeError); TypeError);
assertThrows(function() { Object.observe(this, observer.callback); },
TypeError);
assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError); assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError); assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
assertEquals(obj, Object.observe(obj, observer.callback, [1])); assertEquals(obj, Object.observe(obj, observer.callback, [1]));
...@@ -127,6 +129,8 @@ assertEquals(obj, Object.observe(obj, observer.callback)); ...@@ -127,6 +129,8 @@ assertEquals(obj, Object.observe(obj, observer.callback));
// Object.unobserve // Object.unobserve
assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError); assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
assertThrows(function() { Object.unobserve(this, observer.callback); },
TypeError);
assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError); assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
assertEquals(obj, Object.unobserve(obj, observer.callback)); assertEquals(obj, Object.unobserve(obj, observer.callback));
...@@ -135,6 +139,7 @@ assertEquals(obj, Object.unobserve(obj, observer.callback)); ...@@ -135,6 +139,7 @@ assertEquals(obj, Object.unobserve(obj, observer.callback));
var notifier = Object.getNotifier(obj); var notifier = Object.getNotifier(obj);
assertSame(notifier, Object.getNotifier(obj)); assertSame(notifier, Object.getNotifier(obj));
assertEquals(null, Object.getNotifier(Object.freeze({}))); assertEquals(null, Object.getNotifier(Object.freeze({})));
assertThrows(function() { Object.getNotifier(this) }, TypeError);
assertFalse(notifier.hasOwnProperty('notify')); assertFalse(notifier.hasOwnProperty('notify'));
assertEquals([], Object.keys(notifier)); assertEquals([], Object.keys(notifier));
var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify'); var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
...@@ -1074,6 +1079,8 @@ function TestObserveNonConfigurable(obj, prop, desc) { ...@@ -1074,6 +1079,8 @@ function TestObserveNonConfigurable(obj, prop, desc) {
Object.unobserve(obj, observer.callback); Object.unobserve(obj, observer.callback);
} }
// TODO(rafaelw) Enable when ES6 Proxies are implemented
/*
function createProxy(create, x) { function createProxy(create, x) {
var handler = { var handler = {
getPropertyDescriptor: function(k) { getPropertyDescriptor: function(k) {
...@@ -1113,11 +1120,11 @@ function createProxy(create, x) { ...@@ -1113,11 +1120,11 @@ function createProxy(create, x) {
Object.observe(handler.target, handler.callback); Object.observe(handler.target, handler.callback);
return handler.proxy = create(handler, x); return handler.proxy = create(handler, x);
} }
*/
var objects = [ var objects = [
{}, {},
[], [],
this, // global object
function(){}, function(){},
(function(){ return arguments })(), (function(){ return arguments })(),
(function(){ "use strict"; return arguments })(), (function(){ "use strict"; return arguments })(),
...@@ -1125,9 +1132,10 @@ var objects = [ ...@@ -1125,9 +1132,10 @@ var objects = [
new Date(), new Date(),
Object, Function, Date, RegExp, Object, Function, Date, RegExp,
new Set, new Map, new WeakMap, new Set, new Map, new WeakMap,
new ArrayBuffer(10), new Int32Array(5), new ArrayBuffer(10), new Int32Array(5)
createProxy(Proxy.create, null), // TODO(rafaelw) Enable when ES6 Proxies are implemented.
createProxy(Proxy.createFunction, function(){}), // createProxy(Proxy.create, null),
// createProxy(Proxy.createFunction, function(){}),
]; ];
var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"]; var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
......
...@@ -127,14 +127,6 @@ ...@@ -127,14 +127,6 @@
# array buffer. # array buffer.
'nans': [PASS, ['arch == mips', SKIP]], 'nans': [PASS, ['arch == mips', SKIP]],
# Object.observe is disabled.
'es6/promises': [SKIP],
'array-push7': [SKIP],
'harmony/microtask-delivery': [SKIP],
'es7/object-observe': [SKIP],
'harmony/regress/regress-observe-empty-double-array': [SKIP],
'regress/regress-356589': [SKIP],
'regress/regress-observe-map-cache': [SKIP],
}], # ALWAYS }], # ALWAYS
############################################################################## ##############################################################################
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment