Skip to content
This repository has been archived by the owner on Feb 2, 2021. It is now read-only.

NiceNeighbor

David Schwartz edited this page May 24, 2017 · 2 revisions

(legacy summary: How Cajita coexists with untranslated JavaScript)

See SubsetRelationships for context.

Coexistence Today by Taming


http://google-caja.googlecode.com/svn/trunk/doc/images/nn-today.png


On JavaScript as implemented on browsers today (ES3R), inter-object security is not quite defensible against untrusted, untranslated, unverified JavaScript code. Thus, Cajita's security today relies on the assumption that Cajita objects will only be made directly accessible to

  1. Cajoled code, i.e., the JavaScript code generated by a cajoler -- a trusted Cajita-to-JavaScript compiler.
  2. Trusted JavaScript, such as cajita.js -- the Cajita runtime.
  3. Tamed innocent JavaScript, such as the core ES3 library or the browser's DOM API.

The first two are the subject of TranslationTarget. The third, tamed innocent JavaScript, is the subject of this page. This page explains the taming API provided by the Cajita runtime and how to use it to tame innocent JavaScript. Taming is the design of the interface between cajoled and uncajoled JavaScript. The taming is based on whitelisting -- unless an uncajoled property is explicitly made visible by taming, it is not visible to Cajita code. For those properties that are made visible, taming can attenuate what interactions are possible across the boundary.

Innocent code is code written without awareness of the possibility of evil. Innocent code neither seeks to do evil, nor does it engage in any defensive practices needed to resist evil. Although most existing JavaScript code is innocent, without strong evidence to the contrary, we must classify most code as untrusted, i.e., potentially hostile. If we misclassify hostile code as innocent, Cajita's security is lost. We assume innocent code may be buggy, but that none of these bugs can be exploited by an attacker, via the available attack surface, to cause the innocent code to act as hostile code. Again, if we misclassify exploitable code as innocent, Cajita's security is lost.

Some innocent code is directly provided by the target platform -- such as the core ES3 libraries and the browser's DOM API. This code is not subject to examination or translation. For all other innocent code, we do assume that it has been translated through our InnocentCodeRewriter. Note: As of this writing, all deployments of Caja violate this assumption. Once we fix issue 1019, we must repair this unsafe situation.-

Security Properties to be maintained by taming

Even with the above problem repaired, and even assuming no malicious or exploitable code is misclassified as innocent, previous experiences suggest that taming is the most hazardous part of securing a legacy platform, and bad taming decisions are the most likely source of fatal security problems. The taming of innocent JavaScript must carefully ensure that the security properties enforced by a cajoler together with the Cajita runtime are not violated by innocent JavaScript. These security properties are:

Integrity of Cajita Values

Those invariants enumerated on the CajitaValues page.

Closure encapsulation

JavaScript today has only one almost working security mechanism -- lexical variable capture by nested functions. Although the ES3 specification provides no operations for violating this encapsulation, neither does it prohibit the addition of such operations by conforming platforms. ES3R (ES3 as implemented on today's browsers) universally provides operations -- arguments.caller, function.caller, and function.arguments -- which allow uncajoled code to violate the encapsulation of functions on their call stack. Taming should only allow cajoled code to call uncajoled code when none of the uncajoled code reachable from those entry points might employ one of these operations to violate the encapsulation of a Cajita closure on its call chain.

Simulated Attributes Restrict Property Manipulation

In the language of the ES3 spec, each property has associated with it a set of attributes. (To avoid confusion, we will use the ES5 names for these attributes, even when speaking about ES3.) For example, if the [[Writable]] attribute of a property is false, the property is read-only and cannot be changed by assignment. However, neither ES3 nor ES3R provide any way for JavaScript code to create properties with non-default attribute settings. But Cajita requires such restrictions in order to create tamper-proof objects.

Cajita on the ES3R platform simulates attribute-based restrictions of property manipulation by representing such per-property virtual attribute settings in its own bookkeeping. Cajoling translates Cajita code into cajoled JavaScript code that cannot violate the restrictions represented in this bookkeeping. However, innocent JavaScript knows nothing of the restrictions represented in this bookkeeping, and may therefore innocently violate these restrictions.

For example, say malicious Cajita object Mallet has access to both frozen victim Cajita object Fred and to innocent mutating object Inara. Since Mallet is written in Cajita, it is translated into code which cannot directly mutate Fred. However, if Inara were inappropriately tamed, such that Mallet could pass Fred as an argument to one of Inara's mutating operations, then Mallet could get Inara to innocently do the forbidden mutation on his behalf.

These simulated attributes are also used to freeze all the primordial objects. Untamed code within the same frame must not modify any visible primordial state once Cajita code has been allowed to execute in that frame. This cannot be enforced after the fact by taming; it must be enforced by (necessarily fallible) inspection of innocent code.

No Access to Global object or scope

No Exophoric Functions

Taming API Naming Convention

___.mark*(obj,...) Marks obj as being a particular kind of Cajita object, such as a constructor, and therefore being usable from Cajita in certain ways.
___.grant*(obj,name,...) Grants some kind of access to the name property of obj, perhaps also marking the current value of obj[name] at the same time.

For those taming methods that don't fit either of these patterns, see the documentation on that method.

Taming Normal Property Attributes

The following operations are only relevant for taming constructed objects or their prototypes. All mentionable properties on records and arrays are implicitly readable and enumerable anyway. If the record or array is not frozen, then all its mentionable properties are also implicitly writable and deletable.

Even on constructed objects, stringified numbers (all X such that X === String(Number(X)) and "length" are implicitly whitelisted as well. On an accessible object, access to such properties cannot be denied.

Current API Similar ES5 attribute Common Semantics Differences
___.grantRead(obj,name) none ES5 has no non-readable properties. This operation makes actual uncajoled properties directly visible to cajoled code.
___.grantSet(obj,name) {writable:true, enumerable:true} Allow the actual property to be changed by assignment. none
___.grantEnum(obj,name) {enumerable:true} This property name will be enumerated by a for-in loop on obj. none
___.grantDelete(obj,name) {configurable:true} The property can be deleted. In ES5, if a property is configurable, its attributes may change at runtime. In Cajita taming, uncajoled code currently can change attributes unconditionally but should normally do so only on initialization. Cajita code can only delete deletable properties; it cannot otherwise manipulate attributes of constructed objects.

If obj is a constructed object, then these operations apply directly to own properties of obj. If obj is a prototypical object, then these operations determine the behavior of the property as inherited by constructed objects that inherit from that prototype. Since prototypical objects are implicitly frozen, grantSet and grantDelete make no sense when obj is a prototypical object and should not be used.

The second column of the table shows the attribute settings that would appear in an ES5 call to Object.create or Object.defineProperty, or that would be returned by Object.getOwnPropertyDescriptor. For example, when uncajoled JavaScript today expresses the taming decision ___.grantEnum(foo, 'bar'), the ES5 equivalent would be Object.defineProperty(foo, 'bar', {enumerable: true}).

Installing Property Accessors

Mozilla JavaScript introduced experimental getters and setters -- a pair of functions associated with a property name which, together, simulate the property's value. The getter is called when the property is read. The value returned by the getter is then used as the value of the read operation. The setter is called when the property is assigned to, so that it can perform whatever side effects it chooses. Getters and setters are needed to allow objects written in JavaScript to emulate the peculiar behavior of DOM objects, where assignment to an innerHTML property can cause vast numbers of side effects.

Some other JavaScript implementations followed Mozilla, with oddly different semantics for various corner cases. ES5 has codified an understandable and mutually agreeable semantics for getters and setters.

Due to implementation constraints of translating Cajita to efficient ES3R, which includes Internet Explorer 6 and 7 where getters and setters are not available, Cajita implements getters and setters as a consequence of fault handling, so that it can optimize normal property access to occur on a fast path, with little checking and no calls on the typical property access. As a consequence, if normal property access, including inheritance, can succeed, no fault has occurred, and no fault handler will get invoked. Only if a normal read operation fails will a get handler, if available, be called. Only if a normal assignment fails will a set handler be called.

Current API Similar ES5 attribute Common Semantics Differences
___.useGetHandler(obj,name,getter} {get:getter} Reading the property obtains the result of calling getter.call(obj) Cajita handlers are only tried after normal data lookup fails.
___.useSetHandler(obj,name,setter} {set:setter} Assigning val to the property calls setter.call(obj,val). The value of the assignment expression is val, not the result of the setter call. Cajita handlers are only tried after normal assignment fails. ES5 represents a non-writable accessor property by {set:undefined}. The Cajita equivalent would be a setter that always throws.

One useful taming technique is to provide a virtual version of an actual property by not doing a grantRead on the actual property, so normal access fails, and to install getters and setters to simulate the property as it should be seen by Cajita code. This technique works only on constructed objects. On records and arrays, since all mentionable properties are implicitly whitelisted, the actually property will always be accessed first preventing fault handling. However, getters and setters can still work of records and arrays in order to simulate properties that are not actually present.

Taming Functions

Of the functions not defined by Cajita code, the only ones that should be accessible to Cajita code as values are those tamed as frozen constructors or as frozen simple-functions. In all cases, if optName is provided, it will be used for debugging purposes as the name of the function.

Current API Meaning
___.markCtor(fun,optSuper,optName) Marks fun as a constructor -- it can only be called with new. Returns fun.
___.extend(hiddenFun,someSuper,optName) Use when hiddenFun is a constructor which should not be exposed that makes tamed instances that should be exposed. Returns a new inert tamed constructor which will not make anything, but will be be instanceof-equivalent to hiddenFun.
___.markFuncFreeze(fun,optName) Marks fun as a simple-function -- one that does not mention this -- and freeze it. A simple function can be called as a function, method, or constructor. It is first class -- reading a readable property whose value is a simple-function obtains the simple-function itself.

If fun is a constructor and fun.prototype inherits directly from aSuper.prototype, then the optSuper argument to ___.markCtor should be aSuper. optSuper must be another function marked as a constructor. optSuper may only be absent or undefined when fun.prototype inherits from nothing. Currently, this is only the case for Object.prototype itself.

___.extend should be called before hiddenFun.prototype is initialized, since it will replace hiddenFun.prototype with a prototypical object that inherits from someSuper.prototype. The someSuper argument of ___.extend may either be a hidden constructor used as the first argument of a previous call to ___.extend, or it may be the tamed inert constructor returned from a previous such call. If the returned inert constructor is to be made available to cajoled code under some name, optName should be that name. For example, Domita's TameElement makes tamed wrappers for real HTMLElements, so Domita exposes

nodeClasses.HTMLElement = ___.extend(TameElement, TameBackedNode, 'HTMLElement');

which denies cajoled access to the TameElement constructor; but allows the caja expression node instanceof HTMLElement to succeed if node was made by new TameElement(...).

Taming Methods

The remaining common use of JavaScript functions is as methods. The JavaScript expression a.foo(b) is syntactically a function call whose left operand is the property read expression a.foo. If the value of a.foo is a simple-function, then this accounts for its semantics as well. The first taming call below, ___.grantFunc(obj,name), both makes obj[name] readable (as if by ___.grantRead(obj,name)), and marks its current value as a frozen simple-function (as if by ___.markFuncFreeze(obj[name],name)).

The remaining cases in the table determine the taming of exophoric methods. To decide which one to use, one must understand the semantics of the exophoric method in question. These calls do not make obj[name] directly readable, and it must not be made directly readable by other means, else an exophoric function might become accessible to Cajita code. Instead, they install an appropriate get handler (as if by ___.useGetHandler), which returns a pseudo function, a record containing apply, call, and bind functions that act like methods on the exophoric function as bound to that exophoric function. (To be explained better.)

Note that pseudo functions appear to be normal exophoric functions to Valija code, so only the Cajita programmer need be aware that these reads do not return a function.

Current API Meaning
___.grantFunc(obj,name) Use when obj[name] is a safe simple-function, i.e., a function that does not mention this that should be invokable from Cajita.
___.grantGenericMethod(obj,name) Use when obj[name](...) is safe to call directly from Cajita code, and obj[name] is safe to use generically -- with its this bound to other objects via apply, call, and bind. For example, most methods on Array.prototype are tamed as generic methods.
___.grantTypedMethod(obj,name) Use when obj[name](...) is safe to call directly from Cajita code, but when obj[name] is not safe to use generically. This taming ensures that if its this is bound to other objects via apply, call, and bind, the function is only called if the alternate this inherits from obj. For example, most methods on Date.prototype are tamed as typed methods.
___.grantMutatingMethod(obj,name) Then the method would mutate its this value, which must therefore be guarded by an isFrozen check.
___.handleGenericMethod(obj,name,callHandler) All remaining cases, where the callHandler supplies whatever alternate behavior is to be made available to Cajita code.

Note that only grantFunc makes the property normally readable. All the others make the property readable by installing a get handler, which has the inheritance irregularities explained above. Similarly, only grantFunc and grantGenericMethod make the method directly callable. The others install call handlers, which are similarly tried only if direct calling fails.

Future defensibility from untranslated code


http://google-caja.googlecode.com/svn/trunk/doc/images/nn-future.png


Does ES5 protect Cajita from untrusted JavaScript?

Let's reexamine each of the security properties Cajita relies on.

Closure encapsulation

Mostly, yes.

Cajita functions will translate to ES5-strict functions. The three closure-breaking operations of ES3R -- arguments.caller, function.caller, and function.arguments -- are not available on strict functions, even from nonstrict functions. In designing the new meta API for ES5 (the reflective property manipulation methods on Object, covered next), the EcmaScript committee was careful not to introduce any new ways to violate closure encapsulation.

The only reason for the mostly qualifier above is that the ES5 spec, like the ES3 spec before it, contains a grand loophole (chapter 16) that allows an implementation to provide virtually any extension and still claim conformance to the spec.

Simulated Attributes Restrict Property Manipulation

Yes. But with a new restriction on innocent code.

To be explained.

No Access to Global object or scope

Between frames, yes. Within a frame, we still prevent privilege escalation.

To be explained.

No Exophoric Functions

No. This inability forces us to change our strategy.

To be explained.

Clone this wiki locally