Почему public static final array-дыра в безопасности?

эффективная java говорит:

// потенциальная дыра в безопасности!

статическая публичная конечная вещь [] VALUES = { ... };

может кто-нибудь сказать мне, что это дыра в безопасности?

8 ответов


объявления static final public поля обычно являются отличительной чертой константы класса. Это прекрасно подходит для примитивных типов (ints, Double и т. д..), и неизменяемые классы, такие как strings и java.awt.Color. С массивами проблема заключается в том, что, хотя ссылка на массив постоянна, элементы массива все еще могут быть изменены, и поскольку это поле, изменения неохраняемы, неконтролируемы и обычно нежелательны.

для борьбы с этим, видимость поля массива может быть ограничено частная или пакет private, поэтому у вас есть меньший объем кода для рассмотрения при поиске подозрительных изменений. В качестве альтернативы, и часто лучше, это покончить с массивом вместе и использовать "список" или другой соответствующий тип коллекции. Используя коллекцию, вы контролируете, разрешены ли обновления, так как все обновления проходят через методы. Вы можете предотвратить обновления, обернув свою коллекцию с помощью Collections.unmodifiableList(). Но будьте осторожны, что даже если коллекция неизменяема, вы также должны быть уверены, что типы хранящиеся в нем также неизменяемы, или риск нежелательных изменений на предполагаемой константе снова появится.


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

public class SafeSites {
    // a trusted class with permission to create network connections
    public static final String[] ALLOWED_URLS = new String[] {
        "http://amazon.com", "http://cnn.com"};

    // this method allows untrusted code to connect to allowed sites (only)
    public static void doRequest(String url) {
        for (String allowed : ALLOWED_URLS) {
            if (url.equals(allowed)) {
                 // send a request ...
            }
        }
    }
}

public class Untrusted {
     // An untrusted class that is executed in a security sandbox.

     public void naughtyBoy() {
         SafeSites.ALLOWED_URLS[0] = "http://myporn.com";
         SafeSites.doRequest("http://myporn.com");
     }
}

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

Если ваш код не является частью критически важного приложения безопасности, вы можете игнорировать эту проблему. Но ИМО это плохо идея. В какой-то момент в будущем вы (или кто-то другой) можете повторно использовать свой код в контексте безопасности. Во всяком случае,этой вот почему автор называет общедоступные конечные массивы проблемой безопасности.


Амбер сказал это в комментарии:

не более дыра в безопасности, чем частная, если вы можете прочитать исходный код и / или байт-код в любом случае...

это не правда.

тот факт, что "плохой парень " может использовать исходный код / байт-коды, чтобы определить, что private существует и ссылается на массив is недостаточно взлом защиты. Плохой парень!--20-->и должен ввести код в JVM, который имеет необходимые разрешения для использования отражения. Это разрешение недоступно для ненадежных код, запущенный в (правильно реализованной) песочнице безопасности.


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

-- Эффективная Java, 2-е изд. (стр. 70)


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

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


в этом объявлении клиент может изменить Thing[0], Thing[1] и т. д. (т. е. элементы в массиве).


думаю, это просто означает всю общественную и частную вещь. Предполагается, что хорошей практикой является объявление локальных переменных частными, а затем использование методов get и set, а не прямой доступ к ним. Делает их немного сложнее возиться с вне вашей программы. Насколько мне известно, да.


потому что ключевое слово final гарантирует только ссылочное значение (предположим, что это место памяти, например), но не содержимое в нем.


Я бы также добавил, что Джошуа блох предложил в эффективном Java 3rd edition. Конечно, мы можем легко изменить значение массива, если он объявлен как:

public static final String[] VALUES = { "a", "b" }; 

a.VALUES[0] = "changed value on index 0";
System.out.println(String.format("Result: %s", a.VALUES[0]));

и мы получаем Result: changed value on index 0

Джошуа блох предложил вернуть копию массива:

private static final String[] VALUES = { "a", "b" };   
public static final String[] values()
{
    return VALUES.clone();
}

так что теперь, когда мы пытаемся:

a.values()[0] = "changed value on index 0";
System.out.println(String.format("Result: %s", a.values()[0]));

мы Result: a и это то, чего мы хотели достичь -VALUES являются неизменными.

нет также ничего плохого в объявлении public static final a примитивы значения, строки или другие неизменяемые объекты, такие как public static final int ERROR_CODE = 59;