Подтвердить что ты не робот

Безопасность Java: как очистить/обнулить память, связанную с объектом? (И/или убедитесь, что это единственный экземпляр/копия определенной переменной)

Я участвую в обсуждении того, как защищать конфиденциальную информацию (например, пароли), хранящуюся в программе Java. В соответствии с требованиями безопасности память, содержащая конфиденциальную информацию, очищается, например. путем установки значений байтов для всех нулей. Вызывает беспокойство, что злоумышленник может наблюдать за памятью, связанной с процессом приложения, и поэтому мы хотим как можно больше ограничить время, в течение которого такая конфиденциальная информация зависает. Раньше проекты включали С++, поэтому достаточно было memset().

(Кстати, использование memset() было подвергнуто сомнению, поскольку некоторые компиляторы, как известно, оптимизируют его использование из полученного двоичного кода на основе предположения, что, поскольку память не используется позже, нет необходимости нуль в первую очередь. Этот комментарий является отказом для тех, кто Google для "memset" и "clear memory" и т.д.).

Теперь у нас есть на руках Java-проект, который прижимается к этому требованию.

Для объектов Java я понимаю, что:

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

И для примитивов я понимаю, что:

  • переменная примитивного типа в локальном методе будет выделена в стеке и:
  • когда вы меняете его значение, вы изменяете его непосредственно в памяти (в отличие от использования ссылки для обработки объекта в куче).
  • копии могут/будут выполняться "за кадром" в некоторых ситуациях, например, передавать его в качестве аргумента в методы или бокс (автоматически или нет), создавая экземпляры оболочек, которые содержат другую примитивную переменную с одинаковым значением.

Мой коллега утверждает, что Java-примитивы неизменяемы и что есть документация от NSA и Oracle относительно отсутствия поддержки Java для этого требования.

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

Я пытаюсь проверить, есть ли в JLS или другой "официальной" документации о необходимости поведения JVM, когда дело касается управления памятью в отношении примитивов. Самое близкое, что я мог найти, - это "Правила безопасного кодирования для языка программирования Java" на сайте Oracle, в котором упоминается очистка массивов char после использования.

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

Есть ли окончательные ответы или ссылки на это? Я был бы признателен за все, что могло бы показать мне, где я ошибаюсь, или подтвердить, что я прав.

Изменить: после дальнейшего обсуждения я смог уточнить, что мой коллега думал о примитивных обертках, а не о самих примитивах. Таким образом, мы остаемся с оригинальной проблемой, как безопасно очищать память, желательно от объектов. Кроме того, для уточнения важная информация - это не только пароли, но и такие вещи, как IP-адреса или ключи шифрования.

Существуют ли какие-либо коммерческие JVM, которые предлагают функцию, такую ​​как приоритетная обработка определенных объектов? (Я предполагаю, что это фактически нарушит спецификацию Java, но я думал, что попрошу на всякий случай, если я ошибаюсь.)

4b9b3361

Ответ 1

Изменить: На самом деле у меня было только три идеи, которые действительно могут работать - по-разному для разных значений "работа".

Первый, более или менее документированный, будет ByteBuffer.allocateDirect! Насколько я понимаю, allocateDirect выделяет буфер за пределами обычной кучи java, поэтому он не будет скопирован. Я не могу найти никаких серьезных гарантий, что он не копируется во всех ситуациях, хотя, но для текущей виртуальной машины Hotspot, которая на самом деле имеет дело (то есть она выделена в дополнительной куче), и я полагаю, что это останется таким образом.

Второй использует пакет sun.misc.unsafe, который, как видно из названия, имеет некоторые довольно очевидные проблемы, но по крайней мере это будет практически не зависимо от используемой виртуальной машины - либо она поддерживается (и работает), либо not (и вы получаете ошибки связи). Проблема в том, что код для использования этого материала будет ужасно сложным довольно быстро (только получение небезопасной переменной нетривиально).

Третий должен был бы выделить гораздо больший, MUCH больший размер, чем это действительно необходимо, чтобы объект был распределен в куче старого поколения:

l-XX: PretenureSizeThreshold =, который можно установить для ограничения размер ассигнований в молодом возрасте поколение. Любое распределение больше, чем это не будет предпринято в молодого поколения и так будет выделенных из старого поколения.

Ну, недостаток решения THAT очевиден, я думаю (размер по умолчанию составляет около 64 КБ).

. .

В любом случае, старый ответ:

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

Как сказано в вашем связанном сообщении (здесь), сборщик мусора в значительной степени делает это невозможным гарантировать. Фактически, вопреки тому, что сообщение говорит, проблема здесь не в генерации GC, а в том, что Hotspot VM (и теперь мы специфичны для реализации) использует какой-то Stop и Copy gc для своего молодого поколения по умолчанию.

Это означает, что как только произойдет сбор мусора между сохранением пароля в массиве char и обнулением его, вы получите копию данных, которые будут перезаписаны только после того, как произойдет следующий GC. Обратите внимание, что привязка объекта будет иметь точно такой же эффект, но вместо копирования его в космос он будет скопирован в кучу старого поколения - мы получим копию данных из пространства, которое не перезаписано.

Чтобы избежать этой проблемы, нам в значительной степени понадобился бы какой-то способ гарантировать, что либо сборка мусора НЕТ происходит между сохранением пароля и обнулением, либо тем, что массив char хранится из get go в куче старого поколения. Также обратите внимание, что это зависит от внутренних объектов Hotspot VM, которые могут очень сильно измениться (на самом деле есть разные сборщики мусора, в которых может быть создано много больше копий; iirc Hotspot VM поддерживает параллельный GC с использованием алгоритма поезда). "к счастью" невозможно гарантировать ни один из них (afaik каждый метод call/return вводит безопасную точку!), поэтому вы даже не пытаетесь попробовать (особенно учитывая, что я не вижу никакого способа убедиться, что JIT не оптимизирует обнуление);)

Кажется, единственный способ гарантировать, что данные хранятся только в одном месте, - это использовать JNI для него.

PS: Обратите внимание, что, хотя указанное выше верно только для кучи, вы не можете ничего гарантировать для стека (JIT, вероятно, оптимизирует запись без чтения в стек, поэтому, когда вы возвращаетесь от функции, данные все равно будет в стеке)

Ответ 2

Расскажите своим коллегам, что это беспросветная причина. Как насчет буферов сокета ядра, только для начала.

Если вы не можете помешать нежелательным программам отслеживать в памяти на вашем компьютере, пароли будут скомпрометированы. Период.

Ответ 3

Странно, никогда не думал об этом.

Моей первой идеей было бы сделать char [100], чтобы сохранить ваш пароль. Поместите это там, используйте его для чего угодно, а затем сделайте цикл, чтобы каждый char был пустым.

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

Моя вторая идея заключалась бы в том, чтобы вся аутентификация выполнялась через какой-то вызов JNI на C, но это было бы очень сложно, если вы пытаетесь использовать что-то вроде JDBC....

Ответ 4

Я пытаюсь выработать некоторые аналогичные проблемы с учетными данными.

До сих пор мой единственный ответ - "не использовать строки вообще для секретов". Строки удобны в использовании и хранении с точки зрения человека, но компьютеры могут хорошо работать с байтовыми массивами. Даже примитивы шифрования работают с байтом [].

Если вам больше не нужен пароль, просто заполните массив нулями и не позволяйте GC изобретать новые способы повторного использования ваших секретов.

В другом потоке (Почему строки не могут быть изменены в Java и .NET?), они делают предположение, что это очень короткий взгляд. Что строки неизменны из соображений безопасности; то, что не было разработано, заключается в том, что не всегда операционные проблемы являются единственными существующими и что для защиты иногда требуется определенная гибкость и/или поддержка, чтобы быть эффективной, поддержка не существует в родной Java.

Дополнить. Как мы можем читать пароль без использования строк? Ну... будь креативным и не используйте такие вещи, как Android EditText, с паролем ввода-типа, который просто недостаточно безопасен и требует от вас перехода к строкам.