Aller au contenu
Background Image
  1. Ebpf-Another-Types/

Sondons une bibliothèque avec une uProbe

·1058 mots·5 mins·
Joseph Ligier
Auteur
Joseph Ligier
CNCF ambassador | Kubestronaut 🐝
Sommaire
Apprenons uProbe avec eBPF et Aya - Cet article fait partie d'une série.
Partie 3: Cet article

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 :

Killer coda screenshot


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é.

Je parle de 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

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 type const char * (équivalent en Rust à *const u8).
  • argv: un tableau d’arguments de la commande. Il est de type char *const _Nullable[] (équivalent en Rust à *const *const u8)
    • argv[0] : le nom de la commande
    • argv[1] : la première option
    • etc.
  • envp : un tableau de variables d’environnement de la commande. Il est de type char *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 binaire ls :
/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 :

Summary of ls launching

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 eBPF
  • libc : le nom de la bibliothèque
  • execve : 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
Pour trouver le nom des arguments (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

👉 Devenir membre premium dès maintenant

Apprenons uProbe avec eBPF et Aya - Cet article fait partie d'une série.
Partie 3: Cet article