Tag C
15/3/2010 realpath secure ?
Dans le cadre d'une application serveur (type transfert de fichier : http, ftp, ...), pour contrôler l'accès aux fichiers j'utilise realpath(3). Cette fonction permet d'avoir le path (chemin) réel d'un fichier, sans lien relatif dans un des dossiers. Ainsi on a juste à faire un strncmp(3) pour le comparer à la racine de l'application.
Par exemple une partie du code du serveur HTTP que je suis en train de coder (uri est quelque chose de la forme /../foo/bar/../machin.html et conf_root la racine du style /usr/local/www/
char path[PATH_MAX]; char root[PATH_MAX]; if (!realpath(uri+1, path) || !realpath(conf_root, root) || strncmp(path, root, strlen(root)) != 0) return send_error(404);
Prenez ce post pour une question, à part le chroot(2) et realpath, comment bien contrôler l'accès aux fichiers ?
J'y connais rien en filesystems, mais si on pouvait avoir un moyen rapide de savoir si un fichier est dans tel ou tel fichier ce serait grandement pratique. Sans me venter j'ai souvent des idées géniales en informatique, mais bien souvent ça a été pensé et implémenté depuis plus de 10 ans ! Du coup si vous avez plus d'information, je suis prêt à converser sur ce sujet avec vous...
13/2/2010 Jouons avec kvm
J'ai codé un petit programme tout simple qui fait clignoter les leds de mon routeur dès qu'un service est par terre. Ma méthode (qui ne doit pas être la meilleure mais je m'en fout un peu) consiste à regarder régulièrement la liste des processus et détecter l'arrêt d'un service quand le processus associé n'existe plus. J'ai commencé avec un script shell, puis étant assez mauvais pour tout ce qui ressemble de près ou de loin à du script j'ai décidé de le faire en C (langage d'excellence n'est ce pas...?).
Je me suis volontairement compliqué la tache en utilisant des fonctions très proches du système, ainsi s'il est possible d'obtenir une liste de processus via ps(1) avec un popen(3) super crade ou encore sysctl(3) j'ai voulu utiliser kvm(3) kernel memory interface qui est en programmation système la méthode la plus appropriée (d'ailleurs 'ps' est codé avec ça), c'est juste une interface qui va nous permettre d'accéder au données du kernel (une copie bien entendu), ici la liste des processus en cours.
Le hic c'est que si les fonctions de kvm sont très standard l'implémentation est très différente suivant les systèmes, comme à peu près toute implémentation, l'important c'est que l'interface soit standard. Ainsi à la lecture de la structure qui décrit les processus kinfo_proc de sys/user.h on se dit chouette pleins d'info à disposition, sauf que sur OpenBSD c'est pas du tout la même forme (ni le même fichier d'ailleurs). D'où l'importance d'une interface riche et c'est ce que nous promet kvm.
Assez causé passons au code (je n'explique pas ou peu le code en dehors des fonctions kvm)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <err.h> #include <libgen.h> #include <fcntl.h> #include <limits.h> #include <paths.h> #include <kvm.h> #include <sys/param.h> #include <sys/sysctl.h> #include <sys/wait.h> #include <sys/user.h> void start_leds(void) { /* Ici on execute /usr/sbin/gioctl -q gpio0 led3 (on|off) * suivant la parité du conteur statique 'i' * 60 fois avec une pause d'une seconde. */ static int i = 1; switch(fork()) { case -1: err(1, "fork"); break; case 0: execl("/usr/sbin/gpioctl", "gpioctl", "-q", "gpio0", "led3", (i%2) ? "on" : "off", (char*)NULL); err(1, "execl"); break; default: wait(NULL); break; } sleep(1); if (i++ <= 60) return start_leds(); return; } int main(int argc, char *argv[]) { kvm_t *kd; char berr[_POSIX2_LINE_MAX]; struct kinfo_proc *p, *procs; int n, j, k, found = 0; char **pargv; if (argc < 2) errx(1, "Usage check_proc procs ..."); /* On accède aux données du kernel qui tourne actuellement (NULL) */ if (!(kd = kvm_open(NULL, NULL, NULL, O_RDONLY, berr))) errx(1, "%s", berr); /* On demande la liste des processus, kinfo_proc est décrite * dans sys/sysctl.h sur OpenBSD et sys/user.h sur FreeBSD. * Il nous la donne dans un tableau continu de pointeurs de * taille qu'on récupère dans 'n'. */ if (!(procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &n))) err(1, "kvm_getprocs: %s", kvm_geterr(kd)); for (k = 1; k < argc; k++) { for (j = 0, p = procs; j < n; j++, p++) { /* On accède à la liste des arguments du programme qui a * généré le processus courant. */ pargv = kvm_getargv(kd, p, 0); if (pargv && pargv[0] && strstr(pargv[0], argv[k])) { found++; break; } } } if (-1 == kvm_close(kd)) err(1, "kvm_close"); if (found != argc-1) start_leds(); return 0; }
Et l'exécution, on veut tester si un processus du nom de 'dhcpd' tourne sur le système, si ce n'est pas le cas on lance le clignotement de la led pendant une minute :
# gcc -o check_proc check_proc.c -lkvm # ./check_proc dhcpd
Le mien s'exécute dans un cron toute les minutes :
* * * * * /root/bin/check_proc named dhcpd adsuck ntpd
3/10/2009 Un système de ramasse miette en C
Je viens d'implémenter dans mon shell un système de ramasse miette. Le choix de faire un tel système est particulièrement adapté pour un shell ou pour tout programme qui est fortement cyclique et avec un cycle assez court où chaque allocation peut être libérée rapidement.
En gros l'astuce consiste à créer des fonctions wrapper pour free(3) et malloc(3). Une fois que tous nos appels d'allocation et de libération de mémoire passent par eux il est facile de créer une structure de type pile qui empile l'adresse de chaque allocation pour ensuite tout libérer d'un coup à la fin du cycle.
Pour cela j'ai utilisé les SLIST de sys/queue.h pour la pile que voilà :
typedef struct stack_s { void * adr; SLIST_ENTRY(stack_s) next; } stack_s;
Ensuite le wrapper pour malloc (notez qu'on déclare la tête de pile comme variable globale (dans le fichier avec les fonctions wrapper) pour pouvoir y accéder facilement.
/* déclaration de la pile mstack (qu'on initialise à NULL) */ static SLIST_HEAD(, stack_s) mstack = SLIST_HEAD_INITIALIZER(&mstack); /* Le wrapper pour malloc */ void * xmalloc(size_t size) { void *ret; stack_s *el; /* l'élément a rajouter dans la pile */ ret = malloc(size); if (!ret) { perror("Malloc "); exit (EXIT_FAILURE); } el = malloc(sizeof(*el)); el->adr = ret; SLIST_INSERT_HEAD(&mstack, el, next); return ret; } /* Cette fonction vide toute la pile et libère la mémoire */ void stack_delete(void) { stack_s *el; while (!SLIST_EMPTY(&mstack)) { el = SLIST_FIRST(&mstack); SLIST_REMOVE_HEAD(&mstack, next); free(el->adr); free(el); } return; }
Voilà, maintenant faites tous vos allocations temporaires avec _malloc et videz la pile de temps en temps (ie à la fin du cycle général de votre programme).
Par exemple dans la boucle principale ça donne quelque chose comme ça :
for (;;) { machin = xmalloc(sizeof(*machin) * 10); /* ... ... */ stack_delete(); }
J'espère que c'est compréhensible, si vous avez une question n'hésitez surtout pas à me demander plus d'information.