Почему мой пользовательский SecurityManager вызывает исключения в 16-й раз, когда я создаю объект с помощью Constructor.newInstance?

В настоящее время я работаю над созданием небольшого Java-приложения, в котором доверенный код должен выполняться вместе с ненадежным кодом. Для этого я установил кастом SecurityManager это бросает SecurityExceptions в любое время проверяется разрешение.

В качестве моста между доверенным и ненадежным кодом, у меня есть поток, который использует Constructor.newInstance() создать экземпляр объекта ненадежного типа. Во время выполнения этого вызова диспетчер безопасности настроен на блокировку всего. Интересно, что первые 15 раз, когда я пытаюсь создать объекты, используя Constructor.newInstance(), все отлично работает, но в 16-й раз я получаю SecurityException,

Мне удалось объяснить это простой тестовой программой:

import java.lang.reflect.*;
import java.security.*;

public class Main {
    /* Track how many instances have been created so that we can see when the exception
     * is thrown.
     */
    private static int numInstances = 0;
    public Main() {
        System.out.println("Number created: " + ++numInstances);
    }

    public static void main(String[] args) {
        /* Get the constructor for Main so that we can instantiate everything
         * later on.
         */
        Constructor<Main> ctor;
        try {
            ctor = Main.class.getConstructor();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return;
        }

        /* Install a super prohibitive security manager that disallows all operations. */
        System.setSecurityManager(new SecurityManager() {
            @Override
            public void checkPermission(Permission p) {
                /* Nothing is allowed - any permission check causes a     security
                 * exception.
                 */
                throw new SecurityException("Not permitted: " + p);
            }
        });

        /* Continuously create new Main objects. */
        try {
            while (true) {
                ctor.newInstance();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
    }
}

Эта программа устанавливает SecurityManager чья checkPermission всегда выдает исключение независимо от того, какое разрешение запрашивается. Затем он сидит в цикле и использует ctor.newInstance() создать экземпляр безобидного Main объект, который выводит количество сгенерированных экземпляров. Вывод этой программы в моей системе выглядит следующим образом:

Number created: 1
Number created: 2
Number created: 3
Number created: 4
Number created: 5
Number created: 6
Number created: 7
Number created: 8
Number created: 9
Number created: 10
Number created: 11
Number created: 12
Number created: 13
Number created: 14
Number created: 15
java.lang.SecurityException: Not permitted: ("java.lang.RuntimePermission" "createClassLoader")
    at Main$1.checkPermission(Main.java:32)
    at java.lang.SecurityManager.checkCreateClassLoader(SecurityManager.java:611)
    at java.lang.ClassLoader.checkCreateClassLoader(ClassLoader.java:274)
    at java.lang.ClassLoader.<init>(ClassLoader.java:316)
    at sun.reflect.DelegatingClassLoader.<init>(ClassDefiner.java:72)
    at sun.reflect.ClassDefiner$1.run(ClassDefiner.java:60)
    at sun.reflect.ClassDefiner$1.run(ClassDefiner.java:58)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:57)
    at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399)
    at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:396)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:395)
    at sun.reflect.MethodAccessorGenerator.generateConstructor(MethodAccessorGenerator.java:94)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:48)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at Main.main(Main.java:39)

Согласно Javadoc дляRuntimePermission, createClassLoader Разрешение рискованно предоставлять:

Это чрезвычайно опасное разрешение для предоставления. Вредоносные приложения, которые могут создавать свои собственные загрузчики классов, могут затем загружать в систему свои собственные мошеннические классы. Эти вновь загруженные классы могут быть помещены в любой домен защиты загрузчиком классов, тем самым автоматически предоставляя классам разрешения для этого домена.

У меня есть два вопроса:

  1. Что конкретно вызывает эту ошибку? Почему в 16-й раз я получаю запрос на загрузчик классов? Я подозреваю, что это связано с тем, что Java пытается оптимизировать отражение, генерируя байт-код для непосредственного создания объекта, но я не уверен.

  2. Без внесения в белый список createClassLoader привилегия, которая опасна, есть ли способ создать недоверенные объекты из доверенного кода?

  3. Я фундаментально подхожу к этому неправильно?

Спасибо!

1 ответ

Решение

Проверьте это на GrepCode:

 72 private static int infThreshold = 15;

15 - это значение по умолчанию для порога инфляции, в NativeConstructorAccessorImpl введено количество отражающих вызовов до более агрессивной оптимизации:

 47 if (++ numInvocations> ReflectionFactory.lationThreshold ()) {
48 ConstructorAccessorImpl acc = ( ConstructorAccessorImpl)
49новый MethodAccessorGenerator ().
50 generateConstructor (c. GetDeclaringClass(),
51 в. getParameterTypes (),
52 в. getExceptionTypes (),
53 в. getModifiers ());
54 родитель. setDelegate (acc);

И этот конкретный код вызывает создание нового загрузчика классов, что приводит к вашему исключению на 16-й итерации. Генерация байт-кода происходит в классе MethodAccessorGenerator, и это самый интересный бит:

 387// Класс нагрузки
388 vec. отделка ();
389последний байт [] байт = vec. getData();
390// Примечание: загрузчик классов - единственное, что действительно имеет значение
391// здесь - важно получить сгенерированный код в
392// то же пространство имен, что и у целевого класса. Поскольку сгенерированный код
393// в любом случае является привилегированным, домен защиты, вероятно, не имеет
394// дело.
395вернуть AccessController. doPrivileged (
396новых привилегированных действий< MagicAccessorImpl> () {
397 public MagicAccessorImpl run () {
398 попыток {
399 возврат ( MagicAccessorImpl)
400 ClassDefiner. defineClass
401 (генерируется имя,
402 байта,
403 0,
Длина 404 байта,
405 объявляя класс. getClassLoader()). newInstance();
406 } catch ( InstantiationException e) {
407 броска ( InternalError)
408 новых InternalError(). initCause(e);
409 } catch ( IllegalAccessException e) {
410 броска ( InternalError)
411 новая InternalError(). initCause(e);
412 }
413 }
414 });

Что касается предоставления этого разрешения, у вас все еще есть возможность тщательно сформировать домен защиты для своего кода, которому вы предоставляете разрешение, без предоставления его иностранному коду.

Другие вопросы по тегам