Pourquoi ne pouvez-vous pas / ne voulez-vous pas utiliser l'astuce LD_PRELOAD ?
Exemple de code ici :
/*
* File: soft_atimes.c
* Author: D.J. Capelis
*
* Compile:
* gcc -fPIC -c -o soft_atimes.o soft_atimes.c
* gcc -shared -o soft_atimes.so soft_atimes.o -ldl
*
* Use:
* LD_PRELOAD="./soft_atimes.so" command
*
* Copyright 2007 Regents of the University of California
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#define _FCNTL_H
#include <sys/types.h>
#include <bits/fcntl.h>
#include <stddef.h>
extern int errorno;
int __thread (*_open)(const char * pathname, int flags, ...) = NULL;
int __thread (*_open64)(const char * pathname, int flags, ...) = NULL;
int open(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open) {
_open = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open");
}
if(flags & O_CREAT)
return _open(pathname, flags | O_NOATIME, mode);
else
return _open(pathname, flags | O_NOATIME, 0);
}
int open64(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open64) {
_open64 = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open64");
}
if(flags & O_CREAT)
return _open64(pathname, flags | O_NOATIME, mode);
else
return _open64(pathname, flags | O_NOATIME, 0);
}
D'après ce que j'ai compris... c'est à peu près l'astuce LD_PRELOAD ou un module du noyau. Il n'y a pas beaucoup de terrain d'entente à moins que vous ne vouliez l'exécuter sous un émulateur qui peut piéger votre fonction ou faire de la réécriture de code sur le binaire réel pour piéger votre fonction.
En supposant que vous ne pouvez pas modifier le programme et que vous ne pouvez pas (ou ne voulez pas) modifier le noyau, l'approche LD_PRELOAD est la meilleure, en supposant que votre application est assez standard et n'est pas en fait une qui essaie malicieusement de passer votre interception. (Dans ce cas, vous aurez besoin de l'une des autres techniques.)
Valgrind peut être utilisé pour intercepter n'importe quel appel de fonction. Si vous avez besoin d'intercepter un appel système dans votre produit fini, cela ne servira à rien. Cependant, si vous essayez d'intercepter pendant le développement, cela peut être très utile. J'ai fréquemment utilisé cette technique pour intercepter les fonctions de hachage afin de pouvoir contrôler le hachage renvoyé à des fins de test.
Au cas où vous ne le sauriez pas, Valgrind est principalement utilisé pour rechercher des fuites de mémoire et d'autres erreurs liées à la mémoire. Mais la technologie sous-jacente est essentiellement un émulateur x86. Il émule votre programme et intercepte les appels vers malloc/free etc. La bonne chose est que vous n'avez pas besoin de recompiler pour l'utiliser.
Valgrind a une fonctionnalité qu'ils appellent Function Wrapping , qui est utilisé pour contrôler l'interception des fonctions. Voir la section 3.2 du manuel Valgrind pour plus de détails. Vous pouvez configurer l'habillage de fonction pour n'importe quelle fonction que vous aimez. Une fois l'appel intercepté, la fonction alternative que vous fournissez est alors invoquée.
Commençons par éliminer certaines non-réponses que d'autres personnes ont données :
- Utilisez
LD_PRELOAD
. Ouais tu as dit "OutreLD_PRELOAD
..." dans la question, mais apparemment cela ne suffit pas pour certaines personnes. Ce n'est pas une bonne option car cela ne fonctionne que si le programme utilise libc, ce qui n'est pas nécessairement le cas. - Utilisez Systemtap. Oui, vous avez dit "Outre ... Linux Kernel Modules" dans la question, mais apparemment, cela ne suffit pas pour certaines personnes. Ce n'est pas une bonne option car vous devez charger un module kernal personnalisé, ce qui est très pénible et nécessite également root.
- Valgrind. Cela fonctionne en quelque sorte, mais cela fonctionne en simulant le processeur, donc c'est vraiment lent et vraiment compliqué. Très bien si vous ne faites cela que pour un débogage ponctuel. Ce n'est pas vraiment une option si vous faites quelque chose qui mérite d'être produit.
- Divers éléments d'audit des appels système. Je ne pense pas que la journalisation des appels système compte comme une "interception". Nous voulons clairement modifier les paramètres d'appel système / les valeurs de retour ou rediriger le programme via un autre code.
Cependant, il existe d'autres possibilités qui ne sont pas encore mentionnées ici. Notez que je suis nouveau dans tout cela et que je n'ai encore rien essayé, donc je peux me tromper sur certaines choses.
Réécrivez le code
En théorie, vous pouvez utiliser une sorte de chargeur personnalisé qui réécrit les instructions d'appel système pour passer à un gestionnaire personnalisé à la place. Mais je pense que ce serait un cauchemar absolu à mettre en œuvre.
kprobes
Les kprobes sont une sorte de système d'instrumentation du noyau. Ils n'ont qu'un accès en lecture seule à quoi que ce soit, vous ne pouvez donc pas les utiliser pour intercepter les appels système, mais seulement les enregistrer.
ptrace
ptrace est l'API que les débogueurs comme GDB utilisent pour faire leur débogage. Il y a un PTRACE_SYSCALL
option qui mettra l'exécution en pause juste avant/après les appels système. À partir de là, vous pouvez faire à peu près tout ce que vous voulez, de la même manière que GDB. Voici un article sur la façon de modifier les paramètres d'appel système à l'aide de ptrace. Cependant, il a apparemment des frais généraux élevés.
Seccomp
Seccomp est un système conçu pour vous permettre de filtrer appels système. Vous ne pouvez pas modifier les arguments, mais vous pouvez bloquez-les ou renvoyez des erreurs personnalisées. Les filtres Seccomp sont des programmes BPF. Si vous n'êtes pas familier, il s'agit essentiellement de programmes arbitraires que les utilisateurs peuvent exécuter dans une machine virtuelle de l'espace noyau. Cela évite le changement de contexte utilisateur/noyau qui les rend plus rapides que ptrace.
Bien que vous ne puissiez pas modifier les arguments directement à partir de votre programme BPF, vous pouvez retourner SECCOMP_RET_TRACE
qui déclenchera un ptrace
ing parent à briser. C'est donc fondamentalement la même chose que PTRACE_SYSCALL
sauf que vous devez exécuter un programme dans l'espace du noyau pour décider si vous souhaitez réellement intercepter un appel système en fonction de ses arguments. Cela devrait donc être plus rapide si vous ne souhaitez intercepter que certains appels système (par exemple, open()
avec des chemins spécifiques).
Je pense que c'est probablement la meilleure option. Voici un article à ce sujet du même auteur que celui ci-dessus. Notez qu'ils utilisent le BPF classique au lieu d'eBPF, mais je suppose que vous pouvez également utiliser eBPF.
Edit :En fait, vous ne pouvez utiliser que le BPF classique, pas le eBPF. Il y a un article de LWN à ce sujet.
Voici quelques questions connexes. Le premier vaut vraiment la peine d'être lu.
- EBPF peut-il modifier la valeur de retour ou les paramètres d'un appel système ?
- Intercepter uniquement les appels système avec PTRACE_SINGLESTEP
- Est-ce un bon moyen d'intercepter les appels système ?
- Moyen d'intercepter les appels système avec une surcharge minimale sans modifier le noyau
Il y a aussi un bon article sur la manipulation des appels système via ptrace ici.
Certaines applications peuvent empêcher strace/ptrace de s'exécuter, donc la seule véritable option que j'ai eue est d'utiliser systemtap
Systemtap peut intercepter un tas d'appels système si nécessaire en raison de sa correspondance avec les caractères génériques. Systemtap n'est pas C, mais un langage distinct. En mode de base, le systemtap devrait vous empêcher de faire des choses stupides, mais il peut également fonctionner en "mode expert" qui revient à permettre à un développeur d'utiliser C si cela est nécessaire.
Il ne vous oblige pas à patcher votre noyau (ou du moins ne devrait pas), et une fois qu'un module a été compilé, vous pouvez le copier depuis une boîte de test/développement et l'insérer (via insmod) sur un système de production.
Je n'ai pas encore trouvé d'application Linux qui ait trouvé un moyen de contourner/d'éviter d'être pris par systemtap.