|
La Programmation C de façon sécuritaire
|
Dernière mise à jour le : 6 Août 2001
Auteur: Vincent Labrecque (limitln[at]cooptel.qc.ca)
[------------ Introduction
La programmation en C peut être vue comme un enfer. Le langage peut
sembler vous tirer dans le pied pendant que vous êtes distrait. Mais il
est tellement amusant à utiliser et a ancrer dans nos cerveaux que l'on
continue de l'utiliser quand même.
Ce texte est un petit guide, une liste de notes diverses qui pourraient
vous donner quelques pointeurs sur des choses auxquelles faire
attention. Ce n'est pas un guide très poussé, et certaines parties ont
tout simplement aucune autre utiliée que d'expliquer certaines choses à
ceux qui ne font que commencer à apprendre le C.
Si je peux donner un conseil, c'est d'intégrer la programmation robuste
automatiquement, pas comme un after-though en relisant votre code et en
fixant les problèmes. Faire du bound-checking doit être automatique,
utiliser snprintf() aussi, etc. Sinon c'est sur que vous allez l'oublier
une fois ou l'autre, et finalement une autre fois ou votre programme est
utilisé par un programme root et permet à un script-kiddie de cracker
votre machine :-)
[------------ Redistribution
Vous pouvez librement redistribuer ce texte, en autant que vous
respectez la "GNU Free Documentation License":
Copyright (c) Vincent Labrecque (limitln@cooptel.qc.ca) Permission is
granted to copy, distribute and/or modify this document under the terms
of the GNU Free Documentation License, Version 1.1 or any later version
published by the Free Software Foundation; with no Invariant Sections,
with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the
license is included in the section entitled "GNU Free Documentation
License".
Vous pouvez trouver la version la plus récente de ce document à :
http://mtl2600.dhs.org/textes/secprog_c.txt
[----------- Strings (chaînes de caractères)
Les opérations sur les chaînes de caractères sont parmis les plus
grandes sources de bugs. Par exemple, les buffers overflows sont pour la
majorité des problèmes de fonctions qui ne peuvent pas trouver la fin
d'une string et se promenant partout dans process-space. Il y a
beaucoup de bons textes sur ce sujet, je n'en parlerai donc pas
spécifiquement ici. Le meilleur texte à ce propos est "Smashing The
Stack For Fun And Profit", de Aleph One, qui a été publié dans phrack
49, l'article 14. C'est _la_ référence sur ce sujet.
Le nom de sizeof() peut donner l'impression que c'est une façon sure de
savoir comment de données peuvent entrer dans un certain espace de
mémoire. Il faut cependant faire attention au type que l'ont donne a
sizeof(). Il faut savoir que sizeof() est généré au compile-time, voyez:
$ cat foo.c #include
int main(void) { int a; a = sizeof(a); exit(0); } $ gcc -S foo.c -o -
# générer l'assembleur a stdout [...] movl $4,-4(%ebp)
# a = sizeof(a) [...]
Le 4 (sizeof(int) sur une machine 32bits) est mis directement dans le
code, il faut donc que quand il le génère, il le sache.
#include
#define NELEMS(x) (sizeof(x)/sizeof(x[0]))
int main() { char str[100], *str2;
printf("str a de la place pour %d characteres\n", NELEMS(str)); str2 =
(char *)malloc(2 * sizeof(char)); printf("str2 a de la place pour %d
characteres\n", NELEMS(str2));
return 0; }
$ ./t1 str a de la place pour 100 charactères str2 a de la place pour 4
charactères
Bon, voyons ce qui se passe ici. str[] est une chaîne de caractères
statique, tandis que str2 est simplement un pointeur à un endroit
quelconque dans la mémoire. Quand le compileur génère le code pour ça,
il fout de la place pour les variables locales. Dans ce cas, c'est
| 100 bytes pour str | sizeof(char *)
pour str2 |
Quand malloc alloue de la mémoire, il rend tout simplement une partie de
mémoire dans le heap du programme valide, ce qui est la valeur de str2.
Alors:
| 100 bytes pour str | ptr dans le heap
|
sizeof() est une opération compile-time, le compileur fout cette valeur
dans le code quand il compile ton programme. Donc, nous voyons assez
rapidement que le compileur a AUCUNE idée de combien de mémoire malloc()
a validé. Par contre, str, c'est le compileur lui-même qui à tassé le
pointeur de stack, il sait donc exactement combien de place il a.
Il y a un autre cas à considérer, si le pointeur est passé à une
fonction, peut-elle faire un sizeof() ?
int foo_func(char *p) { printf("sizeof=%d\n",sizeof(p)); }
int main() { char str[4] = "foo"; /* une place pour le '\0' */
foo_func(str); }
Tout ce que nous devons savoir c'est comment la fonction reçoit le
pointeur. Il y a (au moins) deux façons d'aborder ce problème. En C, on
passe les paramètres "par valeur". Pour les strings, ça signifie qu'on
passe le /pointeur/ à la string, pas son contenu. (parce que, après
tout, on peut mettre n'importe quoi dans un char []). Donc, quand
foo_func est appellée, le stack ressemble à:
| saved registers... | pointer a str dans main |
Encore une fois, le compileur a AUCUNE manière de savoir combien il y a
d'espace à l'endroit ou "p" pointe.
Pour ça, attention surtout à du code comme:
char *a;
a = (char *)malloc(2 * sizeof(char)); strncpy(a, foostring, sizeof(a));
Parce que sizeof(char *) > (2 * sizeof(char)) sur plusieurs
architectures
(sur ma machine i386: $ ceval 'printf("%d VS %d\n", sizeof(char *), 2 *
sizeof(char));' 4 VS 2 )
Pour le dire d'une autre façon, quand on appelle sizeof(variable), c'est
comme si on faisait:
sizeof(typeof(variable)), donc
char *foo; a = sizeof(foo);
Est équivalent à sizeof(char *), ce qui est, bien entendu, la grosseur
d'un pointeur et non ce qu'il pointe.
[----------------- Information sur la disposition des chaînes de
caractères
Les chaînes de caractères sont composées d'une chaîne de caractères,
évidemment, suivie d'un NUL byte '\0', pour indiquer la fin de la
chaîne. Seulement, quand vous reservez de la mémoire, qu'elle soit
statique ou non, vous obtenez la grosseur PRイISE que vous avez demandé,
vous devez donc prévoir un byte de plus pour le NUL, si vous prévoyez
utiliser des caractères.
(ex: char *mystrdup(char *ostr) { char *o = malloc(strlen(ostr) + 1);
if(o) strcpy(o, ostr);
return o; } )
C'est simple, mais il ne faut pas oublier le +1, sinon il va manquer 1
byte, et même s'il y a de bonnes chances que malloc nous donne un block
plus gros que (strlen(ostr) + 1), mais il vaut mieux ne pas prendre de
risques.
En fait, cette définition d'une chaîne de caractères est seulement un
standard pour les fonctions str*(), comme strcpy(), strlen(), etc. C'est
aussi ce qui est utilisé quand vous faites "blahblah", donc vous feriez
mieux d'utiliser cela! Si vous voulez un exemple de quelqu'un qui
ré-implémente les fonctions de strings, regardez les programmes de Dan
Bernstein (http://cr.yp.to). Il faut aussi dire que ce gars
ré-implémente tout ce qu'il voit, alors ça ne veut pas dire que ce soit
nécessairement une bonne idée. Après tout, ce n'est pas le format en
tant que tel qui cause des problèmes de sécurités, ce sont les
programmeurs qui codent beaucoup trop vites.
[------------------ integer overflow/underflow:
Petite introduction sur le format de stockage des signed/unsigned ints
sur i386:
Pour les nombres qui sont stictement positifs (unsigned), le stockage
est très simple. 0001 est 1, 0010 est 2, 0011 est 3, etc. Cependant,
pour les nombres négatifs, nous avons un petit problème, comment les
stocker? Premièrement!
La technique utilisée sur les ordinateurs 80x86 est 'two's complement'.
En gros, pour les nombres négatifs, on fait :
1) on prend le nombre, on fait un NOT, et on ajoute 1.
pour -5 :
0000 0101 5 1111 1010 NOT 1111 1011 +1
Si on veut faire l'opération contraire:
1111 1011 -5 0000 0100 NOT 0000 0101 +1
Nous pouvons reconnaître les nombres négatifs parce que le H-O bit est
1.
Supposons ceci:
int main() { /* Un char parce c'est 8 bits, donc on voit plus vite
l'effet */ char a = 0;
for (a = 0; a != -1; a++) { printf("%d %u", a, a); }
return 0; }
Ceci ne devrait jamais terminer, exact? En augmentant 'a', on ne devrait
jamais arriver à -1! Eh bien, essayez-le! Vous devriez avoir droit à un
paquet de chiffres, et *a ira à -1 et arrêtera... Pourquoi!?
Regardons ce qui se produit:
[...] 1 1 2 2 3 3 127 127 -128 4294967168 -127 4294967169 [...] -3
4294967293 -2 4294967294 [...]
0000 0000 0 0000 0001 1 0000 0010 2 0000 0011 3 [.....] 1111 1110 254
1111 1111 255
Mais c'est un signed, donc on augmente encore, et rendu ? 1111 1111 on a
:
0000 0000 NOT 0000 0001 +1
Donc on est à -1
ヒ partir de 128++, on se rend à -127 et on monte -126, etc, jusqu'à ce
qu'on atteigne -1!!
Bon, ca semble peut-être ne rien changer, concrètement, mais songez à
quelque chose comme:
int foofunc(unsigned int x) { int a; /* disons qu'elle est initialisée
quelque part */ static int fixedarray[BIG];
a = x;
/* peut-être faire des modifs à `a' ici */
if(a > sizeof(fixedarray)) return -1;
return fixedarray[a]; }
disons que x=0xffffffff; C'est un unsigned int valide. Par contre, si
on le cast en signed, on a :
[Silenus] 3> ceval 'printf("%d\n",(signed int)0xffffffff);' -1
Ce qui veut dire que vous allez aller à fixedarray[-1], donc vous
crasherez. Le problème peut-être pire que crasher, cependant. Il y a eu
un bug dans OpenSSH il y a quelques mois qui était relié è ça.
Convertion d'integers qui avaient pas la même taille. (ex: int32 a;
int16 b; b = a;)
[----------- format bugs
Dernièrement, une grosse partie des exploits qui arrivent sur Bugtraq
exploitent des format bugs. En gros, ce sont des programmes qui roulent
printf(buf), ou buf vient de l'usager et n'a pas été validé par le
programme.
La cause du problème?
En C, il n'y a pas de moyen portable de savoir quand on arrive à la fin
d'une liste d'arguments variables. printf(), par exemple, fait à peu
près ça:
/* * Pour ceux qui ne connaissent pas * va_start(va_list, dernier
argument connu) * va_end(va_list) * va_arg(va_list, type de
l'argument à prendre du stack) */ int fprintf_(FILE *out, char *fmt,
...) { va_list va; int n; char *p; char *tmp;
n = 0; va = va_start(va, fmt); for (p = fmt; *p; p++) { if (*p == '%') {
switch (*++p) { case 's': tmp = va_arg(va, char *); if (!tmp) tmp =
"(null)"; fputs(tmp, out); n += strlen(tmp); break; case 'c':
fputc(va_arg(va, char), out); n++; break; /* ... case 'i': case 'u':
case 'l': , etc */ } } else { putc(*p, out); n++; } } va_end(va);
return n; }
Comme vous voyez, printf() se fit simplement au format string pour
savoir quoi prendre du stack, il ne fait pas
if (no_more_arguments(va)) { printf("Error - not enough parameters\n");
return -1; }
Parce que la manière dont les appels de fonctions sont fait est
arch-dependent... Habituellement va_arg() prend les prochains X bytes du
stack (dependant du type). Si vous voulez vous amuser, regarder les
definitions de va_arg() dans /usr/include/stdarg.h...
Le problème survient lorsque l'usager commande le format string.
exemple:
int main() { char *str = getenv("STR");
if (!str) str = "foo%s ";
printf(str);
return 0; }
Si $STR est seulement une chaîne de caractères, sans rien de spécial
dedans, (ex: $STR="Foo"), printf fonctionnera parfaitement, il vous la
montrera, tout simplement. Par contre, si vous mettez %s dans la string
(comme le default dans ce programme), printf() va prendre le prochain
char* sur le stack, et afficher la "string" qui est à cette locatoin!
Dans cet exemple, tout ce que ça fait c'est écrire quelque chose sans
but, ou segfaulter.
Le GROS problème est qu'il y a des 'choses' (FIXME) a printf qui
modifient leur argument. Par exemple, %n:
n The number of characters written so far is stored into the
integer indicated by the int * (or vari- ant) pointer argument. No
argument is converted.
Vous voyez peut-être le gros problème. Si l'adresse d'une variable
globale est présentement le prochain sizeof(char*) bytes dans le stack,
l'usager peut y écrire ce qu'il veut...
Fix: Faites printf("%s", s); pour éviter des problèmes.. Ou filtrez le
buffer.
Il y a des patches à GCC qui vérifient si buf n'est pas une constante
que vous pouvez utiliser mais... IMHO, c'est juste cacher le problème.
En plus, ça empèche de faire un format dynamique.
(oui, ça peut être utile. Par exemple:
char buf[32]; int precision;
i = 3; snprintf(buf, sizeof(buf), "%%0%df\n", precision);
printf(buf, 3.3333333333333333);
Ce qui est une des manières les plus simples de printer un float avec
une précision variable, mais qui serait impossible avec une telle
patch.)
Le problème est concrètement que des programmeurs pressés écrivent du
code prévilegie sans valider du input de l'utilisateur. C'est ça qu'il
faut changer, pas simplement mettre des battons dans les roues des
programmeurs.
[----------- Buffer Overflows (overview)
Les buffer overflows sont des problèmes causés lorsque vous entrez plus
de données dans un buffer que ce qu'il est près à accepter. Par exemple:
main() { char buf[2]; char *blah = "foo bar blah blah blah blah";
strcpy(buf, blah); }
Il y a strlen(blah)-2 charactères de trop... En gros, votre programme va
segfaulter quand il va return(0 de main (ou avant, s'il tente de
modifier son text segment ou quelque chose de pas gentil du genre)
OK, alors le programme crash. Le lien avec les trous de sécurité? Sur
i386, l'instruction CALL push le EIP (program counter, la prochaine
execution que le CPU va prendre) sur le stack, puis JMP. Sur les autres
processeurs c'est different, mais le principal est que l'adresse ou le
processeur va aller en partant de la fonction avec RET peut être
modifiée. Pour avoir plus d'information sur les autres processeurs, que
je ne connais malheureusement pas assez pour en parler, lisez le paper
de 'Last Stage of Delirium' disponible à
http://www.lsd-pl.net/asmcodes.html. C'est très instructif et couvre
plusieurs architectures. J'aime surtout l'emphase sur la partie
artistique de la programmation :-)
low memory address high mem
address
__________________________________________________________________ |
variables "auto" | parametres a la fonction | addresse de retour |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
char buf[2] ci-haut était un exemple de variable "auto" (keyword
principalement inutilisé en C, c'est le defaut pour les variables dans
une fonction comme ça). Voyons ce qui se passe quand on copie la string
dedans...
low memory address high mem
address ______________________ |bu|addresse de retour |
~~~~~~~~~~~~~~~~~~~~~~ foo bar blha blah blah balh
Bon, on vient d'overwriter l'addresse de retour de la fonction, donc,
quand la fonction va faire un "ret", ca va aller à une adresse bidon,
illégale, donc le programme va "seg faulter"...
Bon, maintenant, pour exploiter ça... Normalement, au lieu d'être une
string hardcodee, c'est quelqeu chose que l'usager donne au programme,
donc, qu'il peut choisir comme il veut.
En gros, on veut que la fonction return a un bout de code que l'on a
fait nous meme, qui peut partir un shell, ou quelque chose du genre.
Habituellement, l'"exploiter" mets le code (opcodes des operations qu'il
veut faire) dans le buffer, puis s'arrange pour que l'addresse de retour
de la fonction pointe dans la partie "code" de son buffer. Donc quand le
programme fais un RET, il jump dans le code du bad-guy, et l'usager a un
shell avec les permissions du programme quand il a ete exploite...
Je n'irai pas en details dans la creation du code pour rouler le shell,
ni d'aucune autre chose a propos de buffer-overflowing, lisez le texte
d'Aleph One, c'est vraiment _la_ reference sur le sujet, tres precis,
exemples, etc. Je voulais seulement vous donner un apercu..
--------------- Note sur la programmation de daemons
Les programmes qui ont la plus grande importance pour la sécurité d'un
système sont les daemons. フant donné qu'ils offrent des services, ils
sont les plus vulnérables aux attaques externes. En plus, ils roulent
souvent avec des privilèges superieurs pour fournir ces services. Ils
sont donc une très bonne porte d'entrée pour les crackers/script kiddies
en puissance. L'épisode du inet worm l'a très bien démontrée, en
'utilisant' les vulnerabilités de 3-4 daemons très courants, il a pu
infecter la plupart des ordinateurs connectés par Internet. C'est donc
_extrêmement_ important d'être parano病que en programmant des programmes
qui sont le 2e point faible d'un système de sécurité (le 1er *tant les
utilisateurs). Je passerai rapidement sur les étapes pour devenir un
daemon 'conforme', qu'il soit un serveur pour Internet ou quoi que ce
soit de local, car ça doit *tre couvert pas des dizaines de livres et
c'est présent dans le code sources de millions de programmes...
Premièrement, on doit libérer le terminal de l'utilisateur, ou du
programme qui nous a invoqué, pour ne pas le faire bloquer indéfiniment,
fork est utilisé:
1. partir une nouvelle instance de notre programme, et faire quitter le
parent pid_t pid;
if ((pid = fork()) < 0) { /* erreur */ perror("fork failed"); exit(-1);
} else if (pid > 0) /* nous sommes le parent */ exit(0);
/* reste du programme */
Bon, nous avons libéré le processus qui nous a invoqué, mais si il
s'agit d'un terminal (ie. nous avons été invoque d'un VT), ou d'un
programme qui avait lui-meme un "controlling terminal", nous en avons un
aussi. フant donné que nous fermerons stdin/out/err plus tard, nous ne
devrions pas déranger l'utilisateur. Mais si l'utilisateur ferme sa
session, ou quelque chose arrive, nous recevrons un signal SIGTERM et
nous mourrons. Un daemon veut pourtant rester actif tout le temps, même
si la personne ferme son terminal, etc. L'ancienne manière était
d'envoyer un ioctl()à stdin si open("/dev/tty") était bon, etc, mais
maintenant, un call fait tout *a tout seul! setsid(). Il te fais une
session, qui n'a par defaut pas de controlling terminal, et tu ne reçois
les signaux de personne.
2. se déconnecter du terminal
if (setsid() < 0) { perror("setsid"); exit(-1); }
Troisièmement, si on a été invoque d'un répertoire quelconque sur le
disque dur, il ne faudrait pas empêcher l'administrateur d'unmounter une
partition! Cette étape est _très_ simple.
3. libérer le disque, en allant dans le root
if (chdir("/") < 0) { perror("chdir '/' failed"); exit(-1); }
Quatrièmement, si nous créons des fichiers, ils sont créés par defaut
avec la valeur de umask bitwise-AND-ee avec 0777 (toutes les
permissions). Donc, nous donnons a umask() les valeurs que nous voulons
ENLEVER sur les fichiers que nous créons. Donc, si nous voulons enlever
l'accès write à tout le monde (y comprit nous) pour tous les fichiers
créés automatiquement (ie. sans donner le 4e paramètre à open(), nous
faisons:
4. mettre le umask() à une valeur plus préventive.
umask(0222); /* umask retourne pas de message d'erreur, donc pas de test
nécessaire. Pensez tout de meme au '0' au début, sinon le compilateur
va le prendre pour une valeur décimale et... 222 en décimal est 336 en
octal, ce qui fait une assez bonne différence! */
5. fermer tous les file descriptors
/* getdtablesize() sort la grosseur de la table de file descriptors, ce
qui rend ton programme plus portable */
for (i = getdtablesize() - 1; i >= 0; i --) { close(i); }
6. Faire le travail du daemon (founir un service quelconque)
Mais pour sauver du temps :
usage : daemon(a,b);
Si a est positif, il ne fait pas l'étape 3 ( chdir ) Si b est positif,
il ne fait pas l'étape 5 ( close )
Vous devriez utiliser daemon(3) si possible, car ça vous empêche de
faire des erreurs stupides. Mais faire toutes les étapes quelques fois
dans notre vie pourrait être utile! Aussi, vous faire un petit daemon()
de secour pourrait être utile, pour les systèmes ou il ne serait pas
disponible. (Linux, OpenBSD et NetBSD l'ont, je ne sais pas pour
d'autres systèmes.
7. S'arranger pour survivre plus d'une minute!
C'est bien beau, nous sommes un daemon, mais si on est vulnérable à des
DoS, on est pas beaucoup avancés!! Un DoS (Denial Of Service) en général
constitue a envoyer tellement de demandes au serveur qu'il n'a plus de
ressources.
Je ne veux pas repartir un thread comme sur la mailing list de
security-audit sur 'Est-ce que les DoS peuvent être évités'. D'après
moi, non. フant donné qu'un attaqueur peut généralement avoir toutes les
ressources dont il a besoin (En pénétrant des systèmes informatiques
ayant de grosses ressources, par exemple) et qu'une défense complète
serait un DoS en soit-même, car ça consisterait a toujours changer
d'emplacement, donc les clients 'véritables' ne peuvent pas non plus
utiliser i.e. DoS.
La seule chose que vous pouvez faire (mais qui offre une protection
_minimale_), est de vous arranger pour faire faire le plus gros du
travail du cêté client. Par exemple, envoyer un challenge au client, lui
demander de faire quelque chose de spécial, puis de vous envoyer le
resultat. B implique que vous ayez la "bonne réponse" déja prépéree.
Mais tout de même, il faut se dire que vous utilisez encore un peu de
travail, le OS doit recevoir le packet, le router doit être capable de
tenir le coup, etc. La seule chose que vous pouvez faire, c'est de vous
arranger pour que seulement ceux qui ont accès à des super computers et
un bandwidth illimite puissent vous DoSer :)
Je n'ai pas l'expérience requise pour écrire des serveurs qui doivent
tenir le coup sous 100 requêtes authorisés par seconde, et encore moins
des serveurs tenant le coup sous 1000000 requetes non-authorisés par
seconde, donc je ne peux pas vraiment aller dans les détails pour
l'instant. Si quelqu'un a des suggestions a faire, ne vous genez pas!
(mon email est au debut du texte)
[------------- Conseils divers
Voici un paquet de conseils qui ne méritent pas vraiment d'y accorder 50
lignes, surtout que je n'aurais pas grand chose a rajouter!
- char buf[FIXED_SIZE]; sprintf(buf, "blah blah %d %f\n", fooarg,
bararg);
Ne jamais, JAMAIS utiliser sprintf(). C'est demander gentiment aux bugs
de se manifester. Je sais que, par exemple, Ultrix n'a pas de
snprintf(), mais bon... voulez vous que votre programme crash partout ou
marche à la plupart des places?
- Limites : Toujours penser a regarder les limites des tables, car
regarder ? l'index -1, c'est pas vraiment une bonne idée.
Aussi, ne pas oublier qu'une table de N éléments va de 0..N-1, et pas de
1 a N comme dans d'autres langages.
- Faites attention à ce que vous mettez dans /tmp, et SURTOUT aux
symlinks, utilisez lstat sur le fichier avant de l'ouvrir.
Le danger est que vous ouvriez un fichier en écriture dans /tmp/, et
qu'un attaqueur ait prévu le nom du fichier et ait mis, par exemple, un
symlink à /etc/passwd. Vous venez d'overwriter le passwd file. Vous
devez donc utiliser mkstemp(3) autant que possible, car il génère le nom
du fichier et l'ouvre et l'unlink comme une opération atomique (qui ne
risque pas d'être interompue, donc pas de race ou un autre process peut
créer lui-même le fichier avant que vous l'ouvriez...). mkdtemp(3)
existe aussi sur OpenBSD pour créer les répertoires de cette façon, à
vous de prier les développeurs de glibc de l'intégrer!
Juste pour vous montrer comment ça peut être un problème, les fonctions
fopen() "sécurisées" (i.e. parano病ques) que j'ai vu étaient a peu près
30 lignes de tests divers pour s'assurer d'avoir le `droit' d'accéder le
fichier. (oui oui, je devrais en inclure une ici, mais je ne veux pas me
tromper et donner un mauvais exemple de quelque chose d'aussi critique)
Aussi, si vous programmez un programme setuid, je conseilles cette
fonction, qui ouvre un fichier avec les permissions de l'usager qui vous
a appellé, au lieu de root:root. Je ne garantis pas la fonctionnalité de
ce code, mais je ne vois pas ou il pourrait y avoir de problèmes... (on
pourrait utiliser access pour s'assurer d'avoir le droit, mais ca
demanderait que R_OK, W_OK etu X_OK pour access soit les mêmes valeurs
que O_RDONLY et O_WRONLY de fcntl.h. ヒ vous de voir si vous voulez faire
cette assomption ou pas.
/* open() clone that drops privileges if we're setuid */ int
user_open(char *path, int mode) { int fd; uid_t saved;
/* if we're not setuid, no need to check */ if ((saved = geteuid()) ==
getuid()) return(open(path, mode)); if (seteuid(getuid()) == -1)
return(-1); fd = open(path, mode); if (seteuid(saved) == -1) { if (fd !=
-1) close(fd); return(-1); }
return(fd); }
- Security through simplicity
Rendez vos programmes les plus simples possibles. C'est par les
`features' superflus que les trous de sécurité s'installent.
[-------------- Conclusion:
J'espère que ce guide vous a aidé, communiquez avec moi par email si
vous avez d'autres questions et j'essaierai d'y répondre. Et si vous
avez des idées sur des points que je pourrais éclaircir ou que j'ai
oublié, ne vous gênez pas, je suis rarement trop occupé pour répondre à
des emails.
- Vincent
|
|