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