Nous avons vu ce qu’était un programme de type uProbe dans la première partie : ça peut être un moyen de sonder une bibliothèque.
Nous allons vérifier cela avec un programme Aya qui va récupérer les différents arguments de la fonction execve()
de la Libc.
Je suppose que vous êtes déjà dans un environnement pour développer avec Aya et que vous avez installé bpftrace. Si ce n’est pas le cas, vous pouvez utiliser le lab Killercoda :
Que va-t-on vraiment faire ?#
Libc#
Contrairement à la précédente partie où on a attaché notre programme eBPF à un programme, là nous l’attachons à une bibliothèque partagée.
La libc est la bibliothèque standard du C. Donc à chaque fois qu’un programme en C (ou en C++) est exécuté, potentiellement, un programme eBPF pourra être lancé.
libc
tout au long du chapitre. Pour être plus précis, il faudrait parler de la glibc (GNU C Library) l’implémentation la plus répandue dans les distributions GNU/Linux (Comme Debian ou Red Hat). Mais il y a d’autres implémentations comme musl (notamment pour Alpine Linux) ou ulibc qui sont plus légères et adaptées pour les systèmes embarqués.La fonction execve#
execve
est un appel système (un syscall) du noyau Linux. Mais c’est aussi le nom d’une fonction de la libc
qui fait appel à ce même syscall (un wrapper). Ainsi, à chaque fois que la fonction execve()
de la libc
sera appelé, notre programme eBPF de type uProbe sera lancé.
Les arguments de la fonction execve#
Nous devons également récupérer les différents arguments de la fonction execve()
.
Pour trouver ses arguments, on peut évidemment regarder dans le code source de la libc. Mais il y a plus simple :
man execve
La partie qui nous intéresse est la suivante :
int execve(const char *pathname, char *const _Nullable argv[],
char *const _Nullable envp[]);
On voit que la fonction a trois arguments :
pathname
: le nom de la commande avec le chemin complet (exemple/bin/bash
). Il est de typeconst char *
(équivalent en Rust à*const u8
).argv
: un tableau d’arguments de la commande. Il est de typechar *const _Nullable[]
(équivalent en Rust à*const *const u8
)argv[0]
: le nom de la commandeargv[1]
: la première option- etc.
envp
: un tableau de variables d’environnement de la commande. Il est de typechar *const _Nullable[]
(équivalent en Rust à*const *const u8
).
_Nullable
indique simplement que la valeur peut être NULL
.Comment déclencher le programme eBPF ?#
Prenons un exemple simple. Si tu lances une commande dans un terminal par exemple ls
, que va-t-il se passer ?
- Grâce à la variable d’environnement
PATH
, le shell (par exemple le bash) va trouver le bon chemin pour trouver où se trouve le binairels
:
/usr/bin/ls
- Pour exécuter le binaire, le shell va alors appeler la fonction
execve()
de la libc :
execve("/usr/bin/ls", ["ls"], ["PATH=/bin:/usr/bin", ...])
- Le programme eBPF sera enfin déclenché.
Voici un petit résumé de tout cela :
Il y a évidemment d’autres programmes que des shells qui appellent la fonction execve()
de la libc comme systemd
pour le démarrage des différents programmes d’un système Linux.
Ainsi nous allons créer un programme très proche de celui qu’on avait créé avec le Tracepoint sys_enter_execve
lors des articles d’initiation à eBPF mais celui-ci sera attaché au niveau utilisateur à la fonction execve()
de la libc.
Générons un programme Aya de type uProbe#
Nous avons donc déjà les réponses aux deux questions :
🤷 Target to attach the (u|uret)probe? (e.g libc):
🤷 Function name to attach the (u|uret)probe? (e.g getaddrinfo):
Voyons comment créer un programme eBPF Hello world pour ce point d’attache.
Testons avec bpftrace#
Vérifions d’abord que ça fonctionne avec la ligne de commande bpftrace
:
sudo bpftrace -e \
'uprobe:libc:execve { printf("Hello execve\n"); }'
uprobe
: le type de programme eBPFlibc
: le nom de la bibliothèqueexecve
: la fonction à debugger{ printf("Hello execve\n"); }
: le code bpftrace
À chaque fois qu’on lance une commande sur un autre terminal, on voit bien Hello execve
.
Maintenant faisons-le avec Aya.
Génération et compilation du programme Aya#
La commande cargo generate
suivante permet ainsi de génerer le programme eBPF :
cargo generate --name test-uprobe-2 \
-d program_type=uprobe \
-d uprobe_target=libc \
-d uprobe_fn_name=execve \
https://github.com/aya-rs/aya-template
uprobe_target
et uprobe_fn_name
), vous pouvez regarder le fichier test.sh dans le repo aya-template.Maintenant compilons le et installons le dans le noyau Linux :
cd test-uprobe-2/
RUST_LOG=info cargo run
Test du programme#
Sur un autre terminal, lancer un programme quelconque :
ls
Sur le terminal cargo run
vous verrez :
[INFO test_uprobe] function execve called by libc
Dans la partie précédente, on était resté à ce point concernant les uProbes. Regardons comment récupérer les différentes arguments de la fonction execve()
. Commençons par le premier : le nom du binaire.
Récupérons le nom du binaire#
Testons avec bpftrace#
Avant de modifier le code Aya, regardons comment on fait avec bpftrace
. C’est un poil plus compliqué qu’un simple hello world.
Pour récupérer le premier argument, on utilise arg0
:
sudo bpftrace -e \
'uprobe:libc:execve { printf("%d\n", arg0); }'
On récupère l’adresse où se trouve le première argument. Comment le “convertir” en chaîne de caractères ? Il suffit d’utiliser la fonction str()
:
sudo bpftrace -e \
'uprobe:libc:execve { printf("%s\n", str(arg0)); }'
Maintenant qu’on a le brouillon avec bpftrace
, regardons comment l’implémenter avec Aya.
Modifions le code Aya#
Suite de l’article réservée aux membres premium ✨
L’article complet est accessible uniquement aux membres premium.
Devenir membre premium, c’est simple : il suffit de faire un petit don 💖
En échange, vous aurez pendant 1 an (offre early bird) :
- Accès à tous les articles complets dès leur publication
- Lecture anticipée avant la mise en ligne publique
- Participation au soutien de ce blog indépendant
- Accès exclusif à mes photos de vacances à Dubaï
Votre don permet de :
- Me rendre moins dépendant des grandes plateformes
- M’encourager à créer plus de contenu technique
- Lever le paywall plus rapidement pour tous