Consignes

Ce premier TP est un TP d'introduction. Il ne donnera lieu ni à un compte-rendu ni à une note. Les TPs 2 et 3 utiliseront MINIX et tout ce qui se trouve dans ce TP (sauf la dernière partie) sera considéré comme acquis.

Liens utiles

1. Préliminaires : MINIX et QEMU

Linux

Comme nous allons utiliser des outils UNIX, tout le TP se fera sous Linux (système principal) et MINIX (système émulé).

Les systèmes Unix possèdent de nombreux utilitaires en ligne de commande bien pratiques tels que wc (compte les lignes, mots, octets dans un fichier), sed (remplacement de chaînes de caractères dans un fichier), grep (recherche d'expression régulières dans un fichier), cut (sélection de colonnes dans un fichier), ... Certaines de ces commandes sont des filtres et peuvent être composées grâce à l'opérateur |. Nous vous conseillons d'apprendre à les utiliser car sous MINIX, vous aurez seulement une interface en ligne de commande. (Vous aurez par contre toujours accès à votre session graphique Linux.)

MINIX

MINIX ("Mini Unix") est à l'origine un système d'exploitation à but pédagogique. Depuis la version 3, il s'agit également d'un système d'exploitation de type UNIX (conforme à la norme POSIX) qui peut facilement être adapté et utilisé dans des systèmes embarqués. Même s'il s'agit d'un système minimaliste de type micro-noyau, il est possible d'installer toute une chaîne de développement (gcc, emacs, etc.) ainsi qu'un serveur X.

La dernière version stable est la version 3.3.0, mais nous utiliserons une version précédente (3.1.8) qui est plus facile à modifier...

QEMU

Plutôt que d'installer MINIX réellement sur le disque, nous allons l'installer dans une machine virtuelle. Cela a plusieurs intérêts comme :

Nous allons utiliser QEMU, un émulateur libre qui permet d'émuler de nombreux type de processeurs (i386, x86_64, sparc, PowerPC, ...). Autrement dit, nous pouvons choisir le type de processeur utilisé par la machine virtuelle. Comme MINIX est un système 32 bit, nous allons utiliser le processeur i386.

1.1. Installation de MINIX

L'installation de MINIX est relativement aisée. Nous allons installer MINIX dans un « disque virtuel » (contenu dans un fichier) et l'utiliser à travers QEMU.

Préparation du système virtuel

Pour ne pas saturer vos comptes personnels (l'image décompressée du CD d'installation fait environ 115MB, et il faut compter environ 300MB pour pouvoir compiler un système confortablement), nous allons installer MINIX dans un répertoire temporaire.

Ceci signifie que votre installation de MINIX sera disponible uniquement sur l'ordinateur que vous utilisez pour le TP, et seulement jusqu'au prochain redémarrage.

Une autre possibilité est de l'installer dans un sous répertoire de /local/, sur lequel vous avez les droits d'écriture. Votre système virtuel ne sera pas effacé au redémarrage, mais ne sera disponible que sur la machine initiale, et utilisable par toute personne loggée sur cette machine...

Les lignes de commandes que vous devrez taper sont indiquées comme ceci :

LINUX$ commande1
LINUX$ commande2

Notez bien que :

Avant d'installer MINIX à proprement parler, il faut booter une machine virtuelle en utilisant l'image du CD d'installation. Tout ceci se fait de la manière suivante :

  1. création d'un répertoire TP-OS dans /tmp/ :

    LINUX$ cd /tmp/
    LINUX$ mkdir TP-OS
    LINUX$ cd TP-OS
    

  2. vérification de l'espace libre restant sur le disque

    LINUX$ df -h
    
    La partition racine (montée sur /) doit contenir au moins 1G.

  3. vérification que QEMU est bien installé

    LINUX$ qemu --version
    

  4. téléchargement de l'image du CD d'installation dans le répertoire /tmp/TP-OS/, soit par un navigateur (voir les liens au début du sujet), soit avec

    LINUX$ wget http://www.lama.univ-savoie.fr/~hyvernat/Enseignement/1617/info524/minix3_1_8_ide_r8165.iso.bz2
    

  5. décompression de l'image

    LINUX$ bunzip2 minix3_1_8_ide_r8165.iso.bz2
    

  6. vérification de l'empreinte de l'image

    LINUX$ md5sum minix3_1_8_ide_r8165.iso
    
    Vous devez obtenir e3575aba599f07ae58dd532fde8d5407

  7. création d'un disque virtuel minix-TP.img de 300MB avec QEMU

    LINUX$ qemu-img create minix-TP.img 300M
    

Nous avons maintenant tout ce qu'il faut pour installer Minix !

Installation du système virtuel

Nous allons maintenant démarrer le système virtuel en bootant "sur le CDROM virtuel" (fichier contenant l'image ISO du CD d'installation) :

LINUX$ qemu-system-i386 -m 256 -cdrom minix3_1_8_ide_r8165.iso -drive file=minix-TP.img,if=ide,media=disk,cache=writeback -boot d

Les arguments intéressants sont :

Comme la plupart des systèmes réels, QEMU va essayer de booter sur le CD avant de booter sur le disque dur. QEMU lancera donc le CD d'installation.

Si le boot sur le CD virtuel se passe bien, vous devriez obtenir une nouvelle fenêtre QEMU qui contient le système émulé. L'installation se fait maintenant en suivant les instructions du CD d'installation : loggez vous en tant qu'administrateur (root, sans mot de passe).

Attention, il se peut que l'affichage initial ne se mette pas à jours avant le login. Il faut alors appuyer sur Entrée afin de voir le login apparaitre.

Lancez ensuite la commande setup pour commencer l'installation.

MINIX# setup

et suivez les instructions :

Vous pouvez ensuite éteindre la machine virtuelle :

MINIX# shutdown

et quitter QEMU :

cd>off

Félicitations, vous avez installé MINIX sur une machine virtuelle. Pour démarrer cette machine virtuelle, on utilise la commande

LINUX$ qemu-system-i386 -m 256 -drive file=minix-TP.img,if=ide,media=disk,cache=writeback

(Notez qu'on ne déclare plus d'image pour le CD.)

1.2. Test et configuration de MINIX pour ce TP

Lors du démarrage de MINIX, il est préférable de choisir le second système ("2 Start Custom MINIX 3") car c'est le noyau qui est modifié par défaut. Le choix 1 reste (normalement) le système officiel.

Au boot (avant le login), le clavier est par défaut en mode QWERTY. Pour choisir le noyau 2, il faut donc appuyer sur la touche 2 sans la touche Shift.

Les premières étapes pour configurer MINIX seront :

  1. créer un mot de passe pour l'administrateur:

    MINIX# passwd
    
  2. créer un nouvel utilisateur « normal » et lui donner un mot de passe :

    Créez un utilisateur dans le groupe other et donnez lui le login etu. Son répertoire personnel sera dans /home/etu. La commande nécessaire s'appelle adduser...

    Quittez la session root et utilisez ce nouveau compte utilisateur...

L'éditeur par défaut de MINIX, s'appelle mined :

MINIX$ mined <nom_de_fichier>

Il suffit de connaître 2 commandes :

Pour avoir d'autres raccourcis clavier, reportez vous aux commentaires au début du fichier /usr/src/commands/mined/mined1.c, section 4 (lien local).

Pour les TPs suivant, une image MINIX déjà configurée vous sera fournie. Elle contiendra entre autre l'éditeur de texte Nano et ViM afin de rendre l'édition de code source plus facile.

Commencez par éditer le fichier .ashrc dans votre répertoire personnel. Il s'agit d'un fichier texte qui permet de configurer votre shell. Vous pouvez par exemple :

2. Les processus POSIX

Afin de gagner du temps dans la partie suivante, il est fortement conseillé de recompiler le noyau MINIX (sans modification) pendant que vous faites cette partie du TP. Pour cela, loggez-vous comme administrateur sur la deuxième console (accessible avec Alt-F2) et

MINIX# cd /usr/src/tools/
MINIX# make image

Vous pouvez revenir sur la première console (Alt-F1) pendant que le noyau compile...

  1. Écrivez un petit programme en C qui :
    • imprime un message,
    • se duplique (fork()) et affiche le PID du sous-processus créé
    • le processus créé affiche un message.
    Faites en sorte que vos processus ne terminent jamais, pour avoir le temps de faire la suite.

  2. Compilez et lancez votre programme en arrière plan :

    MINIX$ cc proc1.c -o proc1
    MINIX$ ./proc1 &
    Salut, je m'appelle Bob et je suis le pere. Mon fils a le PID 185.
    Hej, je suis le fils de Bob.
    

  3. Vérifiez que vous avez bien créé 2 processus, et que l'un d'eux a le bon PID. Vous pouvez utiliser la commande ps pour obtenir la liste des processus.

  4. Vérifiez que vous pouvez tuer le processus père (avec la commande kill depuis le shell) sans que le fils ne disparaisse.

Recommencez mais tuez cette fois ci le fils. Que se passe t'il ?

Vérifiez qu'un processus zombie disparaît quand son père l'a « attendu ». Comment procédez-vous ?

Rappel : l'appel système pour attendre qu'un processus fils se termine est wait. Il s'utilise de la manière suivante :

    int status, pid;
    ...
    pid = wait(&status);

La variable pid aura comme valeur le PID du fils qui s'est terminé et la variable status contiendra des informations sur comme ce processus s'est terminé. (On n'aura besoin ni de l'un ni de l'autre dans cet exercice...)

Pour ces questions, vous pouvez facilement insérer des sleep(1); dans votre programme pour attendre 1 seconde. Cela vous permettra de regarder l'évolution des processus...

  1. Écrivez un petit programme C qui génère un fils. Le fils se termine et le père génère un nouveau fils, ad infinitum.

    Que se passe-t'il ?

  2. Écrivez un petit programme C qui génère un fils et se termine. Son fils génère lui aussi un fils et se termine, ...

    Que se passe-t'il ? Comment peut-on stopper la génération de ces processus ?

  3. Écrivez un programme C qui génère un processus fils. Le père et le fils génèrent alors chacun un processus fils, etc.

    Que se passe-t'il ? À quoi ressemble l'arborescence des processus ?

3. Recompiler son premier noyau

Pour installer et utiliser un nouveau noyau, il faut :

Attention : lorsque vous modifiez un fichier du noyau, n'oubliez pas de faire une sauvegarde !

MINIX# cp <fichier> <fichier.bak>

Ceci vous permettra de retrouver l'état de départ si vous faites des erreurs !

Recompilez un noyau en changeant le message de bienvenue du début :

Copyright 2010, Vrije Universiteit, Amsterdam, The Netherlands
...

De cette manière, vous pourrez vérifier que vous exécutez bien votre nouveau noyau.

Note : les sources du noyau se trouvent dans le répertoire /usr/src/kernel/

4. Modifier le « process manager »

Les exemples précédents utilisent la fonction POSIX fork(). Cette fonction est gérée par le serveur « process manager » dans /usr/src/servers/pm/.

Trouvez le fichier correspondant et modifier le pour afficher un message lors de chaque exécution de fork().

Recompilez votre noyau et testez le.

fork() n'est pas un appel système. Sous MINIX, l'architecture micro-noyau a pour conséquence qu'il y a très peu de vrais appels système. (Fonctions qui nécessitent le mode noyau.) En général, un appel système se décompose en :

  1. un processus a besoin d'une opération bas-niveau,
  2. il fait une demande au noyau (sendrec),
  3. qui demande au serveur correspondant d'effectuer la tâche,
  4. le serveur répond au noyau,
  5. le noyaux répond au processus utilisateur.

Seules les étapes 2 et 4 se font en mode noyau.

Vous pouvez obtenir la liste des commandes qui vont nécessiter ceci en regardant dans le répertoire /usr/src/lib/libc/syscall. (Je crois...)

5. Ajouter une « backdoor »

Nous allons maintenant ajouter une « porte dérobée », c'est à dire, une manière d'accéder au système en tant qu'administrateur sans fournir de mot de passe !

Si vous arrivez à distribuer votre version modifiée de Minix, vous pourrez ainsi accéder à tous les systèmes installés qui utilisent votre version...

Rappels

Pour installer la backdoor, il va falloir effectuer quelques opérations sur les chaînes de caractères.

Modification du fichier "login.c"

Il n'est pas nécessaire de comprendre tout le code de login.c pour pouvoir le modifier. La stratégie suivante permettra de créer la backdoor assez facilement :

Il n'est pas nécessaire de recompiler le noyau pour avoir la nouvelle version de login : il suffit de recompiler login.c avec la commande make dans le répertoire contenant login.c. Il faut ensuite tester le fichier exécutable, et quand il fonctionne, l'installer.

Vous devrez donc faire quelque chose comme :

MINIX# cd /usr/src/commands/login/
MINIX# cp login.c login-orig.c
MINIX# mined login.c
        < modification de "login.c" >
MINIX# make
MINIX# ./login
        < test de la commande "login" >
MINIX# cp login /usr/bin/login    < seulement si la commande "login" fonctionne correctement >

Attention ! Si vous installez une version buggée de login, vous risquez de ne pas pouvoir vous logger sur le système MINIX...

Faites une copie de sauvegarde du fichier /usr/src/commands/login/login.c, puis modifiez le pour ajouter un « backdoor » dans votre version de Minix : lors de la procédure de login, si l'utilisateur utilise le nom "Gulliver", alors il n'aura pas besoin de mot de passe et obtiendra l'accès au compte administrateur !

Amélioration, modification du fichier "getty.c"

À la fin du processus de boot, le noyau demande au serveur init (dans /usr/src/servers/init/) d'ouvrir des consoles pour se logger. (MINIX en démarre trois par défaut, Linux en démarre six.) Ces consoles sont accessibles avec Alt et une touche Fonction. C'est /usr/src/commands/login/login.c qui gère le login, et /usr/src/commands/getty/getty.c qui appelle la commande de login dans chaque console.

Nous allons modifier la backdoor pour que le mot de passe Gulliver ne fonctionne que sur la troisième console. Il va donc falloir modifier le fichier /usr/src/commands/getty/getty.c. Lorsque que vous avez pu installé la backdoor dans `login``, vous allez :

  1. compiler et installer une version normale de login.c et une version avec backdoor :

    MINIX# mv login.c _login.c
    MINIX# mv login-orig.c login.c
    MINIX# mined Makefile
            < ajout de "_login" sur la ligne "PROG=">
    MINIX# make
    MINIX# cp login _login /usr/bin/
    
    Vous aurez alors deux exécutables : login pour la version standard et _login pour la version avec backdoor.
  2. Dupliquer le code de la fonction do_login (dans le fichier /usr/src/commands/getty/getty.c) pour avoir une version qui appelle /usr/bin/_login à la place de /usr/bin/login,
  3. remplacer l'appel à do_login existant par un test :
    • si la console (variable tty_name) correspond à la troisième console ("/dev/ttyc2"), on appelle la nouvelle version de do_login,
    • sinon, on appelle do_login normalement.
  4. compiler et installer la nouvelle version de getty :

    MINIX# cd /usr/src/commands/getty
    MINIX# cp getty.c getty-orig.c
    MINIX# mined getty.c
            < modification de "getty.c" >
    MINIX# make
    MINIX# cp getty /bin/    < seulement si la compilation se fait sans erreur >
    

Modifiez votre backdoor pour que la procédure de login acceptant "Gulliver" ne soit utilisé que sur la troisième console virtuelle.