package org.signalml.app;
import java.io.FilePermission;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.BasicPermission;
import java.security.Permission;
import java.util.HashMap;
import java.util.PropertyPermission;
import org.apache.log4j.Logger;
import org.signalml.util.FastMutableInt;
import org.signalml.plugin.loader.PluginLoaderHi;
/**
* Svarog security manager.
*
* The policy permits everything that is permitted by the security policy
* defined by the system administrator, as well as all operations that are
* being requested <b>outside</b> of Svarog plugin context.
*
* This security manager can be configured to enforcing/permissive/off.
* Set java property (e.g. mvn exec:java -Dsvarog.security_manager=permissive")
* to change the mode. The default is off.
*
* In enforcing mode, disallowed access causes a RuntimeException.
* In permissive mode, disallowed access only causes a message to be printed.
* When mode is off, this security manager is not installed at all.
*
* @see java.lang.SecurityManager
* @author Stanislaw Findeisen (Eisenbits)
*/
public class SvarogSecurityManager extends java.lang.SecurityManager {
protected static final Logger log = Logger.getLogger(SvarogSecurityManager.class);
/** Edit this should you ever rename SvarogApplication. */
public static final String S_SvarogApplication = "org.signalml.app.SvarogApplication";
private HashMap<Thread,org.signalml.util.FastMutableInt> recLevel =
new HashMap<Thread,org.signalml.util.FastMutableInt>();
public final boolean enforcing;
/**
* Installs this security manager as the security manager for the application
* (in java.lang.System).
*
* @see java.lang.System.setSecurityManager
*/
protected static void install() {
final String mode = System.getProperties().getProperty("svarog.security_manager",
"off");
if (mode.equals("off")) {
log.debug("SvarogSecurityManager is off");
return;
}
final boolean enforcing;
if (mode.equals("permissive")) {
enforcing = false;
} else {
if (!mode.equals("enforcing"))
log.error("svarog.security_manager has invalid value "
+ "'" + mode + "', ignoring");
enforcing = true;
}
// Force class initialization
new FastMutableInt(5);
log.debug("SvarogSecurityManager init...");
PluginLoaderHi.getInstance();
// create and install
System.setSecurityManager(new SvarogSecurityManager(enforcing));
}
private SvarogSecurityManager(boolean enforcing) {
super();
this.enforcing = enforcing;
}
private synchronized void incRecLevel(Thread t) {
FastMutableInt k = recLevel.get(t);
if (k == null) {
recLevel.put(t, new FastMutableInt(1));
} else {
k.inc();
}
}
private synchronized void decRecLevel(Thread t) {
FastMutableInt k = recLevel.get(t);
k.dec();
if (k.isZero())
recLevel.remove(t);
}
private synchronized boolean recursionPresent(Thread t) {
FastMutableInt k = recLevel.get(t);
return ((null != k) && (k.isGE2()));
}
/**
* Checks if there is a plugin code on the stack of the given thread.
* If found, returns the first plugin code stack frame.
*
* @return the first stack frame determined to be plugin code (if found) or null (otherwise)
*/
private StackTraceElement findPluginCtx(Thread t) {
PluginLoaderHi pluginLoaderHi = PluginLoaderHi.getInstance();
if (pluginLoaderHi == null)
return null;
if (!(pluginLoaderHi.hasStartedLoading()))
return null;
StackTraceElement[] stack = t.getStackTrace();
boolean hasRoot = false;
for (final StackTraceElement frame : stack) {
final String className = frame.getClassName();
if (pluginLoaderHi.hasLoaded(className)) {
// a plugin code frame!
// log.debug("PluginCtx: " + className);
return frame;
}
if (!hasRoot) {
// TODO This is an excerpt from Thread.getStackTrace() API docs:
//
// Some virtual machines may, under some circumstances,
// omit one or more stack frames from the stack trace.
// In the extreme case, a virtual machine that has no
// stack trace information concerning this thread is
// permitted to return a zero-length array from this
// method.
//
// This is the reason we do this rudimentary check for a root
// class in the stack.
if ("java.lang.Thread".equals(className) ||
S_SvarogApplication.equals(className))
hasRoot = true;
}
}
if (hasRoot) {
// Yeah, root has been found, no plugin, so we assume the stack is quite
// consistent and that this is not a plugin context...
//
// TODO This is not 100% correct! (see the comment above).
return null;
} else {
throw new java.lang.SecurityException("Stack root not found!");
}
}
@Override
/**
* For top-level calls (no recursion) the permission (p) is granted iff
* at least 1 of the following holds:
*
* <ul>
* <li>p is granted by the super call</li>
* <li>p is a java.lang.PropertyPermission with action eq. "read"</li>
* <li>p is a java.lang.RuntimePermission with name eq. "accessDeclaredMembers"</li>
* <li>p is not a plugin context</li>
* </ul>
*
* @param p requested permission
* @throws SecurityException iff access is denied
*/
public void checkPermission(Permission p) {
final String pn = p.getName();
final String pa = p.getActions();
final Thread t = Thread.currentThread();
boolean permit = true;
StackTraceElement frame = null;
try {
incRecLevel(t);
super.checkPermission(p);
} catch (SecurityException e) {
permit = false;
if (recursionPresent(t)) {
if (p instanceof BasicPermission) {
if (p instanceof RuntimePermission) {
if ("accessDeclaredMembers".equals(pn))
permit = true;
}
} else if (p instanceof FilePermission) {
if ("read".equals(pa))
permit = true;
}
} else {
frame = findPluginCtx(t);
if (frame == null) {
// Not a plugin context, grant permission!
permit = true;
} else {
// we're in plugin context (untrusted code!)
if (p instanceof BasicPermission) {
if (p instanceof PropertyPermission) {
if ("read".equals(pa))
permit = true;
}
} else if (p instanceof FilePermission) {
if ("read".equals(pa))
permit = true;
}
}
}
if (!permit) {
String errMsg = "Permission DENIED [" + t.getId() + "/" +
t.getName() + "]: " + p;
if (frame != null)
errMsg += "; plugin ctx: " + toString(frame);
permissionDenied(t, p, e, frame);
if (this.enforcing)
throw new SecurityException(errMsg, e);
}
} finally {
// if (permit)
// sl.permissionGranted(t, p);
decRecLevel(t);
}
}
// @Override
// public void checkExit(int status) {
// try {
// super.checkExit(status);
// log.debug("checkExit GRANTED: " + status);
// } catch (SecurityException e) {
// log.debug("checkExit GRANTED (2): " + status);
// }
// }
/** Used by {@link SvarogSecurityManager} to log security decisions. */
protected void permissionDenied(Thread t, Permission p, SecurityException e,
StackTraceElement frame) {
String msg = "Permission DENIED [" + (t.getId()) + "/"
+ (t.getName()) + "]: " + p + "; plugin ctx: " + frame;
if (e != null)
msg += "\n" + getExceptionMessage(e);
log.warn(msg);
}
/** Used by {@link SvarogSecurityManager} to log security decisions. */
protected void permissionGranted(Thread t, Permission p) {
log.debug("Permission GRANTED [" + (t.getId()) + "/" + (t.getName()) + "]: " + p);
}
/** Provides an exception string containing the stack trace etc. */
protected String getExceptionMessage(Throwable t) {
// XXX: wtf?
StringWriter ws = new StringWriter();
PrintWriter wp = new PrintWriter(ws);
t.printStackTrace(wp);
return ws.toString();
}
protected String toString(StackTraceElement f) {
return f.getClassName() + "#" + f.getMethodName() + "(" + f.getFileName() + ":"
+ f.getLineNumber() + ")";
}
}