Proposal: #ReflectiveAccessToNonExportedTypes (revised) & #AwkwardStrongEncapsulation: Weak modules & private exports

mark.reinhold at oracle.com mark.reinhold at oracle.com
Thu Oct 27 23:15:37 UTC 2016


2016/10/24 6:46:11 -0700, david.lloyd at redhat.com:
> On 10/11/2016 10:15 AM, mark.reinhold at oracle.com wrote:
>> 2016/9/16 8:30:41 -0700, david.lloyd at redhat.com:
>>> ...
>>> 
>>> #1) The "weak" designation appears to be pejorative
>>> 
>>> Under this solution, many existing frameworks and/or the modules which
>>> consume them must be marked "weak" in order to work correctly, or else a
>>> much more complex module descriptor must be used.  While this seems to
>>> be a stepping stone for migration, we find that it rather makes
>>> middleware appear as a second-class citizen.
>> 
>> No insult is intended; "weak" is used here purely in a technical sense,
>> as the obvious antonym of "strong".  Suggestions for a better word for
>> this concept are welcome.
> 
> It's the concept itself that is problematic, I think.  Really, if I 
> understand correctly, the "weak" notion exists merely as shorthand to 
> say "grant permission on all packages to all modules for private 
> reflection", which is the sort of thing that a user ought to be saying 
> explicitly.  Many users will use weak modules without understanding what 
> they are; some will come to understand it and immediately press for best 
> practices to get rid of them.  I think that regardless of what the 
> concept is named, it's going to spell trouble in the end.

Many others had similar reactions, for a variety of reasons, so
please have a look at the open modules & open packages proposal
I posted earlier today [1].

>>> ...
>>> 
>>> #3) Controlling reflection access is always bound up with controlling
>>> exports
>>> 
>>> The proposed exports system is simplistic but not exactly simple, yet
>>> several useful modes remain left unaccounted-for.  Internally we
>>> examined the usefulness of controlling access to public and so-called
>>> "deep" reflection in combination with whether or not a member was
>>> exported, and we came up with this logical table:
>>> 
>>>          +------------------+---------------------------+
>>>          |                  |        Module is...       |
>>>          |  Reflection is... +------------+--------------+
>>>          |                  |  Exported  | Not Exported |
>>>          +----------------------------------------------+
>>>          |        Forbidden | Not Useful |    Useful    |
>>>          +----------------------------------------------+
>>>          |      Public Only |   Useful   |    Useful    |
>>>          +----------------------------------------------+
>>>          | Public & Private |   Useful   |    Useful    |
>>>          +------------------+------------+--------------+
>> 
>> I'm not sure how to read this table.  Should the cell containing the text
>> "Module is ..." instead contain "Member is ..."?
> 
> Right.
> 
>> By "exported" I'll assume that you mean non-reflective access, i.e., from
>> the language at compile time and bytecodes at run time.
> 
> Yes.
> 
>> Do you have example use cases for each of the "Useful" cells?  Some are
>> obvious, but others are not.
> 
> Sure, I'll go over each one by one with some example use cases.
> 
> Forbidden/Not Exported: Obviously the foundation case for 
> access-oriented encapsulation, this is the default mode.
> 
> Exported modes:
> 
> Public Only/Exported: It doesn't make sense to disallow reflection on 
> public types and members that can already be accessed directly, so this 
> would be the default access mode to imported members when one module 
> imports another.
> 
> Public & Private/Exported: This is the JDK 8 compatibility mode, but a 
> module should have to opt into this mode, and it should be restricted to 
> specific targets whenever possible, else the mechanism is essentially 
> defeated.
> 
> Not exported modes:
> 
> Public Only/Not Exported: This is the mode that would be used (for 
> example) by DI and ORM frameworks and containers which operate on public 
> members.  Normally a module which imports such a framework would grant 
> the necessary reflective access to it as well (though a full export is 
> in this case unnecessary); the framework wouldn't contain requires 
> clauses for modules which consume it.  Like I suggested in my first 
> email, a way to "handshake" this agreement seems like it would be 
> necessary: the framework would not allow itself to be imported without 
> the importing module acknowledging the access in its configuration.
> 
> Public & Private/Not Exported: This is the mode that would be used by 
> serialization and DI/ORM frameworks and containers which operate on 
> private members (e.g. field-level injection).  Like the above though, 
> this level of access is usually granted by the module importing the 
> framework in question and should be explicitly opted into if possible.

Now that I understand it, I think your table maps fairly directly to the
analysis that's driven my current and past proposals, which I summarized
earlier today [2].  Please let me know if you think otherwise.

The open-modules/packages proposal does not support your "Public Only /
Not Exported" mode, which corresponds to the missing "N S" row in the
compact table in [2], i.e., the "partly-encapsulated framework client"
case.  Most reflective frameworks in use today will just reach in via
`setAccessible` whenever they need to, so whether the members of an
unexported package in a client module are public or private doesn't
really matter.  The new proposal intentionally does not support this
case, which results in a simpler design.

> The better situation however is to separate the grant from the export.

That's exactly what the new `opens` directive does [1].

>>> #4) There isn't always a container
>>> 
>>> The justification for not providing an easy mechanism to restrict
>>> private reflective access to a trusted framework is that a container can
>>> always rewrite a module or inject classes.  However there isn't always a
>>> container present - something which the existence of weak modules
>>> acknowledges.  Some frameworks and middleware are meant to be usable
>>> with or without a container, and it is undesirable to force these use
>>> cases into a weak module situation or to require a strong coupling in
>>> the module descriptor in this case.  Some other solution should be found
>>> that works both for containers and "regular" applications.
>> 
>> If you're running outside of a container then how important is it to
>> precisely restrict access to packages meant for deep reflection?  If
>> you're outside of a container then you have to trust the person (you?)
>> who sets up your module path and/or class path.  To take the canonical
>> case of a module that just contains JPA model classes, if you make it a
>> weak module or else `exports private` the relevant packages without
>> qualification so that the hibernate.core (or whatever) module can reflect
>> into it, then the scope for trouble is limited.  Sure, some other module
>> on the module path or even code on the class path could reflect into your
>> module when it's not supposed to, but is it worth complicating the module
>> system just to handle this scenario?
> 
> In either situation, the person (deployer) who sets up the class path or 
> installation can currently do whatever they want.  Nothing in the new 
> (or existing) security framework truly makes it possible to run 
> untrusted code safely in this regard, nor to safely run trusted code in 
> an untrusted environment, nor is it a sufficient solution for secure 
> sandboxing even with a security manager present, with or without a 
> container.  At best it's a mitigation strategy.

Exactly.

> That said: the rise of microservices and reactive programming suggests 
> that such standalone (containerless) deployments are, if anything, more 
> popular than ever, but they may suffer from security problems which are 
> very similar to, if not identical to, those which applications running 
> in containers are subject to.

That could be, and it deserves further thought.

>                                In fact jlink (and the new AOT work for 
> that matter) seem particularly and specifically suited to this type of 
> use case.

Agreed.

> Finally: a JPA model class module is indeed probably harmless in any 
> practical security sense, and locking it down is probably not useful 
> from a security perspective, but even in this case it may yet be useful 
> to restrict package exports such that only _supported_ classes are 
> visible (especially when a module is published within an organization 
> and contracts must be maintained); i.e. encapsulation is not solely a 
> security concern, it also establishes intent as clearly as any interface 
> declaration does.

I completely agree that encapsulation is not just for security -- it's
also about helping developers build reliable systems by defining clear
boundaries between elements at all levels (classes, package, modules)
so that systems are understandable, testable, and so forth.

The open-modules/packages proposal does address your last point: In
contrast to the weak-modules proposal, you can prevent some or all of
the packages of a framework-client module from being compiled against,
i.e., treated as API, by opening them but not exporting them.

>>> ...
>>> 
>>> #7) More loose coupling seems necessary and useful
>>> 
>>> In order for typical applications to function with modern middleware as
>>> modules, without compromising security, it may be necessary to enhance
>>> the loose coupling mechanism (uses/provides), or to provide an
>>> additional, similar mechanism, which allows a symbolic coupling which
>>> would allow modules to declare (in an abstract manner) modules which
>>> need to have reflective access to it in a "friendly" manner.  ...
>> 
>> To confirm my understanding, would it be correct to say that your main
>> concern here is scenarios in which some of the classes in a user module
>> are intended to be manipulated reflectively by some trusted framework
>> module, but that framework module is either not known at compile time or
>> is otherwise inconvenient to name in the declaration of the user module?
> 
> Correct.

Good.  I'll enter this as a new issue:

  #IndirectQualifiedReflectiveAccess --- Provide a means by which a
  client module can grant qualified reflective access to a framework
  module that is not known at compile time, assuming that the name of
  some other module that represents the framework module is known.
  A canonical example of this case is a client POJO module compiled
  against a module that defines the JPA API.  At run time the client
  module, or a container or some other code acting on its behalf,
  must grant qualified reflective access to its POJO packages to the
  JPA implementation in actual use.

- Mark


[1] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-October/000430.html
[2] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-October/000431.html


More information about the jpms-spec-experts mailing list