multi-def values vs. security, elucidated and solved

Remi Forax forax at univ-mlv.fr
Wed Apr 10 19:00:55 UTC 2019


There is another style of attack, inline classes are subject to tearing, so you can be able to forge an invalid value (with respect to the constructor checks) of an inline class from two other valid values.
By example,

import java.util.stream.IntStream;

public @__value__ class SecurityToken {
  private final long id1;
  private final long id2;
  
  public SecurityToken(long id1, long id2) {
    if (id1 != id2) {
      throw new IllegalArgumentException();
    }
    this.id1 = id1;
    this.id2 = id2;
  }
  
  public void check() {
    if (id1 != id2) {
      throw new IllegalStateException();
    }
  }
  
  public static void main(String[] args) {
    var tokens = new SecurityToken[1];
    IntStream.range(0, 2).forEach(id -> {
      new Thread(() -> {
        for(;;) {
          tokens[0].check();
          tokens[0] = new SecurityToken(id, id);
        }
      }).start();
    });
  }
}

you can already do that with longs and double on 32 bits hardware but here the values are non mutable.

Rémi

----- Mail original -----
> De: "John Rose" <john.r.rose at oracle.com>
> À: "valhalla-spec-experts" <valhalla-spec-experts at openjdk.java.net>
> Envoyé: Mercredi 10 Avril 2019 20:24:49
> Objet: multi-def values vs. security, elucidated and solved

> 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