JEP 415, Context-Specific Deserialization Filters
Contexte
Retour sur les nouvelles fonctionalités du JDK 17, le filtre pour se protéger de la désérialisation a été améliorée. Nous avons abordés ce sujet dans le billet précédent
Pour rappel, depuis le JDK 9, nous avons l’interface ObjectInputFilter
:
interface ObjectInputFilter {
Status checkInput(FilterInput filterInfo);
enum Status {
UNDECIDED,
ALLOWED,
REJECTED;
}
...
public static class Config {
public static void setSerialFilter(ObjectInputFilter filter);
public static ObjectInputFilter getSerialFilter(ObjectInputFilter filter) ;
public static ObjectInputFilter createFilter(String patterns);
}
}
JEP 415 Context Specific Deserialization Filters
L’objectif est d’améliorer la granularité au niveau du filtre. En effet, avant le JDK 17, nous avons un paramétrage :
-
soit global au niveau de la JVM
-
soit par flux (c’est à dire pour chaque intance de
ObjectInputStream
)
Comment cela fonctionne
Le principe est la mise en place d’un système de fabrique de filtre. L’objectif est de pouvoir déterminer le filtre à utiliser selon le contexte.
La fabrique doit implémenter l’interface BinaryOperator<ObjectInputFilter>
.
public class FiltreSelonContexte implements BinaryOperator<ObjectInputFilter> {
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
....
}
}
Naturellement, la nouvelle mécanique doit rester compatible avec l’existant.
Ligne de commande
La première possibilité est d’utiliser la ligne de commande. Pour cela, il existe une nouvelle propriété jdk.serialFilterFactory
où nous indiquons la classe concernée (qui doit être accessible par le chargeur de classes)
API
Côté API, nous avons des méthodes qui permettent de positionner la fabrique au niveau de la classe ObjectInputFilter.Config
.
interface ObjectInputFilter {
Status checkInput(FilterInput filterInfo);
...
public static class Config {
...
public static BinaryOperator<ObjectInputFilter> getSerialFilterFactory();
public static void setSerialFilterFactory(BinaryOperator<ObjectInputFilter> filterFactory);
}
}
Les filtres peuvent s’additionner pour s’adapter au contexte. Pour cela, il existe la méthode ObjectInputFilter.merge()
. Voici sa signature :
interface ObjectInputFilter {
ObjectInputFilter ObjectInputFilter.merge(ObjectInputFilter premier, ObjectInputFilter second);
}
Ainsi, nous avons la séquence suivante :
-
Appel du filtre premier et obtient le status
-
Retourne
REJECTED
si le status estREJECTED
-
Appel du filtre second et obtient le status
-
Retourne
REJECTED
si le status du second filtre estREJECTED
-
Retourne
ALLOWED
si l’un des deux status (le premier ou le second) estALLOWED
-
Sinon, retourne
UNDECIDED
Exemple de mise en oeuvre
L’exemple inclus dans le JEP et la Javadoc consiste à obtenir un contexte par thread (donc un filtre par thread). Pour cela, nous commençons par la définition de la classe suivante :
public static final class FilterInThread implements BinaryOperator<ObjectInputFilter> {
private final ThreadLocal<ObjectInputFilter> filterThreadLocal = new ThreadLocal<>();
// Constructeur de la farbique du filtre de la désérialisation
public FilterInThread() {}
// Retourne un filtre composite
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
...
}
// Applique le filtre au niveau du Thread
// et appelle la méthode `Runnable.run()`
public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) {
var prevFilter = filterThreadLocal.get();
try {
filterThreadLocal.set(filter);
runnable.run();
} finally {
filterThreadLocal.set(prevFilter);
}
}
}
La méthode doWithSerialFilter
va ainsi :
-
positionner le filtre souhaité (c’est à dire passer en paramètre)
-
appeler la méthode du
Runnable.run()
. -
puis, repositionner le filtre initial afin de rétablir la configuration par défaut.
Ainsi, c’est le filtre souhaité qui sera utilisé durant toute l’exécution de la méthode (et notamment du Runnable.run()
).
L’utilisation est la suivante :
// Creation de la fabrique de filtre et le positionner de manière globale
var filterInThread = new FilterInThread();
ObjectInputFilter.Config.setSerialFilterFactory(filterInThread);
// Création d'un filtre qui autorise fr.lbenoit.exemple.*, les classes du module
// `java.base` et rejete toutes les autres
var filter = ObjectInputFilter.Config.createFilter("example.*;java.base/*;!*");
filterInThread.doWithSerialFilter(filter, () -> {
byte[] bytes = ...;
var o = deserializeObject(bytes);
});
Moteur de recherche
"Eduquer, ce n'est pas remplir des vases mais c'est d'allumer des feux." - Michel Montaigne
Billets récents
- Eclipse plante systématiquement sous Debian (et autres distribution Linux)
- JEP 463, Implicitly Declared Classes and Instance Main Methods (Second Preview)
- Debian - Montée de version de Debian 11 (Bullseye) à Debian 12 (Bookworm)
- JEP 451, Prepare to Disallow the Dynamic Loading of Agents
- JEP 444, Virtual Threads