On 23/06/2023 11:06 am, Ron Pressler wrote:

On 22 Jun 2023, at 23:50, Peter Firmstone <peter.firmst...@zeus.net.au> wrote:


If you are able to share, I'd be interested to learn about challenges you had 
with SM, if we one day have the opportunity to reimplement it, the lessons 
might be valuable, so we can avoid the same mistakes.
Much of the effort has to do with maintaining the doPrivileged calls in the 
right places, which needs to be done across the board, and testing all those 
locations with and without an installed SM.

Of course, the very thing I was requesting to be preserved is the problem.

It must be an incredible amount of work without tooling, I can't begin to imagine how much time is wasted hand editing policy files.

We have to update all our test policy files for every release of Java.

For example, lets say you needed to write a test that  confirmed a SecurityException is thrown, first we would run the tests to generate the policy files, this would add all required permissions to all domains on the stack for privileges to be granted, which causes the test to fail, then we remove the relevant permissions from the test domain code, and voila, the test passes.


Let me just be clear that even though it’s the cost that made SM harmful and 
justified the effort involved in removing it, we believe that the stack-based 
approach is fundamentally flawed for an ecosystem where deep and wide 
dependency hierarchies are common and configurations must be tailored for a 
specific architecture and dependency set rather than in a black box fashion. We 
would advocate for simpler designs that have shown greater effectiveness.

Ah yes, black box = encapsulation.

So we have a very dynamic environment, Java's standard static policy provider is no good for our application, we would have SecurityExceptions all the time and you'd be forever editing policy files, so we have dynamically granted permissions on top of static permissions for policy.   We took some inspiration from OSGi, and annotated jar files with their required permissions, these are remote micro service proxy's which are dynamically downloaded, each with their own ProtectionDomain, which is related to service identity, based on the authenticating service and it's CodeSource, it also has its own namespace, separate from other services with identical CodeSource's.

Example of required permissions included in a jar file:

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/services/reggie/reggie-dl/src/main/resources/META-INF/PERMISSIONS.LIST

Users are given a list of Permission's they are allowed to grant (in static policy files), called GrantPermission's.  Users and services authenticate each other, and users can dynamically grant Permission's to Service proxy's following authentication, the permissions required are declared, so it's kinda obvious, the Service's back end server runs threads with the authenticated users Subject, so we're not just using POLP on one jvm, we are distributed across many jvm's on different OS's or architectures.   When the local service proxy is no longer in use, dynamically granted permissions become weakly reachable and are garbage collected, so they don't hang around any longer than needed.   This is equivalent to you manually editing your policy files multiple times daily.

There are also other permissions which are granted during the authentication process, eg to allow opening a socket for authentication, (typically using Certificates over TLS, but Kerberos is also possible), during the service lookup process, the service provides a URI list of resources, required for our RFC3986URIClassLoader, which is provisioned following service authentication, DownloadPermission (incorrectly named, should be DynamicClassLoadPermission) is dynamically granted, prior to any class files being dynamically class loaded.   Any CodeSource certificates are transmitted with the URI list during authentication, so they don't need to be known in advance, alternatively we can also use SHA256 or SHA512 hash's to verify downloaded jar files, these policies are generated dynamically on the fly and only remain in memory for the period they apply.

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-pref-class-loader/src/main/java/net/jini/loader/pref/PreferredClassProvider.java

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/net/jini/loader/ProxyCodebaseSpi.java

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-pref-class-loader/src/main/java/net/jini/loader/pref/PreferredProxyCodebaseProvider.java

In our implementation, Permissions aren't stored in PermissionCollection instances, PermissionCollection instances are created on demand and are thread confined for high scalability, to avoid synchronization between threads, if there are many domains on a call stack, permission checks are sent as executor tasks, and executed concurrently, the first domain on the stack to fail a permission check throws SecurityException, and is returned to the permission check call, waiting permission check tasks from the same stack walk are cancelled, they're only all executed when a permission check passes, then the result of the permission check for that context is weakly cached, so we don't have to repeatedly check it.   We also use Comparator's to arrange permissions in the most efficient order for checking, to improve performance, also since we generate our policy files, they contain equal matches for permission's, which often avoids slow implies checks that occur within PermissionCollection's.

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/net/jini/security/policy/DynamicPolicyProvider.java

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/org/apache/river/api/security/CombinerSecurityManager.java

Honestly, it doesn't matter if we have 100's or even 1000's of ProtectionDomain's on a stack during permission checks, we could have 100's of services participate in a transaction, as I mentioned the checks go to executors and get processed concurrently and dynamic policy is held temporarily in memory only.   Security is generally less than 3% of our system load.

Horribly antiquated implementations included in OpenJDK are based on late 1990's design philosophy, they're not even deployable in our environment, damn!  It blocks on DNS network calls?   I mean you can't compare a DNS network call performance wise with bitshift operations in RFC3986 URI normalization, or dynamic policy, with manual editing of policy files, there is no comparison, maybe you could compare it to an old man on a bicycle racing a top fueller at the drags, poor old bugger is gonna be at the line covered in rubber when the top fueller launches.

Everything runs with only the permissions required and no more under least privilege principles.

Anyway, it's been nice to think that what we achieved, was thought impossible.   It's also worth mentioning we know of users who are using this with OSGi, and have many more domains than typically used, due to OSGi's fine grained package level modularity.

Maybe it's our fault, for not widely promoting it, we just never expected JEP411, it came out of left field, we expected a core feature of Java would always be there and we were wrong.

When someone comes up with a simpler design, I'm all up for the effectiveness challenge, I'm pretty sure that whatever it is, we'll blow it away both on performance and effectiveness, we've had years to perfect it, but I would also happily be proven wrong and challenge OpenJDK to implement something that does.

Just be aware, that what you think you know, yours and your experts assumptions are based on theirs and your experiences with OpenJDK's implementation, that our experiences are worlds apart and are not the same.   The leg of your pants getting caught in your bicycle chain as you launch your bicycle off the line is not the same as tuning a top fueller.   Yes, I agree your bicycle is a piece of crap and you should get rid of it, it's just a shame you're removing the track too, since you determined that drag racing is fundamentally flawed based on your understanding of bicycles, were not going to be able to race top fueller's there now either, we need to find somewhere else to race.

I'm sorry that we didn't come to your aid a decade ago, I can only imagine all the time wasted manually editing policy files in test cases, time we could have saved you.

Anyway, it's time to go, I've said my piece, you all have work to do and so do I, the code's available for anyone who is interested, feel free to ask questions.

One final word, I would like to thank Alan for his directness and honesty.

Peter.



Serialization filters are lazily initialized, this is a performance 
optimisation (a security trade off), however if a program doesn't utilise 
serialization and has disabled serialization using a filter, this provides an 
opportunity for an attacker.  For the attack to be successful, the attacker 
needs to change the serialization filter properties, prior to initialization, 
if the attacker is successful, they will be able to disable the only security 
mechanism protecting de-serialization against attack.
I’m not sure that’s right. Briefly looking at the code, it seems that if you 
set jdk.serialFilter on the command line, you cannot set it again from the 
program (you can set the property, but it looks to me like the new value will 
be ignored). But I’m not familiar with the implementation at all and I could be 
wrong. In any event, that’s an unrelated topic.

😇



— Ron

Reply via email to