Как стать автором
Обновить

Комментарии 29

А самую большую проблему сериализации так и не указали: уязвимость удалённого выполнения произвольного кода, которую для того-же EJB даже исправить невозможно.

Мне всегда было непонятно, как такое происходит. Как сериализация, которая вроде должна лишь хранить состояние объекта, не его поведение, в итоге приводит к уязвимости выполнения кода.
уязвимость удалённого выполнения произвольного кода
— а в чем уязвимость? не понял Вашу мысль
Существует целое семейство уязвимостей, связанных с сериализацией.
Грубо говоря, нельзя вызывать бинарную сериализацию/десериализацию на не доверенных данных в бинарных типах, т.к. тут передаются метаданные объекта и атакующий может в эти данные обернуть свой вредоносный код. Такой код называется гаджетом (gadget). Про создание бинарного гаджета см. здесь deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html

Тоже самое касается и JSON/XML сериализации. Как минимум у библиотеки должны быть отключены настройки использования метаданных, в рамках JSON это нотация $types. См. blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
Ссылку на уязвимость, будьте добры
Вот, например:
github.com/frohoff/ysoserial
deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html#execWait

Уязвимость в том, что десериализация вызывает конструктор для произвольного класса, имя которого указано в исходном потоке байтов. Если в вашем classpath достаточно классов, которые делают разные полезные вещи в своем конструкторе, то можно соорудить хитрый объект, который при десериализации будет делать, что вам нужно. Такие «полезные» классы называют «гаджетами», и одни из наиболее популярных — из библиотек Apache Commons, которые почти в каждом крупном проекте используются.

Для заделывания дыр часто применяют черные или белые списки классов, но понятно, что это не дает 100% гарантии. В общем, технология broken by design, что называется.
Судя по javadoc'у, эта уязвимость в Apache Commons Collections была исправлена

не будем вслух упоминать о уязвимостях XML (со времён создания XML) как способа сериализации/десериализации данных, и установках парсера, включённых в Java по-умолчанию

наследуется от java.io.Serializable и получает некую неявную магию

Она ставит себе задачу уметь сериализовать любой произвольный граф объектов

— имплементация программистом интерфейса Serializable означает, что он уверен в сериализуемости объектов класса. Тут нет никакой магии. Вы описали класс, добавили где надо модификатор transient и уверены в его сериализуемости, что и подтверждаете имплементацией интерфейса.
А если программистов несколько? Вот написал ваш так называемый уверенный программист класс, а потом пришёл другой и добавил в него несериализуемое поле и забыл пометить его transient. Вот и всё, вы словите исключение только в рантайме.
Что касается магии — это то, как работает сериализация под капотом. Это страшная вещь. Знаете ли вы, что сериализация работает, даже если в вашем классе отсутствует no-arg конструктор? А знаете, как она обходит это? Она генерирует такой конструктор в рантайме с помощью метода sun.reflect.ReflectionFactory.newConstructorForSerialization. Это ли не магия? А то, что сериализация устанавливает final поля после уже конструирования объекта — не магия? А сериализация циклических графов объектов — не магия?
а потом пришёл другой и добавил в него

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

а потом пришёл другой и добавил в него

— фактически Вы предлагаете заменить ручной код на ту самую «магию», которой пугаете. Магии станет больше.
НЛО прилетело и опубликовало эту надпись здесь
А что значит средствами синтаксиса языка? А как её решать по-другому? Синтаксиса здесь специального созданного для сериализации, кстати, никакого и нет. Паттерн-матчинг — это независимая фича, которая так или иначе появится в языке. open тоже не связан с сериализацией как таковой, это просто более тонкая альтернатива opens из модулей. Аннотации Serializer/Deserializer — ну это просто аннотации. Ну разве что там будет дополнительные синтаксические проверки компилятором, но это не синтаксис.

Возможность сериализовать граф с циклами — ошибка? Нуок.

Так есть же ещё Externalizable.
Почти то же самое что вы описали.
Абсолютно нет. Деэсктернализация происходит по тому же принципу, как и десериализация (восстановление полей в уже сконструированном объекте). Что в итоге выливается в те же самые проблемы, от которых мы хотим уйти. Плюс экстернализация является потокоориентированной, от чего мы тоже хотим уйти.
НЛО прилетело и опубликовало эту надпись здесь
lo, hi — стандартное сокращение для low и high. Такое много где можно увидеть.

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

Цитату можно?

Посмотрел cr.openjdk.java.net/~briangoetz/amber/pattern-match.html — там нет ничего про такое ключевое слово.
Пока коммитмента по поводу того, что оно будет называться в точности так, никто не давал. Но деструктурирующие паттерны упомянались в рассылке и багтрекере OpenJDK множество раз. Например, в контексте записей. Вот тут слово pattern уже было использовано явно. В любом случае, это уже детали. Важна концепция, а не синтаксис.
private open pattern serialize(InternalState is)

или
private open pattern Foo(InternalState is)

А то я что-то нить теряю
Первое — это деконструктор, помеченный аннотацией @Serializer.
Второе — конструктор, помеченный аннотацией @Deserializer.
Деконструктор — это фишка из паттерн-матчинга, обратная конструктору. Конструктор собирает объект из полей, деконструктор — из объекта достаёт поля.
Разве в конструкторе теперь не надо писать имя класса Foo?
Оба варианта предполагались @Serializer, поленился написать.

А, понял, ввёл в заблуждение новый синтаксис.

Это не конструктор, это такой метод?
@Serializer
public pattern Range(int lo, int hi) {
lo = this.lo;
hi = this.hi;
}
Это деконструктор. Он позволит разложить ваш инстанс Range на компоненты. Выглядеть это будет как-то так:
Range range = new Range(0, 10);
let Range(lo, hi) = range;
System.out.println("low=" + lo + ", high=" + hi);

Также вы сможете делать switch:
switch (range) {
    case Range(lo, hi) -> System.out.println("low=" + lo + ", high=" + hi);
    ...
}
Посмотрите вот этот отличный доклад от lany. Там всё разложено по полочкам.
спасибо

А чем не подходит схема с явным конструктором, принимающим хранилище-источник, и методом serialize, принимающим хранилище-приёмник?

Хранилище — это как раз то, отчего хотят уйти (ObjectOutputStream/ObjectInputStream), потому что они слишком завязаны на конкретный формат кодировки. Кроме того, в предложенном вами подходе придётся всегда писать два конструктора (обычный и для десериализации) и дублировать код в них.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории