I am developing some licensing code, so I would like to avoid access to one or two classes through reflection.
I have tried implement a SecurityManager but with no success.
I found that another option would be to define my own policy file but that requires me to set it on JVM launch, and that defeats my purpose, I would like to be able to set either a SecurityManager that prevents access to the specific License classes, or a policy to do so in runtime.
Advertisement
Answer
Notes & Disclaimers
- The answer assumes that you are in control of, or otherwise trust in, the environment; unless this is the case, applying it will be utterly ineffectual.
- The answer is relevant for Java SE <= 8. The code provided is unlikely to work as intended from 9 onwards, and the Module System has reduced the number of cases where use of the security framework is actually necessary in order to achieve isolation.
- Code is (as most things on the internet) provided “as-is”.
Prerequisites
Ensure any untrusted code is isolated from trusted code. Isolation in this context means:
- separation between
ProtectionDomain
s (two domains of two code bases of two different levels of “trustworthiness” should have non-equal, non-implyingCodeSource
s), and - packages, and
ClassLoader
s.
While the first is generally sufficient, in cases when the line between the concerns of authorization and accessibility is blurred, as is the case with reflection, the latter two points are necessitated as well. I’ll leave the details of addressing this to you. And since we’re here, a note on terminology: since the JDK doesn’t consistently authorize client code against the same of the aforementioned attributes, I’m simply going to be referring to code exercising any of the three at times of authorization decisions as an isolation unit.
Option A – Restricting package access
Perhaps the simplest solution is to prevent untrusted code from accessing the particular package at all:
- Have a common (base) package for sensitive code.
- Declare it as restricted, either via the
package.access
property in<JAVA_HOME>/lib/security/java.security
, or programmatically, by appending the package name to the corresponding Security Property, by means ofjava.security.Security#setProperty
. - Have clients access your sensitive code indirectly, via a trusted intermediary of yours, located outside of the restricted package. That class should be granted
RuntimePermission
"accessClassInPackage." + restrictedPackageName
.
But there’s a catch—you also want untrusted code to have full reflective access to “the rest of the universe”, which is by no means possible without also implicitly granting it access to bring the sandbox to its knees, if it so pleases, by, for instance, reassigning System'
s SecurityManager
field. Which leads us to…
Option B – Selectively denying reflective access
Goals
- Grant or deny reflective access on a per-operation basis, based on attributes such as:
- The kind of reflected member (field, method, etc.).
- The target package.
- The target class.
- The member’s name.
- Insert further of your own.
- Model a
Permission
class after the above, which the defaultPolicy
configuration can express, and the defaultSecurityManager
can enforce.
Non-goals
- Automagically prevent privilege escalation resulting from, e.g., code privileged to reflectively invoke some method taking advantage of that method execution’s side effects.
- Enforce such constraints within a single isolation unit.
- Care about performance.
I’ve played with the above a bit, and here’s the result, which is anything but perfect or even bug-free, but hopefully sufficient to demonstrate the idea.
Brief overview (refer to Javadoc for details)
SelectiveReflectPermission
is the permission to grant to untrusted code. While incapable of accommodating for further authorization attributes in its current form, modifying it to include them should be fairly straightforward.AllUnsafePermission
expresses a set of privileges that must not be granted to untrusted code (unless sandbox integrity is irrelevant).DeniedPermission
andDenyingPolicy
are the same classes given here, with small modifications of the former so as to allow wrapping of “target” permissions having a no-arg constructor. These classes only add the convenience of expressing “deny-like” rules in the policy configuration, without otherwise being essential.ReflectionGatekeeper
delegates to some of the most frequently used parts of the reflection API, after checking forSelectiveReflectPermission
. The same logic could be embedded in a customSecurityManager
‘s relevant methods (primarily#checkPermission(Permission)
) invoked by core reflection, which client code would very much appreciate as opposed to having to use of a different API. But (there’s always a “but”) I chose against that option, since it would require brittle “caller-sensitive” code to be introduced to the implementation, which would have to collect the needed authorization attributes by investigating the call stack, also accounting for recursive reflective invocations, as well as invocations by multiple threads… leading to something astonishingly close to a pre-J2SE 1.2 security manager. Furthermore, as a matter of taste, I prefer the manager to contain as little actual authorization logic as possible—ideally it would merely serve as anAccessController
, and, ultimately,Policy
delegate, without shortcuts or loopholes.
Usage
Have either some kind of application launcher, or the initialization logic of your sensitive components themselves establish security (and fail unless successful); this could look as follows:
package com.example.trusted.launcher; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.Policy; import java.util.Collections; import com.example.trusted.security.DenyingPolicy; public class Launcher { public static void main(String... args) throws Exception { /* * Get the actual main application class, which has hopefully been loaded by a different ClassLoader, * and resides in a distinct package and ProtectionDomain. */ Class<?> realMain = getMainFromArgs(args); // install a Policy and SecurityManager /* * To avoid having to administer .policy configurations at the file system, you could bundle a * template with your app/lib, replace any CodeSource URLs you don't know until runtime, temp-save * to the file system (or use some transient URLStreamHandler) and "feed" the corresponding URL to * the Policy provider. Or better yet, if you can spare the time, implement your own Policy provider * as a mutable data structure. */ String policyConfig = new String( Files.readAllBytes(Paths.get(Launcher.class.getResource("policy_template.txt").toURI())), StandardCharsets.UTF_8); // replace any CodeSource URL placeholders (e.g. with realMain's cs URL) policyConfig = adjustPolicyConfig(policyConfig); // temp-save it and hand it over to Policy Path tmpPolicyFile = Files.createTempFile("policy", ".tmp"); Files.write(tmpPolicyFile, Collections.singletonList(policyConfig)); // leading equals sign ensures only the indicated config gets loaded System.setProperty("java.security.policy", "=" + tmpPolicyFile.toUri()); // unnecessary if you don't care about deny rules Policy.setPolicy(new DenyingPolicy()); System.setSecurityManager(new SecurityManager()); Files.delete(tmpPolicyFile); // filter args and call real main invokeMain(realMain, args); } // ... }
…while a potential policy configuration template could be:
// note: curly braces are MessageFormat-escaped // --- // trusted code grant codeBase "{0}" '{' permission java.security.AllPermission; '}'; // sandboxed code grant codeBase "{1}" '{' // all permissions... permission java.security.AllPermission; // ...save for unsafe ones permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.AllUnsafePermission"; // ...with global reflective access via ReflectionGatekeeper permission com.example.trusted.security.SelectiveReflectPermission; // ...with the exception of system code and our own com.example.trusted package permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.SelectiveReflectPermission:*!sun.*!*!*:*"; permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.SelectiveReflectPermission:*!com.sun.*!*!*:*"; permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.SelectiveReflectPermission:*!com.oracle.*!*!*:*"; permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.SelectiveReflectPermission:*!net.java.*!*!*:*"; permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.SelectiveReflectPermission:*!java.*!*!*:*"; permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.SelectiveReflectPermission:*!javax.*!*!*:*"; // currently it's not possible to express both a base package _and_ its sub-packages in a single permission permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.SelectiveReflectPermission:*!com.example.trusted!*!*:*"; permission com.example.trusted.security.DeniedPermission "com.example.trusted.security.SelectiveReflectPermission:*!com.example.trusted.*!*!*:*"; '}';
With those two pieces in place, some class mapped to domain {1}
will not be able to invoke a core reflection method on a Class
outside of its isolation unit; but it will be able to use the ReflectionGatekeeper
for the equivalent, in all cases except those explicitly denied.
How about Java SE > 8? (addition as per the comments)
With the introduction of the Module System in Java 9, accessibility was intended to become the concern of each individual module as opposed to one centrally manageable; refer to usage for provides
/ exports
/ opens
as either module-info.java directives, java launch options or runtime invocations for the essentials. Centralized and/or more dynamic administration remains a possibility in different ways (instrumentation, custom module loading or ditching modules altogether in favor of a home-grown system).
The centralized SM-based approach given above remains to some extent applicable in recent versions, with potential caveats involving the built-in JDK class loaders having since ceased being URLClassLoader
s as well as modular JARs utilizing a distinct URL scheme. It is however nowhere near as useful as it once was, given that the prevalent use case of preventing application or library code from reflectively accessing unsafe JDK internals (or any internals for that matter really) is already addressed by the system — nothing inside a module, A, is accessible from the outside world unless explicitly declared or rendered so at runtime by A itself. Last but no least it must be noted that the SM was terminally deprecated in Java 17.