I found a 90% answer. The short answer is that we need to set up a lot of permissions but the process is obvious and easy to hide in some utility class. It would be nice if it were available somewhere standard, but oh well. le sigh.
All we need to do is check system properties for the classpath and ext dir (and add FilePermissions (read-only) for them), make the necessary SecurityPermissions (read-only), and finally add PropertyPermissions (read-only) for all system properties. All that's left is a handful of really obvious permissions, e.g., granting r/w/d (but not execute) permission to the temp directory, granting 'resolve' access to localhost, etc.
A really secure sandbox may not want to make all of the system properties readable but that's easily fixed and is left to the reader.
public class LoggingSecurityManager extends SecurityManager {
private AccessControlContext ctx;
private Properties properties = new Properties;
private Set missingProperties = new HashSet();
public LoggingSecurityManager() {
properties.add(
new FilePermission(System.get("java.io.tmpdir") + "/-", "read,write,delete"));
// maybe...
properties.add(
new FilePermission(System.get("user.home") + "/-", "read,write,delete"));
addSystemPropertyPermissions();
addSecurityPermissions();
addClassPathPermissions();
addOtherPropertyPermissions();
permissions.add(new RuntimePermission("accessClassInPackage.sun.reflect"));
permissions.add(new RuntimePermission("accessClassInPackage.sun.jdbc.odbc"));
permissions.add(new RuntimePermission("accessClassInPackage.sun.security.provider"));
permissions.add(new SocketPermission("localhost", "resolve"));
permissions.add(new NetPermission("getProxySelector"));
ctx = new AccessControlContext(new ProtectionDomain[] {
new ProtectionDomain(null, permissions)
});
}
/**
* Add read-only permission to read system properties.
* We may want to filter this list to remove sensitive information
*/
public void addSystemPropertyPermissions() {
for (Object key : Collections.list(System.getProperties().keys())) {
permissions.add(new PropertyPermission((String) key, "read"));
}
}
/**
* Add read-only permissions for initializing security.
*/
public void addSecurityPermissions() {
permissions.add(new SecurityPermission("getPolicy"));
permissions.add(new SecurityPermission("getProperty.random.source"));
permissions.add(new SecurityPermission("getProperty.securerandom.source"));
for (int i = 1; i < 10; i++) { // configurable limit?
permissions.add(new SecurityPermission("getProperty.security.provider." + i));
}
String s = Security.getProperty("securerandom.source");
if ((s != null) && s.startsWith("file:/")) {
permissions.add(new FilePermission(s.substring(5), "read"));
}
// should have been covered already but wasn't....
permissions.add(new FilePermission("/dev/random", "read"));
}
/**
* Add read-only permissions for everything on classpath.
*/
public void addClassPathPermissions() {
permissions.add(new FilePermission(String.format("%/lib/-",
System.getProperty("java.home")), "read"));
// add standard class path.
String pathSep = System.getProperty("path.separator");
for (String entry : System.getProperty("java.class.path").split(pathSep)) {
File f = new File(entry);
if (f.isFile()) {
permissions.add(new FilePermission(entry, "read"));
} else if (f.isDirectory()) {
permissions.add(new FilePermission(String.format("%s/-", entry), "read"));
} // or could be neither fish nor fowl
}
// add endorsed extensions.
for (String dir : System.getProperty("java.ext.dirs").split(pathSep)) {
permissions.add(new FilePermission(String.format("%s/-", dir), "read"));
}
}
/**
* Add other standard properties.
*/
public void addOtherPropertyPermissions() {
permissions.add(new PropertyPermission("jdbc.drivers", "read"));
permissions.add(new PropertyPermission("java.security.egd", "read"));
permissions.add(new PropertyPermission("socksProxyHost", "read"));
}
}
There's one scary bit less. These permissions open the door to massive havoc.
// ------------ S C A R Y - B L O C K -----------
permissions.add(new ReflectPermission("suppressAccessChecks")); (!!)
permissions.add(new RuntimePermission("createClassLoader")); (!!)
permissions.add(new SecurityPermission("putProviderProperty.SUN"));
permissions.add(new RuntimePermission("readFileDescriptor"));**
permissions.add(new RuntimePermission("writeFileDescriptor"));
// ------------ S C A R Y - B L O C K -----------
I haven't decided on the best course of action here. I think what I may do is override the checkPermission method and look at the call stack when the first two permissions (at least) are seen. They're probably safe if they're coming from deep within the JDK. They're probably iffy if coming from the user code.