securite pour les telephones portables
Transcription
securite pour les telephones portables
Institut de la Francophonie pour l Informatique Ecole Nationale Supérieure des Télécommunications RAPPORT DE STAGE DE FIN D ETUDES Sujet : SECURITE POUR LES TELEPHONES PORTABLES Etudiant : Pham Viet Tan Nguyen, IFI Responsable : Patrick Bellot, ENST Paris, janvier 2005 2 Remerciements Je tiens à remercier particulièrement Monsieur Patrick Bellot, Professeur du Département Informatique et Réseaux (INFRES), l Ecole Nationale Supérieure des Télécommunications (ENST), pour m avoir accueilli et m avoir bien encadré pendant toute la durée du stage. J aimerais remercier sincèrement Monsieur Michel Riguidel, Responsable du Département Informatique et Réseaux, l Ecole Nationale Supérieure des Télécommunications (ENST), de ses conseils professionnels et de m a donné des meilleures conditions de travail au cours du stage. J exprime également mes vifs remerciements à Monsieur Charles Durand, Directeur de l Institut de la Francophonie pour l Informatique (IFI), d avoir préparé mon stage. Finalement, je remercie aussi vivement toutes les personnes de l ENST et de l IFI, particulièrement les thésards et les stagiaires de l INFRES, qui m ont porté leur amitié. 3 Table des matières Remerciements ................................................................................................................... 2 Table des matières.............................................................................................................. 3 Résumé ................................................................................................................................ 5 Abstract............................................................................................................................... 6 Liste des figures .................................................................................................................. 7 Chapitre 1: Introduction.................................................................................................... 8 Chapitre 2: Systèmes embarqués sécurisés : un défi ..................................................... 10 2.1 Exigences de sécurité.......................................................................................... 10 2.2 Défis de conception d un système embarqué sécurisé ........................................ 13 2.3 Attaques sur le système embarqué...................................................................... 14 2.3.1 Attaques logicielles..................................................................................... 15 Chapitre 3: Téléphones portables ................................................................................... 17 3.1 Architecture ........................................................................................................ 17 3.2 SIM..................................................................................................................... 19 3.3 Interfaces entre le téléphone portable et la SIM.................................................. 20 3.4 Communication par GSM................................................................................... 21 Chapitre 4: Système d exploitation Symbian ................................................................. 24 4.1 Sécurité au niveau de l OS ................................................................................. 24 4.1.1 Contrôle d'accès.......................................................................................... 24 4.1.2 Cryptographie et chiffrement...................................................................... 25 4.1.3 Système de fichiers ..................................................................................... 25 4.1.4 Gestion de mémoire.................................................................................... 25 4.2 Développement d applications ........................................................................... 26 4.2.1 Java............................................................................................................. 26 4.2.2 C++............................................................................................................. 27 4.3 Sécurité des applications du téléphone portable ................................................. 28 4.3.1 Développement........................................................................................... 29 4.3.2 Téléchargement .......................................................................................... 30 4.3.3 Installation .................................................................................................. 31 4.3.4 Exécution.................................................................................................... 33 4.3.5 Exploitation ................................................................................................ 35 Chapitre 5: Améliorer la sécurité des téléphones portables.......................................... 36 5.1 Renforcer la sécurité des téléphones portables ................................................... 38 5.1.1 Utilisation systématique des composants matériels dédiés sécurité ............ 39 5.1.2 Sécurité du code.......................................................................................... 41 5.1.3 Gestion du niveau de sécurité des applications et services ......................... 42 5.2 Analyse............................................................................................................... 43 Chapitre 6: Conclusion .................................................................................................... 44 Annexe : Vérification de code d octet Java .................................................................... 45 A.1 Introduction ........................................................................................................ 45 A.2 Vue d ensemble de la JVM et de vérification de code d octet............................ 46 A.3 Approches et algorithmes ................................................................................... 49 A.3.1 Analyse de flux de données ........................................................................ 49 A.3.2 Vérification de l initialisation d objet......................................................... 55 A.3.3 Problèmes des sous-programmes ................................................................ 58 A.3.4 Vérification de modèles .............................................................................. 62 A.4 Vérification pour les dispositifs mobiles ............................................................ 67 4 A.4.1 Vérification légère de code d octet ............................................................. 68 A.4.2 Vérification pour Java Smart Card.............................................................. 69 A.5 Conclusions ........................................................................................................ 71 Références ......................................................................................................................... 72 5 Résumé Le problème de la sécurité des téléphones portables connaît un regain d intérêt depuis l année 2003 avec la généralisation des plates-formes Symbian OS et Windows CE : leur richesse et leurs failles dans le modèle de sécurité permettent aux attaquants de réaliser des opérations malveillantes comme le ver Cabir et récemment Skulls pour Symbian OS et le ver Dust pour Windows CE. Ce travail a pour but de faire une étude profonde sur les problèmes de sécurité des téléphones portables, notamment ceux qui utilisent le système d'exploitation Symbian et le réseau GSM. Nous nous concentrons sur les raisons pour lesquelles la sécurité des téléphones portables n'est pas actuellement garantie à fin de proposer une approche globale de la sécurité. Cette approche passe par la définition d une architecture de sécurité intégrant le téléphone, la SIM, les services de sécurité, et s appuyant sur la certification du code des applications natives ou sensibles. Un autre intérêt du stage est la vérification de code d octet Java qui donne la possibilité de prévoir le comportement des programmes avant leur exécution réelle. Le stage est réalisé dans le cadre du grand projet européen SEINIT auquel l ENST participe. Mots clés : Sécurité des téléphones portables, Symbian, virus, attaques logicielles, informatique de confiance, vérification de code d octet. 6 Abstract The security problem of mobile phones has taken a lot of interests from 2003 with the generalization of platforms Symbian OS and Windows CE: their richness and weakness in the security model allow attackers realize many malicious operations like the worms Cabir and recently Skulls in Symbian OS or Dust in Windows CE. The purpose of this work is to make a survey on security problems of mobile phones, particularly which use the operating system Symbian and GSM network. We concentrate on reasons for which the security of mobile phones is not currently guaranteed to propose a global approach of security. This approach goes through the definition of a security architecture integrating the phone, the SIM, the services and base on code certification of native or sensible applications. Another interest of this internship is the problem of Java bytecode verification which gives the possibility to predict programs behavior before their real execution. This work is realized in the context of European project SEINIT, in which ENST is involved. Keywords: mobile phone security, Symbian, virus, software attack, Trusted Computing Base, bytecode verification. 7 Liste des figures Figure 1 Les exigences de sécurité pour un smartphone..................................................... 10 Figure 2 Les exigences communes de sécurité de systèmes embarqués. ............................ 11 Figure 3 Les attaques sur les systèmes embarqués. ............................................................ 15 Figure 4 L'architecture logicielle du téléphone portable..................................................... 18 Figure 5 La communication par GSM. ............................................................................... 22 Figure 6 L'architecture de l'OS Symbian v8.0. ................................................................... 24 Figure 7 Le constructeur de deux phases (droit) dans le Symbian et le constructeur standard (gauche)...................................................................................................................... 28 Figure 8 Exemple de code source Java et le code d octet correspondant............................ 47 Figure 9 Quelques expressions de types utilisées par le vérificateur et leur relation de soustype............................................................................................................................. 50 Figure 10 Exemple de sous-programme et le code d'octet correspondant. ......................... 58 Figure 11 Flux de contrôle dans l'approche de vérification de modèles. ............................ 67 8 Chapitre 1: Introduction Aujourd'hui, les dispositifs mobiles apparaissent sous différentes formes et caractéristiques. Un dispositif mobile typique utilise un système d'exploitation qui permet l'installation de logiciels de producteurs tiers. La plupart des produits ont aussi des logiciels préinstallés pour la gestion des informations personnelles telles que des applications de carnet d'adresses ou de calendrier. Quelques dispositifs incluent des applications préinstallées comme des browsers, des applications de traitement de textes, des programmes de courrier électronique, etc. Ces dispositifs sont connus généralement sous le nom d'assistant numérique personnel (PDA par la suite). Pour échanger des données avec les ordinateurs de bureau, les autres PDAs ou le réseau local, les PDA sont souvent incluent les mécanismes de communication (série, IrDA, BlueTooth, réseau local sans fil). En plus de la fonctionnalité standard de PDAs, le smartphone ajoute un téléphone portable intégré. Cette intégration permet aux utilisateurs de porter un seul dispositif au lieu d'un portable et d'un PDA. Le sujet de la sécurité des dispositifs mobiles, en général, ou des téléphones portables, spécifiquement, n'est pas nouveau, en 2000, le virus VBS/Timofonica [52] avait pour charge d'envoyer des SMS1 aux abonnés du service Movistar. Ce virus se propageait toutefois exclusivement sur les ordinateurs personnels (PC par la suite) et n'avait pas d'impact sur les téléphones visés. Depuis l'année 2003, le problème de la sécurité connaît un regain d'intérêt avec la généralisation des plates-formes Symbian OS [46] et Windows CE [35], qui sont beaucoup plus performantes en termes de programmation que les anciens téléphones propriétaires. La richesse de ces plates-formes et leurs failles dans le modèle de sécurité permettent aux attaquants de réaliser des attaques comme le ver Cabir [3] et récemment Skulls [42] pour Symbian OS et le ver Dust [13] pour Windows CE. Ce rapport a pour but de faire une enquête sur les problèmes de sécurité des téléphones portables, notamment les smartphones qui utilisent le système d'exploitation (OS par la suite) Symbian et le réseau GSM. Nous nous concentrons sur les raisons (les exigences de 1 SMS: Short Message Service. 9 sécurité et les solutions existantes) pour lesquelles la sécurité des téléphones portables n'est pas actuellement garantie à fin de proposer une approche globale de la sécurité. Ce stage avait été intitulé dans un premier temps « Analyse de code d octet Java » qui a pour but de faire des études sur la vérification de code d octet Java et d écrire ensuite un analyseur (vérificateur) Java pour Symbian. Au cours de réalisation du stage, nous avons constaté que le but initial n est pas assez pour garantir la sécurité des téléphones portables (comme montré dans ce rapport), le sujet est devenu ensuite « Sécurité pour les téléphones portables ». Ce travail est réalisé dans le cadre d un grand projet, SEINIT (Security Expert Initiative, http://www.seinit.org), entre les universités et les entreprises européens, auquel l ENST participe. L objectif de SEINIT est d assurer un cadre de sécurité et de confiance, fonctionnant sur de multiples dispositifs et sur des réseaux hétérogènes. Omniprésent, interopérable, ce cadre sera également centré autour de l utilisateur final. En particulier, SEINIT définira des modèles de confiance et de sécurité innovants afin de répondre aux nouveaux enjeux de l environnement numérique ambiant. Le reste de ce rapport se compose de cinq chapitres. Le chapitre 2 aborde les exigences de sécurité pour le système embarqué en général ainsi des défis de conception et les attaques sur le système embarqué. Une anatomie de l'architecture des téléphones portables est donnée dans le chapitre 3. Le chapitre 4 concentre sur l'OS Symbian et le développement des applications dans cet environnement. Le chapitre 5 décrit les aspects pour le renforcement de la sécurité des téléphones portables. Finalement, le rapport se termine par la dernière section avec de conclusion dans le chapitre 6. Ce rapport se compose également d une annexe abordant la vérification de code d octet Java. Nous décrivons des divers algorithmes et leurs problèmes principaux, par exemple dans l implémentation existante de Sun. 10 Chapitre 2: Systèmes embarqués sécurisés : un défi 2.1 Exigences de sécurité Les téléphones portables, spécifiquement ou les systèmes embarqués, en général, fournissent souvent les fonctions critiques qui pourraient être sabotées par des entités malveillantes. Avant de discuter les exigences communes de sécurité des systèmes embarqués, il est important de noter qu'il y a beaucoup d'entités impliquées dans la chaîne de conception, de fabrication, et d'utilisation d'un système embarqué. Les exigences de sécurité changent selon les perspectives que nous considérons. Par exemple, considérons un smartphone2 qui est capable de communications de données, de multimédia, et de voix sans fils. Les exigences de sécurité selon les points de vue des différents participants sont comme suit : Utilisateur final Intimité et l intégrité des données personnelles Appels et transactions frauduleux Perte/vol Exécution sécurisée des applications téléchargées Fournisseur de contenu Fournisseur de service des applications Fournisseur de service Fabricant de téléphone Fournisseur de logiciel et de matériel Sécurité de contenu, gestion des droits numériques Communications sécurisées Non-répudiation Accès sécurisé au réseau Utilisation frauduleuse de service Protection des propriétés intellectuelles Figure 1 Les exigences de sécurité pour un smartphone. Les soucis de l'utilisateur peuvent inclure la sécurité des données personnelles stockées et communiquées par le téléphone, pendant que l'inquiétude du fournisseur de contenu peut 2 Le mot smartphone est utilisé dans ce rapport comme une combinaison d un téléphone portable traditionnel et d un assistant numérique personnel (PDA). Les smartphones diffèrent des téléphones portables normaux sur le point qu ils ont un système d exploitation et le stockage local, pour que l utilisateur ou les encarteurs puissent ajouter l information et les applications comme les PDAs. 11 être la protection des contenus multimédias fournis au téléphone, et le fabricant de téléphone pourrait en plus s'intéresser au secret du logiciel de propriété industrielle qui réside dans le téléphone. Pour chacun de ces cas, l'ensemble des entités non sûres (potentiellement malveillantes) peut également changer. Par exemple, dans la perspective du fournisseur de contenu, l'utilisateur du téléphone peut être une entité non sûre. La Figure 2 cite les exigences de sécurité pour les systèmes embarqués, qui sont décrits comme suit : Figure 2 Les exigences communes de sécurité de systèmes embarqués. Identification de l'utilisateur indique le processus d'authentifier les utilisateurs avant de leur permettre d'utiliser le système. Accès sécurisé au réseau fournit une connexion de réseau ou un accès de service seulement si le dispositif est autorisé. Communications sécurisées se composent d'authentification de communication entres les égaux, d'assurance de confidentialité et d'intégrité des données communiquées, d'empêchement de répudiation d'une transaction, et de protection de l'identité des entités communiqués. Stockage sécurisé garantit la confidentialité et l'intégrité des informations sensibles stockées dans le système. Contenu sécurisé impose les restrictions d'utilisation du contenu numérique stocké ou consulté par le système. 12 Disponibilité s'assure que le système peut exécuter sa fonction attendue et servir les utilisateurs légitimes à tout moment, sans être interrompu par des attaques de déni de service. Plusieurs primitives fonctionnelles de sécurité ont été proposées dans le contexte de la sécurité du système informatique. Ceux-ci incluent divers algorithmes cryptographiques utilisés pour chiffrer et déchiffrer les données, et pour vérifier l'intégrité des données. En général, les algorithmes cryptographiques peuvent être classifiés en trois classes : chiffrements symétriques (l expéditeur et le récepteur emploie la même clé secrète pour chiffrer et déchiffrer des données), chiffrements asymétriques (également appelés algorithmes à clés publiques, emploient une clé secrète privée pour le déchiffrage, et une clé publique relative non-secrète pour le chiffrage ou la vérification), et les algorithmes de hachage (fournissent des « signatures » pour des messages et la vérification d intégrité). Les solutions de sécurité pour répondre aux exigences de sécurité se fondent typiquement sur les primitives cryptographiques mentionnés ci-dessus, ou sur les mécanismes de sécurité qui emploient une combinaison de ces primitives d'une façon spécifique (par exemple, des protocoles de sécurité). Des technologies et des mécanismes de sécurité ont été conçus autour de ces algorithmes cryptographiques afin de fournir des services de sécurité spécifiques. Par exemple : Les protocoles de sécurité fournissent des manières d'assurer les canaux de transmission pour le système embarqué. IPSec et SSL sont des exemples populaires des protocoles de sécurité, largement utilisés pour les réseaux privés virtuels (VPNs) et les transactions web. Les certificats numériques fournissent des manières d'associer l'identité à une entité, et les technologies biométriques comme la reconnaissance d'empreinte digitale et la reconnaissance de voix aident à l'authentification d'utilisateur final. Les signatures numériques, qui fonctionnent comme équivalents électroniques des signatures manuscrites, peuvent être utilisées pour authentifier la source de données et également pour vérifier son identité. Les protocoles de gestion des droits numériques, tels que OpenIPMP, MPEG, ISMA, et MOSES, fournissent les cadres sécurisés pour protéger le contenu d'application contre l'utilisation non autorisée. 13 Le stockage sécurisé et l'exécution sécurisée exigent que l'architecture du système soit adaptée pour des considérations de sécurité. Les exemples simples incluent l'usage du matériel pour surveiller des transactions de bus et de bloquer des accès illégaux aux secteurs protégés dans la mémoire [12], l'authentification du progiciel qui s'exécute sur le système, l'isolement d'application pour préserver l'intimité et l'intégrité du code et les données associées à une application ou à un processus donnée [32], les techniques matériels/logiciels pour préserver l'intimité et l'intégrité des données dans toute la hiérarchie de mémoire [43], l'exécution du code chiffré [30], etc. 2.2 Défis de conception d un système embarqué sécurisé Dans le monde des systèmes embarqués, les concepteurs d'un système embarqué doivent supporter de diverses solutions de sécurité afin de traiter un ou plusieurs des exigences de sécurité décrites ci-dessus. Ces exigences présentent des empêchements dans le processus de conception, qui sont brièvement décrits ci-dessous : Brèches de traitement : Dans les systèmes avec les ressources modestes de traitement et de mémoire, les demandes élevées de calculs pour les services de sécurité exigent normalement des changements dans la technologie (par exemple, couper le processus de code d octet Java en deux phases) ou l utilisation des composants dédiés. Flexibilité : Un système embarqué est souvent conçu pour répondre aux exigences d'exécution des multiples et diverses protocoles de sécurité et standards pour supporter : des objectifs de sécurité, l'interopérabilité dans différents environnements, le traitement de sécurité dans différentes couches de réseau (par exemple, un PDA permettant le réseau local sans fil pour la connexion au réseau privé virtuel et supportant la navigation sécurisé de web doit exécuter WEP, IPSec et SSL). En outre, avec des protocoles de sécurité constamment visée par des hackers, il n'est pas étonnant qu'ils continuent à se produire de nouvelles attaques. Il est souhaitable de permettre à l'architecture de sécurité d'être assez flexible (programmable) pour adapter facilement aux changements. Cependant, la flexibilité peut également causer des difficultés envers la sécurité. Tamper-résistance : Les attaques des logiciels malveillants tels que des virus et des chevaux de Troie sont les menaces les plus communes à n'importe quel système embarqué qui est capable d'exécuter des applications téléchargées. Puisque ces attaques peuvent compromettre la sécurité de tous points de vue (l intégrité, des données 14 personnelles, la disponibilité), il est nécessaire de développer et déployer de diverses contre-mesures matériels/logiciels contre ces attaques. Brèches d'assurance : Il est bien connu qu'il est beaucoup plus difficile de construire des systèmes véritablement plus fiables que ceux qui fonctionnent simplement dans la plupart du temps. Les systèmes sûrs doivent pouvoir manipuler des situations qui peuvent se produire par hasard et ils relèvent un plus grand défi : ils doivent continuer à fonctionner sûrement en dépit des attaques réalisées par des adversaires intelligents qui cherchent intentionnellement des modes de défaillances indésirables. Lors que les systèmes deviennent plus complexes, il y a inévitablement des modes de défaillance qui doivent être adressés. Notons que les deux autres facteurs, le coût et la batterie, influent également sur l efficacité des mesures de sécurité d un système embarqué. 2.3 Attaques sur le système embarqué Il est important de définir au début quelles capacités de sécurité sont exigées, comme par exemple les attaques qui doivent être empêchées. Une issue orthogonale est le niveau exigé de l'assurance, c'est-à-dire la probabilité que ces buts ont été atteints. La Figure 3 cite les diverses catégories des techniques utilisées pour attaquer un dispositif. Au niveau supérieur, les attaques peuvent être classifiées dans deux larges catégories : attaques physiques et side-channel, et attaques logiques. Les attaques physiques et side-channel [2][29][39][28] se réfèrent aux attaques qui exploitent l'implémentation du système et/ou identifient les propriétés de l'implémentation. Des attaques physiques et side-channel sont généralement classifiées en des attaques invasives et non-invasives. Les attaques invasives ont pour but d'obtenir l'accès au dispositif pour observer, manipuler, et interférer dans les systèmes internes. Puisque les attaques invasives exigent typiquement une infrastructure relativement chère, elles sont assez difficiles à déployer. Les exemples des attaques invasives incluent micro-probing et rétro-ingénierie (reverse engineering) de la conception. Il y a beaucoup de formes d'attaques non invasives telles que des attaques de synchronisation, des techniques d'induction de faute, des attaques basées sur l'analyse électromagnétique et l analyse de consommation. 15 Attaques physiques et side-channel Invasives Non-invasives Temps de calcul Attaques logiques Attaques logicielles Faiblesses Analyse de consommation SPA Micro-probing Rétro-ingénierie DPA Electromagnétique Virus Chevaux de Troie Faiblesses de crypto et de protocole Analyse SEMA DEMA Injection de faute Figure 3 Les attaques sur les systèmes embarqués. Les attaques logiques sont faciles à déployer contre les capacités d'exécuter les applications téléchargées, et exploitent des faiblesses ou des bogues dans l'architecture globale (matériel/logiciel) aussi bien que des problèmes dans la conception de l'algorithme cryptographique ou du protocole de sécurité. Dans la pratique, les attaquants utilisent souvent une combinaison des plusieurs techniques pour atteindre leurs objectifs. Par exemple, pour un smartphone, du point de vue de l'attaquant, les objectifs visés sont : de rendre inutilisable le téléphone portable; de modifier le comportement du téléphone; d'accéder aux données sensibles; d'émettre des appels voix ou données à l'insu de l'utilisateur; d'outrepasser les politiques de gestion des droits numérique. Dans le cadre de ce rapport, nous concentrons sur la menace la plus importante, les attaques logicielles. 2.3.1 Attaques logicielles Les attaques logicielles représentent une menace importante pour les systèmes embarqués qui sont capables de télécharger et d'exécuter des applications. Par rapport aux attaques physiques et side-channel, les attaques logicielles exigent typiquement une infrastructure qui est facilement disponible. Ces attaques sont implémentées par des agents malveillants 16 comme les virus, les vers, les chevaux de Troie, etc., et ils peuvent exploiter des failles dans l'OS ou le logiciel d'application, obtenir l'accès aux systèmes internes, et perturber ses fonctions normales à fin de manipuler des données ou des processus sensibles (attaques d'intégrité), relever des informations personnelles, et/ou dénier l'accès aux ressources de système (attaques de disponibilité), Des agents malveillants effectuent des attaques logiciels par l'exploitation des faiblesses dans l'architectures du système final sont étudiés dans [9][45][37][4][20]. Ils se présentent typiquement en raison des imperfections dans le logiciel, qui peuvent être nommés comme vulnérabilités [9]. Le problème de débordement de mémoire (buffer overflow) est un trou commun dans les systèmes d'exploitation et les logiciels d'application, qui peut être exploité dans les attaques logicielles [7]. Le problème peut avoir lieu à cause de la vérification réduite des limites des tampons. Les effets de débordement de mémoire peuvent réécrire (overwriting) la mémoire de la pile, les tas, et les pointeurs de fonction. L'attaquant peut utiliser le débordement de mémoire pour réécrire les adresses du programme stocké tout près, ceci lui permet de transférer le contrôle au code malveillant, qui, une fois exécuté, peut avoir des effets indésirables. 17 Chapitre 3: Téléphones portables 3.1 Architecture L'architecture matérielle du téléphone portable repose généralement sur un processeur de type RISC3 de la famille ARM4 dont la capacité de traitement varie de quelques dizaines de MHz à plus de 100 MHz, la puissance du système étant intimement liée aux services proposés: Vidéo, Audio, Jeux, animation 2D/3D. On trouvera différentes mémoires autour de l'unité centrale: mémoire vive (RAM5) et mémoire flash avec des volumes supérieurs au méga-octet, parfois un peu de mémoire morte (ROM6 et OTP7) pour des services dédiés à la sécurité. De plus, des processeurs dédiés multimédia peuvent également être présents afin de compenser le manque de puissance de certains processeurs. Finalement, les interfaces écran, clavier, connecteurs (série, IrDA, BlueTooth), haut-parleur, micro, etc., habillent le tout. Ainsi, l'architecture matérielle est comparable à ce que l'on connaît dans le monde PC, avec bien souvent les mêmes fabricants sauf une différence importante, la configuration matérielle d'un téléphone portable n'est pas modifiable par l'utilisateur. 3 RISC: Reduced Instruction Set Computer. ARM: Advanced RISC Machine. 5 RAM: Random Access Memory. 6 ROM: Read Only Memory. 7 OTP: One Time Programmable. 4 18 Figure 4 L'architecture logicielle du téléphone portable. Au niveau du logiciel, l'architecture est plus variable suivant les fabricants de téléphones portables. L'ensemble des applications du téléphone s'exécute au-dessus de l'OS, (Figure 4) à l'exception de l'application GSM8 et des protocoles associés qui reposent généralement sur une implémentation propriétaires pour des raisons de performance. L'OS fournit l'interface de programmation (API par la suite) qui tient compte de l'intégration de téléphone de n'importe quelle tiers application tiers et donc ces types d'OS sont dits « ouverts ». De plus, les applications peuvent profiter des capacités de communication de données de téléphone. Toutes les applications typiques d'Internet telles que des browseurs, des clients d'email, et des messagers instantanés fonctionnent au-dessus du service des données du téléphone. La tendance actuelle va vers des téléphones portables à base d'OS ouverts, tels que Embedded Linux [14], Symbian [46] et WindowsCE [35], la mise à jour de l'OS étant par ailleurs possible dans certains cas. 8 GSM: Global System for Mobile Communications. 19 Contrairement à la SIM9, notons que les informations/applications ne sont pas propriété exclusive de l'opérateur. Plus précisément, nous pouvons classifier les informations/applications liées aux services de l'opérateur (par exemple, l'application GSM), celles liées à des services additionnels relevant de différents fournisseurs de services (par exemple l'accès à un portail, e-commerce), ou celles de l'utilisateur (par exemple, un agenda électronique). Les mécanismes de sécurité doivent prendre en compte les exigences de chaque acteur (voir section 4.3). Actuellement l'opérateur se positionne en tant que fournisseur de service vis-à-vis de l'utilisateur. A ce titre, il est à même d'assurer que le niveau de sécurité est conforme à l'attente de l'utilisateur. En particulier, dans le contexte d'un commerce électronique, l'opérateur garantit à l'utilisateur la confidentialité, l'authenticité et la validité de la transaction. 3.2 SIM La SIM est venu un long chemin depuis sa conception dans 1988 comme un module d'authentification avec une quantité limitée de mémoire. Dans chaque téléphone portable, cette petite carte à puce est la seule partie de l'opérateur qui reste avec le client. Dans ses premiers jours, la SIM a contenu l'identité d'abonné individuelle, l'algorithme d'authentification et les clés, ainsi que d'autres fonctions de sécurité telles que la PIN10, qui a été présentée la première fois en 1991. La SIM a permis ensuite aux opérateurs de gérer la facturation et d'autres informations sur leurs clients, et d'authentifier l'utilisateur pour l'accès au réseau. L'architecture SIM se compose généralement d'un microprocesseur CISC11 8 bits cadencé à 4,77 MHz, mais de plus en plus, d'un processeur RISC 32 bits à 30 ou voire 100 MHz. La SIM intègre également de la mémoire sous forme de ROM qui accueille essentiellement l'OS, d'EEPROM12 qui sert à stocker les applications et les données persistantes, et de RAM. Il est important de noter que la SIM intègre aussi un cryptoprocesseur dédié aux fonctions cryptographiques. 9 SIM: Subscriber Identity Module. PIN: Personal Identification Number. 11 CISC: Complex Instruction Set Computer. 12 EEPROM: Electrically Erasable Programmable Read-Only Memory. 10 20 La SIM peut être considérée comme un ordinateur à seul processeur qui se compose de l'OS, de système de fichiers et des applications. Les OS propriétaires sont généralement développés pour un unique service (au minimum, les fonctionnalités décrites dans la spécification GSM 11.11 [15]). Avec les OS « ouvert » tels que JavaCard, le téléchargement de nouvelles applications, et dans certains cas, la mise à jour de l'OS est possible. Aujourd'hui, la tendance est à l'uniformisation des OS autour de JavaCard. Une caractéristique fondamentale de la SIM est que son ensemble d'informations et d'applications contenues appartient à l'opérateur, non à l'utilisateur. Nous pouvons donc s'appuyer sur des mécanismes de sécurité fortement centralisés pour garantir un haut niveau de sécurité. 3.3 Interfaces entre le téléphone portable et la SIM L'Institut européen des normes de télécommunication (ETSI) définit dans la spécification GSM 11.11 [15] les caractéristiques physiques (connecteurs), électroniques (protocole de transmission) et logique (commandes) de l'interconnexion du téléphone portable et de la SIM. Au niveau applicatif, la spécification GSM 11.14 ou SIMToolKit de l'ETSI [16] définit une plate-forme pour le développement de fonctionnalités implémentées dans la majorité des téléphones portables, une partie du code s'exécutant dans le téléphone, l'autre au sein de la SIM. Parmi ces fonctionnalités, nous pouvons signaler la possibilité donnée à la SIM d'être « proactif », c'est-à-dire d'initialiser des actions sur le téléphone sous forme de scripts ou d'appliquettes (applets) JavaCard (affichage de texte, appel d'un numéro, établir une connexion GPRS13 data, etc.). Ces applications SIM réalisent au besoin des services de sécurité en s'appuyant sur des clés et algorithmes de la SIM. La spécification WIM14 [53] du OMA forum [38], anciennement WAP forum, définit, pour sa part, une application au sein de la SIM avec une API dédiée à la sécurité dans les protocoles WAP. La SIM peut ainsi être utilisée pour stocker des certificats et des clés de type RSA, réaliser les opérations de signature et d'authentification pour les services de sécurité de la couche transport (protocoles WTLS et TLS). Elle fournit également des 13 14 GPRS: General Packet Radio Service. WIM: WAP Identity Module. 21 services de sécurité applicative type WMLscript et ECMAscript permettant entre autres d'utiliser une signature dans la carte depuis une page WAP. Comme nous l'avons signalé précédemment, les SIM sont de plus en plus souvent de type JavaCard. Dans le monde Java, une appliquette fait appel à une méthode d'une autre Appliquette par RMI15. Au niveau des JavaCard, nous retrouvons un mécanisme équivalent (JCRMI16) pour accéder à une Appliquette de la carte par simple appel de méthode, simplifiant encore l'interconnexion des applications du téléphone avec celles de la SIM. La spécification JSR17 177 [27] étend cette approche aux fonctions de sécurité de la SIM. Plus précisément, la spécification JSR 177 définit un ensemble d'APIs pour accéder par RMI aux fonctions de sécurité implémentées dans la SIM qui est alors vue comme un outil de sécurité J2ME. En résumé, l'ensemble de ces spécifications concourt au développement d'application dont bien souvent la partie principale s'exécute dans le téléphone portable, les services de sécurité étant exécutés dans la SIM. Nous pensons que cette fonctionnalité n'est, à l'heure actuelle, pas assez exploitée, alors qu'elle devrait être le principe de base de tout mécanisme de sécurité présent dans le téléphone portable. 3.4 Communication par GSM La fonction initiale des téléphones portables est évidement l'émission et la réception de voix et de données. En termes de sécurité, l'émission et la réception d'appels posent les problèmes d'accès au réseau de téléphone portable, de sécurité des communications, et pour l'opérateur, d'imputation des communications (lorsqu'il ne s'agit pas de communications prépayées). Une session de communication par GSM passe par trois phases : l'authentification, la génération de clés de session et le chiffrement de données échangés. Au niveau de l'accès au réseau, l'authentification est basée sur la SIM et est réalisée de la manière suivante. 15 RMI: Remote Method Invocation. JCRMI: JavaCard Remote Method Invocation. 17 JSR: Java Specification Requests. 16 22 Chaque SIM contient une clé secrète Kf, le « réseau » retrouvant Kf à l'aide d'une clé mère Km et des données d'identification de la SIM (ex.: numéro de série, etc.) Le « réseau » envoie un challenge RAND (un nombre aléatoire de 128 bits). A la réception de RAND, la SIM calcule la réponse SRES sur 32 bits obtenue en appliquant un algorithme cryptographique appelé A3 (implémenté dans la SIM) au challenge RAND, avec la clé secrète Kf. Le réseau vérifie que la réponse SRES envoyée par la SIM correspond bien au challenge précédemment envoyé; l'authentification de la SIM est valide si tel est le cas, sinon l'authentification a échoué. Figure 5 La communication par GSM. La sécurité des communications est obtenue en chiffrant les échanges entre le téléphone portable et le réseau. Ce chiffrement nécessite la génération d'une clé de session Ks de 64 bits. Cette clé obtenue par la deuxième phase (génération de clés) en appliquant un algorithme appelé A8 (implémenté dans la SIM) au challenge précédant RAND, avec une clé Kf. Une fois la clé de session générée par la SIM, elle est fournie au téléphone portable qui va chiffrer les communications (troisième phase) à l'aide de l'algorithme cryptographique A5. 23 Le schéma global est résumé sur la Figure 5. Nous voyons que la sécurité de l'authentification et de la génération de la clé de session repose exclusivement sur le secret de la clé Kf. Notons que la clé Kf est stockée dans la SIM et n'est jamais communiquée au téléphone (en particulier, les algorithmes A3 et A8 sont exécutés dans la SIM). Ceci est rendu possible dans la mesure où la SIM est propriété exclusive de l opérateur : l'opérateur peut s'assurer que toute application contenue dans la SIM préserve la sécurité de Kf. En reposant sur la clé de session Ks, le chiffrement des communications est à la charge du téléphone pour des raisons de performance. La sécurité des communications revient à protéger le secret de la clé Ks. Si le téléphone est compromis, un attaquant peut avoir accès à cette clé et déchiffrer l'ensemble des communications. Au niveau des contraintes de performance, ce risque est inévitable. Les développeurs doivent prendre en compte ce risque lors du développement des applications, et, en particulier, contrôler l'utilisation des fonctionnalités sensibles afin d'empêcher un attaquant d'y accéder. En pratique, les algorithmes A3 et A8 sont généralement combinés. En particulier, l'ETSI a proposé les algorithmes (secrets) COMP 128 (cassé en avril 1998), COMP 128-2, et plus récemment, COMP 128-3 pour réaliser l'authentification et la génération de la clé de session [10]. Notons que les opérateurs n'utilisent pas l'un de ces algorithmes, mais une version propriétaire. Concernant l'algorithme de chiffrement A5, il est public. Les version A5/2 (chiffrement faible) et A5/1 ont fait l'objet d'attaques respectivement en août et en décembre 1999. En revanche, il n'existe pas, à notre connaissance, d'attaque sur la version A5/3 de l'algorithme. Intéressons-nous maintenant brièvement à l'imputation des communications. Le schéma adopté par les opérateurs repose sur le fait que la SIM est attribuée à un utilisateur donné. Toute communication est alors imputée à l'utilisateur porteur de la SIM authentifiée, ce dernier étant supposé s'être identifié via le code PIN. Notons cependant qu'il est donné à l'utilisateur le moyen de désactiver la vérification du PIN. Si l'utilisateur choisit cette option, en cas de vol, toute communication sera imputée sur son abonnement, et ce malgré l'absence d'authentification du porteur de la SIM. 24 Chapitre 4: Système d exploitation Symbian L'OS Symbian a été précédemment connu comme EPOC. Le système d'exploitation EPOC a été au commencement développé par Psion pour leurs propres dispositifs de PDA. Cependant, Symbian a été fondé pour prendre soin du développement ultérieur d'EPOC. Symbian est possédé par Ericsson, Motorola, Nokia, Panasonic et Psion. Récemment le nom du système d'exploitation a été changé en OS Symbian. Figure 6 L'architecture de l'OS Symbian v8.0. L'OS Symbian est basé sur une architecture de micro noyau multitâche. Les services de système tels que la téléphonie, le middleware de réseau et les moteurs d'application fonctionnent dans leurs propres processus. L'OS a été conçu dans l'esprit des dispositifs mobiles, en utilisant des techniques avancées d'orientée objet, menant à une architecture flexible basée sur les composants. 4.1 Sécurité au niveau de l OS 4.1.1 Contrôle d'accès Le contrôle d'accès est implémenté par la demande d'un mot de passe avant d'accorder l'accès au dispositif. Ceci assure la confidentialité et l'intégrité à un certain niveau, parce qu'aucune donnée ne peut être lues (confidentialité) ou être écrites (intégrité), sans identifier l'utilisateur et autoriser l'accès au dispositif. Cependant, c'est seulement une approche limitée parce qu'après l'identification, l'utilisateur peut accéder à toutes les 25 données sur le dispositif. Et plus important, toutes les applications lancées par l'utilisateur peuvent accéder à toutes les données. 4.1.2 Cryptographie et chiffrement Dans l'OS Symbian, le chiffrement et le déchiffrement des fichiers ne sont pas une partie du système d'exploitation par défaut. Cependant, il y a un module de cryptographie disponible pour des développeurs d'application pour permettre l'implémentation facile des capacités cryptographiques dans leur logiciel. Le module de cryptographie de l'OS Symbian se compose des chiffrements symétriques et asymétriques aussi bien que les fonctions de hachage et le générateur de nombre aléatoire [40]. Néanmoins, tous les produits ne sont pas délivrés avec le support cryptographique à l'exception de ceux pour les partenaires du programme « Symbian Platinum Partner ». 4.1.3 Système de fichiers Il y a différents systèmes de fichiers utilisés dans des dispositifs Symbian. Par exemple, les fichiers stockés dans la mémoire flash interne et ceux stockés sur la carte de mémoire démontable. Une entité appelée File Server est construite sur eux. Elle fournit une interface uniforme pour accéder à tous les fichiers, et aussi le contrôle de tout l'accès au fichier. Néanmoins, il n'y a aucune gestion de permission basée sur l'application initialisant la demande d'accès de fichier dans le serveur de fichiers. Normalement, sur un dispositif cible, le Z: représente la ROM et le C: est une partie de l'espace mémoire disponible de RAM. L'OS Symbian est installé dans la ROM, des programmes et les fichiers de tiers sont typiquement installés sur le C:. 4.1.4 Gestion de mémoire L'OS Symbian souligne l'importance de la gestion sophistiquée de mémoire sur des dispositifs avec des ressources restreintes. Le noyau du système d'exploitation fonctionne en mode privilégié - l'espace d'adressage est protégé et donc un bogue d'une application ne peut pas réécrire la pile ou le tas du noyau - et il est responsable d'attribuer la mémoire aux applications fonctionnant dans le mode d'utilisateur non privilégié [17]. Chaque programme peut accéder seulement à la mémoire assignée pour lui par le noyau. En d'autres termes, des programmes sont protégés contre l'un l'autre en termes d'accès mémoire. En outre, l'OS Symbian a les directives de programmation strictes au sujet de 26 l'attribution de mémoire. Toutes les applications intégrées sont conçues pour ne pas échouer dans toutes les circonstances en raison de bas niveau de mémoire. 4.2 Développement d applications 4.2.1 Java Notons que la quasi-totalité des téléphones portables, mêmes les modèles les plus anciens (sans système d'exploitation ouvert), embarquent une machine virtuelle Java : au minimum la KVM18. Il s'agit d'une machine virtuelle qui est capable de fonctionner avec uniquement 128 Ko de RAM [50]. Les smartphones peuvent embarquer des machines plus « complètes », c'est-à-dire mettant à disposition du développeur une API bien plus riche. A ce sujet, il faut préciser que si l'API de base a été standardisée, de nombreuses extensions ont été proposées par les constructeurs pour supporter des fonctions exotiques telles que clapets, vibreurs, molettes, et autres. Ceci explique pourquoi les applications (et en particulier les jeux) Java les plus élaborées sont spécifiques à une famille de téléphones donnée, malgré l'apparente universalité de ce langage. Les quatre niveaux d'API Java potentiellement disponibles sur les téléphones sont à l'heure actuelle : CLDC (Connection Limited Devices) v1.0 et v1.1 : la base minimale des API disponibles. MIDP (Mobile Information Device Profile) v1.0 et v2.0 : une nouvelle base minimale d'API, sur-ensemble du précédent. Ce standard est aujourd'hui largement implémenté dans les téléphones du commerce et on trouve peu d'équipements compatibles CLDC uniquement. Les applications compatibles MIDP sont appelées « MIDlets ». JSR (Java Specification Requests) : des extensions d'API normalisées ou en cours de normalisation. Par exemple : JSR 82 (Java API for BlueTooth), JSR 135 (Mobile Media API - MMAPI), etc. Des API spécifiques : dans ce domaine tout est permis. En général, la programmation en Java dans Symbian est comme dans les autres OSs, sauf les cas particuliers suivants [50]: 18 KVM: Kilobyte Virtual Machine. 27 sans support pour les types de données en point flottant (float et double). sans support pour le Java Native Interface (JNI). sans support pour le chargeur de classes (class loader) défini par l'utilisateur. sans réflexion. sans support pour les groupes de processus léger (thread groups) et les processus légers démons (daemon threads). sans support pour le mécanisme de finalization des instances de classe. sans référence fiable (weak reference). limitations de traitement des exceptions. 4.2.2 C++ L'OS Symbian est écrit en C++, il est donc justement normal de développer des applications également en C++. Ceci fournit au développeurs la flexibilité. Cependant, cette flexibilité amène avec lui la complexité et dans certain cas, il peut être plus approprié de développer une application en Java, qui est aussi bien supportée sur d'autres dispositifs utilisant l'OS Symbian. Le développement des applications écrites en C++ demande un environnement de développement standard (de type gcc, Visual Studio, MetroWerks Code Warrior, Borland C++ Builder X ou autre) et le SDK19 Symbian qui est disponible gratuitement sur son site [48]. Les deux principaux constructeurs utilisant l'OS Symbian fournissent également des versions « adaptées » du SDK, qui gèrent les spécificités de leurs téléphones. En effet, le système Symbian est fortement « modulable », chaque sous-système pouvant être réécrit [47]. Parmi les SDK « adaptés » disponible actuellement, on trouve UIQ pour Sony Ericsson P800/900, Motorola A920 et les Nokia 60, 80 et 90. A cause des ressources limitées des dispositifs mobiles, les développeurs doivent utiliser quelques particularités qui ne sont pas communes avec les autres systèmes : 19 SDK: Software Development Kit. 28 la récupération de la pile : une méthode pour récupérer la mémoire si l'allocation de mémoire pour une fonction échoue. la règle qu'un constructeur C++ ne peut pas causer une exception (Figure 7). Elle en résulte un système de construction de deux phases : créer un objet vide au début (aucune allocation de mémoire), puis allouer la mémoire dans deuxième phase (par une méthode nommée ConstructL). Figure 7 Le constructeur de deux phases (droit) dans le Symbian et le constructeur standard (gauche). 4.3 Sécurité des applications du téléphone portable Comme mentionné dans la section 2.1, les exigences de sécurité changent selon les perspectives que nous considérons. Intéressons-nous dans un premier temps aux OS propriétaires fermés, c'est-à-dire ne supportant pas le téléchargement d'applications. Du point de vue d'un fournisseur de services, cela signifie soit que son service s'appuie exclusivement sur des services présents par défaut dans les téléphones portables, soit qu'il négocie un accord pour inclure le code nécessaire dans les téléphones portables. Dans le premier cas, le constructeur du téléphone portable pourrait (à la demande de l'opérateur) s'assurer, éventuellement via une évaluation au sens Critères Communs (Common Criteria20), que les services présents dans le téléphone portable ne puissent être détournés à des fins malveillantes. Dans le second cas, l'accord devrait exiger l'évaluation 20 Les Critères Communs (Common Criteria) est une norme internationale qui définit des concepts et des principes généraux pour l évaluation de sécurité et représente le modèle général d évaluation. Il représente des bases pour exprimer des objectifs de sécurité, pour choisir et définir des exigences de sécurité, et pour écrire des spécifications de haut niveau pour les produits et les systèmes. Ce standard se trouve sur le site http://www.commoncriteria.org 29 du code avant installation, afin de vérifier qu'il ne contient ni porte dérobée (backdoor), ni bogue résiduel. Dans les deux cas, le fournisseur de services ne serait pas en mesure d'introduire un programme malveillant dans le téléphone portable. Du point de vue de l'utilisateur, les OS propriétaires fermés rendent impossible le téléchargement de nouvelles applications. En d'autres termes, il n'est pas possible, pour l'utilisateur, de télécharger un programme malveillant en vue d'attaque le téléphone portable et/ou les services. Au contraire des OS fermés, les OS (propriétaires) ouverts supportent au minimum le téléchargement de services ou de contenu via des applications écrites en Java, mais aussi le téléchargement d'application (généralement, en C++) exécutées par l'OS, et dans certains cas, la mise à jour de l'OS lui-même. Dans ce contexte, le risque est grand de voir l'utilisateur installer, volontairement ou non, une application malveillante. Pour faire face à ce problème, les constructeurs de téléphones portables proposent des solutions complémentaires dont les objectifs sont de garantir la sécurité tout au long du cycle de vie des applications. Nous présentons les solutions actuellement déployées dans les différentes phases d'une application (le développement, le téléchargement, l'installation, l'exécution et l'exploitation), ainsi que leur limites. 4.3.1 Développement Les problèmes de sécurité rencontrés dans la phase de développement sont les mêmes que ceux rencontrés dans le monde PC. Les applications sont généralement écrites en Java ou C++ (voir section 4.2) en s'appuyant en grande partie sur les APIs standards, mais les développeurs utilisent aussi des APIs propriétaires afin d'optimiser le code, ou d'accéder à des fonctions spécifiques. Des telles pratiques augmentent les risques liés à la présence de failles de sécurité, ces APIs « contournant » les mécanismes de sécurité de l'OS. De plus, l'existence de bogues résiduels ou de portes dérobées peut être exploités pour attaquer le téléphone portable. Cependant, il existe une différence fondamentale dans le cadre des téléphones portables, à savoir que le code des applications est globalement moins important, rendant ainsi plus efficaces les techniques d'audit de code, ou encore possible l'évaluation, au sens Critères Communs. 30 Nous ne nous étendrons pas davantage sur cet aspect, considérant que les mécanismes de sécurité mis en place dans les phases suivantes ont pour objectifs de précisément de se protéger contre cela et au point de vue des attaquants, ils utilisent toutes les failles de sécurité pour atteindre leurs objectifs. 4.3.2 Téléchargement La phase de téléchargement correspond au chargement d'une application dans le téléphone portable à partir d'un serveur. Notons que le téléchargement peut être à l'initiative de l'opérateur (par exemple, pour une mise à jour logicielle), du fournisseur de services (par exemple, lors de l'accès à un portail), ou bien de l'utilisateur (par exemple, le chargement de nouveaux jeux payants pour la N-Gage). Les applications peuvent également être chargées à partir d'un poste de travail ou d'un autre téléphone portable et dans ce cas, le problème revient à assurer la sécurité dans la phase de l'installation (voir section suivante). Dans cette phase, les problèmes de sécurité se posent au niveau de l'accès au service et du transfert de l'application. Concernant l'accès au service, les mécanismes à mettre en place sont côté serveur (contrôle d'accès par mot de passe, gestion des abonnements, etc.) et ne rentrent pas dans le carde de ce rapport. Au niveau transfert, il s'agit de garantir la confidentialité et/ou l'intégrité des échanges, afin d'empêcher un attaquant d'accéder au code et aux données de l'application, ou de modifier ce code et d'y introduire une faille. A l'image de ce qui fait sur Internet, l'utilisation de TLS/SSL pour sécuriser le niveau transfert nous semble une bonne solution. Ainsi, le WAP Forum a spécifié WTLS [54] qui n'est autre que l'implémentation de TLS dans WAP. Bien que généralement implémentée dans les téléphones portables, signalons cependant qu'une telle solution n'est pas souvent utilisée. Et lorsque c'est le cas, il est rare que l'ensemble des problèmes y afférant soit pris en compte. En particulier, la certification des clés publiques (pour empêcher une attaque de type man in the middle) n'est généralement pas mise en oeuvre, ou mal, ne tenant pas compte du fait que la notion de certification est encore moins compréhensible/intuitive pour un utilisateur mobile que pour un utilisateur PC. Dans la mesure où la possibilité de désactiver la sécurité au niveau transport est souvent donnée à l'utilisateur, l'absence de compréhension risque fort de se traduire par le choix de cette option. 31 En résumé, les solutions pour sécuriser le niveau transport, bien qu'existantes, ne sont généralement pas ou mal mises en oeuvre. Les opérateurs préfèrent considérer que la sécurité au niveau transport est assurée par la sécurité « physique » du coeur de réseau, oubliant que ce dernier est de plus en plus souvent connecté à Internet. 4.3.3 Installation Une fois l'application téléchargée, et avant son installation, il s'agit de vérifier qu'elle est saine, en particulier, qu'elle ne contient pas de virus et autres programmes malveillants. Ceci peut être réalisé soit par des antivirus, soit par des techniques de certification de code. La première n'est, à l'heure actuelle, que rarement utilisée, mais le tapage qu'a alimenté le ver Cabir [3] nous laisse à penser que les distributeurs d'antivirus vont rapidement se lancer dans la course. F-Secure a récemment proposé Mobile Anti-virus pour les séries 60 de Nokia [18] qui recherche automatiquement tous les fichiers lorsqu'ils sont enregistrés, copiés, téléchargées, synchronisés ou modifiés. Le Symantec Client Security [44] pour le Nokia 9500 Communicator et Nokia 9300 est une protection par des technologies d'antivirus et de pare-feu intégrés. Notons que les antivirus s'appuient sur la « signature virale » propre à chaque virus pour les détecter. Il s'agit de la méthode de « recherche de signature », la plus ancienne méthode utilisée par les antivirus. Cette méthode n'est fiable que si l'antivirus possède une base virale à jour, c'est-à-dire comportant les signatures de tous les virus connus. Toutefois cette méthode ne permet pas la détection des virus n'ayant pas encore été répertoriés par les éditeurs d'antivirus. De plus, les programmeurs de virus les ont désormais dotés de capacités de camouflage, de manière à rendre leur signature difficile à détecter, voire indétectable; il s'agit de "virus polymorphes". Certains antivirus utilisent un contrôleur d'intégrité pour vérifier si les fichiers ont été modifiés. Ainsi le contrôleur d'intégrité construit une base de données contenant des informations sur les fichiers exécutables du système (date de modification, taille, et éventuellement une somme de contrôle). Ainsi, lorsqu'un fichier exécutable change de caractéristiques, l'antivirus prévient l'utilisateur. 32 Le problème revient à s'assurer la sécurité des fichiers de définition de virus (signature virale) dans le premier cas et de base de données dans le deuxième cas. D'autre part, il faut s'assurer également que l'antivirus n'est pas infecté par un virus dans tous deux cas et la mise à jour automatique, sécurisée des signatures virales dans la méthode de recherche de signature. L'utilisation d'antivirus est une protection passive, nous pouvons détecter les virus seulement lorsque nous avons le fichier de définition de virus. D'autre part, cette approche ne fonctionne correctement que si nous avons déjà une séparation forte entre un domaine de confiance où faire tourner l'antivirus et un domaine « non sécurisé » où exécuter les applications à surveiller. Une limite importante, que personne n'a été en mesure de contourner jusqu'à présent, est la phase d'installation de l'application « hostile ». Sur le Nokia N-Gage par exemple, l'exécution directe d'un « .APP » est interdite « pour raison de sécurité ». On peut légitimement se demander si le filtrage est effectué sur l'extension ou sur le contenu. Intéressons-nous plus particulièrement à la certification de code. Le principe fondamental est de signer numériquement le code afin de s'assurer de son authenticité. Actuellement, deux approches se distinguent : Symbian Signed [49] et MIDP [36]. Suivant l'approche Symbian Signed, toute application téléchargée doit être signée. De manière générale, l'installation d'un « .SIS » (package d'installation) nécessite au moins deux confirmations de l'utilisateur, sauf si celle-ci a été signé par une autorité reconnue par le téléphone (par exemple, sur le Sony Ericsson P900, les autorités pré-installées sont : Baltimore, Entrust, GTE CyberTrust, GlobalSign, RSA Data, Thawte, Verisign et évidement Sony Ericsson et Symbian). Dans le cas de l'absence de signature valide, l'utilisateur est prévenu, et la possibilité de continuer l'installation de l'application lui est donnée (message « Unable to verify. Continue anyway? » pour Cabir), et ainsi, d'outrepasser le mécanisme de sécurité. En faisant précéder le téléchargement par un « faux » SMS prévenant de la mise à jour nécessaire du système, il est fort à parier que bon nombre d'utilisateurs continueront l'installation malgré le message de mise en garde. 33 Une fois installées, aucune distinction n'est fait entre les applications signées et les autres : il en résulte que tout programme malveillant ainsi installé a accès exactement aux mêmes fonctionnalités qu'une application légitime. Nous pensons donc que la solution retenue par Symbian Signed n'est pas satisfaisante. Intéressons-nous maintenant à la solution MIDP pour environnement Java J2ME. Le principe de base est le même que précédemment, à savoir la signature numérique du code (c'est-à-dire des fichiers .jar) à base de l'infrastructure à clé publique. Pourtant, la signature associe à ce principe la notion de domaine de sécurité : un domaine de sécurité définit un niveau de certification qui est identifié par une ou plusieurs clés de certification. Tout code signé avec une clé de certification Kc est associé au domaine de sécurité identifié par Kc. Tout code non signé (ou avec une signature invalide) est associé à un domaine spécifique (Untrusted Domain). Il est ainsi possible de distinguer les applications présentes dans le téléphone portable: celles signées par l'opérateur, celles signées par un fournisseur de service connu, ou encore celles non signées ou avec une signature invalide. Signalons enfin, mais nous n'étudierons pas ce problème dans ce rapport, qu'une attention particulière doit être apportée aux applications soumises à droits pour lesquelles il ne suffit pas de vérifier que l'application est saine, mais il faut également vérifier que l'utilisateur a acquis les droits l'autorisant à y accéder et sécuriser son installation. 4.3.4 Exécution Lorsqu'une application est installée, elle est « exécutable ». A ce niveau, il peut sembler inutile de mettre en place des mécanismes de sécurité, dans la mesure où, pour être installée, l'application a dû être jugée saine. Néanmoins, une application saine ne veut pas dire qu'elle ne contient pas de portes dérobées ou de bogues résiduels. En outre, le pire ennemi de la sécurité est l'utilisateur à qui on donne encore une fois la possibilité d'outrepasser les mécanismes de sécurité, par exemple en lui demandant de confirmer une action jugée non sûre (message « Unable to verify. Continue anyway? » pour Cabir). L'API Symbian est extrêmement riche. D'autre part, les applications Symbian sont compilées directement en code assembleur ARM, et ont des accès illimités à la plate-forme. Une application native Symbian peut virtuellement effectuer n'importe quelle opération sur 34 le téléphone, à cause de l'absence de modèle de sécurité (pas de notion d'utilisateur, de liste de contrôle d'accès aux fichiers, etc.). De plus, une grande partie de l'API se trouve dans des librairies et non dans le noyau, ce qui permet de contourner plus facilement les éventuels mécanismes de sécurité. Par exemple, pour envoyer un SMS, et en supposant qu'une confirmation utilisateur soit imposée par l'API, il suffirait d'appeler directement la fonction interne d'envoi de SMS plutôt que de passer par l'API standard pour contourner cette sécurité. Il est donc préférable de mettre en place des mécanismes de sécurité afin de contrôler les actions des applications lors de l'exécution. De manière générale, les mécanismes de sécurité à l'exécution dépendent fortement des mécanismes utilisés lors de l'installation. Ainsi, comme nous avons signalé précédemment, la solution Symbian Signed ne permet pas de distinguer les applications signées de celles non signées. Il n'est donc pas possible de contrôler à l'exécution les actions des applications non signées, qui peuvent ainsi accéder aux mêmes fonctionnalités que les applications non signées. Par contre, la notion de domaine de sécurité proposée dans MIDP est utile pour mettre en oeuvre des solutions de contrôle à l'exécution. Dans la machine virtuelle Java, les principes de base consistent d'une part à vérifier que l'exécution est conforme au langage utilisé, et d'autre part, à contrôler l'accès aux ressources et aux données. Le premier point est couvert, dans le cas de MIDP, par l'utilisation obligatoire d'un vérificateur de code d octet21 (bytecode). Pour le second point, à chaque domaine de sécurité est associé un ensemble de droits d'accès, ou privilèges, portant sur les données et les ressources du téléphone portable. Pour une application donnée, son domaine de sécurité définit donc l'ensemble des ressources et des données auxquelles elle est autorisée à accéder. En particulier, en définissant des droits très restreints pour les applications non signées ou à signature invalide, il est possible de les confiner dans un espace ne leur permettant pas d'accéder aux informations et services sensibles. Bien entendu, lorsqu'ils sont mise en oeuvre, ces mécanismes de sécurité doivent être exempts de failles de sécurité. Cela signifie qu'ils doivent faire l'objet d'un audit poussé, ou 21 Voir l'annexe pour une introduction détaillée sur le problème de vérification de code d octet Java. 35 d'une évaluation au sens Critères Communs. En particulier, la machine virtuelle Java doit être vérifié en vue d'éliminer les éventuels bogues de programmation. A ce niveau, il faut signaler que les risques liés à l'exécution d'un MIDlet « hostile » sont limités par la pauvreté de l'API CLDC/MIDP. Il existe des erreurs d'implémentation dans le modèle de sécurité de Java (risque accru compte tenu des compromis imposés par la faible mémoire et la faible puissance de calcul des téléphones portables), par exemple, l'attaque sur le Siemens S55 [41] permettant d'envoyer un SMS avant l'affichage de la fenêtre de confirmation. De plus, lors de la récente conférence Hack in the Box en Malaisie [23], des failles de sécurité dans J2ME ont été mises à jour, failles qu'un attaquant peut exploiter pour installer un programme malveillant. A l'heure actuelle, il existe peu d'attaques publiées démontrant des failles dans les MIDlet Java, mais il est probable que de nombreux chercheurs travaillent sur le sujet. 4.3.5 Exploitation Certaines applications fournissent des services de sécurité que d'autres applications utilisent, à l'image d'applications fournissant l'API JSR 177, ou bien WMLScript et ECMAScript pour WAP [53]. Ces applications deviennent donc aussi sensibles que le code natif, et requièrent donc le même niveau d'exigence en termes de sécurité. Concrètement, dans le cadre de MIDP, cela signifie que ces applications seront associées au même domaine de sécurité que le domaine du code natif. 36 Chapitre 5: Améliorer portables la sécurité des téléphones Comme nous l'avons vu dans la section Chapitre 3:, la sécurité des applications de la SIM est depuis longtemps prise en compte. Suivant le modèle économique du téléphone portable, la SIM est propriété exclusive de l'opérateur, ceci se traduisant par un contrôle absolu sur les applications et informations contenues dans la SIM. En s'appuyant sur les schémas d'évaluation et de certification, les encarteurs sont en mesure de garantir aux opérateurs le niveau de sécurité attendu. Il en résulte que la SIM peut être considérée comme un outil implémentant des services de sécurité de confiance. La sécurité des applications du téléphone portable est moins bien gérée. Ceci est expliqué par plusieurs raisons : la disparité dans les architectures matérielles, la disparité des architectures d'OS et particulièrement des services de sécurité disponible dans chaque OS, la diversité des standards. L'absence d'attaques à grande échelle n'incite pas les acteurs à prendre les mesures nécessaires. Malheureusement, encore une fois, il faudra attendre une telle attaque pour qu'enfin la sécurité soit vue comme un argument commercial, et donc prise en compte. La solution la plus simple est d'empêcher l'exécution d applications malicieuses sur le téléphone portable. D'ailleurs, c'est ce que font la plupart des opérateurs : sur une téléphone portable standard, nous pouvons installer que des applications Java qui sont très bien contrôlées (par le modèle de bac à sable de KVM). Dans le cas d un vrai smartphone, la seule solution est de sécuriser une partie du système et de s'assurer que le monde non sécurisé ne peut pas agir sur le monde sécurisé. C'est assez difficile et aucun téléphone actuel n'utilise cette approche. En particulier, il faut s'assurer que les applications sécurisées peuvent acquérir un contrôle exclusif sur l'écran, le clavier et les autres interfaces quand elles doivent interagir avec l'utilisateur. En d autres termes, les contre-mesures doivent régler les accès des divers composants logiciels (système d'exploitation, code téléchargé, etc.) à différentes parties des systèmes (registre, régions de mémoire, co-processeurs de sécurité, etc.) pendant différentes étapes 37 de l'exécution (processus de démarrage, exécution normale, mode d'interruption, etc.) par une combinaison des changements de matériel et de logiciel. Puisque les contre-mesures efficaces doivent permettre au système de fournir des garanties au sujet de la sécurité immédiatement après le commencement d'opération du système, la plupart des mesures définissent des notions de la confiance ou des frontières de confiance (également nommés des périmètres de sécurité) à travers des diverses ressources de matériel et de logiciel. Ceci permet au système de détecter des infractions à des frontières de confiance (telles que des accès illégaux aux régions de mémoire) et d'imposer des mécanismes de rétablissement (tels que la mise à 0 des registres du processeur et des régions de mémoire). Ainsi, une frontière de confiance fournit une base naturelle et commode pour le système pour prendre des décisions judicieuses ou de compromis au sujet de sécurité. Pour atteindre ce but, il est obligatoire d utiliser l approche en couche en partant du matériel : c est l approche nommée l informatique de confiance (Trusted Computing Base ou TCB). C est en fait un principe récursif, si la couche n-1 est sécurisée et si la couche n-1 a plus de pouvoir que la couche n, alors la couche n-1 peut sécuriser la couche n. Ainsi, on sécurise l unité central, puis le BIOS, puis la mémoire vive, puis la mémoire morte, puis le noyau de l'OS et puis certaines applications de l'OS. Cette approche se base sur deux suppositions suivantes : Supposition 1 : l'utilisation des composants de matériel additionnels augmentent la sécurité considérablement. Si on stocke un secret, tel qu'une clé, sur un périphérique de stockage, il y a alors de bonnes chances que quelqu'un d'autre puisse le lire. Plusieurs outils sont disponibles pour détecter les empreintes de clé sur le stockage ou en RAM. La meilleure manière pour éviter d'exposer la clé à tels l'attaque est de stocker la clé dans un endroit séparé, auquel aucun logiciel malveillant ne peut accéder. Ceci ne résout pas le problème que la clé doit être transférée à la mémoire vive (RAM) pour être traitée par l'unité centrale. La solution est d'avoir un processeur séparé à l'endroit où la clé est stockée. La clé sera traitée là, et il ne la laissera jamais. C'est sécurisé. Ce genre de stockage et d'unité de traitement dédié est implémenté en tant que carte intelligente, si un composant de matériel démontable est exigé, ou comme un module de plate-forme sécurisée (Trusted 38 Platform Module ou TPM)22, dans ce cas il est intégré avec le panneau de processeur. Supposition 2 : la protection ultime pour les données est seulement donnée par le chiffrement. Les mécanismes de contrôle d'accès peuvent fonctionner, mais l'expérience passée a montré que le contrôle d'accès est une barrière qui peut être surmontée sans grand effort. Habituellement le contrôle d'accès ne signifie rien autrement qu'un attribut pour un ensemble de données. Si le logiciel de gestion, soit le système d'exploitation lui-même ou une application, respecte cet attribut, le mécanisme de contrôle d'accès est sécurisé. Si autre logiciel ou système d'exploitation est utilisé pour accéder aux données, alors le mécanisme de contrôle d'accès n'assure plus la protection. Les améliorations de la programmation système de contrôle d'accès n'améliore pas le niveau de la sécurité : il y a un besoin de changer la technologie. La solution à cette tâche de sécurité est le chiffrement. Le chiffrement peut être considéré comme le niveau ultime de la protection de données. Le chiffrage ne protège pas contre détruire le contenu en effaçant un fichier. C'est toujours à la portée du contrôle d'accès. Nous avons vu que le contenu est bien protégé contre la lecture, car il est chiffré. Ce que nous devons protéger avec le niveau ultime de la sécurité sont les clés utilisées pour le chiffrement et le déchiffrement. Ici nous renvoyons à la supposition 1, et nous pouvons alors conclure : des clés de chiffrement doivent être stockées et traitées dans le matériel dédié tel que les cartes intelligentes ou le TPM. 5.1 Renforcer la sécurité des téléphones portables A partir de la SIM, en basant sur le principe de l informatique de confiance, nous proposons une approche pour améliorer la sécurité des téléphones portables. De notre point de vue, le renforcement de la sécurité des téléphones portables passe par la prise en compte des trois aspects qui sont expliqués dans les trois sous-sections ci-dessous. Nous discutons également les solutions et la tendance actuelles qui peuvent être appliquées pour chaque aspect proposé. 22 Le module de plate-forme sécurisée TPM (qui est spécifié par TCG [51]) est une puce silicone qui se compose de générateur de nombre aléatoire, de générateur de clés, de moteur RSA, de stockage non-volatile, de code du programme et de moteur d'exécution. 39 5.1.1 Utilisation systématique des composants matériels dédiés sécurité Le premier aspect concentre sur le niveau le plus bas, les matériels. Comme nous avons signalé dans la section 2.1, les solutions de sécurité se fondent typiquement sur les primitives cryptographiques, c est pourquoi les clés et les certificats sont les éléments fondamentaux de tous les services de sécurité. D autre part, la supposition 1 notée en dessus montre que il faut préserver ces secrets et les traiter dans un endroit séparé. L utilisation des composants dédiés passe par deux points suivants : la carte SIM (ou autre carte) pour tous les services d'authentification, de signature et de gestion de clés. les composants du téléphone portable pour les opérations de cryptographie nécessitant de la performance (chiffrement symétrique par cryptoprocesseurs), pour le stockage des données sensibles et volumineuse, ainsi que pour la protection du code natif (en particulier, le chargeur de démarrage (boot loader) doit être sécurisé par utilisation de mémoire morte). L'usage d'un module de co-processeur sécurisé séparé, qui est dédié au traitement de toutes les informations sensibles dans le système est trouvé dans [55][24][25]. Chaque information qui est envoyée du co-processeur sécurisé est chiffrée. Beaucoup d'architectures des systèmes embarqués se fondent sur l'identification et la maintenance des zones choisies de son sous-système de mémoire (volatile ou non-volatile, hors puce ou sur puce) en tant que des endroits de stockage sécurisé. L'isolement physique est souvent utilisé pour limiter l'accès des zones de mémoire sécurisée aux composants de confiance du système. Quand ce n'est pas possible, un mécanisme de protection de mémoire adopté dans beaucoup de SOCs (System On Chip) embarqués comporte l'utilisation de matériel surveillant le bus, qui peut distinguer l'accès légal et illégal à ces endroits. Par exemple, la solution de sécurité de CrypoCell de Discretix [12] comporte BusWatcher, qui exécute cette fonction. L'assurance de l'intimité et de l'intégrité dans la hiérarchie de mémoire est le centre de [43], qui utilise une gestion sécurisée de contexte de matériel, de nouvelles instructions, et des unités de hachages et de chiffrage dans le processeur. Le travail dans [32] décrit un modèle d'exécution dans la mémoire (eXecutable Only Memory} - XOM), et des techniques architecturales pour le mettre en oeuvre, en 40 utilisant des élargissements de matériel tels que des instructions personnalisées et des champs additionnels dans les caches, avec un moniteur. Des idées semblables ont été également décrites plus tôt dans [21]. A l image de la performance (brèches de traitement), l expérience a montré que si les fonctions de la génération de nombre aléatoire, de la cryptographie et du chargeur de démarrage sont programmées dans le matériel, la sécurité est améliorée avec le prix très petit. Le processeur OMAP 1610 est un exemple, il se compose d un microprocesseur et d un processeur de signal numérique (DSP par la suite). Le DSP est utilisé non seulement pour la performance des applications multimédias mais encore pour les computations de sécurité. Les computations de clé publique sont typiquement consacrées au DSP pour améliorer la vitesse, pendant que les opérations symétriques et de hachage sont déchargées au accélérateur cryptographique. Autres caractéristiques du processeur OMAP 1610 sont : un générateur de nombre aléatoire, un chargeur de démarrage sécurisé pour vérifier l intégrité du code du dispositif et un mode d exécution sécurisé permettant le stockage sécurisé des clés et l authentification au temps d exécution. Pour réaliser les deux dernières options, ce processeur se compose 48kB de ROM sécurisée et 16kB de RAM sécurisée sur puce. Les initiatives commerciales récemment annoncées telles que TrustZone de ARM [1], le Palladium ou NGSCB de Microsoft [34][22], et LaGrande d'Intel [26], etc. présentent des diverses améliorations de matériel pour la sécurité. Notons que la plupart des PDAs et des smartphones utilisent des processeurs ARM, donc il est très probable que la technologie TrustZone soit réellement déployée sur de vrai téléphone. Bien qu il existe de nombreux fournisseurs de sécurité proposent des briques matérielles, des composants spécifiques pour intégrer des services de sécurité tels que le stockage de clés ou des algorithmes de chiffrement et d authentification, notons que ces composants font quelquefois doublon par rapport aux fonctionnalités présentes dans la SIM, cela se traduit par une augmentation du coût du téléphone portable sans pour autant présenter de garantie quant à leur utilisation. 41 5.1.2 Sécurité du code Cet aspect vise au code qui réalise les opérations de sécurité ou les opérations sensibles, par exemple, le déclenchement des services d accès à la SIM (SIMToolKit, etc.), l envoi un SMS ou la présentation de code PIN, la réalisation ou l intervention d un appel, etc. : l'évaluation, au sens Critères Communs; l'authentification du code en s'appuyant sur la carte; la protection du code en s'appuyant sur les composants dédiés au sein du téléphone portable. Ceci s'applique au code mobile natif (propriétaire et non modifiable), ainsi qu'au code mobile téléchargeable. Puisque plusieurs raisons connues des attaques logicielles proviennent des vulnérabilités dans les logiciels sûrs, le premier point a pour but de d éliminer les trous dans les interfaces, les services et les applications qui fournissent ou réalisent les opérations de sécurité ou les opérations sensibles. Dans ce sens, les Critères Communs (une norme internationale des critères pour l évaluation des exigences de sécurité aux différents points de vue) peuvent être utilisés pour sélectionner des mesures appropriées de sécurité. D autre part, à l aide des moteurs de vérification de logiciel nous pouvons détecter les erreurs qui rendent un système enclin aux attaques, par exemple, la vérification statique étendue, qui est utile pour trouver des erreurs de code source dans la compilation, a été employée pour identifier des failles de sécurité dans beaucoup de programme [11][6]. Des techniques formelles de vérification telles que la vérification de modèles (model checking) ont été appliquées avec succès pour vérifier des implémentations des protocoles de sécurité [8][33]. Une fois le code jugé sûr, l origine et l intégrité du code doivent être identifiée et déterminé en basant sur un mécanisme de confiance, l authentification en s appuyant sur la carte. A l image des applications téléchargées, elles sont classifiées automatiquement dans la phase d installation aux domaines de sécurité appropriés selon leur origine. Le dernier problème vise à protéger les codes qui fournissent ou réalisent les opérations de sécurité ou les opérations sensibles contre les accès illégaux. Ceci est rendu possible en 42 utilisant des composants dédiés comme notés dans l aspect précédant et dans le fait que la plupart des cartes de mémoire démontables (avec le prix très réduit) intègrent maintenant une puce silicone (carte intelligente) entre la mémoire et le monde extérieur et deviennent un stockage sécurisé. Notons que les deux derniers points dans cet aspect exigent d une gestion de niveau de sécurité des applications et des services qui sera abordée dans le dernier aspect. 5.1.3 Gestion du niveau de sécurité des applications et services le cloisonnement des applications en s'appuyant sur la notion de domaines de sécurité; la garantie du principe du « moindre privilège »; l'utilisation systématique des services de sécurité certifiés. Au sens de contrôler les accès illégaux aux interfaces, aux services et aux applications qui fournissent ou réalisent les opérations de sécurité ou les opérations sensibles, il faut définir des frontières de confiance, ou en d autre termes des domaines de sécurité. Chaque application, selon son origine, ne peut exécuter que les opérations permises dans le domaine correspondant. De plus, dans chaque domaine, les applications ne doivent avoir accès qu aux fonctions nécessaires ou elles doivent respecter le principe du « moindre privilège » pour rendre le service attendu. La raison la plus importante pour limiter les privilèges de sécurité est que ce principe minimise le danger qui peut avoir lieu à cause d un accident ou d une erreur. Il réduit également le nombre des interactions potentielles entre les programmes privilèges au minimum pour l opération correcte, donc les utilisations involontaires, inattendues ou inexactes du privilège ont moins de chance de se produire. Cet aspect nécessite normalement des améliorations de l OS qui ont pour but de fournir un OS sécurisé. La plupart des arrangements de sécurité se fondent sur des modifications d'OS afin d'assurer la protection au code et/ou aux données sensibles. Par exemple, l'initiative NGSCB de Microsoft utilise un mode sécurisé Nexus pour Windows qui fournit l'isolement de processus fort, et exécute l'attestation au niveau de processus. Cet isolement s'assure 43 que des ressources privées d'un processus peuvent être protégées contre un autre processus, alors que l'attestation s assure que le code peut être authentifié avant d'établir des canaux de communications entre les processus et les dispositifs. D'autres améliorations d'OS pour la sécurité incluent des modifications de la commutation de contexte, de la manipulation d'exception, de la communication d'interprocessus, et de la gestion de mémoire. Il est important de noter que plusieurs de ces changements de système d'exploitation exigeraient des modifications au niveau de l'architecture (telles que les changements du système de gestion de mémoire) pour la sécurité [31]. L'architecture d'OS dans [19] propose la bonne flexibilité et un meilleur isolement que les solutions existantes sous divers scénarios d'application. 5.2 Analyse Dès lors que ces trois aspects sont assurés, l'opérateur, l'utilisateur et les fournisseurs de service ont la garantie que toute opération de sécurité légitime s'appuiera sur des mécanismes fiables. Concrètement, ceci signifie pour l'utilisateur que ses secrets (clés et certificats) sont stockés dans la carte, et n'en sortent pas. Inversement, les acteurs ont la garantie que toute opération illégitime ne mettra pas en danger la sécurité globale du téléphone portable. Ceci déresponsabilise l'utilisateur : même si ce dernier accepte de télécharger et d'installer du programme malveillant, ce programme malveillant ne pourra faire autre chose que des opérations ne mettant pas en danger les services et applications sensibles du téléphone portable. En particulier, le programme malveillant ne sera pas en mesure d'accéder aux opérations utilisant les secrets contenus dans la carte. 44 Chapitre 6: Conclusion Le problème principal pour lequel la sécurité des téléphones portables n'est pas actuellement garantie est un manque d'approche globale de la sécurité. Ce manque est expliqué par la présence de plusieurs acteurs (l'encarteur, l'opérateur, les fournisseurs d'application, les constructeurs de téléphone portable et l'utilisateur), par la disparité des standards, mais surtout par la concurrence des constructeurs de téléphones portables. Du côté utilisateur, ce manque d'approche globale se traduit par un risque important lors du téléchargement des applications, alors que, le téléphone portable appartenant en premier lieu à l'utilisateur. Avec le projet Symbian Signed, le téléchargement est possible mais, encore une fois, l'utilisateur est responsable de continuer ou non dans la phase de l'installation d'une application. Il est très probable que l'utilisateur accepte d'installer un programme malveillant. Une approche globale à la sécurité des téléphones portables revient à construire une informatique de confiance (Trusted Computing Base) à partir de la SIM. Cela passe par la définition d'une architecture de sécurité intégrant le téléphone portable, la SIM, les services de sécurité, et s'appuyant sur la certification du code des applications natives ou sensibles. Dans ce rapport, nous avons brièvement décrit les principes fondamentaux d'une telle architecture. Si un seul principe est à retenir, c'est que les services proposés par la SIM ne doivent pas se limiter au domaine de l'opérateur, mais bel est bien s'ouvrir aux champs applicatifs des OS ouverts. 45 Annexe : Vérification de code d octet Java A.1 Introduction Depuis sa sortie en 1995, les concepteurs de la technologie de Java ont eu une emphase explicite sur la sécurité. Le travail initial dans ce domaine a consisté principalement à définir la sécurité en tant qu'élément de la sémantique du langage de programmation, et la définition des exigences très strictes de sécurité, attendant la découverte des nouvelles technologies qui pourraient permettre un modèle plus flexible. Avec la plateforme de Java 2, les développeurs et les administrateurs ont reçus la capacité d'indiquer un contrôle d'accès fin sur chaque utilisateur individuel. Avec ce point de vue, le modèle de sécurité dans Java a été expliqué en termes de « bac à sable » (sandbox), l'idée est de limiter l'accès aux ressources basées sur la confiance du code. Pourtant, les appliquettes (applet) Web ont popularisé l'idée du téléchargement et de l'exécution des codes non fiables compilés sur le serveur avec le Web browser, sans approbation ou intervention de l'utilisateur. Cela soulève évidemment des problèmes : sans mesures de sécurité appropriées, une appliquette malveillant pourrait faire une variété d'attaques contre l'ordinateur local, tel que détruire les données, modifier les données sensibles, divulguer l'information personnelle sur le réseau, ou modifiant d'autres programmes (attaques de Trojan). De plus, le modèle d'appliquette est maintenant transféré aux dispositifs embarqués de l'haute sécurité comme les cartes intelligentes, qui sont utilisées dans les applications de paiement ou de téléphone mobile. Donc, un trou de sécurité permettant à l'appliquette d'exécuter des transactions de carte de crédit non-autorisées n'est certainement pas acceptable. Une autre forme de programmes écrits en Java est des applications. Elles sont comme les applications écrites en C ou autres langages de programmation. Elles pourraient donc effectuer des opérations qui pourraient compromettre la sécurité. Le premier virus Java s'appelle Strange Brew, apparu en août 1998, est un exemple. On a reconnu en suite les virus qui s'appellent BeanHive et byteverify. 46 La possibilité de prévoir le comportement des programmes, avant leur exécution réelle, devient de plus en plus appropriée car les programmes augmentent dans la complexité et sont utilisés dans des situations critiques. Dans le modèle « bac à sable » de Java, la vérificateur de code d octet (bytecode) s'occupe de cette tâche. La vérification de code d octet est une analyse statique dont le but est d'assurer que le code est bien typé, et n'effectue pas des opérations « mal-typée » comme l'appel direct des méthodes privées. N'importe quel bug dans le vérificateur acceptant un code « mal-typé » d'être accepté peut potentiellement permettre une attaque de sécurité. Le but de cette partie du rapport de stage est de faire une étude profonde sur les travaux de vérification de code d octet. On concentre également sur les problèmes dans l implémentation existante de Sun. Le reste de cette partie d annexe est organisé comme suivant. La première section donne une vue d'ensemble de la JVM et du but de la vérification de code d octet. Les idées principales de différents approches et algorithmes sont décrits dans la section deux : la vérification implémentée dans JVM est comparée avec les autres approches. Les deux aspects avancés, ceux d'initialisation d'objet et de sousprogramme (subroutine) sont abordés dans les deux sous-sections qui suivent. La solution de Sun est toujours comparée avec les autres. Un autre point de vue, vérification de modèles (model checking), est décrit dans la dernière sous-section. La section trois se concentre sur les vérifications pour les petits ordinateurs et on terminera avec quelques conclusions. A.2 Vue d ensemble de la JVM et de vérification de code d octet La machine virtuelle de Java [A16] est une machine abstraite à base de pile d'exécution. La plupart des instructions dépilent (pop) leurs arguments de la pile, et empilent (push) leurs résultats sur la pile. De plus, un ensemble de registres (également appelés les variables locales) est fourni, ils peuvent être accédés via des instructions load et store qui empile la valeur d'un registre donné sur la pile ou stocke le sommet de la pile dans le registre donné, respectivement. La plupart de compilateurs de Java utilisent les registres pour stocker des variables locales et des paramètres de méthode au niveau de la source, et la pile pour tenir des résultats temporaires pendant l'évaluation des expressions. La pile et les registres font partie du rapport d'activation pour une méthode. Ainsi, ils sont préservés à travers des appels de méthode. Le point d'entrée d'une méthode indique le nombre de 47 registres et de blocs de pile utilisés par la méthode, ainsi permettant à un rapport d'activation de la bonne taille d'être alloué sur l'entrée de méthode. Dans le code d octet, le contrôle est manipulé par une variété d'instructions de branchement intra-méthode: le branchement sans conditions (goto), les branchements conditionnels (branche si le sommet de pile est 0), le branchement de multi-chemins (correspondant à switch au niveau de source de Java). Des traiteurs d'exception peuvent être indiqués comme une table de quadruplet (pc1, pc2, C, h), signifiant que si une exception de la classe C ou d'une sous-classe de C est lancée par n'importe quelle instruction entre les points pc1 et pc2, le contrôle est transféré à l'instruction à h (le traiteur d'exception). La Figure 8 montre un exemple simple de code source écrit en Java et le code d octet correspondant. Dans la JVM, environ 200 instructions sont utilisées, y compris des opérations arithmétiques, les comparaisons, la création d'objet, les accès de champ et les invocations de méthode. void spin() { int i; for (i = 0; i < 100; i++) { ; } } Le compilateur peut compiler spin() à : Method void spin() 0 iconst_0 // empiler la constante int 0 1 istore_1 // stocker dans la variable locale 1 (i = 0) 2 goto 8 // la première fois, ne pas augmenter 5 iinc 1 1 // augmenter la variable locale 1 par 1 (i++) 8 iload_1 // empiler la variable locale 1 (i) 9 bipush 100 // empiler la constante int 100 11 if_icmplt 5 // comparer et répéter si (i < 100) 14 return // retourner void quand fini Figure 8 Exemple de code source Java et le code d octet correspondant. 48 Une caractéristique importante de la JVM est que la plupart des instructions sont typées. L'opération correcte de la JVM n'est pas garantie à moins que le code vérifie les conditions suivantes : L'exactitude du type : les arguments d'une instruction sont toujours des types attendus par l'instruction. La taille de la pile : une instruction ne dépile jamais un argument d'une pile vide, ni pousse un résultat sur une pile pleine (dont la taille est égale à la taille maximale de pile déclarée pour la méthode). La position du compteur de programme : le compteur de programme (CP par la suite) doit toujours pointer dans le code pour la méthode, au commencement d'une instruction valide (aucun ne doit tomber de la fin du code de méthode; aucun branchement dans le milieu d'une instruction). L'initialisation de registre : un chargement d'un registre doit toujours suivre au moins d'un stockage dans ce registre; en d'autre termes, les registres qui ne correspondent pas aux paramètres de méthode ne sont pas initialisés à l'entrée de méthode, et c'est une erreur de charger un registre non initialisé. L'initialisation d'objet : quand une instance d'une classe C est créée, un des méthodes d'initialisation pour la classe C (correspondant aux constructeurs de cette classe) doit être invoqué avant que l'instance de classe puisse être utilisée. Le contrôle d'accès : l'invocation de méthode, l'accès aux champ et aux références de classe doivent respecter les modificateurs de visibilité (private, protected, public, etc.) de la méthode, du champ ou de la classe. L approche défensive de la JVM [A4] peut garantir ces conditions dynamiquement, en exécutant le code. Cependant, la vérification de ces conditions au temps d'exécution est chère et ralentit l'exécution de manière significative. Le but de la vérification de code d octet est de vérifier ces conditions une fois pour toutes, par l'analyse statique du code d octet au chargement. Un code d octet qui passe la vérification peut alors être exécuté à la pleine vitesse, sans contrôles dynamiques supplémentaires. Il doit souligner que la vérification de code d octet par elle-même ne garantit pas l'exécution de sûreté du code : beaucoup de propriétés cruciales du code doivent toujours 49 être vérifiées dynamiquement, par exemple les tests de pointeur nul et les tests de contrôle d'accès dans les API. Le but de la vérification de code d octet est de déplacer les vérifications énumérées ci-dessus du temps d'exécution au temps de chargement. A.3 Approches et algorithmes A.3.1 Analyse de flux de données Le premier algorithme de vérification de code d octet de JVM est proposé par Gosling et Yellin chez Sun [A34][A16]. Presque tous les vérificateurs existant de code d octet implémentent cet algorithme. Il peut être décrit comme l'analyse de flux de données appliquée à une interprétation abstraite au niveau des types de la machine virtuelle. Dans cette section, on décrit les ingrédients de base de cet algorithme : l interprète abstrait au niveau des types et le cadres de flux de données. A.3.1.1 Interprète abstrait au niveau des types Le coeur de la plupart des algorithmes de la vérification de code d octet est un interprète abstrait pour l'ensemble d'instructions de la JVM qui exécute ces instructions comme une JVM défensive (y compris des tests de type, des tests de pile, etc.), mais il fonctionne sur les types au lieu des valeurs. Cela signifie que l'interprète abstrait manipule une pile de types (une séquence de type) et un registre de type (un tuple de types associant un type à chaque numéro de registre). Il simule l'exécution des instructions au niveau de types. Par exemple, pour l'instruction iadd (addition de nombres entiers), il vérifie que la pile de types contient au moins deux éléments, et que les deux premiers éléments au sommet sont de type int. Il dépile ensuite les deux premiers éléments et empile le type int correspondant au résultat de l'addition. L'interprète abstrait est présenté comme une relation de transition i : (S, R) (S', R ), où i est l'instruction, S et R sont le type de pile et type de registre avant d'exécuter l'instruction, et S' et R' sont le type de pile et type de registre après exécution de l'instruction. Des erreurs telles que des disparités de type sur les arguments, ou le dépassement de pile, sont dénotées par l'absence d'une transition. Par exemple, il n'y a aucune transition sur iadd d'une pile vide. Les types manipulés par l'interprète abstrait sont similaires aux types au niveau des sources de langage Java. Ils incluent des types primitifs (int, long, float, double), des 50 types de tableau, des types de référence d'objet représentés par les noms entièrement qualifiés des classes correspondantes. Les types boolean, byte, short et char sont identifiés avec int. Trois types supplémentaires sont présentés : null pour représenter le type de la référence nul, pour représenter l'absence de valeur, et pour représenter les contenues des registres non initialisés. (Les instructions de chargement vérifient explicitement que le registre consulté n'a pas de type , de ce fait détectent des accès aux registres non initialisés). Ces types sont équipés d'une relation de sous-type, note <:, qui est essentiellement identique à la relation de sous-type du langage Java. La Figure 9 illustre la relation de sous-type. Des définitions précises de <: peuvent être trouvées dans [A26][A20][A12][A11]. Figure 9 Quelques expressions de types utilisées par le vérificateur et leur relation de soustype. A.3.1.2 Vérification par analyse de flux de données La vérification d'une méthode lorsqu'il n'y a aucune branche dans le code est facile: on fait simplement des itérations avec la fonction de transition de l'interprète abstrait sur les instructions, on considère le type de pile et les types de registre « après » l'instruction précédente comme ceux « avant » l'instruction qui suit. Les types initiaux de la pile et du registre reflètent l'état de la JVM sur l'entrée de méthode : le type de pile est vide; les types de registres 0...n-1 correspondent aux n paramètres de méthode sont mis au type des paramètres correspondants dans la signature de la méthode; les autres registres n...Mreg-1 (Mreg est le nombre maximal de registre) aux variables locales non initialisées reçoivent le type . 51 Si l'interprète ne peut pas faire une transition à partir d'un des états intermédiaires, la vérification échoue et le code est rejeté. Autrement, la vérification réussit et puisque l'interprète abstrait est une approximation correcte d'une JVM défensive, on est certain qu'une JVM défensive n'aura pas de problème où exécutera le code. Ainsi, le code est correct et peut être exécuté sans grand risque par une JVM régulière et non-défensive. Les branches et les traiteurs d'exception introduisent des fourches et des joints dans le flux de contrôle de la méthode. Une instruction donc peut avoir plusieurs prédécesseurs, avec différents types de pile et de registre « après » ces instructions de prédécesseur. La vérificateur de code d octet de Sun traite cette situation de la façon usuelle pour l'analyse de flux de données : l état (type de pile et type de registre) « avant » une instruction est pris pour être la borne supérieure des états « après » tous les prédécesseurs de cette instruction. Par exemple, suppose que les classes D et E héritent la classe C, et on analyse une construction conditionnelle qui stocke une valeur du type D dans le registre 0 dans un bras, et une valeur du type E dans l'autre bras. Lorsque les deux bras se réunissent, on assume que le registre 0 a le type C, qui est la borne supérieure (le plus petit supertype commun) de D et E. Plus précisément, on écrit instr(p) pour l'instruction au point p du programme, in(p) pour l'état « avant » l'instruction p, et out(p) pour l'état « après » l'instruction p. L'algorithme de vérification établit les équations de flux de données suivantes : instr(p) : in(p) out(p) in(p) = bs{out(q) | q prédécesseur de p} pour chaque point p valide du programme, plus: in(0) = ( , (P0, ,Pn-1, , , )) pour le point d'entrée (les Pk sont les types des paramètres de méthode, bs est la borne supérieure). Ces équations sont alors résolues par l'itération standard de point fixe en utilisant l'algorithme de liste de travail (worklist) de Kildall [A17] : un point de programme p est pris à partir de la liste de travail et son état « après » out(p) est déterminé par son état « avant » in(p) à l'aide de l'interprète abstrait; puis, on remplace in(q) par bs(in(q),out(p)) 52 pour chaque successeur q de p, et entre ces successeurs q pour lesquels in(q) a changé dans la liste de travail. Le point fixe est atteint quand la liste de travail est vide, dans ce cas la vérification réussit. La vérification échoue si un état sans transition est produit, ou une des bornes supérieures est non définie. Comme une optimisation ordinaire de l'algorithme au-dessus, les équations de flux de données peuvent être établies au niveau des blocs de base étendus plutôt que des instructions individuelles. En d'autres termes, il suffit de garder dans la mémoire de travail les états in(p) où p est le commencement d'un bloc de base étendu (c est-à-dire une branche cible); les autres états peuvent être recalculés en marche si nécessaires. La borne supérieure de deux états est prise sur les types de pile et ceux de registres. Il est non défini si les types de pile ont différentes tailles, ce qui fait échouer la vérification. Cette situation correspond à un point de programme où la pile d'exécution peut avoir différentes tailles selon le chemin par lequel le point est atteint; un tel code ne peut pas être prouvé correct dans le cadre décrit dans cette section, et doit être rejeté. (voir la section A.3.4.2 pour un algorithme alternatif de vérification qui peut manipuler cette situation.) La borne supérieure de deux types pour un registre (ou le bloc de pile) peut être entraînant le type , de ce registre dans l'état réuni. Ceci correspond à la situation où un registre prend des valeurs de types incompatibles dans deux bras d'un conditionnel (par exemple int dans un bras et une référence d'objet dans l'autre), et donc elle est traitée comme non initialisée (plus de chargements à partir de ce registre) après le point de réunion. L exactitude de l algorithme de vérification de code d octet décrit ci-dessus a été prouvée par Nipkow et Klein [A20][A12][A11] en utilisant Isabell/HOL. D'autres preuves d'exactitude de l'approche de flux de données incluent ceux de Qian [A24], et Stark et al [A29]. La vérification de code d octet peut également être indiquée et a prouvé sa validité en utilisant les systèmes de types [A30][A6]; dans ce cadre, l'algorithme d'analyse de flux de données que l'on a décrit peut être regardé comme algorithme d'inférence de type pour ces systèmes de types. 53 A.3.1.3 Problème des interfaces Le cadre de flux de données présenté ci-dessus exige que l'algèbre de type, ordonnée par la relation sous-type, constitue un semi-treillis (semi-lattice). C'est-à-dire, chaque paire de types possède un plus petit supertype commun (borne supérieure). Malheureusement, cette propriété ne tient pas si on prend l'algèbre de type du vérificateur pour être l'algèbre de type au niveau de source de Java (étendue avec et null) et la relation sous-type étant la relation de compatibilité d'affectation au niveau de source de Java. Le problème est que les interfaces sont des types, juste comme des classes, et une classe peut mettre en application plusieurs interfaces. Considérez les classes suivantes : interface I1 {...} interface I2 {...} class C1 implements I1, I2 {...} class C2 implements I1, I2 {...} La relation sous-type induite par ces déclarations est : Object I1 I2 C1 C2 Ce n'est évidement pas un semi-treillis, puisque les deux types C1 et C2 ont deux supertypes communs I1 et I2 qui ne sont pas comparables, ni sous-type l'un de l'autre. Il y a plusieurs façons pour s'attaquer à ce problème. Une approche est de manipuler des ensembles de types pendant la vérification au lieu des seuls types comme on a décrit plutôt. Ces ensembles de types doivent être interprétés comme des types conjonctifs, c est-à-dire l'ensemble {I1,I2}, comme le type conjonctif I1 I2, représente les valeurs qui ont les types I1 et I2 en même temps. C'est donc est une borne supérieure convenable pour les 54 type {C1} et {C2} dans l'exemple ci-dessus. Cet approche est suivie dans [A24][A23][A29]. Une autre approche est de compléter la hiérarchie de classes et d'interfaces du programme dans un treillis avant d'exécuter la vérification [A14]. C'est un exemple d'une construction mathématique générale connue sous le nom de complétion d'un ensemble ordonné (poset) de Dedekind-MacNeille. Dans l'exemple ci-dessus la complétion ajouterait un point I1 I2 au treillis des types, qui est un sous-type de I1 et de I2, et d'un supertype de C1 et C2. On obtient alors le semi-treillis suivant : Object I2 I1 I1etI2 C1 C1 Le type additionnel I1etI2 joue le même rôle que l'ensemble de types {I1,I2} dans la première approche décrite ci-dessus. La différence est que la complétion de la hiérarchie de classes/interfaces est exécutée une fois pour toutes, et la vérification manipule seulement les types simples plutôt que les ensembles de types. Ceci rend la vérification plus simple et plus rapide. La solution la plus simple au problème des interfaces est trouvée dans l'implémentation de Sun, le vérificateur de code d octet de JDK (cette approche n'est documentée nulle part, mais peut facilement être déduite par expérimentation). La vérification de code d octet ignore les interfaces, traitant tous les types d'interface comme type de classe Object. Ainsi, l'algèbre de types utilisée par le vérificateur ne contient que des classes et aucune interface, et le sous-type entre les classes est simplement la relation d'héritage entre eux. Puisque Java a l'héritage simple (une classe peut implémenter quelques interfaces, mais hérite d'une classe seulement), la relation de sous-type est en forme d'arbre et crée un seme- 55 treillis: la borne supérieure de deux classes est simplement leur ancêtre commun le plus proche dans l'arbre d'héritage. L'inconvénient de l'approche de Sun, comparé à l'approche basée sur l ensemble ou celle basée sur la complétion, est que le vérificateur ne peut pas garantir statiquement qu'une référence d'objet implémente une interface donnée. En particulier, l'instruction de invokeinterface I.m qui invoque la méthode m de l'interface I d'un objet, n'est pas garantie. La seule garantie fournie par le vérificateur de Sun est qu'elle reçoit un argument de type Object, c est-à-dire n'importe quelle référence d'objet. L'instruction invokeinterface I.m doit donc vérifier dynamiquement que l'objet implémente réellement I, et lève une exception dans autres cas. A.3.2 Vérification de l initialisation d objet La création d'objets dans la machine virtuelle de Java est un processus en deux étapes: d'abord, l'instruction new C crée un nouvel objet, une instance de la classe C, avec tous les champs d'instance remplis de valeurs par défaut (0 pour les champs et null pour les champs de référence); puis, un des méthodes d'initialisation pour la classe C (des méthodes appelées C.<init>, les résultats de la compilation des constructeurs de la classe C) doivent être invoquées pour l'objet nouvellement créé. Des méthodes d'initialisation, juste comme leurs constructeurs au niveau des sources, sont typiquement utilisées pour initialiser des champs d'instance aux valeurs non-défauts, bien qu'elles puissent également exécuter des calculs presque arbitraires. La JVM exige que ce protocole de deux étapes d'initialisation d'objet soit respecté. Cela signifie que l'instance d'objet créée par l'instruction new est considéré non initialisée, et aucune des opérations régulières d'objet (c est-à-dire stocker l'objet dans une structure de données, la retourner comme un résultat de méthode, accéder à un de ses champs, appeler un de ses méthodes) n'est permise sur cet objet non initialisé. Seulement lorsqu'une des méthodes d'initialisation pour sa classe est appelée sur le nouveaux objet et retourne normalement, le nouvel objet considéré entièrement initialisé et utilisable comme n'importe quel autre objet (dans [A16][A7][A6], on trouvera les autres restrictions additionnelles). Différemment des propriétés d'initialisation de registre, la propriété d'initialisation d'objet n'est pas cruciale pour assurer la sûreté de type au temps d exécution : puisque l'instruction 56 new initialise les champs d'instance du nouvel objet avec les valeurs correctes pour leurs types, la sûreté de type n'est pas cassée si l'objet défaut-initialisé est utilisé tout de suite sans avoir appelé une méthode d'initialiseur. La vérification statique de l'initialisation d'objet est rendue plus complexe par le fait que les méthodes d'initialisation sont actionnées par effet secondaire : au lieu de prendre un objet non initialisé et renvoyer un objet initialisé, elles prennent simplement un objet non initialisé, mettent à jour ses champs, et ne renvoient rien. Par conséquent, le code produit par des compilateurs de Java pour x = new C(arg) est généralement de la forme suivante: new C // créer une instance non initialisée de C dup // reproduire la référence à cette instance code à calculer arg invokespecial C.<init> // appeler l initialiseur astore 3 // stocker l objet initialisé dans x Deux références à l'instance non initialisée de C sont tenues sur la pile. La référence le plus élevée est consommée par l'invocation de C.<init>. Quand cet initialiseur retourne, la deuxième référence est maintenant au sommet de la pile et pointe sur un objet correctement initialisé, qui est alors stocké dans le registre assigné au x. Le point est que la méthode d'initialisation est appliquée à une référence d'objet sur la pile, mais c'est une autre référence d'objet contenue dans la pile (qui arrive à référencer le même objet) dont états sont de « non-initialisé » à « entièrement initialisé » dans le processus. Comme démontré ci-dessus, la vérification statique de l'initialisation d'objet exige une forme d'analyse d'alias pour déterminer quelles références d'objet dans l'état actuel sont garanties pour se référer au même objet non initialisé qui est passé comme argument à une méthode d'initialiseur. Bien que n'importe quelle analyse d'alias puisse être utilisée, le vérificateur de Sun emploie une analyse assez simple où un objet non initialisé est identifié par la position (valeur de compteur de programme - CP) de l'instruction new qui l'a créé. Cela signifie que l'algèbre de type est enrichie par type C p dénotant une instance non initialisée de classe C créée par une instruction new à CP p. Une invocation d'une méthode 57 d'initialiseur C.<init> vérifie que le premier argument de la méthode est de type C p pour un certain p, puis dépile les arguments outre de la pile de type comme d'habitude, et finalement trouve toutes autres occurrences de type C p dans l'état de l'interprète abstrait (type de pile et types de registre) et les remplace par C. L'exemple suivant montre comment ceci fonctionne pour une initialisation de plusieurs niveaux correspondant à l'expression new C(new C(null)) de Java: 0 : new C // type de pile après : C0 3 : dup // C 0 ,C 0 4 : new C // C0 , C0 , C4 7 : dup // C0 , C0 , C4 , C4 8 : aconst_null // C 0 , C 0 , C 4 , C 4 , null // // // C0 , C0 , C C 9 : invokespecial C.<init> 12 : invokespecial C.<init> 15 : En particulier, le premier invokespecial initialise seulement l'instance créée au CP 4, mais pas celle créée au CP 0. Cette approche est correcte seulement si à un moment donné, l'état de machine contient au plus un objet non initialisé créé à un CP donné. Le vérificateur doit empêcher des situations où plusieurs objets distincts créés par la même instruction new C à p peuvent être « en vol » : ceux-ci seraient donnés le même type C p , et l'initialisation d'un ferait supposer vérificateur inexactement que les autres sont également initialisés. Ceci pourrait potentiellement se produire si une instruction new est exécutée plusieurs fois en tant qu'élément d'une boucle; un autre exemple impliquant des sous-programmes est donné dans [A7]. Pour éviter ce problème, le vérificateur de Sun exige qu'aucun type d'objet non initialisé n'apparaissent dans l'état de machine quand une branche en arrière est prise. Freund et Mitchell [A7] formalisent une restriction plus simple et également effective : ils proposent qu'en vérifiant une instruction new C à la position p, il ne doit y avoir aucune occurrence 58 du type C p dans le type de pile et type de registre à p. Bertot [A1] prouve l'exactitude de cette approche en utilisant le prouveur de théorème de Coq, et extrait un algorithme de vérification à partir de la preuve. A.3.3 Problèmes des sous-programmes Les sous-programmes (subroutines) dans la JVM sont des fragments de code qui peuvent s'appeler de plusieurs points à l'intérieur du code d'une méthode. À cet effet, la JVM fournit deux instructions : jsr branche à une étiquette donnée dans le code de méthode et pousse une adresse de retour à l'instruction suivante; le ret récupère une adresse de retour (à partir d'un registre) et branche à l'instruction correspondante. void tryFinally() { try { tryItOut(); } finally { wrapItUp(); } } et le code compilé est : Method void tryFinally() 0 1 4 7 8 aload_0 invokevirtual #6 jsr 14 return astore_1 9 12 13 14 15 16 19 jsr 14 aload_1 athrow astore_2 aload_0 invokevirtual #5 ret 2 // Commencement du bloc try // Méthode tryItOut()V // Appeler le bloc finally // Fin du bloc finally // Commencement du traiteur de tous les déclenchements (throw) // Appeler le bloc finally // Empiler la valeur déclenchée // et re-déclencher la valeur à l invoqueur // Commencement du bloc finally // Empiler this // Méthode wrapItUp()V // Retourner à partir du bloc finally Figure 10 Exemple de sous-programme et le code d'octet correspondant. Les sous-programmes sont utilisés par le compilateur pour produire un code d octet plus compact [A16]. La Figure 10 montre un exemple de sous-programme et le code d octet correspondant [A16]. Les sous-programmes compliquent de manière significative la vérification de code d octet par analyse de flux de données. D'abord, elle n'est pas évidente pour déterminer les successeurs d'une instruction ret, puisque l'adresse de retour est une 59 valeur de première classe. Comme une première approximation, on peut dire qu'une instruction ret peut se brancher à n'importe quelle instruction qui suit un jsr dans le code de méthode. Deuxièmement, le point d'entrée de sous-programme agit en tant qu'un point de fusion dans le graphe de flux de contrôle, entraînant les types de registre aux points d'appel à ce sous-programme à fusionner. Ceci peut mener à la perte excessive de précision dans les types de registre impliqués. Ce comportement est contre intuitif. L'appel d'un sous-programme qui n'utilise pas un registre donné ne modifie pas la valeur au temps d'exécution de ce registre, ainsi on peut espérer qu'il ne modifie pas le type de au temps de vérification de ce registre, l'un ou l'autre. En effet, si le corps de sous-programme était étendu inline aux deux emplacements de jsr, la vérification de code d octet réussirait comme prévu. A.3.3.1 Implémentation de Sun On décrit d'abord l'approche implémentée par le vérificateur de JDK de Sun. Il est décrit informellement dans [A16], et formalisé (avec de divers degrés de perfection et de fidélité à l'implémentation de Sun) dans [A30][A24][A29][A6]. Cette approche implémente l'intuition qu'un appel à un sous-programme ne devrait pas changer les types de registres qui ne sont pas utilisés dans le corps de sous-programme. D'abord, on a besoin de préciser ce qu'un corps de sous-programme est : puisque le code d octet de la JVM est non structuré, des sous-programmes ne sont pas syntaxiquement délimités dans le code; des points d'entrée de sous-programme sont facilement détectés (comme cibles des instructions jsr), mais il n'est pas immédiatement évident de savoir quelles instructions peuvent être atteintes du point d'entrée de sous-programme. Ainsi, une analyse de flux de données est exécutée, avant ou parallèlement à l'analyse principale de type. Le résultat de cette analyse est un étiquetage cohérent de chaque instruction par le point d'entrée pour le sous-programme à qui il appartient logiquement. À partir de cet étiquetage, on peut alors déterminer, pour chaque point d'entrée de sous-programme , l'instruction de retour Ret( ) pour le sous-programme, et l'ensemble de registres Used( ) qui sont lus ou écrits par des instructions appartenant à ce sous-programme. L'équation de flux de données pour des appels de sous-programme est alors comme suit. i est une instruction jsr , et j est l'instruction juste après i. (Sjsr,Rjsr) = out(i) est l'état 60 « après » jsr, et (Sret,Rret) = out(Ret( )) est l'état « après » ret qui termine le sousprogramme. Alors : in( j ) S ret ,{r Rret (r ) si r Used ( ) } R jsr (r ) si r Used ( ) En d'autres termes, l'état « avant » l'instruction j suivant le jsr est identique à l'état « après » le ret, sauf les types des registres qui ne sont pas utilisés par le sousprogramme, qui sont pris de l'état « après » le jsr. Bien qu'elle est efficace dans la pratique, l'approche de Sun à la vérification de sousprogramme soulève un problème difficile : la détermination de la structure de sousprogramme. Non seulement des sous-programme ne sont pas syntaxiquement délimités, mais des adresses de retour sont stockées dans des registres d'usage général plutôt que sur une pile spécifique à la sous-programme, qui rend la trace des adresses de retour et la recherche de la paire ret/jsr plus difficile. Pour faciliter la détermination de la structure de sous-programme, les spécifications de JVM énoncent un certain nombre de restrictions au code correct de JVM, tel que deux sous-programmes différents ne peuvent pas fusionner leur exécution à une instruction ret simple [A16]. Ces restrictions semblent plutôt improvisées et spécifiques à l'algorithme d'étiquetage de sous-programme particulier qui sont utilisé dans le vérificateur de Sun. D'ailleurs, la description d'étiquetage de sousprogramme donnée dans les spécifications de JVM est incomplète et informelle. Plusieurs reconstructions et formalisations rationnelles de cette partie de vérificateur de Sun ont été publiées. Les présentations les plus proches de l'implémentation de Sun sont de Qian [A24] et de Stark et al [A29]. Une caractéristique de l'implémentation de Sun, correctement capturée par ces présentations, est que la structure de sous-programme et les ensembles Used( ) ne sont pas déterminés avant l'établissement et la résolution équations de flux de données, comme on a suggéré en haut; au lieu de cela, les types et les ensembles Used( ) sont inférés simultanément pendant l'analyse de flux de données. Ce calcul simultané des types et des ensembles Used( ) complique l'analyse : comme montré par Qian [A25], la fonction de transfert de l'analyse de flux de données n'est plus monotone, et des stratégies spéciales d'itération sont exigées pour atteindre le point fixe. 61 A.3.3.2 Autres approches pour la vérification de sous-programmes Beaucoup d'alternatives à la vérification de sous-programme de Sun ont été proposées dans la littérature, souvent dans le contexte de petit sous ensembles de la JVM, menant aux algorithmes qui sont plus simples et plus élégants, mais en général ne sont pas complets pour la JVM entière. Le premier travail formel sur la vérification de sous-programme est de Abadi et Stata [A30]: il se fonde sur une analyse séparée, exécutée avant la vérification de type, qui étiquette des instructions de code d octet avec les noms des sous-programmes auxquels elles appartiennent, de ce fait reconstruisant la structure des sous-programmes. ensembles Used( ) peuvent alors être calculés pour chaque Les de sous-programme, et être injectés dans les équations de flux de données comme décrit plus haut. Le travail postérieur par Hagiya et de Tozawa [A9] se repose également sur la détermination antérieure de la structure de sous-programme, mais exprime le flux des types par des sous-programmes d'une manière différente, en utilisant le type spécial last(n) pour se référer au type de registre n dans l'appeleur de la sous-programme. Les types last(n) se comportent comme des variables de types dans un système de type comportant le polymorphisme paramétrique. En d'autres termes, le sous-programme reçoit un type qui est polymorphe au-dessus des types des variables locales qu'il n'utilise pas. Bien que ces travaux soient considérables au niveau des sous-programmes, ils sont portés dans le contexte de petits sous-ensembles de la JVM qui excluent les exceptions et l'initialisation d'objet en particulier. Les interactions sensibles entre les sous-programmes et l'initialisation d'objet ont été discutées dans la section A.3.2. Quant aux exceptions, la manipulation d'exception complique de manière significative la détermination de la structure de sous-programme. L'examen du code d octet produit par un compilateur de Java montre deux situations possibles : un traiteur d'exception couvre une gamme des instructions entièrement contenues dans un sous-programme, dans ce cas le code du traiteur d'exception est considéré en tant qu'une partie du même sous-programme (par exemple il peut se brancher de nouveau à l'instruction ret qui termine le sous-programme); ou, un traiteur d'exception couvre des instructions appartenant d'un sous-programme et des instructions non-sous-programme, dans ce cas le code du traiteur est considéré comme en dehors de la sous-programme. Le problème est dans le deuxième cas, on a une branche (via 62 le traiteur d'exception) d'une instruction de sous-programme à une instruction de non-sousprogramme, et cette branche n'est pas une instruction ret; cette situation n'est pas permise dans le système d'étiquetage de sous-programme de Abadi et Stata. Ainsi, c'est désirable de développer des stratégies pour la vérification de sous-programme, qui ne se fondent pas sur la détermination antérieure de la structure de sous-programme, mais plutôt « découvrir » cette structure pendant la vérification. Les algorithmes de vérification de polyvariant que l'on discute après dans la section A.3.4 répondent à cette exigence. O'Callahan [A21] propose une approche différente, basée sur des types de continuation : le type assigné à une adresse de retour contient le type complet de pile et de registre attendu au point de programme après l'instruction jsr. Pourtant, O'Callahan donne seulement les règles vérifiant le type, mais aucun algorithme efficace d'inférence de type. A.3.4 Vérification de modèles Dans ce rapport, jusqu'ici, on a présenté la vérification de code d octet comme analyse de flux monovariant : à chaque point de programme, seulement un état abstrait (type de registre et type de pile) est considéré. Les analyses de flux de polyvariant, également appelées les analyses sensibles au contexte [A19] répondent à cette restriction : on permet plusieurs états par point de programme. Comme on montre dans cette section, la vérification polyvariante de code d octet fournit une solution alternative au problème de sous-programme de la section A.3.3 : l analyse polyvariante permet à des instructions à l'intérieur des corps de sous-programme d'être analysées plusieurs fois. A.3.4.1 Vérification polyvariante basée sur contours La première vérification polyvariante que l'on décrit est basée sur des contours, et est utilisée dans le vérificateur de Java Card hors carte [A32]. Dans l'analyse de flux polyvariant basé sur les contours, les états distincts maintenus à chaque point de programme sont indexés par les contours. Dans le cas de la vérification de code d octet, les contours sont des piles d'appel de sousprogramme : les listes d'adresses de retour pour la séquence des instructions jsr qui mènent à l'état correspondant. En l'absence des sous-programmes, tout le code d octet pour une méthode est analysé dans le contour vide. Donc, seulement un état est associé à chaque instruction et l'analyse se dégénère dans l'analyse monovariant de flux de données de la section A.3.1.2. Cependant, quand une instruction jsr est produite dans le contour 63 courante c, elle est traitée comme une branche à l'instruction à dans le contour augmenté .c . De même, une instruction ret r est traitée en tant qu'une branche qui restreint le contexte courant c en désempilant un ou plusieurs adresses de retour de c (comme déterminé par le type du registre r). Plus formellement, l'équation polyvariant de flux de données pour une instruction jsr à i suivie par celle à j est : in( , j.c) = (retaddr(j).S, T) où (S,T) = out(i,c) Pour une instruction ret r à i, l'équation est : in(j,c ) = out(i,c) où le type de registre r dans l'état out(i,c) est retaddr(j)23 et le contexte c est obtenu à partir de c par le dépilage d'adresses de retour jusqu'à ce que j soit trouvé, c est-à-dire c=c .j.c. Finalement, pour les autres instructions i que jsr et ret, l'équation est simplement : i : in(i,c) out(i,c) et donc l'instruction ne déclenche aucun changement de contour. Une autre manière de regarder la vérification polyvariante est que c'est exactement équivalent à effectuer la vérification monovariante sur une version étendue du code d octet où chaque appel de sous-programme a été remplacé par une copie distincte du corps de sous-programme. Au lieu de prendre N copies du corps de sous-programme, on les analyse N fois dans N contours différents. Il est que ce n'est pas pratique de reproduire des corps de sous-programmes avant la vérification monovariante, parce qu'elle demande la connaissance antérieure de la structure de sous-programme (pour déterminer quelles instructions appartiennent à quel corps de sous-programme), et comme montré, la structure de sous-programme est difficile à déterminer exactement. La beauté de l'analyse polyvariante est qu'elle détermine la structure de sous-programme dynamiquement, via les calculs sur les contours exécutés pendant l'analyse de flux de données. D'ailleurs, cette 23 Le type retaddr(i) représente une adresse de retour à l instruction à i. Il est sous-type de T et lui-même seulement. 64 détermination profite de l'information de typage telle que les types de retaddr(ra) pour déterminer avec certitude le point auquel une instruction ret se branche en cas de retour tôt à partir des sous-programmes nichés. Un autre avantage de la vérification polyvariante est qu'elle manipule le code qui est accessible à partir des corps de sous-programme et de programme principal, tel que les traiteurs d'exception mentionnés dans la section A.3.3.2 : plutôt que de décider si de tels traiteurs d'exception font partie d'un sous-programme ou pas, l'analyse polyvariant les analyse simplement plusieurs fois, une fois dans le contour vide et une fois ou plusieurs fois dans des contours de sous-programme. Un inconvénient de la vérification polyvariante est qu'elle est plus complexe que l'approche de Sun au niveau de calculs. En particulier, si des sous-programmes sont nichées à la profondeur N, et chaque sous-programme est appelé k fois, les instructions de la sousprogramme la plus intérieure sont analysées kN fois au lieu d'une fois dans l'algorithme de Sun. Cependant, en pratique, la plus part des méthodes de Java a N 1, très peu a N = 2 et N > 2 est inconnue. Donc, le coût extra de vérification polyvariante est entièrement acceptable dans la pratique. Un autre inconvénient plus sérieux de cette approche est que la vérification polyvariante basée sur les contours peut ne pas accepter le code valide parce que la structure de sousprogramme qu'elle infère par la construction des contours peut être infinie. Alternativement, la terminaison peut être assurée en exigeant que les contours ne contiennent jamais deux fois la même étiquette de sous-programme : un jsr contour contenant dans un est rejeté, selon les spécifications la de JVM qui déclare que les sous- programmes ne peuvent pas être récursives. (C'est ce que le vérificateur de Java Card [A32] fait.) Mais dans ce cas, on finit par le rejet d'un morceau valide de code d octet de JVM, généré par la compilation d'un programme Java valide. L'utilisation des contours données-indépendants pour diriger la vérification polyvariante peut causer trop ou même infiniment beaucoup de différents états qui doivent être stockés pour un point donné de programme, même si ces états sont exactement identiques. On 65 décrit maintenant une approche alternative à la vérification polyvariante, n'employant pas des contours, qui évite ces problèmes. A.3.4.2 Vérification de modèles des interprétations abstraites Les analyses de flux de données peuvent être vue comme la vérification de modèles (model checking) des interprétations abstraites [A28]. Puisqu'une grande partie des vérifications de code d octet est évidemment une interprétation abstraite (d'une JVM défensive au niveau de type), il est normal de regarder les parties restantes dans une perspective de vérification de modèles. Posegga et Vogt [A22] étaient les premiers dans cette approche. Ils décrivent un algorithme qui prend le code d octet pour une méthode et produit une formule temporelle de logique qui est vérifiée si et seulement si le code d octet est sûr. Ils emploient alors un vérificateur de modèle disponible immédiatement pour déterminer la validité de la formule. Tandis que cette application emploie seulement une petite partie de la puissance et de la généralité de la logique temporelle et du vérificateur de modèle, l'approche semble intéressante pour établir des propriétés plus fines du code d octet qui dépassent les propriétés de base de sûreté de la vérification de code d octet. Brisset [A2] et Coglio [A3], indépendamment, extraient l'essence de l'approche de vérification de modèles : l'idée d'explorer tous les états accessibles de l'interprète abstrait. Ils considèrent la relation de transition obtenue en combinant la relation de transition de l'interprète abstrait au niveau de type avec la relation de « successeur » entre les instructions. Cette relation est de la forme (p,S,R) (p',S',R'), signifiant que l'interprète abstrait, commencée au CP p avec le type de pile S et le type de registre R, peut abstraitement exécuter l'instruction à p et arriver au CP p avec le type de pile S et enregistrer le type R . Les transitions additionnelles (p,S,R) err sont proposées pour refléter les états (p,S,R) dans lesquels l'interprète abstrait est « obstrué » (ne peut pas faire une transition parce qu'un certain vérification échoué). (p,S,R) (p ,S ,R ) si instr(p) : (S,R) (S ,R ) et p est un successeur de p (p,S,R) err si instr(p) : (S,R) L'algorithme de vérification BC (Brisset-Coglio) explore simplement tous les états accessibles par des applications répétées de la relation étendue de transitions commençant 66 par l'état initial = (0, ,(P0, ,Pn-1,T, ,T)) correspondant à l'entrée de méthode. En 0 d'autre termes, on écrit C( ) = | . pour la fermeture d'une étape d'un ensemble d'états , l'algorithme BC calcule par itération de point fixe l'ensemble le plus petit c contenant 0 et fermé sous C. Si err c, l'état d'erreur est accessible et le code d octet est rejeté. Autrement, le code d octet passe la vérification. Dans le dernier cas (err n'est pas accessible), l'exactitude de l'interprétation abstraite (comme prouvé dans [A23]) garantit que l'interprète concret et défensif de JVM n'échouera jamais une vérification de sûreté pendant l'exécution du code de méthode, par conséquent le code d octet est sûr. Cet algorithme se termine toujours parce que le nombre d'états distincts est fini (quoique grand), puisqu'il y a un nombre fini de types distincts utilisés dans le programme, et que la taille de la pile est bornée, et que le nombre de registres est fixe. Le problème avec des sous-programmes décrits dans la section A.3.3 disparaît complètement dans cette approche. Il suffit d'employer les transitions suivantes pour les instructions jsr et ret : (p,S,R) ( ,retaddr(p+3).S,R) si instr(p) = jsr (p,S,R) (q,S,R) si instr(p) = ret r et R(r) = retaddr(q) (p,S,R) err si instr(p) = ret r et R(r) retaddr(q) Un autre intérêt de cette approche est qu'il nous permet de reconsidérer certaines des décisions de conception expliquées dans les sections A.3.1.2, A.3.1.3 et A.3.2. En particulier, l'algorithme BC ne calcule jamais les bornes supérieures des types, mais vérifie simplement des relations de sous-types entre les types. Ainsi, il peut être appliqué à n'importe quelle relation de sous-type bien fondée, pas seulement les relations qui forment un semi-treillis. En effet, il peut suivre des types d'interface et vérifier des instructions invokeinterface exactement, sans devoir traiter des ensembles de types ou de complétion de treillis. De même, il est possible de vérifier le code où la taille de pile n'est pas la même sur tous les chemins menant à une instruction. Cette approche a été prouvée l exactitude à l aide de Coq dans [A2] et aussi dans [A13] en utilisant Isabelle/HOL. Pourtant, l'algorithme de vérification BC basé sur la vérification de modèles peut être impraticable : il fonctionne à temps exponentiel en le nombre de 67 branches de conditionnelle ou de N-chemins dans la méthode. Considérons le joint de flux de données représenté sur la Figure 11, partie gauche. Tandis que les algorithmes de flux de données vérifient les instructions suivant le point de joint seulement une fois dans la supposition r : C où C = bs(D,E) (bs est la borne supérieure), l'algorithme BC les vérifie deux fois, une fois dans la supposition r : D, une fois dans la supposition r : E. Considérons maintenant le flux de contrôle montré dans la partie droite de la Figure 11. Il se compose de N constructions conditionnelles, chacune assigne un type différent aux registres r1 rN. Les instructions après le dernier conditionnel doivent être vérifiées 2N fois sous 2N types différents du registre. Figure 11 Flux de contrôle dans l'approche de vérification de modèles. Une façon pour améliorer l'efficacité de l'algorithme BC est de réduire le nombre d'états qui doivent être explorés par l'application judicieuse d'étapes d'élargissement (widening steps) : quelques transitions (pc,S,R) (pc',S',R') peuvent être remplacées par (pc,S,R) (pc',S'',R'') où R' <: R'' et S' <: S''. Si l'état d'erreur err n'est toujours pas accessible, le code d octet reste sûr. Une étape d'élargissement comme ci-dessus réduit le nombre total d'états à explorer si on choisit judicieusement R et S , par exemple si l'état élargi (pc',S'',R'') est déjà accessible. A.4 Vérification pour les dispositifs mobiles Les machines virtuelles de Java fonctionnent non seulement dans les ordinateurs individuels et les postes de travail, mais également dans une variété d'ordinateurs embarqués, tels que les PDAs, les téléphones portables, et les cartes intelligentes (smartcards). L'application du modèle de Java à ces dispositifs exige que la vérification de code d octet soit effectuée sur le système embarqué, lui-même. Cependant, la vérification de code d octet est un processus cher qui peut excéder les ressources (capacité de 68 traitement et espace mémoire fonctionnant) de petits systèmes embarqués. Par exemple, une carte typique de Java (Java-enabled smart card) a 1 ou 2 kilo-octets de mémoire de fonctionnement, 16 à 32 kilo-octets de mémoire persistante réenregistrable (rewritable persistent memory), et un microprocesseur de 8 bits qui est approximativement 1000 fois plus lent qu'un ordinateur individuel. Avec tels petits dispositifs, l'algorithme conventionnel de vérification de code d octet (tel que celui de Sun ou ceux de vérificateurs polyvariants décrits en ce rapport) ne peut pas fonctionner dans la mémoire de fonctionnement : la quantité de RAM disponible est trop petite pour stocker la pile et pour enregistrer des types impliqués pendant la vérification de code d octet. Une façon est d'employer la mémoire persistante réenregistrable au lieu de la RAM pour maintenir les structures de données du vérificateur. On a longtemps cru que cette solution était impraticable, parce que ces structures de données changent rapidement pendant la vérification de code d octet. Le travail récent par Deville et Grimaud [A5] développe des codages spéciaux des types et des représentations de mémoire des données de vérification qui allègent cette issue, et suggère que cette approche est en effet faisable. Une autre façon pour adapter un vérificateur de code d octet dans un petit dispositif est de concevoir les nouveaux algorithmes de vérification qui peuvent fonctionner dans des quantités minuscules de mémoire de fonctionnement. On discute maintenant deux tels algorithmes. A.4.1 Vérification légère de code d octet Inspiré par le code de Necula et Lee [A18], Rose et Rose [A27] proposent de couper la vérification de code d octet en deux phases : le producteur de code calcule les types de pile et de registre aux cibles de branchement et transmet ces « certificats » avec le code d octet; le système embarqué, alors, vérifie simplement que le code est bien typé par rapport aux types donnés dans les certificats, au lieu d'inférer ces types lui-même. En d'autres termes, le système embarqué ne résout plus itérativement les équations de flux de données caractérisant le code d octet correct, mais simplement les vérifie que les types fournis dans les certificats de code sont en effet une solution de ces équations. Les avantages de cette approche sont doubles. D'abord, la vérification d'une solution est plus rapide que l'inférence, puisque on évite le coût de l'itération de point fixe. Ceci 69 accélère la vérification dans une certaine mesure. Deuxièmement, des certificats sont seulement lus, mais pas modifiés pendant la vérification. Par conséquent, ils peuvent être stockés dans la mémoire persistante réenregistrable des dispositifs mobiles. Une limitation pratique de cette approche est que les certificats sont relativement grands : environ 50% de la taille du code qu'ils annotent [A15]. Même si des certificats sont stockés dans la mémoire persistante, ils peuvent excéder l'espace mémoire disponible. Le travail de Rose [A26] aborde la question de la taille de certificat en notant que l'information de type pour certaines branches de cible peut être omises par le certificat, lorsque cette information de type est correctement calculée par l'algorithme de vérification dans son balayage linéaire du code d octet. Cette stratégie hybride de vérification réduite donc la taille du certificat, au coût d'exigences de mémoire de fonctionnement augmentée pour stocker l'information de type inférée pour des branches de cible non décrites dans le certificat. La vérification légère de code d octet est utilisée dans le KVM, une des variantes embarquées de la JVM de Sun [A31]. Elle a été formalisée et prouvée valide et complète par Klein et Nipkow [A11], en utilisant le prouveur Isabelle/HOL. Ces présentations diffèrent dans leur traitement des sous-programmes. La présentation de Rose [A27][A26] traite seulement la vérification monovariante et ne manipule pas des sous-programmes. L'implémentation de KVM suit cette approche, et se fonde sur l'expansion de sousprogramme avant la vérification, en tant qu'une partie de la phase de génération de certificat. La formalisation de Klein et de Nipkow, cependant, est applicable non seulement à la vérification monovariante, mais à n'importe quel algorithme de vérification qui peut être exprimé en utilisant des équations de flux de données, y compris les algorithmes polyvariants de la section A.3.4. A.4.2 Vérification pour Java Smart Card Le vérificateur de code d octet de Java Card décrit dans [A15] s'occupe du problème de mémoire sur un autre angle. Comme les vérificateurs standards de code d octet, il résout des équations de flux de données en utilisant l'itération de point fixe. Pour réduire des exigences de mémoire, cependant, il a seulement un type global de registre qui est partagé entre tous les points de contrôle dans la méthode. En d'autres termes, la solution qu'il 70 implique est qu'un registre donné a le même type dans toute la méthode. Pour des raisons semblables, elle exige également que la pile d'expression soit vide à chaque instruction de branchement. Avec ces restrictions supplémentaires, la vérification de code d octet peut être faite dans l'espace O(Mpile + Mreg), au lieu de O(Nbranche × (Mpile + Mreg)) pour l'algorithme de Sun, où Nbranche est le nombre de branches de cible. Dans la pratique, les exigences de mémoire sont assez petites que toutes les structures de données adaptées confortablement dans la RAM sur une smart card. Un inconvénient de cette approche est que l'initialisation de registre ne peut plus être vérifiée statiquement, et doit être remplacé par l'initialisation au temps d'exécution à une valeur sûre (typiquement, la valeur null) sur l'entrée de méthode. Un autre inconvénient est que les restrictions supplémentaires imposées par le vérificateur sur-carte cause le rejet de code d octet parfaitement légal (qui passe le vérificateur de Sun). Pour adresser le denier problème, ce vérificateur se fonde sur une transformation horscarte, exécutée sur le code d octet de l'appliquette, qui transforme n'importe quel code d octet légal (qui passent le vérificateur de Sun) en code d octet équivalent qui passe le vérificateur sur-carte. Les transformations hors-carte incluent des normalisations de pile autour de la réallocation de branches et de registres par la coloration du graphe, et sont décrites dans [A15]. Ces transformations peuvent augmenter la taille du code, et aussi le nombre de registres utilisés par une méthode. Cependant, l'expérience montre que ces augmentations sont minimales : sur le code typique de Java Card, l'augmentation du nombre d'instructions est de moins de 2%, et l'augmentation des registres est négligeable [A15]. Cette phase de transformation hors-carte de code joue un rôle semblable à celui d'ajouter des certificats de Rose et Rose : tous les deux sont des traitements hors-carte qui ajoute assez d'information à l'appliquette pour faciliter sa vérification sur-carte. Cependant, le transformateur hors-carte de code inclut directement son information à l'intérieur du code, 71 via des transformations de code, au lieu de le stocker dans les certificats séparés. D'ailleurs, la taille d'information supplémentaire est beaucoup plus petite (2% vs. 50%). A.5 Conclusions La vérification de code d octet de Java est maintenant une technique bien étudiée. Une question est si la vérification de code d octet peut dépasser les propriétés de base de sûreté et d'initialisation de type, et établit statiquement des propriétés plus avancées des appliquettes et des applications, tels que l'utilisation de ressource (bornant la quantité de mémoire allouée) et le réactivation (bornant le temps de fonctionnement d'une appliquette ou d'une application entre deux interactions avec le monde extérieur). D'autres propriétés d'intérêt incluent le contrôle d'accès et le flux de l'information. Actuellement, le Java Security Manager exécute tous les contrôles d'accès dynamiquement, en utilisant l'inspection de pile [A8]. On a proposé diverses analyses et transformations statiques de programme pour exécuter certains de ces contrôles statiquement [A10][A33]. Dans le monde des systèmes embarqués, l implémentation actuelle de Sun dans KVM répond aux questions de ressources limitées en coupant le processus de vérification en deux phases. A l image de la taille d information supplémentaire facilitant la vérification surcarte, l approche décrite dans la section A.4.2 donne un meilleur résultat. Notons finalement que la sécurité du modèle de bac à sable se fonde non seulement sur la vérification de code d octet, mais également sur l'implémentation appropriée de l'API. La majorité d'attaques basée sur les appliquettes connues exploitent des bugs dans l'API d'une manière de sûreté de type, plutôt que de casser la sûreté de type par des bugs dans le vérificateur. La vérification de l'API est un domaine d'application pour les méthodes formelles. 72 Références [1] Alves T., Felton D., ARM Ltd., 2004. TrustZone: Integrated Hardware and Software Security. http://www.arm.com/pdfs/TZWhitepaper.pdf [2] Anderson R., Kuhn M., 1996. Tamper http://www.cl.cam.ac.uk/users/rja14/tamper.html [3] Cabir. http://www.f-secure.com/v-decs/cabir.shtml [4] CERT Coordination Center. Vulnerability notes database. http://www.kb.cert.org/vuls/ [5] Chen Y., Venkatesan R., Cary M., Sinha S., Jakubowski M.H., 2002. Oblivious hashing : A stealthy software integrity veriffication primitive. Proc. Int. Wkshp. Information Hiding. [6] Chess B., 2002. Improving computer security using extended static checking. Proc. IEEE Symposium on Security and Privacy. [7] Chien. E, Szor P. Blended attack exploits, vulnerabilities, and buffer-overow techniques in computer virus. Symantec White Paper. http://securityresponse.symantec.com/avcenter/whitepapers.html [8] Clarke E.M., Jha S., Marrero W., 1998. Using state space exploration and a natural deduction style message derivation engine to verify security protocols. Proc. IFIP Working Conf. on Programming Concepts and Methods. [9] Common Vulnerabilities and Exposures. http://cve.mitre.org/ Resistance [10] COMP128 (GSM authentication algorithm) http://www.riscure.com/CryptoTools/COMP128.html - A tool, Cautionary version Note. 1.0. [11] Detlefs D.L., Leino K., Nelson G., Saxe J., 1998. Extended static checking. Systems Research Center, Compaq Inc. [12] Discretix Technologies Ltd. http://www.discretix.com [13] Dust. http://www.f-secure.com/v-descs/dtus.shtml [14] Embedded Linux Consortium, http://www.embedded-linux.org/ [15] ETSI, 1995. Digital cellular telecommunications system (phase 2+), Specification of the Subcriber Identity Module - Mobile Equipment (SIM - ME) interface (GSM 11.11). 73 [16] ETSI, 1996. Digital cellular telecommunications system (phase 2+), Specification of the SIM Application Toolkit for the Subscriber Identity Module - Mobile Equipment (SIM - ME) interface (GSM 11.14). [17] Forum Nokia, 2003. Symbian OS: Getting Started with C++ Application Development. White Paper. http://www.forum.nokia.com/ [18] F-Secure Antivirus. http://www.f-secure.com/products/fsmavs60/ [19] Garfinkel T., Rosenblum M., Boneh D., 2003. Flexible OS Support and Applications Trusted Computing. Proc. 9th Wkshp Hot Topics in Operating Systems. [20] Ghosh A.K., Swaminatha T.M, 2001. Software security and privacy risks in mobile ecommerce. Communications of the ACM. [21] Gilmont T., Legat J.D., Quisquater J.J, 1998. An Architecture of Security Management Unit for Safe Hosting of Multiple Agents. Proc. Int. Wkshp on Intelligent Communications and Multimedia Terminals. [22] Glaskowsky P.N., 2003. Microsoft Details Secure PC Plans. Microprocessor Report, Instat/MDR. [23] Gowdiak A., 2004. Java 2 Micro Edition security vulnerabilites. HITB Security Conference, Kuala Lampur, Malaysia. [24] IBM Inc. Secure Coprocessing. http://www.research.ibm.com/scop/ [25] IBM Inc. The IBM 3.ibm.com/security/cryptocards/ PCI Cryptographic [26] Intel Inc. LaGrande Technology http://www.intel.com/technology/security Coprocessor. for Safer http://www- Computing. [27] Java Community Process. Java Specification Request JSR 177 Security and Trust Services API for J2ME (SATSA) 1.0. http://www.jcp.org/en/jsr/detail?id=177 [28] Kelsey J., Schneier B., Wagner D., Hall C. 1998. Side channel cryptanalysis of product ciphers. In Proceedings of the ESORICS 98. [29] Kommerling, O., Kuhn, M. G. 1999. Design principles for tamper-resistant smartcard processors. In Proceedings of the USENIX Wkshp on Smartcard Technology (Smartcard 99). [30] Kuhn M., 1997. The TrustNo http://www.cl.cam.ac.uk/mgk25/trustno1.pdf 1 Crytoprocessor Concept. [31] Lie D., Thekkath A., Horowitz M., 2003. Implementing an untrusted operating system on trusted hardware. Proc. ACM Symposium on Operating Systems Principles. 74 [32] Lie D., Thekkath C., Mitchell M. Lincoln P., Boneh D., Mitchell J.C., Horowitz M., 2000. Architectural support for copy and tamper resistant software. Proceedings of the ACM Architectural Support for Programming Languages and Operating Systems (ASPLOS). [33] Lowe G., 1998. Towards a completeness result for model checking of security protocol. Proc. 11th Computer Security Foundations Wkshp. [34] Microsoft Inc. Next-Generation Secure Computing http://www.microsoft.com/resources/ngscb/default.mspx Base (NGSCB). [35] Microsoft Windows Embedded. http://www.microsoft.com/windowsce [36] Mobile Information Device http://java.sun.com/products/midp/ Profile (MIDP), JSR 37, JSR 118. [37] National Institute of Standards and Technology. Virus Information. Computer Security Resource Center. http://csrc.nist.gov/virus/ [38] OMA Forum, Open Mobile Alliance. http://www.openmobilealliance.org/ [39] Quisquater J. J., Samyde D. 2002. Side channel cryptanalysis. In Proceedings of the SECI. [40] Security guide in Symbian OS Guide, http://www.symbian.com/developer/techlib/v70sdocs/doc_source/DevGuides/SecurityGuide/i ndex.html [41] SecurityFocus. Siemens S55 Cellular Telephone SMS Confirmation Message Bypass Vulnerability. http://www.securityfocus.com/bid/10227/discussion/ [42] Skulls. http://www.f-secure.com/v-descs/skulls.shtml [43] Suh, G.E., Clarke D., Gassend B. Van Dijk M., Devedas S., 2003. AEGIS: Architecture for tamper-evident and tamper-resistant processing. Proceedings of the International Conference on Supercomputing (ICS '03). [44] Symantec Corporation. Symantec Client Security for Nokia Communicator. http://enterprisesecurity.symantec.com/products/products.cfm?ProductID=663&EID=0 [45] Symantec Corporation. Latest http://www.symantec.com/avcenter/vinfodb.html Virus Threats. [46] Symbian Ltd. http://www.symbian.com/ [47] Symbian OS System Definition Detailed View. http://www.symbian.com/developer/techlib/papers/SymbOS_cat/SymbianOS_cat.html [48] Symbian SDKs. http://www.symbian.com/developer/sdks.asp 75 [49] Symbian Signed. https://www.symbiansigned.com/ [50] The K Virtual Machine. http://java.sun.com/products/cldc/wp/ [51] Trusted Computing Group. https://www.trustedcomputinggroup.org/ [52] http://www.securer.com/alertes/2000/timifonica.htm [53] WAP-198, Wireless Identity Module. [54] WAP-199, Wireless Transport Layer Security Specification. [55] Yee B., 1994. Using Secure Coprocessors. PhD thesis, Carnegie Mellon University. [A1] Bertot Y., 2001. Formalizing a JVML verifier for initialization in a theorem prover. Proc. Computer Aided Verification (CAV 01), Lecture Notes in Computer Science. [A2] Brisset P., 1998. Vers un vérifieur de bytecode Java certifié. Ecole Normale Supérieure, Paris, October 2nd 1998. [A3] Coglio A., 2002. Simple verification technique for complex Java bytecode subroutines. 4th ECOOP Workshop on Formal Techniques for Java-like Programs. [A4] Cohen R., 1997. The defensive Java virtual machine specification. Technical report, Computational Logic Inc. [A5] Deville D. et G. Grimaud, 2002. Building an impossible verifier on a Java Card. USENIX Workshop on Industrial Experiences with Systems Software (WIESS 02). [A6] Freund S. N. et J. C. Mitchell. A type system for the Java bytecode language and verifier. Journal of Automated Reasoning. [A7] Freund S. N. et J. C. Mitchell, 1999. A type system for object initialization in the Java bytecode language. ACM Transactions on Programming Languages and Systems. [A8] Gong L., 1999. Inside Java 2 platform security: architecture, API design, and implementation. The Java Series. Addison-Wesley. [A9] Hagiya M. et A. Tozawa, 1998. On a new method for dataflow analysis of Java virtual machine subroutines. Lecture Notes in Computer Science. 76 [A10] Jensen, T., D. Le Metayer, et T. Thorn, 1999. Verification of control flow based security properties. IEEE Symposium on Security and Privacy. [A11] Klein G., 2003. Verified Java bytecode verification. Ph.D. thesis, Technische Universitat Munchen. [A12] Klein G. et T. Nipkow, 2002. Verified bytecode verifiers. Theoretical Computer Science. [A13] Klein G. et M. Wildmoser. Verified Bytecode Subroutines. Journal of Automated Reasoning. [A14] Knoblock T. et J. Rehof, 2000. Type elaboration and subtype completion for Java bytecode. 27th symposium Principles of Programming Languages. [A15] Leroy X., 2002. Bytecode Verification for Java Smart Card. Software Practice & Experience. [A16] Lindholm T. et F. Yellin, 1999. The Java Virtual Machine Specification. The Java Series. Addison-Wesley. Second edition. [A17] Muchnick S. S., 1997. Advanced compiler design and implementation. Morgan Kaufmann. [A18] Necula G. C., 1997. Proof-carrying code. 24th symposium Principles of Programming Languages. pp. [A19] Nielson F., H. R. Nielson, et C. Hankin, 1999. Principles of program analysis. SpringerVerlag. [A20] Nipkow T., 2001. Verified bytecode verifiers. Foundations of Software Science and Computation Structures (FOSSACS 01), Lecture Notes in Computer Science. [A21] O'Callahan R., 1999. A simple, comprehensive type system for Java bytecode subroutines. 26th symposium Principles of Programming Languages. [A22] Posegga J. et H. Vogt, 1998. Java bytecode verification using model checking. Workshop Fundamental Underpinnings of Java. [A23] Pusch C., 1999. Proving the soundness of a Java bytecode verifier specification in Isabelle/HOL. Lecture Notes in Computer Science. [A24] Qian Z., 1998. A formal specification of Java virtual machine instructions for objects, methods and subroutines. Lecture Notes in Computer Science. Springer - Verlag. 77 [A25] Qian Z., 2000. Standard fixpoint iteration for Java bytecode verification. ACM Transactions on Programming Languages and Systems. [A26] Rose E., 2002. Vérification de code d'octet de la machine virtuelle Java : formalisation et implantation. Ph.D. thesis, University Paris 7. [A27] Rose E. et K. Rose, 1998. Lightweight bytecode verification. OOPSLA Workshop on Formal Underpinnings of Java. [A28] Schmidt D. A., 1998. Data flow analysis is model checking of abstract interpretations. 25th symposium Principles of Programming Languages. [A29] Stark R., J. Schmid, et E. Borger, 2001. Java and the Java Virtual Machine. Springer-Verlag. [A30] Stata R. et M. Abadi, 1999. A type system for Java bytecode subroutines. ACM Transactions on Programming Languages and Systems. [A31] Sun Microsystems White paper, 2000. Java 2 platform micro edition technology for creating mobile devices. [A32] Trusted Logic, 2001. Off-card bytecode verifier for Java Card. Distributed as part of Sun's Java Card Development Kit. [A33] Walker D., 2000. A type system for expressive security policies. 27th symposium Principles of Programming Languages. [A34] Yellin F., 1995. Low level security in Java. Proceedings of the Fourth International World Wide Web Conference. This document was created with Win2PDF available at http://www.daneprairie.com. The unregistered version of Win2PDF is for evaluation or non-commercial use only.