multi-def values vs. security, elucidated and solved
John Rose
john.r.rose at oracle.com
Wed Apr 10 18:24:49 UTC 2019
One recurrent question about inlined value types is
whether they are less secure than regular object types.
The question revolves around a scenario where an
inlined value instance X functions as a security token,
and the value of a private field of X (X.p) must be
secured. In this scenario, the attacker creates a
series of guesses G1, G2, … which attempt to
replicate the value X, substituting various guessed
values for X.p (G1.p, G2.p, etc.). If the attacker
finds a guess Gi where Gi==X, then the attacker
has "unlocked" X by exposing the value of X.p,
since it must be the same as Gi.p which the attacker
has already guessed and now has confirmed.
This attack scenario is relatively narrow because
it requires that the possible values of X.p can be
enumerated in the time the attacker has to perform
the attack. The time order for this attack is thus
O(N) where N is the number of possible values of
X.p.
(If X implements Comparable and X.p is a key in the
comparison, then the attack can be performed in
O(log N). This is often feasible where the O(N)
attack is not.)
Why is this not a problem with classic indirect
objects (those which have identity)? Because the
tool for comparing Gi with X, the == operator,
immediately returns false for any of the Gi,
since those were created by the attacker.
(If X is a classic object which implements Comparable,
then the attack is more feasible, even with classic
objects, since the attacker can use the compareTo
operation to bracket the X.p value between positive
and negative results. This problem applies equally
to classic indirect objects and inline value objects.)
So classic indirect objects are highly resistant to
equality tests against attacker-created indirect objects,
because the equality test will fail unless the attacker
compares X with X itself—which gives the attacker
no new information.
Meanwhile, inline value objects are not resistant to
equality tests, so the guessing can eventually (in O(N)
time) produce a match against X.
In short, an exactly copy of the inline value object X
can be forged (as a lucky Gi) by an independent party.
Pulling back from the attack per se, we can observe
that a classic indirect object possesses an identity
thas is created at that object's defining site (a "new X"
expression or bytecode). No other defining site in
space or time will ever create the same identity.
An inline value object V possesses no such identity,
and, therefore, several defining sites (a "new V"
expression or invokestatic bytecode) can end up
creating the *same* value, over and over again.
All occurrences of the same indirect object have
the same defining site; they are all connected by
a chain of data-flow from definition to use.
Multiple occurrences of the same value may have
*distinct* defining sites, *not* connected by
chains of data flow. The first time the two copies
of the same value come together might be when
they are first compared. They will compare equal
(if they are the same value), even though they came
from different data-flow chains of definition to
use (from two different definitions). This never
happens for classic indirect objects.
This difference between classic indirect and
new inline types suggests a defense against
the attack scenario proposed above. What if
we could ask a value type to emulate the special
property that a definition-to-use data-flow chain
is the only way for one value (of a given type X)
to be a copy of itself? Forging a series of guesses
G1, G2, … would then be impossible.
In fact, this is readily done, and without damaging
the other desirable properties of inline value types.
Simply endow the type "X" with an extra private
field "X.q" which is initialized (in the constructor
of X) by the expression, "new Object()". This
augmented version of X will (drum roll, please)
possess a bona fide *object identity* which cannot
be forged by an attacker.
If you think about this, the status of the JVM's
invisible object header takes on a new aspect,
that of a *field* which carries the *object identity*,
and is *inherited* from the type of all classic
indirect objects. We have sometimes called this
hypothetical type "RefObject". The idea here is
that every classic indirect object inherited,
from RefObject, an object identity, notionally
stored in the object header. (Actually, it's the
address of the object header which is used,
but the point remains that if you have a header,
you can derive an object identity from it, by
taking its address.) Meanwhile, every inline
value object does *not* have such a header.
(Some of its many copies *may* have headers,
but these headers are prevented from being
significant.) So an instance of C <: RefObject
*inherits* an object identity from RefObject.
Meanwhile, an inline value instance X is not
an instance of RefObject, and does *not* inherit
the header nor the object identity. *But*,
if the instance X wishes to acquire an object
identity, it can do so by *aggregation* instead
of *inheritance*. Et voila; the upgraded version
of X has no header, but its object identity lives
on, in the field X.q. Problem solved.
Therefore, if an inline value object is going to
be used as an unforgeable security token, and
the author is worried about an object-forging
attack, the attack can be headed off by adding
an object identity *field*. There will be a cost
in footprint, but the object will continue to
possess all the other properties of inline values,
including flattenability. Perhaps the author of
the class is already including a classic indirect
object reference X.a in the class definition.
If that is the case, a quick "clone" operation in
the constructor before setting X.a can smuggle in
an object identity without an increase in footprint.
I think these observations adequately answer the
persistent security concern about forging inline
value objects. And they also help us understand
more deeply "what's in a value".
— John
More information about the valhalla-spec-observers
mailing list