Brevet FR2863074A1 — publié le 20050603

La présente invention concerne un procédé permettant l'interruption sûre d'applications dans un système informatique distribué comportant un moteur d'exécution logiciel (ou machine virtuelle). Elle s'applique notamment, mais non exclusivement, à des petits dispositifs informatiques disposant de faibles ressources (en mémoire comme en temps d'exécution) et soumis à de fortes contraintes sécuritaires (protection de l'information, confidentialité, intégrité des données, etc.). Les cartes à puce et petits terminaux (bancaires, téléphoniques, assistants personnels, claviers sécurisés, etc.) sont des exemples typiques de tels dispositifs. Ces dispositifs comportent un matériel et un ensemble de logiciels de base (système opératoire, gestionnaire de mémoire, gestionnaires des périphériques, etc.) qu'on désigne collectivement sous le nom de plates-formes. Sur ces plates- formes s'exécutent des applications spécifiques (parfois désignées sous le nom d' applets ). Plusieurs standards décrivant des plates-formes sécuritaires (Java Card, "STIP", "FINREAD", "GlobalPlatform", etc.) sont basés sur un modèle de calcul distribué du type client-serveur. Dans ce modèle, un contrôleur envoie des requêtes aux applets. Ces applets peuvent faire appel à des services de bas niveau offerts par la plate-forme à travers une interface de programmation, appelée ci-après API. Du point de vue sécuritaire, la plate-forme, le contrôleur 2863074 -2- et le code des APIs sont considérés sûrs. Par contre, les applets peuvent être potentiellement malicieuses. Les systèmes informatiques client-serveur peuvent être de type synchrone ou asynchrone. La propriété qui caractérise les systèmes synchrones est que, une fois que le client envoie sa requête au serveur, il arrête tout calcul et reste bloqué en attendant la réponse de celui-ci. En revanche, dans les systèmes asynchrones, une fois la requête envoyée, le client et le serveur poursuivent leurs tâches en parallèle, jusqu'à ce qu'ils décident de prendre en charge le message envoyé ou reçu. Chaque processus poursuit donc un fil d'exécution ("thread", en anglais) différent. La programmation asynchrone est souvent une tâche compliquée, qui rend le comportement des programmes difficile à prévoir. Pour cette raison, plusieurs standards basés sur le modèle client-serveur ont choisi de n'autoriser qu'un seul fil d'exécution pour traiter les requêtes que le contrôleur envoie à l'applet (programmation synchrone). Au début de ce fil d'exécution, le contrôleur a la main et il envoie une commande à l'applet. Par la suite, l'applet et le contrôleur se rendent la main successivement jusqu'au traitement complet de la requête. Dans ce cadre, les seuls événements asynchrones du système sont ceux liés à la gestion des périphériques d'entrée/sortie de la plate-forme (écran et clavier d'un terminal, etc.). Lorsque l'applet a le contrôle, elle peut déclencher ces événements par l'appel d'une méthode de l'API qui déclenchera un fil d'exécution parallèle au traitement de la requête. Il est donc naturellement possible que plusieurs événements concernant l'entrée/sortie soient en cours d'exécution à un moment donné, en parallèle avec le fil d'exécution de la requête. On a supposé que le contrôleur est notifié des événements asynchrones 30 concernant les périphériques. Si ceux-ci finissent avant le fil d'exécution de la requête, ils sont sauvegardés dans une file d'attente et traités par le contrôleur 2863074 3 quand la requête a été entièrement traitée. On a supposé aussi que ces notifications possèdent toute l'information nécessaire à leur gestion par le contrôleur. Les événements liés à la gestion des entrées/sorties peuvent être de nature complexe (exécution non-atomique). On a supposé aussi que le code embarqué sur la plate-forme (aussi bien celui du client que celui du serveur) n'est pas directement exécutable sur le processeur de la plate-forme, mais qu'il est interprété par un autre programme appelé le moteur d'exécution (ou, de manière équivalente, la machine virtuelle). Afin de gérer l'invocation des méthodes, le moteur d'exécution maintient une pile de structures de données appelées "frames" pour le fil d'exécution correspondant au traitement de la requête. Chaque "frame" contient les données nécessaires à l'exécution des méthodes invoquées précédemment dans ce fil d'exécution, et dont l'exécution n'est pas encore terminée. En particulier, chaque "frame" contient une référence à l'instruction de la méthode qui est en cours d'exécution. Quand une méthode est invoquée, une nouvelle "frame" est créée et installée au sommet de la pile. Quand l'exécution de la méthode en cours finit normalement, la "frame" au sommet de la pile est retirée (ou dépilée ), et le moteur d'exécution poursuit avec l'instruction qui suit celle référencée dans la nouvelle "frame" au sommet de la pile. Quand l'exécution de la méthode en cours s'arrête avec une exception, les frames dans la pile sont retirées l'une après l'autre jusqu'à la rencontre d'une frame de méthode dont le code contient un bloc de traitement pour l'exception en question. Dans ce cas, le moteur d'exécution poursuit avec la nouvelle frame au sommet de la pile, à partir de l'instruction indiquée dans le bloc de traitement. Il s'avère que le problème qui se pose avec le plus d'acuité est celui de l'interruption sûre d'applets suite à des événements exceptionnels, comme, par exemple, un comportement anormal de l'applet qui met en cause la sécurité de 2863074 4 la plate-forme ou perturbe l'exécution d'autres applets. Dans ce schéma d'exécution, il existe deux moyens d'interrompre l'exécution d'une requête: 1. Si l'applet rend la main au contrôleur normalement, celui-ci peut envoyer à l'applet un message spécial lui demandant de s'arrêter. L'applet peut donc faire le nécessaire pour terminer son exécution proprement (par exemple, nettoyer ses structures de données, sauver son état sur une mémoire persistante, etc.). Dans ce cas, l'applet peut terminer proprement les différents fils d'exécution liés aux périphériques (clôture des fichiers, etc.). Néanmoins, pour des raisons de sécurité, le contrôleur peut vérifier aussi que les périphériques ont bien été arrêtés une fois que l'applet lui rend la main. 2. Si l'applet rend la main au contrôleur avec une exception, alors elle peut ne pas être en mesure de s'arrêter proprement. Dans ce cas, le contrôleur doit au moins assurer que tous les fils d'exécution liés aux périphériques s'arrêtent proprement, et que la plate-forme restera dans un état sûr. Pour cela, lorsqu'il reçoit la notification d'un événement concernant un périphérique, il fera le nécessaire pour l'arrêter proprement. Par contre, dans ce schéma d'exécution synchrone, il n'est pas possible d'arrêter l'applet et de transiter vers un état sûr du système si elle ne rend pas la main. Or, cela peut être nécessaire dans plusieurs situations, comme par exemple: É quand une applet malicieuse veut monopoliser les ressources et les rendre indisponibles pour les autres applications; É quand un processus de surveillance (ou audit) extérieur au fil d'exécution de la requête détecte que l'applet a violé la politique de sécurité (par exemple, dans le cas de Java Card, la plate-forme détecte 2863074 5 que l'applet a lancé et rattrapé une exception de sécurité ("SecurityException" en Java) pendant son exécution) ; É quand l'applet est incapable de répondre car elle a déclenché une erreur fatale. L'invention a donc plus particulièrement pour but de résoudre le problème suivant: comment, dans tous les cas, interrompre proprement le fil d'exécution de l'applet et les fils d'exécution des périphériques afin que la plate-forme puisse revenir à un état sûr et ce, notamment, dans le cadre de plates-formes à faibles ressources telles que celles précédemment mentionnées (Java Card, "FINREAD", "STIP", "GlobalPlatform", etc.), et dont la sécurité est un souci de premier ordre. Pour résoudre le problème de l'arrêt sûr d'un fil d'exécution Java, a déjà été proposée une solution développée sous le nom de terminaison légère ("soft termination") consistant à classifier les "frames" du fil d'exécution en "sûrs" et "non-sûrs". Quand l'administrateur du système veut arrêter un fil d'exécution, il dépile les "frames" non-sûres, mais laisse poursuivre l'exécution des "frames" sûres sur la pile jusqu'à leur arrêt normal. Le code des applets est alors réécrit en insérant des instructions qui testent l'état d'une variable logique (drapeau), et lancent une exception si la variable a été mise à vrai. Par exemple, si le drapeau est vu comme une variable statique booléenne, et l'instruction b à la position i est un passage obligé, alors b pourrait être remplacée par les instructions suivantes (dans le bytecode du langage Java) : I: getstatic ifeqO j new java.lang.ThreadDeath dup invokevirtual java.lang.ThreadDeath athrow j b 2863074 6 Pour assurer que la variable sera effectivement testée si la méthode réécrite boucle, on a proposé d'insérer les nouvelles instructions devant des instructions qui pourraient potentiellement prolonger la durée de vie de la méthode, à savoir: É un branchement du flot de contrôle vers l'arrière É le début de chaque méthode É le début de chaque bloc de traitement d'exceptions Certaines situations qui pourraient empêcher cette solution de fonctionner (existence d'une variable avec le même nom que le drapeau, bloc de traitement d'une exception se recouvrant à lui-même) sont écartées par une analyse préalable du code pendant son chargement. L'analyse recherche ces situations particulières, et refuse de charger le code si elles sont présentes dans le code de l'applet. La méthode de terminaison légère par réécriture du code des applets présente au moins trois défauts. D'abord, du fait qu'elle a été conçue dans le cadre de l'utilisation de Java comme langage de programmation d'applications dynamiques (ajoutables à un navigateur d'Internet), la solution précédemment décrite prend en compte la dégradation des performances de l'applet réécrite en temps d'exécution, mais pas en termes d'augmentation de la taille du code. En effet, la réécriture du code des applets fait grossir la taille du code des applets. Or, pour les plates- formes visées dans le contexte de la présente invention, la mémoire est souvent une ressource critique, ce qui peut rendre cette approche inapplicable en pratique. Deuxièmement, la solution précédemment décrite garantit que toute applet malicieuse finira par rendre la main, mais elle permet à l'applet de poursuivre son exécution jusqu'à ce qu'elle rencontre l'une des instructions qui pourrait 2863074 -7 prolonger sa vie. Or, dans un contexte de fortes contraintes sécuritaires comme celui adressé par les standards précédemment cités, il est souhaitable de pouvoir arrêter net l'exécution de l'applet, afin d'empêcher qu'elle puisse continuer à faire des dégâts, tout en laissant aux autres applets le temps de s'arrêter correctement. Il s'avère donc que cette solution pénalise toutes les applications de la même façon: dès que la procédure d'arrêt est engagée à cause d'une action engagée par l'une des applets, toutes les autres applets sont aussi empêchées d'invoquer des méthodes (et finalement arrêtées). Troisièmement, l'utilisation d'une variable booléenne Java pour communiquer l'arrêt éventuel de l'applet représente un point faible pour la sécurité. En effet, une applet malicieuse pourrait essayer de manipuler elle-même cette variable (en l'affectant à faux) pour éviter les contre-mesures de la plate-forme. Ceci implique en principe de vérifier l'absence de telles manipulations avant la réécriture du code. Pour que les codes des applets restent indépendants de la plate-forme, cette vérification doit être faite sur la plate-forme elle-même. Or, le coût en temps et taille de code de ce genre d'analyse n'est pas négligeable dans des dispositifs à faibles ressources, comme ceux envisagés dans le contexte de la présente invention. L'invention a donc plus particulièrement pour but de résoudre ces problèmes et donc de supprimer ces inconvénients. Elle propose, à cet effet, un procédé consistant à adapter le moteur d'exécution du système informatique ainsi que les APIS selon un processus comprenant les étapes suivantes: l'attribution aux "frames" d'une annotation permettant de déterminer si la méthode correspondant à la "frame" est sûre ou pas, 2863074 8 le dépilement systématique, lors d'événements spécifiques (simplement appelés événements par la suite), des "frames" non sûres et, ceci, jusqu'à ce qu'une "frame" sûre soit atteinte, la poursuite normale de l'exécution une fois qu'une "frame" sûre est atteinte, l'empêchement de tout nouvel empilement d'une nouvelle "frame" non sûre à partir d'une "frame" sûre dès lors qu'un événement a été détecté. Avantageusement: Les codes sûrs pourront être adaptés de manière à enrober tout appel à une méthode non sûre par un mécanisme de traitement des exceptions de façon à poursuivre proprement l'exécution desdits codes sûrs après un appel à une méthode non sûre. La séparation en codes sûrs et non sûrs pourra alternativement: É être établie de façon permanente et inscrite dans le code ou les données du moteur d'exécution, É évoluer dynamiquement lors de l'exécution du système, É résulter d'une analyse d'un registre d'événements potentiellement dangereux. Les événements pourront être déclenchés par un mécanisme matériel et/ou par un composant logiciel différent du moteur d'exécution et/ou par le moteur d'exécution lui-même. - Le composant logiciel qui déclenche l'événement pourra notifier cet événement au moteur d'exécution à travers une structure de données partagées pouvant notamment consister en une simple variable booléenne et/ou comporter des informations complémentaires, comme, par exemple, un niveau de priorité, les méthodes qui sont considérées non sûres, des instructions concernant la procédure d'arrêt à utiliser. Cette structure de données partagée pourra être consultée par le moteur d'exécution systématiquement après l'exécution d'un certain nombre d'instructions ou par le moteur d'exécution lors de l'exécution de certaines instructions annotées des applets (ou instructions alternatives). 2863074 9 Les instructions correspondant à des invocations de méthodes potentiellement non sûres pourront être remplacées par des instructions spéciales reconnues par le moteur d'exécution, qui empêche alors tout empilement d'une "frame" non sûre. La plate-forme pourra posséder un second moteur d'exécution qui est appelé lors de la détection d'un événement et qui empêche tout empilement d'une "frame non sûre", ce second moteur d'exécution pouvant comporter pour chacune des "frames" non sûres un chronomètre comptabilisant des unités de temps écoulées depuis que la "frame" a été empilée et qui est utilisée par ledit moteur pour interrompre l'exécution des "frames" non sûres. Des modes d'exécution de l'invention seront décrits ci-après, à titre d'exemples non limitatifs, concernant les trois phases principales du procédé, à savoir: - le déclenchement d'un événement et sa détection par le moteur d'exécution, les traitements effectués par le moteur suite à la détection d'un événement, et les mécanismes permettant d'éviter l'empilement des "frames" non sûres. En ce qui concerne le déclenchement et la détection des événements, trois variantes sont envisageables, ces trois variantes pouvant être combinées dans une implémentation: Le déclenchement par interruption matérielle ("Hardware") : Cette solution consiste à traiter les événements déclenchés par un système d'interruptions matériel. Dans ce cas, le moteur détection est interrompu directement et aucun mécanisme spécifique n'est nécessaire. Le déclenchement par le moteur d'exécution: Dans certaines situations, le moteur d'exécution peut déclencher lui-même l'événement. Dans ces cas particuliers, c'est donc le moteur qui, à la fois, 2863074 -10- déclenche l'événement et traite cet événement. En Java Card par exemple, tout rattrapage d'une exception de la classe "SecurityException" par une applet peut être détecté par la machine virtuelle. Le déclenchement par un composant logiciel (qui nécessite un mécanisme spécifique de détection des événements) : Cette solution prévoit un déclenchement de manière asynchrone de l'événement par un autre composant logiciel de la plate-forme. Dans ce cas, le composant qui déclenche l'erreur doit notifier au moteur d'exécution la demande d'arrêt immédiat, par exemple à travers une structure de données partagée avec les composants pouvant demander un arrêt. Cette structure de données peut être un simple drapeau (variable booléenne) ou bien une structure plus complexe, contenant par exemple un niveau de priorité pour le message, les méthodes qui sont considérées non sûres au moment de l'interruption, des instructions concernant la procédure d'arrêt à utiliser, etc. Dans ce cas, il faut assurer la détection (et le traitement) de l'événement par le moteur d'exécution. Un moyen de satisfaire cette exigence est de programmer le moteur d'exécution de façon à ce qu'il consulte régulièrement l'état du drapeau. Cette approche peut être modulée selon la fréquence de consultation du drapeau. Conformément à l'invention, la détection des événements peut s'effectuer grâce aux mécanismes suivants: a) Un mécanisme d'inspection à intervalles réguliers: Dans son expression la plus simple, ce mécanisme comprend la programmation du moteur d'exécution de manière à vérifier l'état du drapeau systématiquement après l'exécution d'un certain nombre d'instructions. 2863074 -11- b) Un mécanisme de détection du prolongement de vie potentiel: La consultation régulière du drapeau peut ralentir sensiblement l'exécution du code des applets. Une solution alternative consiste à ne consulter le drapeau que pour un sous-ensemble des instructions de l'applet, mais sans faire grossir le code de celle-ci: au lieu de réécrire le code de l'applet, on annote certaines instructions des applets qui constituent un sur-ensemble des points de passage obligés dans leur exécution, à partir de n'importe quelle instruction du code. Ce marquage peut se faire en remplaçant le nom des instructions choisies qui apparaît dans le code de l'applet (appelé "opcode" dans le jargon de Java) par un autre. Lors de l'exécution du code, chaque fois que l'interpréteur trouve une instruction marquée, il teste si un message d'arrêt est arrivé. Si c'est le cas, il déclenche la procédure d'arrêt de l'applet. Sinon, il interprète l'instruction normalement. Pour des raisons de sécurité, il est préférable que le drapeau soit maintenu comme une structure de données native, et donc pas accessible par les applets. L'utilisation d'une structure native permet d'éviter des vérifications additionnelles pour prouver que l'applet ne peut modifier elle-même la valeur du drapeau. L'ensemble de points de passage obligés peut notamment résulter d'une analyse statique du code de l'applet. Si l'analyse se fait hors de la plate-forme, les instructions à modifier peuvent être envoyées sur la plate-forme avec le code de l'applet. Par exemple, dans une plate-forme Java Card, elles peuvent être annexées dans un composant additionnel spécifique du format de fichiers "CAP". Dans une plate-forme "GlobalPlatform", elles peuvent faire partie des données des commandes "INSTALL" ou "LOAD". Dans tous les cas, le procédé de chargement du code de l'application sur la plate-forme doit aussi assurer l'intégrité de cette information (par exemple, en utilisant un canal de communication sécurisé par l'utilisation d'un protocole spécial, comme les protocoles SCPO1 et SCPO2 utilisés dans "GlobalPlatform"). 2863074 -12- Il est préférable que l'annotation des instructions de l'applet se fasse lors de l'installation de l'applet sur la plate-forme, par exemple, pendant la phase d'édition de liens du code. L'avantage d'annoter le bytecode sur la carte est qu'il assure que toute applet peut être exécutée sur la plate-forme modifiée de façon à reconnaître les nouveaux "opcodes", même si elle a été conçue pour être exécutée sur une autre plate-forme. Le déclenchement mixte: Il consiste à combiner les méthodes de déclenchement précédemment décrites dans l'implantation (par exemple, déclenchement par interruption matérielle pour les exceptions de sécurité et déclenchement logiciel pour les autres événements). En ce qui concerne le traitement des événements: L'action réalisée par le moteur d'exécution au moment de la détection d'un événement peut être arbitrairement complexe. Juste à titre d'exemple, elle peut effectuer différents traitements (ou lancer différentes classes d'exceptions) en fonction du niveau de priorité attribué à l'événement par le composant qui la demande. Par exemple, pour des événements de priorité basse, il peut être utile de laisser l'applet faire le nécessaire pour que son état interne reste sûr. Mais si l'applet doit être arrêtée pour des raisons de sécurité, il est préférable de l'empêcher d'exécuter tout autre code. Pour cela, une exception sécuritaire peut posséder un attribut x indiquant si l'événement a déjà été rattrapé par l'applet. Cet attribut est initialisé lors du lancement de l'exception, en fonction du niveau de priorité de la demande d'arrêt. Lors du rattrapage d'une exception sécuritaire, le moteur d'exécution vérifie la valeur de l'attribut. Si l'attribut vaut faux, il accepte d'exécuter le code du bloc de rattrapage. Sinon, la "frame" non sûre au sommet de la pile est dépilée et le 2863074 - 13 - moteur traite la "frame" suivante (dans la pile). Le traitement de l'exception peut inclure l'affectation à vrai de l'attribut x. Le contrôle du rattrapage des exceptions permet d'omettre une vérification 5 statique pour éviter qu'une applet puisse ne jamais rendre la main en utilisant un bloc de rattrapage qui se recouvre lui-même. 2863074 - 14 - Revendications 1. Procédé permettant l'interruption sûre d'applications dans un système informatique distribué comportant un moteur d'exécution logiciel (ou machine 5 virtuelle), caractérisé en ce qu'il comprend les étapes suivantes: l'attribution aux "frames" (structures de données empilées lors d'un appel de méthode) d'une annotation permettant de déterminer si la méthode correspondant à la "frame" est sûre ou pas, le dépilement systématique, lors d'événements spécifiques, des "frames" non sûres et, ceci, jusqu'à ce qu'une "frame" sûre soit atteinte, la poursuite normale de l'exécution une fois qu'une "frame" sûre est atteinte, l'empêchement de tout nouvel empilement de "frame" non sûre à partir d'une "frame" sûre dès lors qu'un événement a été détecté. 2. Procédé selon la revendication 1, caractérisé en ce que les codes sûrs sont adaptés de manière à enrober tout appel à une méthode non sûre par un mécanisme de traitement des exceptions de façon à poursuivre proprement l'exécution desdits codes sûrs après un appel à une méthode non sûre. 3. Procédé selon la revendication 1, caractérisé en ce que la séparation en codes sûrs et non sûrs est établie de façon permanente et inscrite dans le code ou les données du moteur 25 d'exécution. 4. Procédé selon la revendication 1, caractérisé en ce que la séparation en codes sûrs et non sûrs peut évoluer dynamiquement lors de l'exécution du système. 2863074 - 15 - 5. Procédé selon la revendication 4, caractérisé en ce que la séparation en codes sûrs et non sûrs résulte d'une analyse d'un registre d'événements potentiellement dangereux. 6. Procédé selon la revendication 1, caractérisé en ce que lesdits événements sont déclenchés par un mécanisme matériel (interruptions matérielles, par exemple). 7. Procédé selon la revendication 1, 10 caractérisé en ce que lesdits événements sont déclenchés par le moteur d'exécution lui-même. 8. Procédé selon la revendication 1, caractérisé en ce que lesdits événements sont déclenchés par un composant logiciel différent du moteur d'exécution. 9. Procédé selon la revendication 8, caractérisé en ce que le composant logiciel qui déclenche l'événement notifie cet événement au moteur d'exécution à travers une structure de données 20 partagée. 10. Procédé selon la revendication 9, caractérisé en ce que ladite structure de données est une simple variable booléenne. 11. Procédé selon la revendication 9, caractérisé en ce que ladite structure de données comporte des informations complémentaires, comme par exemple un niveau de priorité, les méthodes qui sont considérées non sûres au moment du déclenchement de la procédure d'arrêt, ou des instructions concernant la procédure d'arrêt à utiliser. - 16 - 12. Procédé selon la revendication 9, caractérisé en ce que ladite structure de données partagée est consultée par le moteur d'exécution systématiquement après l'exécution d'un certain nombre d'instructions. 13. Procédé selon la revendication 9, caractérisé en ce que ladite structure de données partagée est consultée par le moteur d'exécution lors de l'exécution de certaines instructions annotées des applications (ou instructions alternatives). 14. Procédé selon la revendication 1, caractérisé en ce que différents traitements sont effectués par le moteur d'exécution lors de la détection d'un événement, selon les informations associées à cet événement (priorité par exemple). 15. Procédé selon la revendication 1, caractérisé en ce que les instructions correspondant à des invocations de méthodes potentiellement non sûres pourront être remplacées par des instructions spéciales reconnues par le moteur d'exécution qui empêche alors tout empilement d'une "frame" non sûre. 16. Procédé selon la revendication 1, caractérisé en ce que la plateforme possède un second moteur d'exécution qui est appelé lors de la détection d'un événement et qui empêche tout empilement 25 d'une "frame" non sûre. 17. Procédé selon la revendication 16, caractérisé en ce que le second moteur d'exécution comporte pour chacune des "frames" non sûres un chronomètre comptabilisant les unités de temps écoulées depuis que la "frame" a été empilée et qui est utilisée par ledit moteur pour interrompre l'exécution desdites "frames" non sûres.