de.unkrig.commons.lang.security.Sandbox Maven / Gradle / Ivy
/*
* de.unkrig.commons - A general-purpose Java class library
*
* Copyright (c) 2014, Arno Unkrig
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.unkrig.commons.lang.security;
import java.security.AccessControlContext;
import java.security.Permission;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import de.unkrig.commons.lang.AssertionUtil;
import de.unkrig.commons.nullanalysis.Nullable;
/**
* This class establishes a security manager that confines the permissions for code executed through specific classes,
* which may be specified by class, class name and/or class loader.
*
* To 'execute through a class' means that the execution stack includes the class. E.g., if a method of class {@code
* A} invokes a method of class {@code B}, which then invokes a method of class {@code C}, and all three classes were
* previously {@link #confine(Class, Permissions) confined}, then for all actions that are executed by class {@code
* C} the intersection of the three {@link Permissions} apply.
*
*
* Once the permissions for a class, class name or class loader are confined, they cannot be changed; this prevents
* any attempts (e.g. of a confined class itself) to release the confinement.
*
*
* Code example:
*
*
* Runnable unprivileged = new Runnable() {
* public void run() {
* System.getProperty("user.dir");
* }
* };
*
* // Run without confinement.
* unprivileged.run(); // Works fine.
*
* // Set the most strict permissions.
* Sandbox.confine(unprivileged.getClass(), new Permissions());
* unprivileged.run(); // Throws a SecurityException.
*
* // Attempt to change the permissions.
* {
* Permissions permissions = new Permissions();
* permissions.add(new AllPermission());
* Sandbox.confine(unprivileged.getClass(), permissions); // Throws a SecurityException.
* }
* unprivileged.run();
*
*
* @see ORACLE: Java Essentials:
* The Security Manager
*/
public final
class Sandbox {
static { AssertionUtil.enableAssertionsForThisClass(); }
private Sandbox() {}
private static final Map, AccessControlContext>
CONFINED_CLASSES = Collections.synchronizedMap(new WeakHashMap, AccessControlContext>());
private static final Map
CONFINED_CLASS_NAMES = new ConcurrentHashMap();
private static final Map
CONFINED_CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap());
static {
// Install our custom security manager.
if (System.getSecurityManager() != null) {
throw new ExceptionInInitializerError("There's already a security manager set");
}
System.setSecurityManager(new SecurityManager() {
final Class> me = this.getClass();
@Override public void
checkPermission(@Nullable Permission perm) {
assert perm != null;
Class>[] classContext = this.getClassContext();
// Ignore the first execution frame, because that is THIS class.
for (int i = 1; i < classContext.length; i++) {
Class> clasS = classContext[i];
// Prevent endless recursion when we call "getClassLoader()", below, which in turn (indirectly)
// calls "checkPermission()".
if (clasS == this.me) return;
// Check if an ACC was set for the class.
{
AccessControlContext acc = Sandbox.CONFINED_CLASSES.get(clasS);
if (acc != null) acc.checkPermission(perm);
}
// Check if an ACC was set for the class name.
{
AccessControlContext acc = Sandbox.CONFINED_CLASS_NAMES.get(clasS.getName());
if (acc != null) acc.checkPermission(perm);
}
// Check if an ACC was set for the class loader.
{
AccessControlContext acc = Sandbox.CONFINED_CLASS_LOADERS.get(clasS.getClassLoader());
if (acc != null) acc.checkPermission(perm);
}
}
}
});
}
// --------------------------
/**
* All future actions that are executed through the given clasS will be checked against the given {@code
* accessControlContext}.
*
* @throws SecurityException Permissions are already confined for the clasS
*/
public static void
confine(Class> clasS, AccessControlContext accessControlContext) {
if (Sandbox.CONFINED_CLASSES.containsKey(clasS)) {
throw new SecurityException("Attempt to change the access control context for '" + clasS + "'");
}
Sandbox.CONFINED_CLASSES.put(clasS, accessControlContext);
}
/**
* All future actions that are executed through the given clasS will be checked against the given {@code
* protectionDomain}.
*
* @throws SecurityException Permissions are already confined for the clasS
*/
public static void
confine(Class> clasS, ProtectionDomain protectionDomain) {
Sandbox.confine(clasS, new AccessControlContext(new ProtectionDomain[] { protectionDomain }));
}
/**
* All future actions that are executed through the given clasS will be checked against the given {@code
* permissions}.
*
* @throws SecurityException Permissions are already confined for the clasS
*/
public static void
confine(Class> clasS, Permissions permissions) {
Sandbox.confine(clasS, new ProtectionDomain(null, permissions));
}
// --------------------------
/**
* All future actions that are executed through the named class will be checked against the given {@code
* accessControlContext}.
*
* @throws SecurityException Permissions are already confined for the className
*/
public static void
confine(String className, AccessControlContext accessControlContext) {
if (Sandbox.CONFINED_CLASS_NAMES.containsKey(className)) {
throw new SecurityException("Attempt to change the access control context for '" + className + "'");
}
Sandbox.CONFINED_CLASS_NAMES.put(className, accessControlContext);
}
/**
* All future actions that are executed through the named class will be checked against the given {@code
* protectionDomain}.
*
* @throws SecurityException Permissions are already confined for the className
*/
public static void
confine(String className, ProtectionDomain protectionDomain) {
Sandbox.confine(className, new AccessControlContext(new ProtectionDomain[] { protectionDomain }));
}
/**
* All future actions that are executed through the named class will be checked against the given {@code
* permissions}.
*
* @throws SecurityException Permissions are already confined for the className
*/
public static void
confine(String className, Permissions permissions) {
Sandbox.confine(className, new ProtectionDomain(null, permissions));
}
// --------------------------
/**
* All future actions that are executed through classes that were loaded through the given classLoader
* will be checked against the given accessControlContext.
*
* @throws SecurityException Permissions are already confined for the classLoader
*/
public static void
confine(ClassLoader classLoader, AccessControlContext accessControlContext) {
if (Sandbox.CONFINED_CLASS_LOADERS.containsKey(classLoader)) {
throw new SecurityException("Attempt to change the access control context for '" + classLoader + "'");
}
Sandbox.CONFINED_CLASS_LOADERS.put(classLoader, accessControlContext);
}
/**
* All future actions that are executed through classes that were loaded through the given classLoader
* will be checked against the given protectionDomain.
*
* @throws SecurityException Permissions are already confined for the classLoader
*/
public static void
confine(ClassLoader classLoader, ProtectionDomain protectionDomain) {
Sandbox.confine(classLoader, new AccessControlContext(new ProtectionDomain[] { protectionDomain }));
}
/**
* All future actions that are executed through classes that were loaded through the given classLoader
* will be checked against the given permissions.
*
* @throws SecurityException Permissions are already confined for the classLoader
*/
public static void
confine(ClassLoader classLoader, Permissions permissions) {
Sandbox.confine(classLoader, new ProtectionDomain(null, permissions));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy