[e-lang] Bug (0.8.26g): TextWriter/__printOn authority leak
Kevin Reid
kpreid at attglobal.net
Fri Apr 16 19:49:25 EDT 2004
On Apr 15, 2004, at 23:32, Mark S. Miller wrote:
> * I also when through all the implementations of __printOn written in
> E,
> fixing as needed to ensure that they all declare their parameter as
> ":TextWriter". While doing this, I noticed one place in your den code
> where
> you make this same error (in makeFancyRevoker.emaker):
>
> to __printOn(out) :void {
> if (Ref.isBroken(underlying)) {
> out.print(`<revoked: ${Ref.optProblem(underlying)}>`)
> } else {
> out.print("<revokable>")
> out.print(underlying)
> }
> }
I noticed this and have fixed it. Unfortunately the CVS server for Den
is down right now.
> This serves as a good example for why we need to guard __printOn's
> parameter
> if we're going to make calls like 'out.print(<internals>)' : If a
> malicious
> client passes in a non-TextWriter to this method, they can obtain
> direct
> access to the underlying, which is what your forwarder is trying to
> prevent.
> (The E source tree had several errors of this form.)
Perhaps, given the ease with which this mistake can be made, and the
severity of the consequences, the E
parser/expander/compiler/implementation should give a warning if it
finds e??`method __printOn(@{x ? (x !~ epatt`@_ :TextWriter`)}) {@_}`?
Or, see below.
> * Given the above fixes, the revocation logic for printing only needs
> to be in
> TextWriter. So, finally, I modified TextWriter with custom revocation
> logic,
> essentially as you originally suggested. Besides doing the
> try/finally as
> already discussed, I create the sub-TextWriter as follows:
>
> TextWriter sub = new TextWriter(this,
> myNewline,
> myAutoflush,
> false,
> myContext);
>
> The only important difference is that the parent and child share the
> same
> context map. Otherwise, these changes would have broken the
> cycle-breaking
> logic.
Oops.
> Altogether, I feel rather discouraged about borrowing the printOn
> pattern
> from Smalltalk into E. Unlike either the Java toString() pattern or
> the
> Data-E uncall/uneval serialization pattern, the printOn pattern is
> surprisingly hard to secure. The "surprisingly" is worse news than the
> "hard". I'm thinking that I should deprecate __printOn in favor of
> one of
> the other two patterns.
toString is unsuitable due to not handling cycles, of course.
Data-E is not fundamentally easier. It only looks so because
__optUncall is a non-amplifying interface (provides no additional
authority), whereas __printOn is (allows anything passing the
TextWriter guard to traverse the object(s-to-be-printed) graph, which
is often not available to regular clients of the object.
The argument guard of __printOn is equivalent to this, which is very
much like an __optSealedDispatch-based uncall:
to __optSealedDispatch(brand) :any {
switch (brand) {
match ==printBrand {
return printSealer.seal([["print", "<revokable>"], ["quote",
underlying]])
}
match _ {}
}
}
Hm. I now see that the above interface has the advantage of being
fail-stop - implementations can't have a security hole by omitting the
guard.
Unfortunately, it's syntactically ugly.
A way to make __printOn safe: use a transforming guard, such that the
argument to __printOn is not usable for printing without the
transformation.
Some untested implementations follow, all of which would work with
existing __printOn methods (assuming safeScope["TextWriter"] is
replaced).
(If anything like this is used, I suggest making the sealer and/or
stamp available in the privileged scope so that custom printers could
be defined.)
Implementation #1:
def [textWriterSealer, textWriterUnsealer] := <elib:sealing.Brand>
interface _TextWriter guards TextWriterStamp {}
def textWriter implements TextWriterStamp {
to print(obj) :void {
obj.__printOn(textWriterSealer.seal(textWriter))
}
}
def TextWriter extends __templateGuard(TextWriter) {
to coerce(var specimen, optEjector) :any {
if (textWriterUnsealer.optUnseal(specimen, any) =~ x :notNull) {
x
} else {
return _TextWriter.coerce(specimen, optEjector)
}
}
}
Implementation #2:
interface _TextWriter guards TextWriterStamp {}
def textWriter implements TextWriterStamp {
to print(obj) :void {
obj.__printOn([textWriter])
}
}
def TextWriter extends __templateGuard(TextWriter) {
to coerce(var specimen, optEjector) :any {
if (specimen =~ [x]) {
specimen := x
}
return _TextWriter.coerce(specimen, optEjector)
}
}
Implementation #3:
def [textWriterSealer, textWriterUnsealer] := <elib:sealing.Brand>
def textWriter {
to __optSealedDispatch(brand) :any {
switch (brand) {
match ==(textWriterSealer.getBrand()) {
return textWriterSealer.seal(textWriter)
}
match _ {}
}
}
to print(obj) :void {
obj.__printOn(def stub {
to __optSealedDispatch(brand) :any {
switch (brand) {
match ==(textWriterSealer.getBrand()) {
return textWriterSealer.seal(textWriter)
}
match _ {}
}
}
})
}
}
def TextWriter extends __templateGuard(TextWriter) {
to coerce(var specimen, optEjector) :any {
if (textWriterUnsealer.amplify(specimen) =~ [x]) {
x
} else {
throw.eject(optEjector, "not a TextWriter")
}
}
}
> As capability programming patterns grow and adapt conventional object
> programming patterns, it will be very important to notice and record
> such
> surprises. I wouldn't be at all surprised if there were other such
> nasty
> surprises already in the E source tree. Sigh.
--
Kevin Reid
More information about the e-lang
mailing list