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.