Intro
I wanted to try this because I thought it might be useful to add more security to our beloved CodeApplet. I wanted to understand the "Java way" to block certain things to certain class like what happens with applets. Note that this will only work in a Java application because applets are not allowed to install their own SecurityManager.
Explanations
Sandboxed.java is the nasty class that may attempt to cause harm to our system. The harm done here is that it simply tries to read a file that I've got in the root of my HD.
try {
BufferedReader fs = new BufferedReader(new FileReader("C:\\INSTALL.LOG"));
System.out.println(fs.readLine());
}
catch (FileNotFoundException e) {}
catch (IOException e) {}Nothing very harmful there but this is just a demo. Let's suppose that I don't know what this class does and I just want to make sure it doesn't read a random file from my HD. The trick here is to install a SecurityManager class that will be called everytime something suspicious is done by any class in the application.
This is the role of my Sandbox class which extends the default SecurityManager class. The SecurityManager class has several check''XXX'' that are called by classes in the Java API. For my example, I only consider checkRead() methods. All the other methods are handled by the system default SecurityManager which apparently denies everything. So as far as Java goes, this is nicely secure.
For more information on the SecurityManager class, check here.
public void checkRead(String file) {
if (isNastyClass())
throw new SecurityException("HA HA! Can't do that!");
}
public void checkRead(String file, Object context) {
if (isNastyClass())
throw new SecurityException("HA HA! Can't do that!");
}The isNastyClass method walks back in the call hierarchy to search for the class we don't want. If the class is found, that means that the file IO call originates from the class we want to watch and this is obviously not good.
I think this method should be pretty much self-explanatory even to Java newbies.
/** Check if our nasty class is in the calling stack.
* @return: Returns true if our 'Sandboxed' class is somewhere. */
public boolean isNastyClass() {
int i;
Class[] cc = this.getClassContext();
for (i = 0; i < cc.length; i++) {
if (cc[i].getName().indexOf("Sandboxed") != -1)
return true;
}
return false;
}The true concrete part of the program is in the Sandboxer class. The call to System.setSecurityManager(new Sandbox()); installs the new SecurityManager in the VM. Remember that this class will be used by everything else in the system. That means that if you decide to deny everything to everyone, the Java VM will be unable to load .class files!
It's possible to define a ProtectionDomain per ClassLoader, so that only classes loaded by that ClassLoader get restricted by the policy in the ProtectionDomain. To do that, you need to subclass ClassLoader and override the findClass method. Then, when calling defineClass, provide a ProtectionDomain. -- JoshuaTauberer
Yes. I've seen that feature in ClassLoader but wasn't sure how to use it. Beside, I'm not sure in what versions of the Java VM this feature in present. Versions 1.4x are not widely used. You seem to know quite a bit on the subject so if you can provide a short example, I think this whole wiki would be glad
-- FrancoisDenisGonthier
To demonstrate that this thing really works I have made to small file IO attempt: 1 from the Sandboxed class which is denied basically everything and another from the Sandboxer class which should be allowed to read from the disk (but not write it since the default behavior of SecurityManager is to disallow it [not totally sure]).
Here:
/** Try to do unallowed file IO. */
try {
sb.doNastyFileIO();
System.out.println("If we reach here, that means the class was sucessful, which is not good!");
} catch (SecurityException e) {
System.out.println("See? That did'nt work!");
}the call truly fails. But this part, which is a copy of the Sandboxed.doNastyFileIO() method, will succeed:
/** File IO from here should be allowed. */
try {
BufferedReader fs = new BufferedReader(new FileReader("C:\\INSTALL.LOG"));
System.out.println(fs.readLine());
}
catch (FileNotFoundException e) {}
catch (IOException e) {}
Main file (Sandboxer.java)
package sandboxing;
import java.io.*;
import java.security.*;
/** This is the class that load the evil class. The main class of this mini-app. */
public class Sandboxer {
public Sandboxer() {
Sandboxed sb = new Sandboxed();
System.setSecurityManager(new Sandbox());
/** Try to do unallowed file IO. */
try {
sb.doNastyFileIO();
System.out.println("If we reach here, that means the class was sucessful, which is not good!");
} catch (SecurityException e) {
System.out.println("See? That did'nt work!");
}
/** File IO from here should be allowed. */
try {
BufferedReader fs = new BufferedReader(new FileReader("C:\\INSTALL.LOG"));
System.out.println(fs.readLine());
}
catch (FileNotFoundException e) {}
catch (IOException e) {}
}
public static void main(String[] args) {
Sandboxer sb = new Sandboxer();
}
}
The nasty class we want to control (Sandboxed.java)
package sandboxing;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
/** This is the evil class that tries to do things we don't want. */
public class Sandboxed {
public Sandboxed() {
}
public void doNastyFileIO() {
try {
BufferedReader fs = new BufferedReader(new FileReader("C:\\INSTALL.LOG"));
System.out.println(fs.readLine());
}
catch (FileNotFoundException e) {}
catch (IOException e) {}
}
}
Our own SecurityManager
package sandboxing;
import java.net.*;
import java.io.*;
import java.security.*;
import java.util.Vector;
public class Sandbox extends SecurityManager {
public Sandbox() { }
/** Check if our nasty class is in the calling stack.
* @return: Returns true if our 'Sandboxed' class is somewhere. */
public boolean isNastyClass() {
int i;
Class[] cc = this.getClassContext();
for (i = 0; i < cc.length; i++) {
if (cc[i].getName().indexOf("Sandboxed") != -1)
return true;
}
return false;
}
public void checkRead(String file) {
if (isNastyClass())
throw new SecurityException("HA HA! Can't do that!");
}
public void checkRead(String file, Object context) {
if (isNastyClass())
throw new SecurityException("HA HA! Can't do that!");
}
}