diff --git a/lst/ctor-factory-priv-exploit.js b/lst/ctor-factory-priv-exploit.js new file mode 100644 index 0000000..ca0c6a2 --- /dev/null +++ b/lst/ctor-factory-priv-exploit.js @@ -0,0 +1,29 @@ +var Database = Class( +{ + // ... + + forEachRow: function( callback ) + { + for ( row in this._rows ) + { + callback( row ); + } + }, + + _getPassword: function() + { + return 'secret'; + } +); + +var db = Database( '...' ), + passwd = ''; + +// ... +db.forEachRow( function( row ) +{ + passwd = db.__priv._getPassword(); +} ); + +// oops +unauthorizedDbOperation( 'user', passwd ); diff --git a/lst/ctor-factory-priv-nested-clear.js b/lst/ctor-factory-priv-nested-clear.js new file mode 100644 index 0000000..640b2ee --- /dev/null +++ b/lst/ctor-factory-priv-nested-clear.js @@ -0,0 +1,13 @@ +var Database = Class( +{ + __construct: function( host, user, pass ) + { + // __priv contains private members + var ip = this.__priv._resolveHost(); + + // __priv is undefined + this.__priv._connect( ip, user, pass ); + }, + + // ... +} ); diff --git a/lst/ctor-factory-priv-nested-fix.js b/lst/ctor-factory-priv-nested-fix.js new file mode 100644 index 0000000..124f71c --- /dev/null +++ b/lst/ctor-factory-priv-nested-fix.js @@ -0,0 +1,19 @@ + function wrap( method ) + { + return function() + { + var prev = this.__priv; + + // expose private member object + this.__priv = + this.constructor[ _privname ]; + + var retval = method.apply( + this, arguments + ); + + // restore previous value + this.__priv = prev; + return retval; + }; + } diff --git a/lst/ctor-factory-priv.js b/lst/ctor-factory-priv.js index 341450f..3899185 100644 --- a/lst/ctor-factory-priv.js +++ b/lst/ctor-factory-priv.js @@ -118,6 +118,6 @@ var Foo = Class( } ); var inst = new Foo(); -inst.getValue(); // "private" -inst._getPrivValue; // undefined -inst.__priv; // undefined +inst.getValue(); // "private" +inst._getPrivValue(); // TypeError +inst.__priv; // undefined diff --git a/sec/encap-hacks.tex b/sec/encap-hacks.tex index 30af510..0015903 100644 --- a/sec/encap-hacks.tex +++ b/sec/encap-hacks.tex @@ -480,4 +480,117 @@ concept with the wrapper implementation, we arrive at lastline=97 ]{lst/ctor-factory-priv.js} -(INCOMPLETE.) +In order to expose the private methods \emph{only} from within wrapped methods, +\jsref{lst:ctor-factory-priv} relies on the fact that only the constructor +factory knows the name of the private ``prototype'' object (as denoted by +\var{\_privname}). Wrappers, before invoking the wrapped function (method), will +assign the private object to \code{this.\_priv} (line 84) and unassign it after +the wrapped function returns (line 91).\footnote{We set the value to +\keyword{undefined} rather than using the \operator{delete} operator because the +latter causes a slight performance hit under v8.} Methods may then access +private members by referencing \code{this.\_priv}. +% TODO: REFERENCE NEEDED FOR ABOVE V8 MENTION + +\func{copyTo()}, now receiving both public and private destination objects as +arguments, will place all members prefixed with an underscore on the private +member object (lines 71--72). As has already been mentioned, the member will be +wrapped regardless of whether or not it is private (line 74), ensuring that +public methods also have access to private members. + +\jsref{lst:ctor-factory-priv-ex} demonstrates how this implementation may be +used to define a private method \func{\_getPrivValue()} that is accessable only +to other methods; attempting to invoke the method publically would result in a +\code{TypeError} (resulting from an attempt to call \keyword{undefined} as if it +were a function). Also note that \var{Foo.\_\_priv}, although defined from +within the method \func{getValue()}, is \keyword{undefined} after the method +returns. + +\lstinputlisting[% + label=lst:ctor-factory-priv-ex, + caption=Demonstrating use of private methods with constructor factory, + firstline=107 +]{lst/ctor-factory-priv.js} + +\subsubsection{Wrapper Implementation Concerns} +The wrapper implementation is not without its dilemmas. Firstly, consider how +\func{wrap()} clears \var{\_\_priv} in \jsref{lst:ctor-factory-priv} (lines +84--91). The wrapper requires that the call stack be cleared up to the point of +the invocation of the wrapped function. Consequently, this means that any code +executed before the call stack is cleared to that point will have access to the +instance during which time \code{this.\_priv} is assigned, giving that code +access to private members and breaking encapsulation. + +\lstinputlisting[% + label=lst:ctor-factory-priv-exploit-stack, + caption=Exploiting wrapper implementation via callbacks to gain access to + private members outside of the class +]{lst/ctor-factory-priv-exploit.js} + +This fatal flaw is demonstrated in \jsref{lst:ctor-factory-priv-exploit-stack}, +which executes a callback before the wrapped function returns. That callback, +which has access to the instance that called it, is able to access the private +members because it is executed before its caller returns. There are three ways +to work around this: + +\begin{enumerate} + \item Remove the assignment before invoking the callback, + \item Use \func{setTimeout()} or \func{setInterval()} to invoke the callback, + allowing the stack to clear before the callback is invoked, or + \item Do not invoke any functions that are not defined on the class itself. +\end{enumerate} + +None of the above options are acceptable solutions. The first option adds +unnecessary logic to the method that makes assumptions about the underlying +system, which is especially dangerous if the implementation of \func{wrap()} +were to ever change. The second solution does not suffer from the same design +issues as the first, but forces the method to be asynchronous, which is not +always desirable. The third option is terribly prohibitive, as it not only +disallows any type of serial callback, but also disallows invoking any methods +of injected dependencies. + +% TODO: Reference to section where this issue is highlighted, once available +A proper solution to this issue is obvious, but its discussion will be deferred +to future implementations due to additional complexities raised when dealing +with properties. Until that time, the reader should be aware of the issue and +consider potential solutions. + +The second concern is a bit more subtle. Once again, we focus around lines +82--91 in \jsref{lst:ctor-factory-priv}. Consider what problems that this +wrapper may cause when dealing with nested method calls --- that is, one method +calling another on the same instance. + +\lstinputlisting[% + label=lst:ctor-factory-priv-nested-clear, + caption=Problems with nested method calls given the \func{wrap()} + implementation in \jsref{lst:ctor-factory-priv} +]{lst/ctor-factory-priv-nested-clear.js} + +This issues is demonstrated by \jsref{lst:ctor-factory-priv-nested-clear}. The +\var{Database} class's \func{\_\_construct()} method performs two private method +calls --- \func{\_resolveHost()}, to get the IP address of the host, and +\func{\_connect()}, which attempts to connect to the database. Unfortunately, +after the call to \func{\_resolveHost()}, its wrapper sets \var{\_\_priv} to +\keyword{undefined} (line 91 in \jsref{lst:ctor-factory-priv}), which will cause +the second method call to fail! + +To resolve this issue, \func{wrap()} could store the previous value of +\code{this.\_\_priv} and then, instead of setting the value to +\keyword{undefined} after the wrapped function has returned, restore +\code{this.\_\_priv} to its original value. This modification is shown in +\jsref{lst:ctor-factory-priv-nested-fix}. + +\lstinputlisting[% + label=lst:ctor-factory-priv-nested-fix, + caption=Fixing private object assignment in nested wrapped function calls by + restoring the previous value, + firstnumber=80 +]{lst/ctor-factory-priv-nested-fix.js} + +When the first wrapper is invoked, the previous value of \code{this.\_\_priv} +will be \keyword{undefined}, allowing the wrapper to continue to operate as it +used to. When nested wrappers are invoked, the previous value will contain the +private member object and will allow \code{this.\_\_priv} to be properly +restored on return. This functions much like a stack, using the call stack +instead of an array.\footnote{This solution could have easily been worked into +\jsref{lst:ctor-factory-priv}, but hopefully the additional discussion provided +insight into critical design decisions.}