Sérialisation

Schéma d'une sérialisation puis d'une désérialisation de données

En informatique, la sérialisation[1] (de l'anglais américain serialization, aussi appelé marshalling) est le codage d'une information sous la forme d'une suite d'informations plus petites (dites atomiques, voir l'étymologie de atome) pour, par exemple, sa sauvegarde (persistance) ou son transport sur le réseau (proxy, RPC…). L'activité réciproque, visant à décoder cette suite pour créer une copie conforme de l'information d'origine, s'appelle la désérialisation (ou unmarshalling).

Le terme marshalling (mobilisation, canalisation, organisation) est souvent employé de façon synonyme, de même que le terme linéarisation. Les termes marshalling et unmarshalling s'emploient le plus souvent dans le contexte d'échanges entre programmes informatiques, alors que les termes sérialisation et désérialisation sont plus généraux[2].

D'apparence simple, ces opérations posent en réalité un certain nombre de problèmes, comme la gestion des références entre objets ou la portabilité des encodages. Par ailleurs, les choix entre les diverses techniques de sérialisation ont une influence sur les critères de performances comme la taille des suites d'octets sérialisées ou la vitesse de leur traitement.

Contexte

Comme pour beaucoup de choix algorithmiques, plus le mécanisme de sérialisation est spécialisé pour un type de données spécifique, plus il sera performant. Par exemple, si on ne veut transmettre que dix nombres dont les valeurs sont comprises entre 0 et 255, il suffira de 1 octet par nombre. Si par contre on ne sait pas à l'avance la quantité d'objets à transmettre on devra prévoir un ou plusieurs octets supplémentaires pour transmettre cette quantité. Si en plus ce ne sont pas seulement des nombres entiers, mais des objets quelconques que l'on souhaite transmettre, il faudra prévoir d'y associer les informations qui permettront de coder le type précis de chaque objet.

Plus globalement, il est nécessaire de faire un a priori sur les ressources disponibles au moment de la désérialisation pour déterminer les informations que l'on pourra reconstruire à l'aide d'une simple référence et celles qu'il est nécessaire d'encoder. C'est par exemple le cas des polices de caractères dans un fichier PDF : selon que l'on souhaite privilégier l'exactitude du rendu sur toutes les machines ou la taille du fichier généré, il est possible de transmettre la définition complète du tracé des caractères ou de se contenter de transmettre le nom de la police et quelques autres caractéristiques de base, en laissant le soin aux machines cibles de rechercher la police la plus adaptée parmi celles dont elle dispose.

Il existe enfin des informations dont la nature ne permet pas d'être sérialisées et qu'il faudra de toutes façons reconstruire. C'est le cas par exemple d'un descripteur de fichier. D'une machine à l'autre, et même d'une exécution à l'autre du programme sur une même machine, ces descripteurs sont attribués de manière arbitraire par le système d'exploitation : il n'y a donc pas de sens à sérialiser leur contenu et il faudra plutôt encoder des informations qui permettront de le reconstruire lors de la désérialisation (par exemple, le nom complet du fichier accédé via le descripteur). Un autre cas typique est la sérialisation des pointeurs, qui fait l'objet d'une technique spécifique : la mutation de pointeurs (en) (en anglais : swizzling).

Encodage

Le choix de base est entre format binaire et format texte :

  • les fichiers binaires sont généralement plus compacts, le code pour parser ce type de données est plus simple à mettre au point, la lecture et l'écriture sont moins exigeantes en ressources processeurs ;
  • les fichiers textes sont plus simples à vérifier ou à modifier manuellement, ils posent moins de problèmes de portabilité, ils sont plus simples à maintenir et à faire évoluer en fonction des besoins.

Codages binaires

L'une des contraintes des codages binaires est la portabilité. Par exemple une machine utilisant un autre modèle de processeur que l'ordinateur d'origine doit pouvoir désérialiser un bloc de données, en prenant en compte les problèmes d'alignement de données et d’endianness. C'est pourquoi, même si l'objet ne comporte pas de pointeurs, la simple copie de l'empreinte mémoire d'un objet n'est généralement pas une solution acceptable.

Il faut donc ici aussi utiliser des encodages conventionnels. Il est assez courant d'utiliser les conventions suivantes : aucun alignement; encodage des types C entiers en fonction de leur empreinte mémoire tous au format big-endian, les nombres en virgule flottante utilisent la norme IEEE 754.

Des protocoles comme GIOP de CORBA ou RMI de Java emploient tous deux des codages binaires.

Codages textes

Définir un codage textuel nécessite de choisir un protocole pour séparer les champs, pour encoder des données binaires (par exemple uuencode, base64 ou échappement des caractères non ASCII)…

Il était relativement courant d'utiliser un dérivé du format XML. En 2019, le format de codage texte le plus courant est le format JSON[3].

Les protocoles basés sur des fichiers textes XML sont SOAP, FIX et XML-RPC. Le format binaire de SOAP a pour nom Common Data Representation[réf. nécessaire].

La sérialisation PHP utilise un format spécifique.

Sérialisation et encodage sur internet

Le transfert de fichier texte avec l'apparition de l'internet a laissé place à des protocoles client/Serveur gérant le transfert de données sous forme de classes. Les anciens clients avaient des cookies dont la taille et l'origine étaient limités. Les objets sont l'évolution des cookies et peuvent ou non être sauvegardés dans l'espace de travail du navigateur web.

  • AMF (Action Message Format) est utilisé dans de nombreux projets libres et commerciaux. Différentes implémentations existent : PHP[4], Java[5],[6], PErl[7]. AMF est utilisé par flash pour son serveur média et s'interface facilement avec les objets java. AMF 0 apparaît en 2001 dans le Player Flash 6 (as 1.0) et évolue en AMF 3.0 avec le Player 9 (as 3.0). AMF est un format d'échange binaire mettant en jeu des vidéos Flash ou un serveur et un client Flash.
  • Google Gears est un plug in AJAX pour navigateur web. Il permet de façon transparente de sauvegarder des données localement dans une base de données SQLite durant une connexion internet. Ces données pourront être utilisées en mode non connecté. Il est fourni par défaut avec Google Chrome. Les services web en ligne Google Reader et Remember the Milk sont compatibles Google Gears
  • EOF et WOF sont des objets utilisés par le serveur java WebObjects d'Apple pour une visualisation html. WOF (WebObjects Framework) s'occupe du rendu html tandis que EOF (Enterprise Objects Framework) simplifie l'accès aux bases de données. L'abandon de webobjects et la gratuité a permis à Apple de se concentrer sur le moteur, et de voir naître des outils open source (Eclipse IDE avec plug in WOLIPs).
  • WCF de Microsoft permet de sérialiser des données par plusieurs objets (Xml DataContract netDataContract). Après la sérialisation, l'encodage se fait dans différents formats (Binary, Text, MTOM). Le client Silverlight supporte WCF.
  • zodb dans zope utilise le module pickle de python pour sérialiser des objets côté serveur. Rien n'existe côté client où les objets sont rendus visualisables (html, flash, pdf…).
  • COM, Rewire permet l'échange de données entre deux applications sans sauvegarde ; il n'y a pas sérialisation.

Sérialisation d'un objet atomique

Un objet atomique est un objet qui ne comporte aucune référence vers d'autres objets.

Type de l'objet

Selon les possibilités du langage, la réanimation pourra utiliser un mécanisme de métaclasse apporté par le langage, ou une fabrique spécifique. Dans tous les cas, il est nécessaire de conserver les informations qui permettront de sélectionner le type d'objet à créer.

Si le nombre de types d'objets à sérialiser est connu à l'avance, les informations de type peuvent être codées de manière très compacte (par exemple, sur un simple octet si celles-ci n'excèdent pas 256).

Sinon, il sera nécessaire d'utiliser des conventions, comme celle des packages du langage Java. Ces noms conventionnels pouvant être volumineux, il pourra être utile de prévoir un mécanisme d'alias pour éviter les répétitions lors du traitement de plusieurs objets du même type.

Il est aussi possible de transmettre directement le code mettant en œuvre le type encodé. C'est le cas par exemple du module marshal de la bibliothèque standard du langage Python, et c'est un mécanisme qui est plus généralement supporté par tous les langages interprétés supportant la mise en cache de leur byte code.

Données

Chaque type de donnée est responsable de l'archivage et de la restauration de ses données membres. Pour les types composites, il s'agit de sérialiser chacun des champs dans un ordre prédéfini.

Types hiérarchiques

En programmation orientée objet, il est nécessaire de traiter les données gérées par le type de base avant d'archiver les données du type dérivé.

Parcours d'un graphe d'objets

C'est une problématique qui est assez commune et que l'on retrouve par exemple quand on cherche à mettre en œuvre un clonage ou un ramasse-miettes.

Des algorithmes plus ou moins performants peuvent être choisis suivant les a priori que l'on peut faire sur la topologie du graphe :

  • arbre ;
  • graphe uniquement connecté aux feuilles ;
  • graphe quelconque.

Parcours manuel

Dans le cas général il est nécessaire de mémoriser les objets parcourus pour détecter les cycles.

Ce n'est pas une bonne idée d'utiliser les objets eux-mêmes pour pointer leur statut visité :

  • la méthode de sérialisation se met à muter les objets, ce qui perturbe les optimisations de type Copy-On-Write utilisé par exemple lors d'un fork sous UNIX ;
  • cela pose des problèmes de réentrance.

Il est préférable d'utiliser une table de hachage (adresse de l'objet, compteur) qui sera par ailleurs utilisée pour mettre en œuvre la mutation de pointeur. Il faut alors veiller à retenir chaque objet pour éviter les collisions d'adresses avec de nouveaux objets.

Parcours par introspection

Les langages qui supportent l'introspection peuvent fournir un mécanisme de sérialisation par défaut.

Désérialisation

La désérialisation pose également un certain nombre de problèmes comme la réanimation d'un objet immuable. Les objets ne peuvent pas être utilisés pendant la désérialisation.

La désérialisation pose aussi des problèmes de sûreté du typage.

Sécurité

Pour éviter les problèmes de sécurité, la sérialisation, et surtout la désérialisation doivent être utilisées avec une grande prudence.

Désérialisation de données non fiable

Un attaquant contrôlant les données fournies à une application puis désérialisées peut, de fait, injecter ses propres objets dans l'application. En fonction des classes disponibles et des méthodes appelées sur les données désérialisées, les risques vont de la divulgation de données sensibles à l'exécution de code arbitraire[8].

Les vulnérabilités de ce type sont regroupées dans la catégorie CWE-502[9]. Bien que moins nombreuses que d'autres types de vulnérabilités, on en comptait quand même 252 publiées début 2020[10].

Sérialisation de données sensibles

Réciproquement, un attaquant pouvant obtenir la désérialisation de classes contenant des données sensibles pourrait en obtenir la divulgation. Ces classes doivent donc interdire leur sérialisation ou, au moins, protéger les données sensibles en surchargeant les méthodes correspondantes.

Ces vulnérabilités sont regroupées dans la catégorie CWE-499[11].

Gestion des versions

Il est souvent nécessaire de garantir une compatibilité ascendante ou descendante, c’est-à-dire la possibilité de relire ses données avec une nouvelle version du logiciel ou de permettre à une ancienne version du logiciel de lire des données créées à partir d'une version plus récente. Cela nécessite d'une part un mode de versionnage qui permet de connaître les versions compatibles et un moyen pour les versions les plus anciennes d'ignorer les données qu'elle ne savent pas interpréter.

Alternatives

La sérialisation est un mécanisme de codage atomique : il n'est pas destiné à permettre d'accéder à un fragment des données sans avoir tout décodé.

Certains mécanismes, comme NSKeyedArchiver de la bibliothèque Cocoa, permettent une réanimation partielle des objets. Ils se rapprochent des systèmes de base de données.

Notes et références

  1. anglicisme en lieu et place du mot français sériation
  2. Marcelo d'Amorim, Efficient Explicit-state Model Checking for Programs with Dynamically Allocated Data, ProQuest, (lire en ligne)
  3. Deserialization Cheat Sheet sur le site d'OWASP https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
  4. (en) AMFPHP.
  5. (en) openamf.
  6. (en) FlashORB.
  7. (en) FLAP.
  8. tbowan, « Délinéarisation et injection d’objet en PHP », sur Arsouyes.org (consulté le )
  9. « CWE - CWE-502: Deserialization of Untrusted Data (4.0) », sur cwe.mitre.org (consulté le )
  10. « CVE security vulnerabilities related to CWE (Common Weakness Enumeration) 502 », sur www.cvedetails.com (consulté le )
  11. « CWE - CWE-499: Serializable Class Containing Sensitive Data (4.0) », sur cwe.mitre.org (consulté le )

Voir aussi

Sur les autres projets Wikimedia :