Sécurité et architecture du chargeur de classe

Précédent 1 2 Page 2 Page 2 de 2

Chargeurs de classes et espaces de noms

Pour chaque classe chargée, la JVM garde la trace de quel chargeur de classe - qu'il soit primordial ou objet - a chargé la classe. Lorsqu'une classe chargée fait d'abord référence à une autre classe, la machine virtuelle demande la classe référencée au même chargeur de classe qui a chargé à l'origine la classe de référence. Par exemple, si la machine virtuelle charge la classe Volcanovia un chargeur de classe particulier, elle tentera de charger toutes les classes auxquelles il Volcanofait référence via le même chargeur de classe. Si Volcanofait référence à une classe nommée Lava, peut-être en appelant une méthode dans la classe Lava, la machine virtuelle demandera Lavaau chargeur de classe qui a chargé Volcano. La Lavaclasse renvoyée par le chargeur de classe est liée dynamiquement à la classe Volcano.

Étant donné que la machine virtuelle Java adopte cette approche pour charger les classes, les classes ne peuvent par défaut voir que les autres classes chargées par le même chargeur de classe. De cette manière, l'architecture Java vous permet de créer plusieurs espaces de noms dans une seule application Java. Un espace de nom est un ensemble de noms uniques des classes chargées par un chargeur de classe particulier. Pour chaque chargeur de classe, la machine virtuelle Java gère un espace de nom, qui est rempli par les noms de toutes les classes qui ont été chargées via ce chargeur de classe.

Une fois qu'une JVM a chargé une classe nommée Volcanodans un espace de nom particulier, par exemple, il est impossible de charger une classe différente nommée Volcanodans ce même espace de nom. VolcanoCependant, vous pouvez charger plusieurs classes dans une JVM, car vous pouvez créer plusieurs espaces de noms dans une application Java. Vous pouvez le faire simplement en créant plusieurs chargeurs de classes. Si vous créez trois espaces de nom séparés (un pour chacun des trois chargeurs de classe) dans une application Java en cours d'exécution, alors, en chargeant une Volcanoclasse dans chaque espace de nom, votre programme pourrait charger trois Volcanoclasses différentes dans votre application.

Une application Java peut instancier plusieurs objets de chargeur de classe à partir de la même classe ou de plusieurs classes. Il peut donc créer autant d'objets de chargeur de classe (et autant de types différents) qu'il en a besoin. Les classes chargées par différents chargeurs de classes sont dans des espaces de noms différents et ne peuvent pas accéder les unes aux autres à moins que l'application ne l'autorise explicitement. Lorsque vous écrivez une application Java, vous pouvez séparer les classes chargées à partir de différentes sources dans différents espaces de noms. De cette manière, vous pouvez utiliser l'architecture de chargeur de classe de Java pour contrôler toute interaction entre le code chargé à partir de différentes sources. Vous pouvez empêcher le code hostile d'accéder au code convivial et de le subvertir.

Chargeurs de classes pour applets

Un exemple d'extension dynamique avec des chargeurs de classe est le navigateur Web, qui utilise des objets de chargeur de classe pour télécharger les fichiers de classe d'une applet sur un réseau. Un navigateur Web déclenche une application Java qui installe un objet de chargeur de classe - généralement appelé chargeur de classe d'applet - qui sait comment demander des fichiers de classe à un serveur HTTP. Les applets sont un exemple d'extension dynamique, car lorsque l'application Java démarre, elle ne sait pas quels fichiers de classe le navigateur lui demandera de télécharger sur le réseau. Les fichiers de classe à télécharger sont déterminés au moment de l'exécution, car le navigateur rencontre des pages contenant des applets Java.

L'application Java lancée par le navigateur Web crée généralement un objet chargeur de classe d'applet différent pour chaque emplacement du réseau à partir duquel elle récupère les fichiers de classe. Par conséquent, les fichiers de classe de différentes sources sont chargés par différents objets de chargeur de classe. Cela les place dans différents espaces de noms à l'intérieur de l'application Java hôte. Étant donné que les fichiers de classe des applets provenant de différentes sources sont placés dans des espaces de noms séparés, le code d'une applet malveillante ne peut pas interférer directement avec les fichiers de classe téléchargés à partir de toute autre source.

Coopération entre chargeurs de classe

Souvent, un objet de chargeur de classe s'appuie sur d'autres chargeurs de classe - à tout le moins, sur le chargeur de classe primordial - pour l'aider à répondre à certaines des demandes de chargement de classe qui se présentent à lui. Par exemple, imaginez que vous écrivez une application Java qui installe un chargeur de classe dont la manière particulière de charger les fichiers de classe est obtenue en les téléchargeant sur un réseau. Supposons qu'au cours de l'exécution de l'application Java, une demande soit faite à votre chargeur de classe pour charger une classe nommée Volcano.

Une façon d'écrire le chargeur de classe est de lui demander d'abord de demander au chargeur de classe primordial de trouver et de charger la classe à partir de son référentiel de confiance. Dans ce cas, comme Volcanone fait pas partie de l'API Java, supposons que le chargeur de classe primordial ne trouve pas de classe nommée Volcano. Lorsque le chargeur de classe primordial répond qu'il ne peut pas charger la classe, votre chargeur de classe peut alors tenter de charger la Volcanoclasse de sa manière personnalisée, en la téléchargeant sur le réseau. En supposant que votre chargeur de classe soit capable de télécharger la classe Volcano, cette Volcanoclasse pourrait alors jouer un rôle dans le futur cours d'exécution de l'application.

Pour continuer avec le même exemple, supposons que quelque temps plus tard, une méthode de classe Volcanoest appelée pour la première fois et que la méthode référence la classe à Stringpartir de l'API Java. Comme c'est la première fois que la référence est utilisée par le programme en cours d'exécution, la machine virtuelle demande à votre chargeur de classe (celui qui a chargé Volcano) de se charger String. Comme précédemment, votre chargeur de classe passe d'abord la requête au chargeur de classe primordial, mais dans ce cas, le chargeur de classe primordial est capable de renvoyer une Stringclasse à votre chargeur de classe.

Le chargeur de classe primordial n'a probablement pas eu à se charger Stringà ce stade car, étant donné qu'il Strings'agit d'une classe si fondamentale dans les programmes Java, il était presque certainement utilisé auparavant et donc déjà chargé. Très probablement, le chargeur de classe primordial vient de renvoyer la Stringclasse qu'il avait précédemment chargée à partir du référentiel approuvé.

Puisque le chargeur de classe primordial a pu trouver la classe, votre chargeur de classe n'essaye pas de la télécharger sur le réseau; il transmet simplement à la machine virtuelle la Stringclasse retournée par le chargeur de classe primordial. À partir de là, la machine virtuelle utilise cette Stringclasse chaque fois que la classe fait Volcanoréférence à une classe nommée String.

Chargeurs de classes dans le bac à sable

Dans le bac à sable de Java, l'architecture du chargeur de classe est la première ligne de défense contre le code malveillant. C'est le chargeur de classe, après tout, qui introduit le code dans la JVM - code qui pourrait être hostile.

L'architecture du chargeur de classe contribue au sandbox de Java de deux manières:

  1. Il empêche le code malveillant d'interférer avec du code bienveillant.
  2. Il garde les frontières des bibliothèques de classes de confiance.

L'architecture du chargeur de classe protège les frontières des bibliothèques de classes approuvées en s'assurant que les classes non approuvées ne peuvent pas prétendre être approuvées. Si une classe malveillante pouvait tromper la JVM en lui faisant croire qu'il s'agissait d'une classe de confiance de l'API Java, cette classe malveillante pourrait potentiellement franchir la barrière du sandbox. En empêchant les classes non approuvées d'emprunter l'identité des classes approuvées, l'architecture du chargeur de classe bloque une approche potentielle pour compromettre la sécurité de l'environnement d'exécution Java.

Espaces de nom et boucliers

L'architecture du chargeur de classe empêche le code malveillant d'interférer avec le code bienveillant en fournissant des espaces de noms protégés pour les classes chargées par différents chargeurs de classe. Comme mentionné ci-dessus, l' espace de noms est un ensemble de noms uniques pour les classes chargées qui est géré par la JVM.

Les espaces de noms contribuent à la sécurité car vous pouvez, en fait, placer un bouclier entre les classes chargées dans différents espaces de noms. À l'intérieur de la machine virtuelle Java, les classes du même espace de noms peuvent interagir directement les unes avec les autres. Les classes dans différents espaces de noms, cependant, ne peuvent même pas détecter la présence les unes des autres à moins que vous ne fournissiez explicitement un mécanisme permettant aux classes d'interagir. Si une classe malveillante, une fois chargée, avait garanti l'accès à toutes les autres classes actuellement chargées par la machine virtuelle, cette classe pourrait potentiellement apprendre des choses qu'elle ne devrait pas savoir, ou elle pourrait interférer avec la bonne exécution de votre programme.

Créer un environnement sécurisé

Lorsque vous écrivez une application qui utilise des chargeurs de classe, vous créez un environnement dans lequel le code chargé dynamiquement s'exécute. Si vous voulez que l'environnement soit exempt de failles de sécurité, vous devez suivre certaines règles lorsque vous écrivez vos chargeurs d'application et de classe. En général, vous souhaiterez écrire votre application afin que le code malveillant soit protégé du code bienveillant. En outre, vous souhaiterez écrire des chargeurs de classe de manière à protéger les frontières des bibliothèques de classes approuvées, telles que celles de l'API Java.

Espaces de nom et sources de code

Pour bénéficier des avantages de sécurité offerts par les espaces de noms, vous devez vous assurer de charger des classes de différentes sources via différents chargeurs de classes. Il s'agit du schéma décrit ci-dessus utilisé par les navigateurs Web compatibles Java. L'application Java déclenchée par un navigateur Web crée généralement un objet de chargeur de classe d'applet différent pour chaque source de classes qu'elle télécharge sur le réseau. Par exemple, un navigateur utilise un objet chargeur de classe pour télécharger des classes depuis //www.niceapplets.com et un autre objet chargeur de classe pour télécharger des classes depuis //www.meanapplets.com.

Garder les paquets restreints

Java permet aux classes du même package de s'accorder des privilèges d'accès spéciaux qui ne sont pas accordés aux classes en dehors du package. Donc, si votre chargeur de classe reçoit une requête pour charger une classe qui, par son nom, déclare effrontément faire partie de l'API Java (par exemple, une classe nommée java.lang.Virus), votre chargeur de classe doit procéder avec prudence. Si elle est chargée, une telle classe pourrait obtenir un accès spécial aux classes de confiance de java.langet pourrait éventuellement utiliser cet accès spécial à des fins détournées.

Par conséquent, vous écririez normalement un chargeur de classe afin qu'il refuse simplement de charger toute classe qui prétend faire partie de l'API Java (ou de toute autre bibliothèque d'exécution approuvée) mais qui n'existe pas dans le référentiel local approuvé. En d'autres termes, une fois que votre chargeur de classe a transmis une demande au chargeur de classe primordial, et que le chargeur de classe primordial indique qu'il ne peut pas charger la classe, votre chargeur de classe doit vérifier pour s'assurer que la classe ne se déclare pas membre. d'un package de confiance. Si c'est le cas, votre chargeur de classe, au lieu d'essayer de télécharger la classe sur le réseau, doit lever une exception de sécurité.

Garder les paquets interdits

De plus, vous avez peut-être installé des packages dans le référentiel approuvé qui contiennent des classes que vous souhaitez que votre application puisse charger via le chargeur de classe primordial, mais que vous ne souhaitez pas être accessible aux classes chargées via votre chargeur de classe. Par exemple, supposons que vous avez créé un package nommé absolutepoweret installé sur le référentiel local accessible par le chargeur de classe primordial. Supposons également que vous ne voulez pas que les classes chargées par votre chargeur de classe puissent charger n'importe quelle classe du absolutepowerpackage. Dans ce cas, vous écririez votre chargeur de classe de telle sorte que la toute première chose qu'il fasse soit de s'assurer que la classe demandée ne se déclare pas comme membreabsolutepowerpaquet. Si une telle classe est demandée, votre chargeur de classe, plutôt que de transmettre le nom de classe au chargeur de classe primordial, doit lever une exception de sécurité.

Le seul moyen pour un chargeur de classe de savoir si une classe provient ou non d'un package restreint, tel que java.lang, ou d'un package interdit, tel que absolutepower, est par le nom de la classe. Ainsi, un chargeur de classe doit recevoir une liste des noms des paquets restreints et interdits. Étant donné que le nom de la classe java.lang.Virusindique qu'elle provient du java.langpackage et se java.langtrouve sur la liste des packages restreints, votre chargeur de classe doit lever une exception de sécurité si le chargeur de classe primordial ne peut pas le charger. De même, comme le nom de la classe absolutepower.FancyClassLoaderindique qu'elle fait partie du absolutepowerpackage et que le absolutepowerpackage est sur la liste des packages interdits, votre chargeur de classe doit lever une exception de sécurité.

Un chargeur de classe soucieux de la sécurité

Une façon courante d'écrire un chargeur de classe axé sur la sécurité consiste à utiliser les quatre étapes suivantes:

  1. S'il existe des packages que ce chargeur de classe n'est pas autorisé à charger, le chargeur de classe vérifie si la classe demandée se trouve dans l'un de ces packages interdits mentionnés ci-dessus. Si tel est le cas, il lève une exception de sécurité. Sinon, il passe à l'étape deux.

  2. Le chargeur de classe transmet la demande au chargeur de classe primordial. Si le chargeur de classe primordial renvoie avec succès la classe, le chargeur de classe renvoie cette même classe. Sinon, il passe à l'étape trois.

  3. S'il existe des packages de confiance auxquels ce chargeur de classe n'est pas autorisé à ajouter des classes, le chargeur de classe vérifie si la classe demandée se trouve dans l'un de ces packages restreints. Si tel est le cas, il lève une exception de sécurité. Sinon, il passe à l'étape quatre.

  4. Enfin, le chargeur de classe tente de charger la classe de manière personnalisée, par exemple en la téléchargeant sur un réseau. En cas de succès, il renvoie la classe. En cas d'échec, il génère une erreur "aucune définition de classe trouvée".

En exécutant les étapes un et trois comme indiqué ci-dessus, le chargeur de classe protège les frontières des packages approuvés. Avec la première étape, il empêche une classe d'un paquet interdit d'être chargé du tout. Avec l'étape trois, il ne permet pas à une classe non approuvée de s'insérer dans un package approuvé.

Conclusion

L'architecture du chargeur de classe contribue au modèle de sécurité de la JVM de deux manières:

  1. en séparant le code en plusieurs espaces de nom et en plaçant un "bouclier" entre le code dans différents espaces de nom
  2. en gardant les frontières des bibliothèques de classes de confiance, telles que l'API Java

Ces deux capacités de l'architecture de chargeur de classe de Java doivent être utilisées correctement par les programmeurs afin de récolter les avantages de sécurité qu'elles offrent. Pour tirer parti du bouclier d'espace de nom, le code de différentes sources doit être chargé via différents objets de chargeur de classe. Pour tirer parti du garde-frontière des paquets de confiance, les chargeurs de classes doivent être écrits afin qu'ils vérifient les noms des classes demandées par rapport à une liste de paquets restreints et interdits.

Pour un aperçu du processus d'écriture d'un chargeur de classe, y compris un exemple de code, consultez l' article JavaWorld de Chuck McManis , «Les bases des chargeurs de classe Java».

Le mois prochain

Dans l'article du mois prochain, je continuerai la discussion sur le modèle de sécurité de la JVM en décrivant le vérificateur de classe.

Bill Venners écrit des logiciels de manière professionnelle depuis 12 ans. Basé dans la Silicon Valley, il fournit des services de conseil et de formation en logiciels sous le nom d'Artima Software Company. Au fil des ans, il a développé des logiciels pour les secteurs de l'électronique grand public, de l'éducation, des semi-conducteurs et de l'assurance-vie. Il a programmé dans de nombreux langages sur de nombreuses plateformes: langage d'assemblage sur différents microprocesseurs, C sous Unix, C ++ sous Windows, Java sur le Web. Il est l'auteur du livre: Inside the Java Virtual Machine, publié par McGraw-Hill.

En savoir plus sur ce sujet

  • Le livre The Java virtual machine Specification (//www.aw.com/cp/lindholm-yellin.html), de Tim Lindholm et Frank Yellin (ISBN 0-201-63452-X), qui fait partie de The Java Series (// www.aw.com/cp/javaseries.html), d'Addison-Wesley, est la référence définitive des machines virtuelles Java.
  • Secure Computing avec JavaNow and the Future (un livre blanc) // www.javasoft.com/marketing/collateral/security.html
  • FAQ sur la sécurité des applets

    //www.javasoft.com/sfaq/

  • Sécurité de bas niveau en Java, par Frank Yellin //www.javasoft.com/sfaq/verifier.html
  • La page d'accueil de la sécurité Java

    //www.javasoft.com/security/

  • Voir la page d'accueil des applets hostiles

    //www.math.gatech.edu/~mladue/HostileApplets.html

  • Le livre Java SecurityHostile Applets, Holes, and Antidotes, par Dr. Gary McGraw et Ed Felton, donne une analyse approfondie des problèmes de sécurité liés à Java. //www.rstcorp.com/java-security.html
  • Articles précédents "Under The Hood":
  • La machine virtuelle Lean, Mean - Donne une introduction à la machine virtuelle Java.
  • Style de vie du fichier de classe Java - Donne un aperçu du fichier de classe Java, le format de fichier dans lequel tous les programmes Java sont compilés.
  • Java's Garbage- Collected Heap - Donne un aperçu du garbage collection en général et du segment garbage-collecté de la machine virtuelle Java en particulier.
  • Notions de base sur le bytecode - Présente les bytecodes de la machine virtuelle Java et discute des types primitifs, des opérations de conversion et des opérations de pile en particulier.
  • Arithmétique à virgule flottante - Décrit la prise en charge de la virgule flottante de la machine virtuelle Java et les bytecodes qui effectuent des opérations en virgule flottante.
  • Logique et arithmétique - Décrit la prise en charge de la machine virtuelle Java pour l'arithmétique logique et entière, et les bytecodes associés.
  • Objets et tableaux - Décrit la manière dont la machine virtuelle Java traite les objets et les tableaux et décrit les bytecodes pertinents.
  • Exceptions - Décrit la façon dont la machine virtuelle Java traite les exceptions et décrit les bytecodes pertinents.
  • Try-finally - Décrit comment la machine virtuelle Java implémente les clauses try-finally et discute des bytecodes appropriés.
  • Flux de contrôle - Décrit comment la machine virtuelle Java implémente le flux de contrôle et décrit les bytecodes pertinents.
  • L'architecture des aglets - Décrit le fonctionnement interne des aglets, la technologie d'agent logiciel autonome d'IBM basée sur Java.
  • The Point of Aglets - Analyse l'utilité réelle des agents mobiles tels que les aglets, la technologie d'agent logiciel autonome basée sur Java d'IBM.
  • Invocation et retour de méthode - Décrit les quatre façons dont la machine virtuelle Java appelle des méthodes, y compris les bytecodes appropriés.
  • Synchronisation des threads - Montre comment la synchronisation des threads fonctionne dans la machine virtuelle Java. Discute des bytecodes pour entrer et sortir des moniteurs.
  • Architecture de sécurité de Java - Donne un aperçu du modèle de sécurité intégré à la JVM et examine les fonctionnalités de sécurité intégrées de la JVM.

Cette histoire, "La sécurité et l'architecture du chargeur de classe" a été publiée à l'origine par JavaWorld.