What am I trying to achieve?
I am working on a java application that can be extended by additional jars that get integrated via ServiceLoader. These loaded extensions should run with some restrictions by the SecurityManager, of course simply to improve the security. As an example each Extension shall get one specific directory where it can store whatever, but access to any other file/folder should be restricted. The main application is trusted code and can therefore run without any restrictions. Furthermore the main application provides some api implementations for each extension that shall also run without restrictions. That means an extension mustn't access a file outside of its directory but when the extension is calling an api method that tries to access any other file, the access should be granted.
Question
How can I achieve the mentioned behaviour that only 'direct' calls from extension classes get restricted but not any code from the main application?
Running extensions in different threads/threadGroups might be a good solution anyway but since calls to the api might run under the same thread(group) it might not help to identify whether access should be restricted or not based only on the thread.
Example
I created a simplified test environment. On one hand there are these two interfaces:
public interface Extension {
void doSomethingRestricted();
void doSameViaApi(ExtensionApi api);
}
public interface ExtensionApi {
void doSomethingWithHigherPermissions();
}
For testing I created a jar containing this extension:
public class SomeExtension implements Extension {
public void doSomethingRestricted() {
System.out.println(System.getProperty("user.home"));
}
public void doSameViaApi(final ExtensionApi api) {
api.doSomethingWithHigherPermissions();
}
}
In the main application I would like do something like this:
final ExtensionApi api = () -> System.out.println(System.getProperty("user.home"));
try {
final URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { jarFile.toURI().toURL() });
for(final Extension extension : ServiceLoader.load(Extension.class, urlClassLoader)) {
extension.doSomethingRestricted();
extension.doSameViaApi(api);
}
}
So when I call extension.doSomethingRestricted(); it should result in a SecurityException but calling extension.doSameViaApi(api); should work just fine.
So both methods try to do the same but one does try to do it via the api call. The only approach I could think of is iterating through the call history and checking the classloaders to analyze whether the access request is based on trusted code or extension code. But I feel like this might be a nasty error-prone solution so maybe I missed some better approaches?
First ensure your "main" JAR's classes get to enjoy full privileges. Programmatically this may be accomplished as follows:
package q46991566;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Policy;
import java.util.Collections;
public class Main {
public static void main(String... args) throws Exception {
// policy configuration contents: this JAR gets all permissions, others get nothing
StringBuilder sb = new StringBuilder("grant {};\n\ngrant codebase \"")
.append(Main.class.getProtectionDomain().getCodeSource().getLocation())
.append("\" {\n\tpermission java.security.AllPermission;\n};\n");
// temp-save the policy configuration
Path policyPath = Files.createTempFile(null, null);
Files.write(policyPath, Collections.singleton(sb.toString()));
// convey to the default file-backed policy provider where to obtain its configuration from;
// leading equals ensures only the specified config file gets processed
System.setProperty("java.security.policy", "=".concat(policyPath.toUri().toURL().toString()));
// establish a policy; "javaPolicy" is the default provider's standard JCA name
Policy.setPolicy(Policy.getInstance("javaPolicy", null));
// policy loaded; backing config no longer needed
Files.delete(policyPath);
// establish a security manager for enforcing the policy (the default implementation is more than
// sufficient)
System.setSecurityManager(new SecurityManager());
// ...
}
}
Alternatively, you will either have to a) modify the JRE distribution's java.policy (or specify a different configuration via the policy.url.n properties in java.security), or b) replace the implementation of the System ClassLoader with one that statically grants AllPermission to the ProtectionDomain associated with classes loaded from the "main" JAR.
Secondly, when loading Extensions from some JAR, employ a URLClassLoader subclass that a) manages extension-specific directories and b) includes a java.io.FilePermission in the permission collection being statically accorded to the protection domain mapped to its defined classes. Crude sample implementation (note that there is no persistent relationship between an extension JAR and a directory; also note that two Extensions originating from the same JAR (but loaded by different class loaders, of course) will get different directories):
package q46991566;
import java.io.FilePermission;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.Objects;
public final class ExtensionLoader extends URLClassLoader {
private static void copyPermissions(PermissionCollection src, PermissionCollection dst) {
for (Enumeration<Permission> e = src.elements(); e.hasMoreElements();) {
dst.add(e.nextElement());
}
}
private final CodeSource origin;
private final PermissionCollection perms = new Permissions();
private final Path baseDir;
public ExtensionLoader(URL extensionOrigin) {
super(new URL[] { extensionOrigin });
origin = new CodeSource(Objects.requireNonNull(extensionOrigin), (Certificate[]) null);
try {
baseDir = Files.createTempDirectory(null);
perms.add(new FilePermission(baseDir.toString().concat("/-"), "read,write,delete"));
copyPermissions(super.getPermissions(origin), perms);
perms.setReadOnly();
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
#Override
protected PermissionCollection getPermissions(CodeSource cs) {
return (origin.implies(cs)) ? perms : super.getPermissions(cs);
}
// ExtensionApiImpl (or ExtensionImpl directly -- but then ExtensionLoader would have to be relocated
// into a separate, also fully privileged JAR, accessible to the extension) can call this to relay to
// extensions where they can persist their data
public Path getExtensionBaseDir() {
return baseDir;
}
// optionally override close() to delete baseDir early
}
Lastly, for unprivileged Extensions to be able to execute privileged operations via ExtensionApi, the latter's implementation must wrap privileged method (methods issuing SecurityManager::checkXXX requests) invocations within Privileged(Exception)Actions and pass them to AccessController::doPrivileged; e.g.:
ExtensionApi api = () -> {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
try {
Files.write(Paths.get("/root/Documents/highly-sensitive.doc"), Collections.singleton("trusted content"),
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
return null;
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
});
};
For details on the (proper) use of "privileged blocks", refer to the AccessController documentation and the "Secure Coding Guidelines for Java SE" document.
Related
I am trying to delete a DLL which has been loaded into JNA and later disposed. I have tried all the solutions described in the answer to this question, but they are not working: How to dispose library loaded with JNA
Here is code I've tried without a time delay:
import java.io.File;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
class Filter {
private static ExtDLLTool DLLUtil;
final private static String dllPath = "./ExternalDownloader_64.dll";
static {
DLLUtil = (ExtDLLTool) Native.loadLibrary(dllPath, ExtDLLTool.class);
}
public static void main(String[] args) {
if (DLLUtil != null) {
DLLUtil = null;
NativeLibrary lib = NativeLibrary.getInstance(dllPath);
lib.dispose();
}
File dllFile = new File(dllPath);
if(dllFile.exists()){
boolean isDeleted = dllFile.delete();
if(!isDeleted){
System.out.println("Unable to delete dll file, since it hold by jvm");
}
}
}
private interface ExtDLLTool extends Library {
String validateNomination(String dloadProps);
}
}
I added a time delay to give the native code time to release the handle:
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
class Filter {
private static ExtDLLTool DLLUtil;
final private static String dllPath = "./ExternalDownloader_64.dll";
static {
DLLUtil = (ExtDLLTool) Native.loadLibrary(dllPath, ExtDLLTool.class);
}
public static void main(String[] args) throws Exception{
if (DLLUtil != null) {
DLLUtil = null;
NativeLibrary lib = NativeLibrary.getInstance(dllPath);
lib.dispose();
Thread.sleep(3000);
}
File dllFile = new File(dllPath);
if(dllFile.exists()){
Files.delete(Paths.get(dllPath));
// boolean isDeleted = dllFile.delete();
if(dllFile.exists()){
System.out.println("Unable to delete dll file, since it hold by jvm");
}
}
}
private interface ExtDLLTool extends Library {
String validateNomination(String dloadProps);
}
}
This code results in an exception implying the JVM has not released the file.
Exception in thread "main" java.nio.file.AccessDeniedException: .\ExternalDownloader_64.dll at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:83) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) at sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:269)
In the end the problem is, that Native#open is called twice and Native#close only once. The assumption behind the presented code is, that:
NativeLibrary lib = NativeLibrary.getInstance(dllPath);
yields the same NativeLibrary instance, that is used by:
DLLUtil = (ExtDLLTool) Native.loadLibrary(dllPath, ExtDLLTool.class);
This assumption does not hold. Indeed NativeLibrary#load does use caching and if invoked with the same parameters it will yield only a single instance.
The codepath behind Native.loadLibrary passes two options to Native#loadLibrary: calling-convention and classloader. The calling-convention is equal to the default calling convention, so can be ignored. It is/would be automatically added in NativeLibrary#getInstance. The classloader though is not set to a default value and there is the difference. The options are part of the caching key and thus a second instance of the NativeLibrary is created and not the first returned.
To make it work, the call to NativeLibrary#getInstance must pass the correct classloader. If you modify the sample like this:
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
class Filter {
private static ExtDLLTool DLLUtil;
final private static String dllPath = "./ExternalDownloader_64.dll";
static {
DLLUtil = (ExtDLLTool) Native.loadLibrary(dllPath, ExtDLLTool.class);
}
public static void main(String[] args) throws Exception{
if (DLLUtil != null) {
DLLUtil = null;
NativeLibrary lib = NativeLibrary.getInstance(dllPath, ExtDLLTool.class.getClassLoader());
lib.dispose();
Thread.sleep(3000);
}
File dllFile = new File(dllPath);
if(dllFile.exists()){
Files.delete(Paths.get(dllPath));
// boolean isDeleted = dllFile.delete();
if(dllFile.exists()){
System.out.println("Unable to delete dll file, since it hold by jvm");
}
}
}
private interface ExtDLLTool extends Library {
String validateNomination(String dloadProps);
}
}
it works as expected.
After discussion there is another requirement: The cache path is only hit in a limited number of cases:
the library name is the filename of the library (without a prefix)
the library name is the absolute path to the library
the library name is the "base" name without any prefixes or suffixes the default library search mechanism adds (on windows ".dll" should be stripped, on linux "lib" prefix and ".so" suffix should be stripped) (UNTESTED!)
The TL;DR version: find the absolute path name and use that for interface loading and NativeLibrary loading.
I was able to reproduce the problem with your code, but only on Windows. When reproducible, I was able to successfully delete the file by adding a garbage collection suggestion before the time delay:
if (DLLUtil != null) {
DLLUtil = null;
NativeLibrary lib = NativeLibrary.getInstance(dllPath);
lib.close();
System.gc();
System.gc();
Thread.sleep(3000);
}
When JNA loads a Windows DLL via Native.loadLibrary(), it internally executes the WinAPI LoadLibraryExW function.
Internally the Java instance is stored in a map to be re-used when possible -- however for this to happen, it requires two things to look up the same Java object:
the DLL Path must be an absolute path
the options must match. In this case, you would need to pass the classloader as an argument as Matthias Bläsing indicated in his answer:
// if loaded like this:
DLLUtil = (ExtDLLTool) Native.loadLibrary(dllPath, ExtDLLTool.class);
// fetch from cache like this:
NativeLibrary lib = NativeLibrary.getInstance(dllPath, ExtDLLTool.class.getClassLoader());
lib.dispose();
This should allow you to delete the file.
However, in your case, with the relative path, the library is getting unloaded but the old java object isn't getting closed until GC occurs.
The dispose() (or close() as of 5.12) call in JNA eventually calls the Native.close() method which uses the Windows API FreeLibrary function. This unloads the DLL from the Process memory, so the advice on the linked question on how to dispose is still accurate in the case that you want to re-load the library. If you're not reloading the library, using dispose() (5.11-) or close() (5.12+) is optional.
If you must use a relative path, consider this approach using a PhantomReference inspired by this answer to track the deletion:
if (DLLUtil != null) {
// Unload the DLL from process memory
// Optional here, as it will be called by a cleaner on GC below
NativeLibrary lib = NativeLibrary.getInstance(dllPath);
lib.close();
System.out.println("Closed.");
// Remove any internal JVM references to the file
final ReferenceQueue rq = new ReferenceQueue();
final PhantomReference phantom = new PhantomReference(DLLUtil, rq);
DLLUtil = null;
// Poll until GC removes the reference
int count = 0;
while (rq.poll() == null) {
System.out.println("Waiting...");
Thread.sleep(1000);
if (++count > 4) {
// After 5 seconds prompt for GC!
System.out.println("Suggesting GC...");
System.gc();
}
}
System.out.println("Collected.");
}
The DLL was successfully deleted following this sequence. It did take a second GC call to take effect:
Closed.
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Suggesting GC...
Waiting...
Suggesting GC...
Collected.
Deleted!
My objective is to look at some lines of codes of an external file and count the number of functions of a class are called then.
For example, if I have the following code:
import java.io.BufferedReader;
import whatever.MyClass;
import java.util.ArrayList;
...
...
public void example(){
InputStreamReader isr = new InputStreamReader (whatever);
MyClass object = new MyClass();
someArrayList.add(whatever2)
someArrayList.add(whatever3)
}
In this case, BufferedReader and MyClass functions were called once, and ArrayList functions were called twice.
My solution for that is get a list of all methods inside the used classes and try to match with some string of my code.
For classes created in my project, I can do the following:
jar -tf jarPath
which returns me the list of classes inside a JAR . And doing:
javap -cp jarPath className
I can get a list of all methods inside a JAR whit a specific class name. However, what can I do to get a external methods names, like add(...) of an "external" class java.util.ArrayList?
I can't access the .jar file of java.util.ArrayList correct? Anyone have another suggestion to reach the objective?
The compiler doesn't put the imports into the object file. It throws them away. Import is just a shorthand to the compiler.(Imports are a compile-time feature ).
first step :
use Qdox https://github.com/paul-hammant/qdox to get all the imports in a class :
String fileFullPath = "Your\\java\\ file \\full\\path";
JavaDocBuilder builder = new JavaDocBuilder();
builder.addSource(new FileReader( fileFullPath ));
JavaSource src = builder.getSources()[0];
String[] imports = src.getImports();
for ( String imp : imports )
{
System.out.println(imp);
}
second step :
inspire from that code , loop through your imports (String array) and apply the same code and you will get the methods .
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Tes {
public static void main(String[] args) {
Class c;
try {
c = Class.forName("java.util.ArrayList");
Arrays.stream(getAccessibleMethods(c)).
forEach(System.out::println);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Method[] getAccessibleMethods(Class clazz) {
List<Method> result = new ArrayList<Method>();
while (clazz != null) {
for (Method method : clazz.getDeclaredMethods()) {
result.add(method);
}
clazz = clazz.getSuperclass();
}
return result.toArray(new Method[result.size()]);
}
}
Output :
public void java.util.ArrayList.add(int,java.lang.Object)
public boolean java.util.ArrayList.add(java.lang.Object)
public boolean java.util.ArrayList.remove(java.lang.Object)
public java.lang.Object java.util.ArrayList.remove(int)
public java.lang.Object java.util.ArrayList.get(int)
public java.lang.Object java.util.ArrayList.clone()
public int java.util.ArrayList.indexOf(java.lang.Object)
public void java.util.ArrayList.clear()
.
.
.
All the code - one class :
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.JavaSource;
public class Tester {
public static void main(String[] args) {
// put your .java file path
// CyclicB is a class within another project in my pc
String fileFullPath =
"C:\\Users\\OUSSEMA\\Desktop\\dev\\OCP_Preparation\\src\\w\\CyclicB.java";
JavaDocBuilder builder = new JavaDocBuilder();
try {
builder.addSource(new FileReader( fileFullPath ));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JavaSource src = builder.getSources()[0];
String[] imports = src.getImports();
for ( String imp : imports )
{
Class c;
try {
c = Class.forName(imp);
Arrays.stream(getAccessibleMethods(c)).
forEach(System.out::println);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public static Method[] getAccessibleMethods(Class clazz) {
List<Method> result = new ArrayList<Method>();
while (clazz != null) {
for (Method method : clazz.getDeclaredMethods()) {
result.add(method);
}
clazz = clazz.getSuperclass();
}
return result.toArray(new Method[result.size()]);
}
}
Output all the methods within the classes imported in the file CyclicB.java :
private void java.lang.Throwable.printStackTrace(java.lang.Throwable$PrintStreamOrWriter)
public void java.lang.Throwable.printStackTrace(java.io.PrintStream)
public void java.lang.Throwable.printStackTrace()
public void java.lang.Throwable.printStackTrace(java.io.PrintWriter)
public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace()
.
.
.
You may look into OpenJDK project that has a Java compiler. Learn to build the modified versions. Investigate the syntax analysis layer of this compiler and find where the method calls are handled. Put the logging into these locations and now you only need to build your java file with the modified compiler to get the information about the calls.
The build is complex, but you will likely only need a careful editing in a few files. It is not exactly very low hanging fruit but I think it should be possible to discover these files and make changes in them, and still may be a simpler/cleaner approach than to implement the own Java syntax parser (also doable with JavaCC).
If you also need to track calls from the external libraries, build them with the modified compiler as well and you will have the needed records.
GNU Classpath is another open source project where you can do the similar thing, and it may be easier to build. However, unlike OpenJDK, GNU Classpath java system library is not complete.
This approach may not discover some methods called during reflection. But it would discover that reflection framework methods have been called. If it is a security - related project, the simplest would be to agree that reflection is not allowed. It is uncommon to use reflection in a normal Java application that is not a framework.
I'm trying to prevent plugins that run "inside" the main Java application from accessing things they shouldn't. I've read about Policies, and AccessControllers, and ProtectionDomains, but they're very oriented around JARs.
I've tried this:
import java.nio.file.Files
import java.nio.file.Paths
import java.security.*
fun main(args: Array<String>) {
Policy.setPolicy(object : Policy() {})
System.setSecurityManager(SecurityManager())
val domain = ProtectionDomain(null, Permissions() /* no permissions */)
AccessController.doPrivileged(PrivilegedAction {
untrusted()
}, AccessControlContext(arrayOf(domain)))
}
fun untrusted() {
try {
// Works as expected
Files.readAllBytes(Paths.get("build.gradle"))
throw IllegalStateException("Was able to access file, but shouldn't have been able to")
} catch (e: AccessControlException) {
}
try {
// Should throw AccessControlException, but doesn't
AccessController.doPrivileged(PrivilegedAction {
Files.readAllBytes(Paths.get("build.gradle"))
})
throw IllegalStateException("Was able to access file, but shouldn't have been able to")
} catch (e: AccessControlException) {
}
}
Even though I'm invoking untrusted() via a custom limited ProtectionDomain, it seems it can trivially break out of it. I'm expecting the the doPrivileged call in untrusted to operate with the intersection of the permissions of the outermost ProtectionDomain (the main program, which has all permissions) and the caller's ProtectionDomain (which has no permissions), resulting in untrusted having essentially 0 permissions.
I've also tried with the domain set like this:
val domain = ProtectionDomain(CodeSource(URL("http://foo"), null as Array<CodeSigner>?), Permissions() /* no permissions */)
but this also doesn't work -- the Policy is queried with the main program's ProtectionDomain and not the one calling untrusted(). (Obviously I'd need to update the Policy to handle "http://foo" correctly, but it doesn't even check that ProtectionDomain anyway)
So where has my understanding gone wrong?
After doing some research on this, I think I have an answer. I could write a significantly longer answer, but I think I'll just cut to the chase here.
Each class loaded by a ClassLoader has a ProtectionDomain+CodeSource associated with it. These are somewhat coarse -- a CodeSource represents where a class came from, but it's not a pointer to an individual .class file or anything -- it's to a directory or a JAR. Thus two classes in the same JAR or directory will generally have identical permissions. Any class or script that has an identifiable ProtectionDomain+CodeSource can be whitelisted/blacklisted by your Policy.
The exception (kinda) to this is, of course, is AccessController.doPrivileged with Permission arguments. This lets you clamp down the permissions of a region of code. But that code could, in theory, call AccessController.doPrivileged with just the callback. That method signature means "don't check my entire call stack for permissions; just look up my ProtectionDomain+CodeSource in the Policy file and see what it says." So if you're running truly untrusted code, you better make sure that a. it has a ProtectionDomain+CodeSource different from your trusted application, and b. that your Policy is able to identify that code and grant it appropriately-limited permissions.
Here is one way for the example to run as intended, i.e., to effectively blacklist subsequent execution paths under the same domain. The core permission-intersection-based authorization model should still hold. The sample must be run with -Djava.system.class.loader=com.example.Test$AppClassLoader (this replacement system class loader is only needed in order to attain a working single-file example).
Obligatory disclaimer: While technically many things are possible, to the point of dynamically white-/blacklisting individual instances and beyond, they all involve additional context of some sort being introduced into the already non-trivial authorization process. Such approaches should be avoided whenever possible. The proper solution, sufficing in the vast majority of cases, as documented in the OP's answer's conclusion, is to package trusted code separately from untrusted (and, when manually managing class-to-domain mappings, ensuring that code bases of distinct trustworthiness are mapped to distinct domains), and assign appropriate permissions to the resulting domains.
package com.example;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static final class AppClassLoader extends URLClassLoader {
private static final URL[] CLASS_PATH;
private static final String SANDBOXABLE_DOMAIN_CLASS_NAME = "com.example.Test$SandboxableDomain";
static {
String[] paths = System.getProperty("java.class.path").split(File.pathSeparator);
List<URL> classPath = new ArrayList<>();
for (String path : paths) {
try {
classPath.add(new URL("file://" + path));
}
catch (MalformedURLException ex) {}
}
CLASS_PATH = classPath.toArray(new URL[0]);
}
private final Constructor<?> sandboxableDomainCtor;
{
try {
// ensure this loader defines SandboxableDomain so that normal code
// can safely / conveniently access it via class literal
Class<?> sandboxableDomainClass = loadClass(SANDBOXABLE_DOMAIN_CLASS_NAME, true);
sandboxableDomainCtor = sandboxableDomainClass.getConstructor(CodeSource.class,
PermissionCollection.class, ClassLoader.class);
}
catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
public AppClassLoader(ClassLoader parent) {
super(CLASS_PATH, parent);
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java") || name.startsWith("sun")) {
return super.loadClass(name, resolve);
}
Class<?> ret = findLoadedClass(name);
if (ret != null) {
return ret;
}
ProtectionDomain assignedDomain;
byte[] classData;
try {
URL classResource = getResource(name.replace(".", "/") + ".class");
CodeSource assignedCodeSource = new CodeSource(classResource, (Certificate[]) null);
classData = Files.readAllBytes(Paths.get(classResource.toURI()));
if (SANDBOXABLE_DOMAIN_CLASS_NAME.equals(name)) {
// loading the domain class itself; ensure _its own_ domain is fully privileged,
// so that it doesn't affect authorization
PermissionCollection perms = new Permissions();
perms.add(new AllPermission());
assignedDomain = new ProtectionDomain(assignedCodeSource, perms, this, null);
}
else {
// the per-class code source (URL) is unintentional; normally all classes under
// the same class path entry would share one
assignedDomain = (ProtectionDomain) sandboxableDomainCtor.newInstance(assignedCodeSource,
getPermissions(assignedCodeSource), this);
}
}
catch (NullPointerException | URISyntaxException | IOException | ReflectiveOperationException ex) {
throw new ClassNotFoundException(name);
}
ret = defineClass(name, classData, 0, classData.length, assignedDomain);
if (resolve) {
resolveClass(ret);
}
return ret;
}
}
public static final class SandboxableDomain extends ProtectionDomain {
private static final Permission DO_SANDBOXED_PERM = new RuntimePermission("com.example.doSandboxed");
private final ThreadLocal<Boolean> sandboxed = new InheritableThreadLocal<>();
public SandboxableDomain(CodeSource cs, PermissionCollection permissions, ClassLoader classLoader) {
super(cs, permissions, classLoader, null);
sandboxed.set(false);
}
// no equivalent doUnsandboxed here for escaping the sandbox on-demand;
// firstly because it's fishy; secondly because it would be impossible
// to distinguish a privileged caller based on permissions alone
public void doSandboxed(Runnable action) {
if (!sandboxed.get()) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(DO_SANDBOXED_PERM);
}
}
sandboxed.set(true);
try {
action.run();
}
finally {
sandboxed.set(false);
}
}
#Override
public boolean implies(Permission permission) {
if (sandboxed.get()) {
// static only (AppClassLoader only grants essentials like reading from own directory)
PermissionCollection perms = getPermissions();
return (perms == null) ? false : perms.implies(permission);
}
// static + policy
return super.implies(permission);
}
}
public static void main(String[] args) throws Exception {
initSecurity();
SandboxableDomain ownDomain = (SandboxableDomain) Test.class.getProtectionDomain();
System.out.println("Try unsandboxed"); // should succeed
untrusted();
System.out.println("---\n\nTry sandboxed"); // should fail
ownDomain.doSandboxed(Test::untrusted);
System.out.println("---\n\nTry unsandboxed from within a child thread"); // should succeed
new Thread(Test::untrusted).start();
Thread.sleep(1000);
System.out.println("---\n\nTry unsandboxed from within a sandboxed child thread"); // should fail
ownDomain.doSandboxed(() -> new Thread(Test::untrusted).start());
}
private static void initSecurity() throws Exception {
Path tempPolicyConfig = Files.createTempFile(null, null);
// self-grant AllPermission
Files.write(tempPolicyConfig,
Collections.singletonList(new StringBuilder("grant codebase \"")
.append(Test.class.getProtectionDomain().getCodeSource().getLocation()).append("\"{permission ")
.append(AllPermission.class.getName()).append(";};").toString()));
System.setProperty("java.security.policy", "=" + tempPolicyConfig.toString());
System.setSecurityManager(new SecurityManager());
Files.delete(tempPolicyConfig);
}
private static void untrusted() {
try {
untrusted0();
System.out.println("\tSucceeded");
}
catch (AccessControlException ex) {
System.out.println("\tFailed; try via doPrivileged");
try {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
untrusted0();
return null;
});
System.out.println("\t\tSucceeded");
}
catch (AccessControlException ex1) {
System.out.println("\t\tFailed anew");
}
}
}
private static void untrusted0() {
try {
Files.readAllBytes(Paths.get("build.gradle"));
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
The following code exposes the bug, I know I could just tail the the results of getFile() & getPath() but I'm looking for something a big more elegant and robust. I want to be able to walk the folders in both the containing jar by default and with user selected/configuration paths to find & load resources.
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
public class UrlBug {
UrlBug() {
try {
final URL resourceRoot = this.getClass().getClassLoader().getResource(".");
System.out.println(resourceRoot.getFile());
System.out.println(resourceRoot.getPath());
System.out.println(resourceRoot.toURI());
System.out.println(resourceRoot.toExternalForm());
System.out.println(resourceRoot.toString());
new URL(resourceRoot.getFile());
} catch (final URISyntaxException e) {
e.printStackTrace();
} catch (final MalformedURLException e) {
e.printStackTrace();
}
}
public static void main(final String[] args) {
new UrlBug();
}
}
The output produced below with 'bug'
/C:/Users/Me/workspaces/.../target/classes/ <-- Malformed filename
/C:/Users/Me/workspaces/.../target/classes/ <-- Malformed path
file:/C:/Users/Me/workspaces/.../target/classes/
file:/C:/Users/Me/workspaces/.../target/classes/
file:/C:/Users/Me/workspaces/.../target/classes/
java.net.MalformedURLException: no protocol:
/C:/Users/Me/workspaces/scratch/target/classes/ at
java.net.URL.<init>(URL.java:585) at
java.net.URL.<init>(URL.java:482) at
java.net.URL.<init>(URL.java:431) at
scratch.UrlBug.<init>(UrlBug.java:20) at
scratch.UrlBug.main(UrlBug.java:39)
I'm quite prepared to accept the bug is in my code. However I think this is a problem with URL class and Kohsuke seems to agree
I'm not sure why you want to walk the jar directory structure yourself. Java has existing classes designed to handle just this case. To fix your code, you can just use the toString() method:
new URL(resourceRoot.toString());
The "file:/" prefix is the "protocol". Using the toString() method guarantees that the "file:/" protocol is prepended to the string. According to the docs:
Protocol handlers for the following protocols are guaranteed to exist on the search path:
http, https, ftp, file, and jar
http://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL(java.lang.String, java.lang.String, int, java.lang.String)
I'm trying to implement a custom java.security.Permission type, which should be checked at runtime (so no policy file, but in code). This checking is done by a java.security.Policy. I understood I should implement my own java.security.PolicySpi for this.
I cannot find any explanation on how to initialise and use a PolicySpi, or is there a better way to do this?
Checking permissions
In your question you stated that you then want to check the permission with java.security.Policy, but without using a spi.policy file.
From the PolicySpi API, you can see that a PolicySpi object features 4 methods:
engineGetPermissions(CodeSource codesource)
engineGetPermissions(ProtectionDomain domain)
engineImplies(ProtectionDomain domain, Permission permission)
engineRefresh()
However, you might not need PolicySpi as there are easier solutions to check permissions.
See:
Security Manager vs Access Controller
AccessController usage
Since you haven't specified what kind of permission you will grant, I will assume it is a permission concerning a java.security.CodeSource object.
To check all current permissions for a file:
public static void main(String[] args) {
CodeSource source;
try {
source = new CodeSource(new URL("file:/c:/*"), (java.security.cert.Certificate[]) null);
Policy policy = Policy.getPolicy();
System.out.println(policy.getPermissions(source));
} catch (IOException e) {
e.printStackTrace();
}
}
A nice example for the SecurityManager checkPermission() is this tutorial.
For checking specific FilePermissions, you can use:
FilePermission perm = new FilePermission("path/file", "read");
AccessController.checkPermission(perm);
Granting permissions
Granting permissions at runtime can be done with java.lang.RuntimePermission.
For other examples of how to grant permissions to a file, I suggest you read the following:
Access Control Mechanisms and Algorithms
Configuring spi.policy files
Security Managers and Permissions
That should bring you a long way! Good luck!
The previous answer lists alternatives to using PolicySpi (and more generally custom Policy implementations ). This answer will instead give a simplistic example on how a PolicySpi implementation can actually be used as a replacement of the system-default Policy.
Author a JCA Provider.
package com.example;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.Provider;
public final class TestProvider extends Provider {
private static final long serialVersionUID = 5544432861418770903L;
public TestProvider() {
super("TestProvider", 1, "TestProvider 1.0");
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
putService(new TestPolicySpiService(this));
return null;
});
}
}
Author the sole Service descriptor encapsulated by the provider.
package com.example;
import java.security.Policy.Parameters;
import java.security.PolicySpi;
import java.security.Provider;
import java.security.Provider.Service;
import java.util.Collections;
final class TestPolicySpiService extends Service {
TestPolicySpiService(Provider p) {
super(p, "Policy", "TestPolicy", PolicySpi.class.getName(), Collections.emptyList(), Collections.emptyMap());
}
#Override
public PolicySpi newInstance(Object constructorParameter) {
Parameters policyParams = null;
if (constructorParameter instanceof Parameters) {
policyParams = (Parameters) constructorParameter;
}
return new TestPolicySpi(policyParams);
}
#Override
public boolean supportsParameter(Object parameter) {
return parameter instanceof Parameters;
}
}
Author the actual service (the PolicySpi implementation in this case) that the service descriptor produces.
package com.example;
import java.security.Permission;
import java.security.Policy.Parameters;
import java.security.PolicySpi;
import java.security.ProtectionDomain;
final class TestPolicySpi extends PolicySpi {
TestPolicySpi(Parameters policyParams) {}
#Override
protected boolean engineImplies(ProtectionDomain domain, Permission permission) {
// deny unconditionally
return false;
}
}
Register the provider either statically, by modifying the security.provider.n properties in JAVA_HOME/lib/security/java.security, or programmatically, via java.security.Security.addProvider(Provider) / java.security.Security.insertProviderAt(Provider, int).
Replace the default Policy.
package com.example;
import java.security.NoSuchAlgorithmException;
import java.security.Policy;
public class Main {
public static void main(String... args) throws NoSuchAlgorithmException {
// the following assumes that the provider has been statically registered
Policy.setPolicy(Policy.getInstance("TestPolicy", null));
System.setSecurityManager(new SecurityManager());
// test
System.out.println(System.getProperty("user.home")); // should raise AccessControlException
}
}
Is there a better way to do this?
There certainly is a less involved way, as long as the consequent tight coupling between application and policy does not irk you too badly: Just subclass Policy directly and pass an instance of your implementation to Policy.setPolicy(Policy).
Further reading:
Java Cryptography Architecture (JCA) Reference Guide
How to Implement a Provider in the Java Cryptography Architecture
Standard Algorithm Name Documentation for JDK 8
Troubleshooting Security
As of Java 6, the default implementation for PolicySpi is sun.security.provider.PolicySpiFile. You can get inspired from the source code of PolicySpiFile:
package sun.security.provider;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PolicySpi;
import java.security.ProtectionDomain;
import java.security.URIParameter;
import java.net.MalformedURLException;
/**
* This class wraps the PolicyFile subclass implementation of Policy
* inside a PolicySpi implementation that is available from the SUN provider
* via the Policy.getInstance calls.
*
*/
public final class PolicySpiFile extends PolicySpi {
private PolicyFile pf;
public PolicySpiFile(Policy.Parameters params) {
if (params == null) {
pf = new PolicyFile();
} else {
if (!(params instanceof URIParameter)) {
throw new IllegalArgumentException
("Unrecognized policy parameter: " + params);
}
URIParameter uriParam = (URIParameter)params;
try {
pf = new PolicyFile(uriParam.getURI().toURL());
} catch (MalformedURLException mue) {
throw new IllegalArgumentException("Invalid URIParameter", mue);
}
}
}
protected PermissionCollection engineGetPermissions(CodeSource codesource) {
return pf.getPermissions(codesource);
}
protected PermissionCollection engineGetPermissions(ProtectionDomain d) {
return pf.getPermissions(d);
}
protected boolean engineImplies(ProtectionDomain d, Permission p) {
return pf.implies(d, p);
}
protected void engineRefresh() {
pf.refresh();
}
}