<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Hi Martin, <br>
</p>
<p>I'm curious, you sound like you arrived at this opinion from
experience? Rather than being an upper layer only concern, my
opinion is that it requires lower layer intervention / controls,
with upper layers providing the decision making context.<br>
</p>
<p>My reason for asking is, we're basically waiting for finalizers
to be disabled, so that we can instrument the java api with access
controls to replace SM.<br>
</p>
<p>In our implementation, we use the SM infrastructure like this:</p>
<ol>
<li>Distributed computing - protection domains and ClassLoaders
are used for service identity and isolation at the client.
(Simplification because a server can also be a client and vice
versa).<br>
</li>
<li>All client subjects are authenticated over secure connections,
threads on the server are run with the client subject, from the
client jvm, for access control decisions, eg do I trust the data
enough to parse it? Callbacks for client listeners (services)
are run with the server's subject at the client.<br>
</li>
<li>We re-implemented java de-serialization, with a public api
that uses constructors. Unlike perl taint mode, we rely on the
authenticated subject's principals, to determine whether to
allow permission to deserialize (parse data). SM allows us to
handle tainted data, because we put permission checks into our
deserialization implementation, if there's no authenticated
subject, or the remote end doesn't have the required principal,
then deserialization doesn't proceed at all, because no one
vouches for the data, it cannot be trusted. Our
deserialization implementation provides an atomic input
validation api, to validate data (sanitize) from trusted
sources, in theory it would allow us to parse untrusted data,
but we use authentication to reduce our exposure. Rather than a
bolted on external kind of white listing filtering mechanism,
it's a class level implementation concern.<br>
</li>
<li>Clients dynamically download requested proxy jar files,
(streams are not annotated like RMI), prior to download, the
client authenticates the service's server, after authentication,
the client loads the jar files, and deserializes the proxy
object state into a designated ClassLoader (unique to the
service identity, services that share jar file URI will not
share ClassLoader's and don't resolve to the same class type).
After authentication, the service provides URI and advisory
permissions and the client may dynamically grant the
intersection of those permissions which it has permission to
grant and those the service requests.</li>
<li>Our services are discoverable over multicast IPv6 (globally
and on local networks, usually the two are kept somewhat
separate).<br>
</li>
<li>We have service constraints, these are upper layer controls
that lower layers use to ensure connections use strongly
encrypted TLS protocols for example, or that a connection can be
authenticated with a particular principal. If a service is
obtained from another service, our lower layer communications
ensure that the same constraints apply to the second service,
the client may apply new constraints after receiving a service
proxy.<br>
</li>
</ol>
<p>JEP411's successor will remove or change the functionality of
Java's access controls and will break all our TLS connections and
our ability to have different levels of access control for
different services.<br>
</p>
<p>We can of course just do some kind of no op on later versions of
Java with missing api's via reflection, which will also disable
encrypted connections, then we can allow services to communicate
over trusted networks or VPN's, and allow deserialization and jar
file downloads, all without any jvm layer security, but we lose
our ability to dynamically discover services globally, they will
need to be known in advance and the secure network connections
established in advance.<br>
</p>
<p>We solved all problems with SM mentioned in JEP 411, with the
exception of the maintenance cost for OpenJDK. My understanding
is it is company policy around security that makes it expensive to
maintain. We have a policy generation tool (based on principles
of least privilege), our policy provider has a less than 1%
performance impact. We have a PermissionComparator that avoids
equals calls on Permission's, we have a URI 3986 implementation,
that also normalizes IPv6 addresses, uses bitshift operations for
case conversions and is extremely fast, it's used by our
ClassLoader and Policy implementations.<br>
</p>
<p>The only remaining irritations were the structures of the
Permissions themselves, eg SocketPermission can't constrain
communications to subnet IP address ranges.<br>
</p>
<p>What Li Gong provided was very well designed, Sun just never
finished it, and pretty much let it rot on the vine, few people
used it, because of the amount of work required to make it work
properly, and the fact that security is a nice to have feature,
but budget constraints and delivery deadlines, and now it's
subject to defenestration. Hand edited policy files? Sorry,
that's not a finished product.<br>
</p>
<p>The other mistake was the Java trusted computing base became too
large, it needed to be restricted to core java language
features. There's too much trusted code in the JVM.
Deserialization and XML (data parsing), never required any
permissions, so it couldn't be disabled by assigning the necessary
permissions to the principal of the authenticating subject.
Serialization and XML shouldn't have been part of the trusted code
base. Even if Java deserialization was insecure, as it was for
many years, if it required authentication of the data source prior
to deserialization proceeding, well maybe history might have been
different. Also too many classes are Serializable.<br>
</p>
<p>So by removing SM, in effect we're just making the trusted
codebase larger, now it will encompass all third party libraries
and their dependencies, while also removing the only available
mechanism to determine whether data from an external source can be
trusted based on who it was provided by (authenticated).</p>
<p>Of course there will be those of us who will re-implement an
authorization layer, hopefully we'll learn from Java's mistakes,
not repeat them, but make a bunch of new mistakes instead.<br>
</p>
<p>Regards,</p>
<p>Peter.<br>
</p>
<p><br>
</p>
<div class="moz-cite-prefix">On 23/04/2022 12:58 pm, Martin Balao
wrote:<br>
</div>
<blockquote type="cite"
cite="mid:ce5643f9-ab10-803b-e0ae-433cdf680d8e@redhat.com">
<pre class="moz-quote-pre" wrap="">Hi,
On 4/8/22 11:13 AM, Sean Mullan wrote:
</pre>
<blockquote type="cite">
<pre class="moz-quote-pre" wrap="">In general, I think authorization is best done at a higher layer within
the application and not via low-level SM callouts. Authorize the subject
first and if not acceptable, prevent the operation or API from being
called in the first place. Once the operation is in motion, you have
already taken a greater risk that something might go wrong.
</pre>
</blockquote>
<pre class="moz-quote-pre" wrap="">
I completely agree with this vision, and also agree with the other
arguments that both Sean Mullan and Andrew Dinn mentioned before in this
thread. In my view, authorization decisions at higher layer generally
have better context, are more clear and less riskier. At a lower layer
there is more complexity and chances of both subtle combinations or
unseen paths that may lead to check bypasses. I lean towards not
splitting authorization responsibility through different layers, which
might create confusion or even a false sense of security in some cases.
To illustrate with a trivial example, if a subject is not supposed to
access to some information, it's the application that has enough context
to decide and block right away. Letting the attempt go though the call
stack down to the network or the file system might be the recipe for
missing a channel.
I won't enter the untrusted code case -which has been extensively
discussed already- but want to briefly mention something about the
"trusted code performing risky operations" case. My first point is that
vulnerabilities at the JVM level (i.e.: memory safety compromises) are
serious enough to potentially bypass a SecurityManager. My second point
is that the SecurityManager is really unable to deal with tainted data
flowing from low to high integrity domains, and sanitation must be
performed by the application or the library anyways because there are
infinite ways in which it can be harmful. Even when the data is obtained
at a low integrity domain, there will be a flow towards a high integrity
domain to perform a legitimate action (i.e.: SQL query, OS command
execution, etc). The OS and the DB engine, in this example, have the
knowledge, granularity and power to be the next level of enforcement
after data sanitation. Again, I wouldn't split this responsibility or
pass it to the JDK for the same reasons than before.
Best,
Martin.-
</pre>
</blockquote>
<pre class="moz-signature" cols="72">
</pre>
</body>
</html>