Ответ 1
Вы ищете менеджер безопасности. Вы можете ограничить разрешения приложения, указав policy.
Я хочу сделать свое приложение для запуска кода других людей, а также плагинов. Тем не менее, какие параметры я должен сделать, чтобы это было безопасно, чтобы они не писали вредоносный код. Как я могу контролировать то, что они могут или не могут сделать?
Я споткнулся о том, что у JVM есть функция "встроенная песочница" - что это такое и это единственный способ? Существуют ли сторонние библиотеки Java для создания песочницы?
Какие у меня есть варианты? Ссылки на руководства и примеры приветствуются!
Вы ищете менеджер безопасности. Вы можете ограничить разрешения приложения, указав policy.
Определение и регистрация вашего собственного менеджера безопасности позволит вам ограничить то, что делает код - см. документацию oacle для SecurityManager.
Также рассмотрите создание отдельного механизма для загрузки кода - т.е. вы можете написать или создать экземпляр другого Classloader, чтобы загрузить код из специального места. У вас может быть соглашение о загрузке кода - например, из специального каталога или из специально отформатированного zip файла (в виде файлов WAR и JAR файлов). Если вы пишете загрузчик классов, он ставит вас в положение необходимости выполнять работу, чтобы загрузить код. Это означает, что если вы видите что-то (или какую-то зависимость), которую хотите отклонить, вы можете просто не загружать код. http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html
Посмотрите проект java-sandbox, который позволяет легко создавать очень гибкие изолированные программные среды для запуска ненадежного кода.
Для приложения AWT/Swing вам необходимо использовать нестандартный класс AppContext
, который может измениться в любое время. Таким образом, чтобы быть эффективными, вам нужно будет начать другой процесс для запуска кода плагина, и иметь дело с коммуникацией между ними (немного как Chrome). Для процесса подключаемого модуля потребуется установить SecurityManager
и ClassLoader
как изолировать код подключаемого модуля, так и применить соответствующие классы ProtectionDomain
к плагинам.
Здесь как проблема может быть решена с помощью SecurityManager:
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.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
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.
* <p>
* 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 <i>intersection</i> of the three {@link Permissions} apply.
* <p>
* Once the permissions for a class, class name or class loader are confined, they cannot be changed; this prevents any
* attempts (e.g. of the confined class itself) to release the confinement.
* <p>
* Code example:
* <pre>
* 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();
* </pre>
*/
public final
class Sandbox {
private Sandbox() {}
private static final Map<Class<?>, AccessControlContext>
CHECKED_CLASSES = Collections.synchronizedMap(new WeakHashMap<Class<?>, AccessControlContext>());
private static final Map<String, AccessControlContext>
CHECKED_CLASS_NAMES = Collections.synchronizedMap(new HashMap<String, AccessControlContext>());
private static final Map<ClassLoader, AccessControlContext>
CHECKED_CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<ClassLoader, AccessControlContext>());
static {
// Install our custom security manager.
if (System.getSecurityManager() != null) {
throw new ExceptionInInitializerError("There already a security manager set");
}
System.setSecurityManager(new SecurityManager() {
@Override public void
checkPermission(@Nullable Permission perm) {
assert perm != null;
for (Class<?> clasS : this.getClassContext()) {
// Check if an ACC was set for the class.
{
AccessControlContext acc = Sandbox.CHECKED_CLASSES.get(clasS);
if (acc != null) acc.checkPermission(perm);
}
// Check if an ACC was set for the class name.
{
AccessControlContext acc = Sandbox.CHECKED_CLASS_NAMES.get(clasS.getName());
if (acc != null) acc.checkPermission(perm);
}
// Check if an ACC was set for the class loader.
{
AccessControlContext acc = Sandbox.CHECKED_CLASS_LOADERS.get(clasS.getClassLoader());
if (acc != null) acc.checkPermission(perm);
}
}
}
});
}
// --------------------------
/**
* All future actions that are executed through the given {@code clasS} will be checked against the given {@code
* accessControlContext}.
*
* @throws SecurityException Permissions are already confined for the {@code clasS}
*/
public static void
confine(Class<?> clasS, AccessControlContext accessControlContext) {
if (Sandbox.CHECKED_CLASSES.containsKey(clasS)) {
throw new SecurityException("Attempt to change the access control context for '" + clasS + "'");
}
Sandbox.CHECKED_CLASSES.put(clasS, accessControlContext);
}
/**
* All future actions that are executed through the given {@code clasS} will be checked against the given {@code
* protectionDomain}.
*
* @throws SecurityException Permissions are already confined for the {@code 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 {@code clasS} will be checked against the given {@code
* permissions}.
*
* @throws SecurityException Permissions are already confined for the {@code clasS}
*/
public static void
confine(Class<?> clasS, Permissions permissions) {
Sandbox.confine(clasS, new ProtectionDomain(null, permissions));
}
// Code for 'CHECKED_CLASS_NAMES' and 'CHECKED_CLASS_LOADERS' omitted here.
}
Обсуждение этого вопроса вдохновило меня на запуск моего собственного проекта песочницы.
https://github.com/Black-Mantha/sandbox
В нем я столкнулся с важным вопросом безопасности: "Как вы разрешаете коду вне песочницы обходить SecurityManager
?"
Я помещаю код песочницы в свою собственную ThreadGroup и всегда предоставляю разрешение, если вне этой группы. Если вам нужно запустить привилегированный код в этой группе (в обратном вызове, например), вы можете использовать ThreadLocal, чтобы установить флаг только для этого потока. Классный загрузчик предотвратит доступ песочницы к ThreadLocal. Кроме того, если вы это сделаете, вам нужно запретить использование финализаторов, поскольку они запускаются в выделенном потоке вне ThreadGroup.