JDK-8231313 : 5.4.4: Enhance access control for dynamically-determined nest membership
  • Type: Sub-task
  • Component: specification
  • Sub-Component: vm
  • Affected Version: 14
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2019-09-20
  • Updated: 2020-05-01
  • Resolved: 2020-05-01
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 15
15Fixed
Related Reports
Relates :  
Description
A hidden class is created according to the process described by `Lookup::defineHiddenClass`, distinct from the class loading and derivation process described by JVMS 5.3. Hidden classes cannot participate in the nest membership defined by `NestHost` and `NestMembers` attributes because they do not have binary names that can be expressed, in internal form, in those attributes. However, `Lookup::defineHiddenClass` can arrange for a hidden class to be a member of a nest (namely, the nest to which the lookup class of the `Lookup` object belongs). This means that, from the JVM's point of view, a class's nest membership may have been determined prior to access control, in which case, we wish for the JVM to use that membership during access control, in particular, during the nestmate test.

Separately, we wish to reduce the failure modes of access control in connection with nest membership. The nestmate test introduced in Java SE 11 is brittle because any failure to determine a nest host results in the JVM throwing a LinkageError other than an IllegalAccessError. This behavior is surprising, and inconsistent with how both `Lookup::defineHiddenClass` and `Class::getNestHost` behave when they fail to determine a nest host. Accordingly, if the JVM attempts to determine a nest host for a class, but various ancillary exceptions occur, then the JVM should swallow them, and determine that the class is its own nest host. In this way, the JVM never fails to determine a nest host; the nestmate test may then succeed or fail in the normal way, based on the nests determined for the accessor and accessee.

The second half of 5.4.4 is modified as follows, using *** to indicate addition, and ~~~ to indication deletion.

--------
If R is not accessible to D, then ***access control throws an IllegalAccessError.*** Otherwise, access control succeeds.

~~~If R is public, protected, or has default access, then access control throws an IllegalAccessError.~~~
~~~If R is private, then the nestmate test failed, and access control fails for the same reason.~~~~

A nest is a set of classes and interfaces that allow mutual access to their private members. One of the classes or interfaces is the nest host. It enumerates the classes and interfaces which belong to the nest, using the NestMembers attribute (��4.7.29). Each of them in turn designates it as the nest host, using the NestHost attribute (��4.7.28). A class or interface which lacks a NestHost attribute belongs to the nest hosted by itself; if it also lacks a NestMembers attribute, this nest is a singleton consisting only of the class or interface itself.

***The nest host for a given class or interface (that is, the nest to which the class or interface belongs) is determined by the Java Virtual Machine as part of access control, rather than when the class or interface is loaded. Certain methods of the Java SE Platform API may determine the nest host for a given class or interface prior to access control, in which case the Java Virtual Machine respects the prior determination during access control.***

To determine whether a class or interface C belongs to the same nest as a class or interface D, the nestmate test is applied. C and D belong to the same nest if and only if the nestmate test succeeds. The nestmate test is as follows:

- If C and D are the same class or interface, then the nestmate test succeeds.

- Otherwise, the following steps are performed, in order:
   
   1. ~~~The nest host of D, H, is determined (below). If an exception is thrown, then the nestmate test fails for the same reason.~~~  ***Let H be the nest host of D, if the nest host of D has previously been determined. If the nest host of D has not previously been determined, then it is determined using the algorithm below, yielding H.***

   2. ~~~The nest host of C, H', is determined (below). If an exception is thrown, then the nestmate test fails for the same reason.~~~  ***Let H' be the nest host of C, if the nest host of C has previously been determined. If the nest host of C has not previously been determined, then it is determined using the algorithm below, yielding H'.***

   3. H and H' are compared. If H and H' are the same class or interface, then the nestmate test succeeds. Otherwise, the nestmate test fails. ~~~by throwing an IllegalAccessError.~~~


The nest host of a class or interface M is determined as follows:

- If M lacks a NestHost attribute, then M is its own nest host.

- Otherwise, M has a NestHost attribute, and its host_class_index item is used as an index into the run-time constant pool of M. The symbolic reference at that index is resolved ~~~to a class or interface H ~~~ (��5.4.3.1).

   ~~~During resolution of this symbolic reference, any of the exceptions pertaining to class or interface resolution can be thrown. Otherwise, resolution of H succeeds.~~~
   ***If resolution of the symbolic reference fails, then M is its own nest host. Any exception thrown as a result of failure of class or interface resolution is not rethrown.***

   ~~~If any of the following is true, an IncompatibleClassChangeError is thrown:~~~
   ***Otherwise, resolution of the symbolic reference succeeds. Let H be the resolved class or interface. The nest host of M is determined by the following rules:***

  ***- If any of the following is true, then M is its own nest host:
          H is not in the same run-time package as M.
          H lacks a NestMembers attribute.
          H has a NestMembers attribute, but there is no entry in its classes array that refers to a class or interface with the name N, where N is the name of M.***
   ***- Otherwise, H is the nest host of M.***
----------------
Comments
For the record, a note on how verification interacts with hidden classes. Traditionally, verification refers to classes using the internal form of their binary names. For example, the `loadedClass` predicate maps a name and initiating loader to a `class` definition: `loadedClass("A/B/C", L, class("A/B/C", L))`. However, the name is something of a black box during verification; no predicate deconstructs strings which denote class names. This is convenient because hidden classes cannot be referred to using the internal form of their binary names, as they do not have binary names in the first place. A hidden class is derived with a name like "A/B/C.5" -- the internal form of the binary name in `this_class` + ".[suffix]" -- and verification uses that derived name to refer to the class. Accordingly, the API spec for `Lookup::defineHiddenClass` says the following: "During verification, whenever it is necessary to load the [hidden] class named CN [that is, derived with the name "A/B/C.5"], the attempt succeeds, producing class C. No request is made of any class loader." (In contrast, Class::getName returns "A.B.C/5" -- the binary name in `this_class` + "/[suffix]".) Here is an example of how verification uses the derived name. Consider verifying the body of an instance method which is declared in the hidden class, where the declared return type of the method is Object. Verifying an `aload_0` means pushing an entry `class("A/B/C.5", L)` on the stack. Verifying `areturn` means popping the entry and checking assignability of `class("A/B/C.5", L)` to Object. Checking assignability involves querying the `loadedClass` dictionary for `class("A/B/C.5", L)`, using the defining loader of the hidden class as the initiating loader L. This query will succeed per the API spec above.
01-05-2020

From email: We have to allow for the introduction of a dynamic nestmate in the JVMS by some means. That will require modifying or expanding 5.4.4 to allow nest-based access control without the static nest-related attributes. What Mandy refers to as the "runtime nest host" is the operational model for this that we have discussed previously in emails - so we have the implementation with the desired properties. But this needs to be allowed for by the JVMS. One of the current consequences of 5.4.4 is that a nest-based access check involving a class C with an invalid NestHost will always fail with the same error. If that class C has a dynamic nestmate C' injected into it then the nest-based access check of (C, C') must succeed - contrary to 5.4.4. One of the ways 5.4.4 could be updated, as Dan has mentioned elsewhere, is to do away with the requirement to always fail with the same error. I'm envisaging something along the lines of: "The static nest host of a class or interface C, is the class designated as nest host by C's NestHost attribute; or C itself if it has no NestHost attribute. At runtime a class can be programmatically defined to belong to a particular nest. [cross-reference to Lookup.defineHiddenClass API]. The runtime nest host of a class C is its static nest host if that can be resolved without error, else C itself if the static nest host cannot be resolved without error, else the nest host of the nest the class was programmatically defined to be a member of." --- We would then need to update the "nestmate test" in terms of the runtime nest host and adjust the access control check accordingly. Something like: "If C and D are the same class or interface, then the nestmate test succeeds. Otherwise, if C and D have the same runtime nest host, then the nestmate test succeeds. Otherwise, the following steps are performed in order: 1. The runtime nest host of D, H is determined (below). If an exception is thrown, then the nestmate test fails for the same reason. 2. The runtime nest host of C, H' is determined ... 3. H and H' are compared ... The runtime nest host of a class or interface M is determined as follows: - If M was programmatically injected into a nest with nest host H, then H is the runtime nest host - If M lacks a NestHost attribute, then M is its own runtime nest host. - Other M has a NestHost attribute and ..." --- Now the above doesn't quite work they way we want because it would cause the first access check between a class and its dynamic nest member to fail if the class had an invalid static nest host. We probably need to allow for the runtime nest host being determined programmatically ( e.g. via Class.getNestHost()) not just as part of the access check described in 5.4.4. But I'm not sure how to accommodate that exactly.
14-11-2019