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