ptrace
Table des matières
Retour à l'index
NOM
ptrace - Suivre un processus
BIBLIOTHÈQUE
Bibliothèque C standard (libc, -lc)
SYNOPSIS
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request op, pid_t pid,
void *addr, void *data);
DESCRIPTION
L'appel système ptrace() fournit à un processus (l'« observateur ») un
moyen d'observer et de contrôler l'exécution d'un autre processus
(l'« observé »), et d'examiner et éditer la mémoire et les registres de
l'observé. L'utilisation principale de cette fonction est l'implémentation
de points d'arrêt pour le débogage, et pour suivre les appels système.
Un observé doit d'abord être attaché à l'observateur. L'attachement et les
commandes suivantes sont par thread : dans un processus multithreadé, chaque
thread peut être attaché individuellement à un observateur (éventuellement
différent), ou être laissé détaché et donc non débogué. Par conséquent,
l'« observé » signifie toujours « (un) thread », jamais « un processus
(éventuellement multithreadé) ». Les commandes ptrace sont toujours envoyées
à un observé spécifique en utilisant un appel de la forme
ptrace(PTRACE_truc, pid, ...)
où pid est l'identifiant de thread du thread Linux correspondant.
(Remarquez que dans cette page, un « processus multithreadé » signifie un
groupe de threads constitué de threads créés en utilisant l'attribut
CLONE_THREAD de clone(2).)
Un processus peut démarrer un suivi en appelant fork(2) et faire en sorte
que l'enfant créé fasse un PTRACE_TRACEME, suivi (en général) par un
execve(2). Autrement, un processus peut commencer à suivre un autre
processus en utilisant PTRACE_ATTACH ou PTRACE_SEIZE.
L'observé s'arrêtera à chaque fois qu'un signal lui sera distribué, même si
le signal est ignoré (à l'exception de SIGKILL qui a les effets
habituels). L'observateur sera prévenu à son prochain appel de waitpid(2)
(ou un des appels système liés à « wait ») ; cet appel renverra une valeur
status contenant les renseignements indiquant la raison de l'arrêt de
l'observé. Lorsque l'observé est arrêté, l'observateur peut utiliser
plusieurs opérations ptrace pour inspecter et modifier
l'observé. L'observateur peut également laisser continuer l'exécution de
l'observé, en ignorant éventuellement le signal ayant déclenché l'arrêt, ou
même en envoyant un autre signal.
Si l'option PTRACE_O_TRACEEXEC n'est pas effective, tous les appels
réussis d'execve(2) par le processus suivi déclencheront l'envoi d'un
signal SIGTRAP, ce qui permet au parent de reprendre le contrôle avant
que le nouveau programme commence son exécution.
Quand l'observateur a fini le suivi, il peut forcer l'observé à continuer
normalement, en mode non suivi, avec PTRACE_DETACH.
La valeur de l'argument op indique précisément l'opération à
entreprendre.
- PTRACE_TRACEME
-
Le processus en cours va être suivi par son parent. Un processus ne devrait
sans doute pas envoyer cette opération si son parent n'est pas prêt à le
suivre. Dans cette requête pid, addr, et data sont ignorés.
-
L'opération PTRACE_TRACEME ne sert qu'à l'observé. Les opérations
restantes ne servent qu'à l'observateur. Dans les observations suivantes,
pid précise l'identifiant de thread de l'observé sur lequel agir. Pour
d'autres opérations que PTRACE_ATTACH, PTRACE_SEIZE,
PTRACE_INTERRUPT et PTRACE_KILL, l'observé doit être arrêté.
- PTRACE_PEEKTEXT
-
PTRACE_PEEKDATA
Lire un mot à l'adresse addr dans l'espace mémoire de l'observé et le
renvoyer en résultat de l'appel ptrace(). Linux ne sépare pas les espaces
d'adressage de code et de données, donc ces deux opérations sont
équivalentes (data est ignoré, consultez la section NOTES).
- PTRACE_PEEKUSER
-
Lire un mot à la position addr dans l'espace USER de l'observé, qui
contient les registres et divers renseignements sur le processus (voir
<sys/user.h>). Le mot est renvoyée en résultat de ptrace(). En
principe, l'adresse doit être alignée sur une frontière de mots, bien que
cela varie selon les architectures. Consultez la section NOTES. (data
est ignoré, consultez la section NOTES).
- PTRACE_POKETEXT
-
PTRACE_POKEDATA
Copier le mot data vers l'adresse addr de la mémoire de
l'observé. Comme pour PTRACE_PEEKTEXT et PTRACE_PEEKDATA, ces deux
opérations sont équivalentes.
- PTRACE_POKEUSER
-
Copier le mot data vers l'adresse addr dans l'espace USER de
l'observé. Comme pour PTRACE_PEEKUSER, les emplacements doivent être
alignés sur une frontière de mot. Pour maintenir l'intégrité du noyau,
certaines modifications de la zone USER sont interdites.
- PTRACE_GETREGS
-
PTRACE_GETFPREGS
Copier les registres généraux ou du processeur en virgule flottante de
l'observé, vers l'adresse data de l'observateur. Consultez
<sys/user.h> pour les détails sur le format de ces données
(addr est ignoré). Remarquez que les systèmes SPARC ont la signification
de data et addr inversée, c'est-à-dire que data est ignoré et les
registres sont copiés vers l'adresse addr. PTRACE_GETREGS et
PTRACE_GETFPREGS ne sont pas présents sur toutes les architectures.
- PTRACE_GETREGSET (depuis Linux 2.6.34)
-
Lire les registres de l'observé. addr indique, de manière dépendante de
l'architecture, le type de registres à lire. NT_PRSTATUS (avec une valeur
numérique de 1) a pour conséquence habituelle la lecture de registres
généraux. Si le processeur a, par exemple, des registres en virgule
flottante ou en vecteur, ils peuvent être récupéré en configurant addr à
la constante NT_foo correspondante. data pointe vers une struct iovec, qui décrit l'emplacement et la taille du tampon de destination. Le
noyau modifie iov.len au retour pour indiquer le véritable nombre
d'octets renvoyés.
- PTRACE_SETRGS
-
PTRACE_SETFPREGS
Modifier les registres généraux ou du processeur en virgule flottante de
l'observé, depuis l'adresse data de l'observateur. Comme pour
PTRACE_POKEUSER, certaines modifications de registres généraux pourraient
être interdites (addr est ignoré). Remarquez que les systèmes SPARC ont
la signification de data et addr inversée, c'est-à-dire que data
est ignoré et les registres sont copiés depuis l'adresse
addr. PTRACE_SETREGS et PTRACE_SETFPREGS ne sont pas présents sur
toutes les architectures.
- PTRACE_SETREGSET (depuis Linux 2.6.34)
-
Modifier les registres de l'observé. La signification de addr et data
est analogue à PTRACE_GETREGSET.
- PTRACE_GETSIGINFO (depuis Linux 2.3.99-pre6)
-
Récupérer des renseignements sur le signal qui a provoqué l'arrêt. Pour ce
faire, copier une structure siginfo_t (consultez sigaction(2)) de
l'observé à l'adresse data de l'observateur (addr est ignoré).
- PTRACE_SETSIGINFO (depuis Linux 2.3.99-pre6)
-
Définir les renseignements de signaux : copier une structure siginfo_t de
l'adresse data de l'observateur vers l'observé. Cela n'affecte que les
signaux qui auraient dû être distribués à l'observé et ont été interceptés à
cause de ptrace(). Différencier ces signaux normaux des signaux créés par
ptrace() lui-même peut être délicat (addr est ignoré).
- PTRACE_PEEKSIGINFO (depuis Linux 3.10)
-
Récupérer les structures siginfo_t sans supprimer les signaux d’une file
d’attente. addr pointe vers une structure ptrace_peeksiginfo_args qui
indique la position ordinale à partir de laquelle la copie des signaux
devrait commencer et le nombre de signaux à copier. Les structures
siginfo_t sont copiées dans le tampon pointé par data. La valeur de
retour contient le nombre de signaux copiés (zéro indique qu’il n’y a pas de
signal correspondant à la position ordinale indiquée). Dans les structures
siginfo renvoyées, le champ si_code contient des renseignements
(__SI_CHLD, __SI_FAULT, etc.) qui sinon ne sont pas exposés à l’espace
utilisateur.
struct ptrace_peeksiginfo_args {
u64 off; /* Position ordinale dans la file d’attente
où commencer la copie de signaux */
u32 flags; /* PTRACE_PEEKSIGINFO_SHARED ou 0 */
s32 nr; /* Nombre de signaux à copier */
};
-
Actuellement, seul l’attribut PTRACE_PEEKSIGINFO_SHARED permet de vider
les signaux de la file de signaux par processus. Si cet attribut n’est pas
défini, les signaux sont lus depuis la file par thread du thread indiqué.
- PTRACE_GETSIGMASK (depuis Linux 3.11)
-
Placer une copie du masque des signaux bloqués (consultez sigprocmask(2))
dans le tampon pointé par data qui devrait être un pointeur vers un
tampon de type sigset_t. L’argument addr contient la taille du tampon
pointé par data (c’est-à-dire sizeof(sigset_t)).
- PTRACE_SETSIGMASK (depuis Linux 3.11)
-
Modifier le masque des signaux bloqués (consultez sigprocmask(2)) à la
valeur indiquée dans le tampon pointé par data qui devrait être un
pointeur vers un tampon de type sigset_t. L’argument addr contient la
taille du tampon pointé par data (c’est-à-dire sizeof(sigset_t)).
- PTRACE_SETOPTIONS (depuis Linux 2.4.6, consultez les remarques de BOGUES)
-
Définir les options de ptrace à partir de l'adresse data (addr est
ignoré). data est interprété comme un masque d'options, qui est construit
à partir des attributs suivants.
-
- PTRACE_O_EXITKILL (depuis Linux 3.8)
-
Envoyer un signal SIGKILL à l'observé si l'observateur existe. Cet option
est utile pour les gardiens ptrace qui veulent s'assurer que les observés ne
peuvent jamais échapper au contrôle de l'observateur.
- PTRACE_O_TRACECLONE (depuis Linux 2.5.46)
-
Arrêter l'observé au prochain clone(2) et commencer automatiquement à
suivre le nouveau processus cloné, qui démarrera avec un signal SIGSTOP,
ou PTRACE_EVENT_STOP si PTRACE_SEIZE est utilisé. Un waitpid(2) par
l'observateur renverra une valeur status comme
-
status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8))
-
Le PID du nouveau processus peut être récupéré avec PTRACE_GETEVENTMSG.
-
Cette option peut ne pas intercepter tous les appels clone(2). Si
l'observé appelle clone(2) avec l'attribut CLONE_VFORK,
PTRACE_EVENT_VFORK sera envoyé si PTRACE_O_TRACEVFORK est
utilisé. Sinon, si l'observé appelle clone(2) avec SIGCHLD comme
signal de terminaison, PTRACE_EVENT_FORK sera envoyé si
PTRACE_O_TRACEFORK est utilisé.
- PTRACE_O_TRACEEXEC (depuis Linux 2.5.46)
-
Arrêter l'observé au prochain execve(2). Un waitpid(2) par
l'observateur renverra une valeur status comme
-
status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
-
Si le thread en cours d'exécution n'est pas un leader de groupe de threads,
l'identifiant de thread est réinitialisé à l'identifiant du leader de groupe
de threads avant cet arrêt. Depuis Linux 3.0, le premier identifiant de
thread peut être récupéré avec PTRACE_GETEVENTMSG.
- PTRACE_O_TRACEEXIT (depuis Linux 2.5.60)
-
Arrêter l'observé à la terminaison. Un waitpid(2) par l'observateur
renverra une valeur status comme
-
status>>8 == (SIGTRAP | (PTRACE_EVENT_EXIT<<8))
-
L'état de fin de l'observé peut être récupéré avec PTRACE_GETEVENTMSG.
-
L'observé est arrêté tôt dans la terminaison du processus, alors que les
registres sont toujours disponibles, ce qui permet au processus utilisant
ptrace() de voir où la terminaison s'est produite, alors que la
notification de terminaison normale a lieu à la fin de cette
terminaison. Même si le contexte est disponible, l'observateur ne peut pas
empêcher la terminaison à ce moment là.
- PTRACE_O_TRACEFORK (depuis Linux 2.5.46)
-
Arrêter l'observé au prochain fork(2) et commencer automatiquement à
suivre le nouveau processus créé, qui démarrera avec un signal SIGSTOP,
ou PTRACE_EVENT_STOP si PTRACE_SEIZE est utilisé. Un waitpid(2) par
l'observateur renverra une valeur status comme
-
status>>8 == (SIGTRAP | (PTRACE_EVENT_FORK<<8))
-
Le PID du nouveau processus peut être récupéré avec PTRACE_GETEVENTMSG.
- PTRACE_O_TRACESYSGOOD (depuis Linux 2.4.6)
-
Lors des interceptions d'appel système, positionner le bit 7 sur le numéro
de signal (envoyer SIGTRAP|0x80). Cela facilite pour l'observateur la
distinction entre les interceptions normales et celles provoquées par un
appel système.
- PTRACE_O_TRACEVFORK (depuis Linux 2.5.46)
-
Arrêter l'observé au prochain vfork(2) et commencer automatiquement à
suivre le nouveau processus créé, qui démarrera avec un signal SIGSTOP,
ou PTRACE_EVENT_STOP si PTRACE_SEIZE est utilisé. Un waitpid(2) par
l'observateur renverra une valeur status comme
-
status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK<<8))
-
Le PID du nouveau processus peut être récupéré avec PTRACE_GETEVENTMSG.
- PTRACE_O_TRACEVFORKDONE (depuis Linux 2.5.60)
-
Arrêter l'observé à la fin du prochain vfork(2). Un waitpid(2) par
l'observateur renverra une valeur status comme
-
status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK_DONE<<8))
-
Le PID du nouveau processus peut (depuis Linux 2.6.18) être récupéré avec
PTRACE_GETEVENTMSG.
- PTRACE_O_TRACESECCOMP (depuis Linux 3.5)
-
Arrêter l'observé quand une règle SECCOMP_RET_TRACE de seccomp(2) est
déclenchée. Un waitpid(2) par l'observateur renverra une valeur status
comme
-
status>>8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP<<8))
-
Si cela entraîne un arrêt PTRACE_EVENT, c'est équivalent à un
arrêt-entrée-appel-système. Pour des détails, voir la remarque sur
PTRACE_EVENT_SECCOMP ci-dessous. Les données du message de l'événement
seccomp (issues de la partie SECCOMP_RET_DATA de la règle du filtre
seccomp) peuvent être récupérées avec PTRACE_GETEVENTMSG.
- PTRACE_O_SUSPEND_SECCOMP (depuis Linux 4.3)
-
Suspendre les protections seccomp de l'observé. Cela s'applique quel que
soit le mode et peut être utilisé lorsque l'observé n'a pas encore installé
de filtres seccomp. Cela veut dire qu'un cas d'utilisation valable consiste
à suspendre les protections seccomp d'un observé avant qu'elles ne soient
installées par l'observé, laisser l'observé installer les filtres et vider
cet attribut quand les filtres doivent être réactivés. La définition de
cette option implique que l'observateur ait la capacité CAP_SYS_ADMIN,
n'ait pas de protection seccomp installée et n'ait pas de
PTRACE_O_SUSPEND_SECCOMP positionné sur lui-même.
- PTRACE_GETEVENTMSG (depuis Linux 2.5.46)
-
Récupérer un message (dans un unsigned long) concernant l'événement
ptrace qui vient d'arriver, en le plaçant à l'adresse data de
l'observateur. Pour PTRACE_EVENT_EXIT, il s'agit du code de retour de
l'observé. Pour PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK,
PTRACE_EVENT_VFORK_DONE et PTRACE_EVENT_CLONE, il s'agit du PID du
nouveau processus. Pour PTRACE_EVENT_SECCOMP, il s'agit des
SECCOMP_RET_DATA du filtre seccomp(2) associées à la règle déclenchée
(addr est ignorée).
- PTRACE_CONT
-
Redémarrer l'observé arrêté. Si data est non nul, il est interprété comme
un numéro de signal à distribuer à l'observé ; sinon aucun signal n'est
distribué. L'observateur peut ainsi contrôler si un signal envoyé à
l'observé doit lui être distribué ou non (addr est ignoré).
- PTRACE_SYSCALL
-
PTRACE_SINGLESTEP
Redémarrer l'observé arrêté comme pour PTRACE_CONT, mais en s'arrangeant
pour qu'il soit arrêté à la prochaine entrée ou sortie d'un appel système,
ou après la prochaine instruction, respectivement (l'observé sera aussi
arrêté par l'arrivée d'un signal). Du point de vue de l'observateur,
l'observé semblera être arrêté par SIGTRAP. Ainsi, pour PTRACE_SYSCALL
l'idée est d'inspecter les arguments de l'appel système au premier arrêt
puis de faire un autre PTRACE_SYSCALL et d'inspecter la valeur de retour
au second arrêt. Le paramètre data est interprété comme pour
PTRACE_CONT (addr est ignoré).
- PTRACE_SET_SYSCALL (depuis Linux 2.6.16)
-
Lorsqu'il est en arrêt-entrée-appel-système, passer le numéro de l'appel
système qui va être exécuté à celui indiqué dans le paramètre data. Le
paramètre addr est ignoré. Cette opération n'est actuellement prise en
charge que sur arm (et arm64, quoique uniquement à des fins de
rétrocompatibilité), mais la plupart des autres architectures ont d'autres
moyens de faire cela (en général en modifiant le registre qui a passé
l'appel système au code au niveau de l'utilisateur).
- PTRACE_SYSEMU
-
PTRACE_SYSEMU_SINGLESTEP (depuis Linux 2.6.14)
Pour PTRACE_SYSEMU, continuer puis s'arrêter lors du prochain appel
système, qui ne sera pas exécuté. Voir la documentation des syscall-stops
ci-dessous. Pour PTRACE_SYSEMU_SINGLESTEP, faire la même chose, mais
exécuter pas à pas s'il ne s'agit pas d'un appel système. Cette fonction est
utilisée par des programmes comme User Mode Linux, qui veulent émuler tous
les appels système de l'observé. Le paramètre data est interprété comme
pour PTRACE_CONT. L'argument addr est ignoré. Ces opérations ne sont
pour l'instant disponibles que sur x86.
- PTRACE_LISTEN (depuis Linux 3.4)
-
Redémarrer l'observé arrêté, mais en l'empêchant de s'exécuter. L'état
résultant de l'observé est similaire a celui d'un processus qui a été arrêté
par un SIGSTOP (ou autre signal d'arrêt). Consultez la sous-section
Arrêt-groupe pour des renseignements supplémentaires. PTRACE_LISTEN ne
fonctionne que sur les observés attachés par PTRACE_SEIZE.
- PTRACE_KILL
-
Envoyer à l'observé un signal SIGKILL pour le terminer (addr et
data sont ignorés).
-
Cette opération est obsolète, ne l'utilisez pas. À la place, envoyez un
SIGKILL directement en utilisant kill(2) ou tgkill(2). Le problème
avec PTRACE_KILL est qu'il nécessite que l'observé soit en
arrêt-distribution-signal, sinon cela risque de ne pas fonctionner
(c'est-à-dire risque de se terminer avec succès sans tuer l'observé). En
revanche, envoyer SIGKILL directement n'est pas concerné par cette
limite.
- PTRACE_INTERRUPT (depuis Linux 3.4)
-
Arrêter un observé. Si l’observé est en cours d’exécution ou en sommeil dans
l’espace utilisateur et que PTRACE_SYSCALL est effectif, l’appel système
est interrompu et l'arrêt-sortie-appel-système est signalé (l’appel système
interrompu est redémarré quand l’observé est redémarré). Si l’observé avait
déjà été arrêté par un signal et que PTRACE_LISTEN lui avait été envoyé,
l’observé s’arrête avec PTRACE_EVENT_STOP et WSTOPSIG(status) renvoie
le signal d’arrêt. Si n’importe quel autre arrêt-ptrace est créé en même
temps (par exemple, si un signal est envoyé à l’observé), cet arrêt-ptrace
arrive. Si rien de ce qui précède ne s’applique (par exemple si l’observé
est en cours d’exécution en espace utilisateur), il s’arrête avec
PTRACE_EVENT_STOP avec WSTOPSIG(status) ==
SIGTRAP. PTRACE_INTERRUPT ne fonctionne que sur les observés attachés
par PTRACE_SEIZE.
- PTRACE_ATTACH
-
Attacher le processus numéro pid, pour le suivre. L'observé va recevoir
un SIGSTOP, mais il ne sera peut-être pas arrêté tout de suite, utilisez
waitid(2) pour attendre son arrêt. Consultez la sous-section
Attachement et détachement pour obtenir de plus amples renseignements
(addr et data sont ignorés).
-
Le droit d'effectuer un PTRACE_ATTACH est géré par la vérification
PTRACE_MODE_ATTACH_REALCREDS du mode d'accès de ptrace ; voir ci-dessous.
- PTRACE_SEIZE (depuis Linux 3.4)
-
Attacher au processus indiqué dans pid, en faire un observé du processus
appelant. Contrairement à PTRACE_ATTACH, PTRACE_SEIZE n'arrête pas le
processus. Les arrêts-groupe sont signalés en tant que PTRACE_EVENT_STOP
et WSTOPSIG(status) renvoie le signal d'arrêt. Les enfants
automatiquement attachés avec PTRACE_EVENT_STOP et WSTOPSIG(status)
renvoient SIGTRAP au lieu de recevoir un signal SIGSTOP. execve(2)
n'envoie pas d'autres SIGTRAP. Seul un processus PTRACE_SEIZEé peut
accepter des commandes PTRACE_INTERRUPT et PTRACE_LISTEN. Le
comportement « seized » qui vient d'être décrit est récupéré par les enfants
automatiquement attachés en utilisant PTRACE_O_TRACEFORK,
PTRACE_O_TRACEVFORK et PTRACE_O_TRACECLONE. addr doit être de
zéro. data contient un masque de bit des options ptrace à activer
immédiatement.
-
Le droit d'effectuer un PTRACE_SEIZE est géré par une vérification
PTRACE_MODE_ATTACH_REALCREDS du mode d'accès ptrace ; voir ci-dessous.
- PTRACE_SECCOMP_GET_FILTER (depuis Linux 4.4)
-
Cette opération autorise l'observateur à vider les filtres BPF classiques de
l'observé.
-
addr est un entier indiquant l'index du filtre à vider. Le filtre le plus
récemment installé a le numéro d'index 0. Si addr est supérieur aux
numéros des filtres installés, l'opération échoue avec l'erreur ENOENT.
-
data est soit un pointeur vers un tableau struct sock_filter assez
grand pour stocker le programme BPF, soit NULL si le programme ne va pas
être stocké.
-
En cas de succès, le code de retour est le nombre d'instructions du
programme BPF. Si data était NULL, ce code de retour peut être utilisé
pour dimensionner correctement le tableau struct sock_filter passé dans
un appel ultérieur.
-
Cette opération échoue avec l'erreur EACCES si l'appelant n'a pas la
capacité CAP_SYS_ADMIN ou s'il est en mode seccomp filtré ou
restreint. Si le filtre auquel renvoie addr n'est pas un filtre BPF
classique, l'opération échoue avec l'erreur EMEDIUMTYPE.
-
Cette opération n'est disponible que si le noyau a été configuré avec les
options CONFIG_SECCOMP_FILTER et CONFIG_CHECKPOINT_RESTORE.
- PTRACE_DETACH
-
Relancer l'observé arrêté comme avec PTRACE_CONT, mais en commençant par
s'en détacher. Sous Linux un observé peut être détaché ainsi quelque soit la
méthode employée pour démarrer le suivi (addr est ignoré).
- PTRACE_GET_THREAD_AREA (depuis Linux 2.6.0)
-
Cette opération effectue une tâche identique à get_thread_area(2). Elle
lit l'entrée TLS dans le GDT dont l'index est donné dans addr, mettant
une copie de l'entrée dans la struct user_desc vers laquelle pointe
data (contrairement à get_thread_area(2), entry_number de la
struct user_desc est ignorée).
- PTRACE_SET_THREAD_AREA (depuis Linux 2.6.0)
-
Cette opération effectue la même tâche que set_thread_area(2). Elle
positionne l'entrée TLS dans le GDT dont l'index est donné dans addr, en
lui affectant les données fournies dans la struct user_desc vers laquelle
pointe data (contrairement à set_thread_area(2), entry_number de la
struct user_desc est ignorée ; autrement dit, cette opération de ptrace
ne peut pas être utilisée pour affecter une entrée TLS libre).
- PTRACE_GET_SYSCALL_INFO (depuis Linux 5.3)
-
Récupérer des informations sur l'appel système qui a provoqué l'arrêt. Les
informations sont placées dans le tampon vers lequel pointe le paramètre
data, lequel doit être un pointeur vers un tampon de type struct ptrace_syscall_info. Le paramètre addr contient la taille du tampon vers
lequel pointe le paramètre data (à savoir sizeof(struct ptrace_syscall_info)). Le code de retour contient le nombre d'octets que le
noyau peut écrire. Si la taille des données que le noyau doit écrire dépasse
celle indiquée par le paramètre addr, les données de sortie sont
tronquées.
-
La structure ptrace_syscall_info contient les champs suivants :
-
struct ptrace_syscall_info {
__u8 op; /* Type d'appel système d'arrêt */
__u32 arch; /* Valeur AUDIT_ARCH_* ; voir seccomp(2) */
__u64 instruction_pointer; /* Pointeur vers l'instruction du processeur */
__u64 stack_pointer; /* Pointeur vers la pile du processeur */
union {
struct { /* op == PTRACE_SYSCALL_INFO_ENTRY */
__u64 nr; /* Numéro de l'appel système */
__u64 args[6]; /* Paramètres de l'appel système */
} entry;
struct { /* op == PTRACE_SYSCALL_INFO_EXIT */
__s64 rval; /* Code de retour de l'appel système */
__u8 is_error; /* Attribut d'erreur de l'appel système ;
Booléen : rval contient-il
un code d'erreur (-ERRCODE) ou
un code de retour de non erreur ? */
} exit;
struct { /* op == PTRACE_SYSCALL_INFO_SECCOMP */
__u64 nr; /* Numéro de l'appel système */
__u64 args[6]; /* Paramètres de l'appel système */
__u32 ret_data; /* Partie SECCOMP_RET_DATA de la valeur
de retour de SECCOMP_RET_TRACE */
} seccomp;
};
};
-
Les champs op, arch, instruction_pointer et stack_pointer sont
définis pour tous les types d'arrêts de l'appel système ptrace. Le reste de
la structure est une union ; on ne doit lire que les champs significatifs
pour le type d'arrêt de l'appel système indiqué par le champ op.
-
Le champ op prend une des valeurs suivantes (définies dans
<linux/ptrace.h>), indiquant le type d'arrêt qui s'est produit et
la partie remplie de l'union :
-
- PTRACE_SYSCALL_INFO_ENTRY
-
Le composant entry de l'union contient des informations liées à un arrêt
d'entrée appel système.
- PTRACE_SYSCALL_INFO_EXIT
-
Le composant exit de l'union contient des informations sur un arrêt de
sortie d'appel système.
- PTRACE_SYSCALL_INFO_SECCOMP
-
Le composant seccomp de l'union contient des informations concernant un
arrêt PTRACE_EVENT_SECCOMP.
- PTRACE_SYSCALL_INFO_NONE
-
Aucun composant de l'union ne contient d'informations pertinentes.
-
Dans le cas où une entrée ou une sortie d'appel système s'interrompt, les
données renvoyées par PTRACE_GET_SYSCALL_INFO sont limitées au type
PTRACE_SYSCALL_INFO_NONE à moins que l'option PTRACE_O_TRACESYSGOOD ne
soit définie avant que l'arrêt de l'appel système correspondant se produise.
Mort sous ptrace
Quand un processus (éventuellement multithreadé) reçoit un signal pour le
tuer (un dont la disposition est configurée à SIG_DFL et dont l'action
par défaut est de tuer le processus), tous les threads se terminent. Chaque
observé signale sa mort à son ou ses observateurs. La notification de cet
événement est distribuée par waitpid(2).
Remarquez que le signal tueur provoquera d'abord un
arrêt-distribution-signal (sur un seul observé) et, seulement après être
injecté par l'observateur (ou après être envoyé à un thread qui n'est pas
suivi), la mort du signal arrivera sur tous les observés d'un processus
multithreadé (le terme « arrêt-distribution-signal » est expliqué plus bas).
SIGKILL ne génère pas d'arrêt-distribution-signal et l'observateur ne
peut par conséquent pas le supprimer. SIGKILL tue même à l'intérieur des
appels systèmes (arrêt-sortie-appel-système n'est pas créé avant la mort par
SIGKILL). L'effet direct est que SIGKILL tue toujours le processus
(tout ses threads), même si certains threads du processus sont suivis avec
ptrace.
Quand l'observé appelle _exit(2), il signale sa mort à son
observateur. Les autres threads ne sont pas concernés.
Quand n'importe quel thread exécute exit_group(2), tous les observés de
son groupe de threads signalent leur mort à leur observateur.
Si l'option PTRACE_O_TRACEEXIT est active, PTRACE_EVENT_EXIT arrivera
avant la mort réelle. Cela s'applique aux terminaisons avec exit(2),
exit_group(2) et aux morts de signal (sauf SIGKILL, selon la version
du noyau ; voir les BOGUES ci-dessous), et lorsque les threads sont détruits
par execve(2) dans un processus multithreadé.
L'observateur ne peut pas assumer que l'observé arrêté-ptrace
existe. L'observé risque de mourir avant d'être arrêté dans plusieurs cas
(comme avec SIGKILL). Par conséquent, le tracé doit être préparé pour
traiter une erreur ESRCH sur n'importe quelle opération
ptrace. Malheureusement, la même erreur est renvoyée si l'observé existe
mais n'est pas arrêté-ptrace (pour les commandes qui nécessitent un observé
arrêté), ou s'il n'est pas suivi par le processus qui a envoyé l'appel
ptrace. L'observateur doit garder une trace de l'état arrêté ou en
fonctionnement de l'observé, et interpréter ESRCH comme « l'observé s'est
achevé de manière inattendue » seulement s'il sait que l'observé est
effectivement entré en arrêt-ptrace. Remarquez qu'il n'est pas garanti que
waitpid(WNOHANG) signale de façon fiable l'état de mort de l'observé si
une opération ptrace renvoie ESRCH. waitpid(WNOHANG) pourrait plutôt
renvoyer 0. Autrement dit, l'observé pourrait « ne pas être encore
mort », mais déjà refuser des opérations ptrace.
L'observateur ne peut pas assumer que l'observé finit toujours sa vie en
signalant WIFEXITED(status) ou WIFSIGNALED(status) ; dans certains cas
ça n'arrive pas. Par exemple si un thread différent du leader de groupe de
threads fait un execve(2), il disparaît ; son PID ne sera plus jamais vu,
tous les arrêts suivants de ptrace seront signalés sous le PID du leader de
groupe de threads.
États arrêtés
Deux états existent pour un observé : en cours d'exécution ou à l'arrêt. Du
point de vue de ptrace, un observé qui est bloqué dans un appel système
(comme read(2), pause(2), etc.) est néanmoins considéré en cours
d’exécution, même si l’observé est bloqué depuis longtemps. L’état de
l’observé après PTRACE_LISTEN est en quelque sorte une zone d’ombre : il
n’est dans aucun arrêt-ptrace (les commandes ptrace n’auront aucun effet sur
lui et il distribuera des notifications waitpid(2)), mais il pourrait
aussi être considéré « arrêté » parce qu’il n’est pas en train d’exécuter
des instructions (pas de programmation) et, s’il était en arrêt-groupe avant
PTRACE_LISTEN, il ne répondra pas aux signaux avant de recevoir
SIGCONT.
De nombreuses sortes d'états sont possibles quand l'observé est arrêté, et
les discussions dans ptrace sont souvent confondues. Par conséquent,
l'utilisation de termes précis est importante.
Dans cette page de manuel, tous les états d'arrêt dans lesquels l'observé
est prêt à accepter des commandes ptrace de l'observateur sont appelés
arrêt-ptrace. Les arrêts-ptrace peuvent ensuite être sous-divisés en
arrêt-distribution-signal, arrêt-groupe,
arrêt-appel-système, arrêt-PTRACE_EVENT, etc. Ces états d'arrêt sont
décrits en détail ci-dessous.
Lorsque l'observé en cours d'exécution entre en arrêt-ptrace, il avise son
observateur en utilisant waitpid(2) (ou un des autres appels système
« wait »). La plupart de cette page de manuel suppose que l'observateur
attend avec :
pid = waitpid(pid_ou_moins_1, &status, __WALL);
Les observés arrêtés-ptrace sont signalés comme renvoyés avec un pid
strictement positif et WIFSTOPPED(status) vrai.
L'attribut __WALL ne contient pas les attributs WSTOPPED et
WEXITED, mais implique leur fonctionnalité.
La configuration de l'attribut WCONTINUED en appelant waitpid(2) n'est
pas conseillée : l'état « exécuté » est relatif au processus et l'utiliser
peut embrouiller le vrai parent de l'observé.
Utiliser l'attribut WNOHANG pourrait forcer waitpid(2) à renvoyer 0
(« aucun résultat d'attente encore disponible ») même si l'observateur sait
qu'il devrait y avoir une notification. Exemple :
errno = 0;
ptrace(PTRACE_CONT, pid, 0L, 0L);
if (errno == ESRCH) {
/* l'observé est mort */
r = waitpid(tracee, &status, __WALL | WNOHANG);
/* r peut encore valoir 0 ici ! */
}
Les sortes d'arrêts-ptrace suivants existent : arrêts-distribution-signal,
arrêts-groupe, arrêts PTRACE_EVENT et arrêts-appel-système. Ils sont
signalés par waitpid(2) avec WIFSTOPPED(status) vrai. Ils peuvent être
distingués en examinant la valeur status>>8, et en cas
d'ambiguïté dans cette valeur, en faisant une requête PTRACE_GETSIGINFO
(remarque : la macro WSTOPSIG(status) ne peut pas être utilisée pour
réaliser cet examen, car elle renvoie la valeur (status>>8) & 0xff.)
Arrêt-distribution-signal
Quand un processus (éventuellement multithreadé) reçoit n'importe quel
signal sauf SIGKILL, le noyau choisi un thread arbitraire pour traiter le
signal (si le signal est créé avec tgill(2), le thread cible peut être
explicitement choisi par l'appelant). Si le thread choisi est observé, il
entre en arrêt-distribution-signal. À ce moment là, le signal n'est pas
encore distribué au processus, et peut être supprimé par l'observateur. Si
l'observateur ne supprime pas le signal, il passe le signal à l'observé lors
de l'opération suivante de redémarrage de ptrace. Cette deuxième étape de
distribution de signal est appelée injection de signal dans cette page de
manuel. Remarquez que si le signal est bloqué, l'arrêt-distribution-signal
n'arrive pas avant que le signal soit débloqué, à l'exception habituelle que
SIGSTOP ne peut pas être bloqué.
L'arrêt-distribution-signal est respecté par l'observateur tant que
waitpid(2) retourne avec WIFSTOPPED(status) vrai, avec le signal
renvoyé par WSTOPSIG(status). Si le signal est SIGTRAP, cela pourrait
être un arrêt-ptrace de nature différente ; consultez les sections
Arrêts-appel-système et execve(2) sous ptrace plus bas pour obtenir de
plus amples précisions. Si WSTOPSIG(status) renvoie un signal d'arrêt,
cela pourrait être un arrêt-groupe ; voir ci-dessous.
Injection et suppression de signal
Après un arrêt-distribution-signal respecté par l'observateur, l'observateur
devrait redémarrer l'observé avec l'appel
ptrace(PTRACE_restart, pid, 0, sig)
où PTRACE_restart est une des opérations ptrace de redémarrage. Si sig
est 0, alors aucun signal n'est distribué. Sinon, le signal sig est
distribué. Cette opération est appelée injection de signal dans cette
page de manuel, pour la distinguer de l'arrêt-distribution-signal.
La valeur de sig peut être différente de celle de WSTOPSIG(status) :
l'observateur peut provoquer l'injection d'un autre signal.
Remarquez qu'un signal supprimé provoque toujours un retour prématuré des
appels système. Dans ce cas, les appels système seront redémarrés :
l'observateur forcera l'observé à réexécuter l'appel système interrompu (ou
l'appel système restart_syscall(2) pour les quelques appels système qui
utilisent un autre mécanisme de redémarrage) si l'observateur utilise
PTRACE_SYSCALL. Même les appels système (comme poll(2)) qui ne sont
pas redémarrables après le signal sont redémarrés après la suppression du
signal ; cependant, des bogues du noyau existent et certains appels système
échouent avec EINTR même si aucun signal observable n'est injecté dans
l'observé.
Lors du redémarrage des commandes ptrace émises dans d'autres arrêts-ptrace
qu'arrêt-distribution-signal, l'injection de signal n'est pas garantie, même
si sig est non nul. Aucune erreur n'est signalée ; un sig non nul
risque simplement d'être ignoré. Les utilisateurs de ptrace ne devraient pas
essayer de « créer un nouveau signal » de cette façon : utilisez plutôt
tgkill(2).
Le fait que des opérations d'injection de signal puissent être ignorées lors
du redémarrage de l'observé après des arrêts ptrace qui ne sont pas des
arrêts-distribution-signal est une source de confusion pour les utilisateurs
de ptrace. Un scénario typique est que l'observateur remarque un
arrêt-groupe, le confonde avec un arrêt-distribution-signal, et redémarre
l'observé avec
ptrace(PTRACE_restart, pid, 0, stopsig)
dans le but d'injecter stopsig, mais stopsig sera ignoré et l'observé
continuera de fonctionner.
Le signal SIGCONT a pour effet de bord de réveiller (tous les threads
d')un processus arrêté-groupe. Cet effet de bord arrive avant un
arrêt-distribution-signal. L'observateur ne peut pas supprimer cet effet de
bord (il ne peut que supprimer l'injection de signal, qui force seulement le
gestionnaire de SIGCONT à ne pas être exécuté dans l'observé, si un
gestionnaire de ce type est installé). En fait, le réveil depuis un
arrêt-groupe pourrait être suivi par un arrêt-distribution-signal pour le ou
les signaux différents de SIGCONT, s'ils étaient en attente quand
SIGCONT a été distribué. Autrement dit, SIGCONT pourrait ne pas être
le premier signal remarqué par l'observé après avoir été envoyé.
L'arrêt de signaux force (tous les threads d')un processus à entrer en
arrêt-groupe. Cet effet de bord arrive après une injection de signal, et
peut par conséquent être supprimé par l'observateur.
Sous Linux 2.4 et les versions précédentes, le signal SIGSTOP ne pouvait
pas être injecté.
PTRACE_GETSIGINFO peut être utilisé pour récupérer une structure
siginfo_t qui correspond au signal distribué. PTRACE_SETSIGINFO
pourrait être utilisé pour le modifier. Si PTRACE_SETSIGINFO a été
utilisé pour modifier siginfo_t, le champ si_signo et le paramètre
sig de la commande de redémarrage doivent correspondre, sinon le résultat
est indéfini.
Arrêt-groupe
Quand un processus (éventuellement multithreadé) reçoit un signal d'arrêt,
tous les threads s'arrêtent. Si certains threads sont suivis, ils entrent en
arrêt-groupe. Remarquez que le signal d'arrêt provoquera d'abord un
arrêt-distribution-signal (sur un seul observé) et, seulement après avoir
été injecté par l'observateur (ou après avoir été envoyé à un thread qui
n'est pas suivi), l'arrêt-groupe sera initié sur tous les observés d'un
processus multithreadé. Comme d'habitude, tous les observés signalent leur
arrêt-groupe séparément à l'observateur correspondant.
L'arrêt-groupe est respecté par l'observateur tant que waitpid(2)
retourne avec WIFSTOPPED(status) vrai, avec le signal d'arrêt disponible
par l'intermédiaire de WSTOPSIG(status). Le même résultat est renvoyé par
d'autres classes d'arrêts-ptrace, par conséquent la méthode conseillée est
de réaliser l'appel
ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo)
L'appel peut être évité si le signal n'est pas SIGSTOP, SIGTSTP,
SIGTTIN ou SIGTTOU ; seuls ces quatre signaux sont des signaux
d'arrêt. Si l'observateur voit autre chose, ce ne peut pas être un
arrêt-groupe. Sinon, l'observateur doit appeler PTRACE_GETSIGINFO. Si
PTRACE_GETSIGINFO échoue avec EINVAL, alors c'est définitivement un
arrêt-groupe (d'autres codes d'échec sont possibles, comme ESRCH (« pas
de processus de ce type ») si un SIGKILL a tué l'observé).
Si l’observé était attaché en utilisant PTRACE_SEIZE, un arrêt-groupe est
indiqué par PTRACE_EVENT_STOP : status>>16 == PTRACE_EVENT_STOP. Cela permet la détection d’arrêts-groupe sans nécessiter
d’appel PTRACE_GETSIGINFO supplémentaire.
Depuis Linux 2.6.38, après que l'observateur a vu l'arrêt-ptrace de
l'observé et jusqu'à ce qu'il le redémarre ou le tue, l'observé ne
fonctionnera pas, et n'enverra pas de notification (sauf mort par
SIGKILL) à l'observateur, même si l'observateur entre dans un autre appel
waitpid(2).
Le comportement du noyau décrit dans le paragraphe précédent pose un
problème avec la gestion transparente de signaux d'arrêt. Si l'observateur
redémarre l'observé après un arrêt-groupe, le signal d'arrêt est
effectivement ignoré — l'observé ne reste pas arrêté, il fonctionne. Si
l'observateur ne redémarre pas l'observé avant d'entrer dans le prochain
waitpid(2), les signaux SIGCONT suivants ne seront pas signalés à
l'observateur ; cela pourrait forcer des signaux SIGCONT à être sans
effet sur l'observé.
Depuis Linux 3.4, une méthode permet d'éviter ce problème : à la place de
PTRACE_CONT, une commande PTRACE_LISTEN peut être utilisée pour
redémarrer un observé de façon à ce qu'il ne s'exécute pas, mais attende un
nouvel événement qu'il peut signaler à l'aide de waitpid(2) (comme s'il
était redémarré par un SIGCONT).
Arrêts PTRACE_EVENT
Si l'observateur configure des options PTRACE_O_TRACE_*, l'observé
entrera en arrêts-ptrace appelés arrêts PTRACE_EVENT.
Les arrêts PTRACE_EVENT sont respectés par l'observateur pendant que
waitpid(2) renvoie WIFSTOPPED(status) et que WSTOPSIG(status)
renvoie SIGTRAP (ou pour PTRACE_EVENT_STOP, renvoie le signal d'arrêt
si l'observé est dans un arrêt de groupe). Un bit supplémentaire est
configuré dans l'octet le plus haut du mot d'état : la valeur
status>>8 sera
((PTRACE_EVENT_foo<<8) | SIGTRAP).
Les événements suivants existent.
- PTRACE_EVENT_VFORK
-
Arrêt avant de revenir de vfork(2) ou clone(2) avec l'attribut
CLONE_VFORK. Quand l'observé est continué après cet arrêt, il attendra
une sortie ou exécution de l'enfant avant de continuer son exécution
(autrement dit, le comportement normal avec vfork(2)).
- PTRACE_EVENT_FORK
-
Arrêt avant de revenir de fork(2) ou clone(2) avec le signal de sortie
configuré à SIGCHLD.
- PTRACE_EVENT_CLONE
-
Arrêt avant de revenir de clone(2).
- PTRACE_EVENT_VFORK_DONE
-
Arrêt avant de revenir de vfork(2) ou clone(2) avec l'attribut
CLONE_VFORK, mais après que l'enfant a débloqué son observé par sortie ou
exécution.
Pour les quatre arrêts décrits ci-dessus, l'arrêt arrive dans le parent
(c'est-à-dire l'observé), pas dans le nouveau thread
créé. PTRACE_GETEVENTMSG permet de récupérer l'identifiant du nouveau
thread.
- PTRACE_EVENT_EXEC
-
Arrêt avant le retour d'execve(2). Depuis Linux 3.0,
PTRACE_GETEVENTMSG renvoie le premier identifiant de thread.
- PTRACE_EVENT_EXIT
-
Arrêt avant la sortie (y compris la mort depuis exit_group(2)), la mort
du signal ou la sortie provoquée par execve(2) dans un processus
multithreadé. PTRACE_GETEVENTMSG renvoie l'état de sortie. Les registres
peuvent être examinés (contrairement à quand une « vraie » sortie
arrive). L'observé est toujours actif ; il a besoin de PTRACE_CONT ou
PTRACE_DETACH pour terminer sa sortie.
- PTRACE_EVENT_STOP
-
Arrêt causé par la commande PTRACE_INTERRUPT, ou arrêt-groupe, ou
arrêt-ptrace initial quand un nouvel enfant est attaché (seulement s’il est
attaché en utilisant PTRACE_SEIZE).
- PTRACE_EVENT_SECCOMP
-
Arrêt différé par une règle seccomp(2) sur l'entrée appel système de
l'observé quand PTRACE_O_TRACESECCOMP a été positionné par
l'observateur. Les données du message de l'événement seccomp (issues de la
portion SECCOMP_RET_DATA de la règle de filtrage seccomp) peuvent être
récupérées avec PTRACE_GETEVENTMSG. La sémantique de cet arrêt est décrit
en détails dans une section distincte ci-dessous.
PTRACE_GETSIGINFO sur les arrêts PTRACE_EVENT renvoie SIGTRAP dans
si_signo, avec si_code configuré à (event<<8) | SIGTRAP.
Arrêts-appel-système
Si l'observé était redémarré par PTRACE_SYSCALL ou PTRACE_SYSEMU,
l'observé entre en arrêt-entrée-appel-système juste avant d'entrer dans
n'importe quel appel système (qui ne sera pas exécuté si le redémarrage
utilisait PTRACE_SYSEMU, quels que soient les changements apportés aux
registres à ce point ou à la manière dont l'observé redémarre après cet
arrêt). Peu importe la méthode qui a conduit en arrêt-entrée-appel-système
si l’observateur redémarre l’observé avec PTRACE_SYSCALL, l’observé entre
en arrêt-sortie-appel-système quand l’appel système est terminé ou s'il est
interrompu par un signal (c'est-à-dire qu'un arrêt-distribution-signal
n'arrive jamais entre un arrêt-entrée-appel-système et un
arrêt-sortie-appel-système ; il arrive après
l'arrêt-sortie-appel-système). Si l'observé est poursuivi en utilisant une
autre méthode (notamment PTRACE_SYSEMU), aucun arrêt-sortie-appel-système
ne se produit. Remarquez que toutes les mentions PTRACE_SYSEMU
s'appliquent également à PTRACE_SYSEMU_SINGLESTEP.
Toutefois, même si l'observé a été poursuivi en utilisant PTRACE_SYSCALL,
il n'est pas garanti que le prochain arrêt sera un
arrêt-sortie-appel-système. D'autres possibilités sont que l'observé
pourrait s'arrêter dans un arrêt PTRACE_EVENT, sortir (s'il est entré en
_exit(2) ou exit_group(2)), être tué par SIGKILL ou mourir
silencieusement (s'il s'agit d'un leader de groupe de threads, que
l'execve(2) est arrivé dans un autre thread et que ce thread n'est pas
suivi par le même observateur ; cette situation sera abordée plus tard).
Les arrêt-entrée-appel-système et arrêt-sortie-appel-système sont respectés
par l'observateur tant que waitpid(2) retourne avec WIFSTOPPED(status)
vrai, et que WSTOPSIG(status) donne SIGTRAP. Si l'option
PTRACE_O_TRACESYSGOOD était configurée par l'observateur, alors
WSTOPSIG(status) donnera la valeur (SIGTRAP | 0x80).
Les arrêts-appel-système peuvent être distingués d'un
arrêt-distribution-signal avec SIGTRAP en demandant PTRACE_GETSIGINFO
pour les cas suivants.
- si_code <= 0
-
SIGTRAP a été distribué comme résultat d'une action en espace
utilisateur, par exemple, un appel système (tgkill(2), kill(2),
sigqueue(3), etc.), l'expiration d'un minuteur POSIX, la modification
d'état sur une file de messages POSIX où la fin d'une opération d'E/S
asynchrone.
- si_code == SI_KERNEL (0x80)
-
SIGTRAP a été envoyé par le noyau.
- si_code == SIGTRAP ou si_code == (SIGTRAP|0x80)
-
C'est un arrêt-appel-système.
Cependant, les arrêts-appel-système arrivent très souvent (deux fois par
appel système) et réaliser PTRACE_GETSIGINFO pour chaque
arrêt-appel-système pourrait être assez coûteux.
Certaines architectures permettent de distinguer ces cas en examinant les
registres. Par exemple, sur x86, rax == -ENOSYS en
arrêt-entrée-appel-système. Puisque SIGTRAP (comme tout autre signal)
arrive toujours après l'arrêt-sortie-appel-système et que rax ne
contient à ce moment presque jamais -ENOSYS, le SIGTRAP ressemble à un
« arrêt-appel-système qui n'est pas un arrêt-entrée-appel-système » ;
autrement dit, il ressemble à un « arrêt-sortie-appel-système perdu » et
peut être détecté de cette façon. Une telle détection est néanmoins fragile,
elle est donc a éviter.
L'utilisation de l'option PTRACE_O_TRACESYSGOOD est la méthode conseillée
pour distinguer les arrêts-appel-système des autres sortes d'arrêts-ptrace,
puisqu'il est fiable et n'induit pas de perte de performances.
Les arrêt-entrée-appel-système et arrêt-sortie-appel-système ne sont pas
différentiables l'un de l'autre. L'observateur doit garder une trace de la
suite d'arrêts-ptrace afin de ne pas mal interpréter un
arrêt-entrée-appel-système comme un arrêt-sortie-appel-système ou vice
versa. Généralement, l'arrêt-entrée-appel-système est toujours suivi par un
arrêt-sortie-appel-système, un arrêt PTRACE_EVENT ou la mort de
l'observé ; aucune autre sorte d'arrêt-ptrace ne peut arriver
entre-deux. Toutefois, remarquez que les arrêts seccomp (voir ci-dessous)
peuvent provoquer des arrêts-sortie-appel-système sans
arrêt-entrée-appel-système préalable. Si seccomp, il faut faire attention à
ne pas mal interpréter de tels arrêts en arrêts-entrée-appel-système.
Si suite à un arrêt-entrée-appel-système, l'observateur utilise une commande
de redémarrage différente de PTRACE_SYSCALL, l'arrêt-sortie-appel-système
n'est pas créé.
PTRACE_GETSIGINFO sur les arrêts-appel-système renvoie SIGTRAP dans
si_signo, avec si_code configuré à SIGTRAP ou (SIGTRAP | 0x80).
Arrêts PTRACE_EVENT_SECCOMP (Linux 3.5 à Linux 4.7)
Le comportement des arrêts PTRACE_EVENT_SECCOMP et leur interaction avec
les autres types d'arrêt ptrace a changé entre les versions du noyau. Nous
documentons ici le comportement lors de leur introduction dans Linux 4.7
(inclus). Le comportement dans les versions postérieures du noyau est
documenté dans la section suivante.
Un arrêt PTRACE_EVENT_SECCOMP se produit à chaque fois qu'une règle
SECCOMP_RET_TRACE est déclenchée. Cela est indépendant de la méthode
utilisée pour redémarrer l'appel système. En particulier, seccomp s'exécute
toujours même si l'observé a été redémarré en utilisant PTRACE_SYSEMU et
cet appel système est sauté sans condition.
Les redémarrages à partir de cet arrêt se comporteront comme si l'arrêt
s'était produit juste avant l'appel système en question. En particulier,
tant PTRACE_SYSCALL que PTRACE_SYSEMU provoqueront normalement un
arrêt-entrée-appel-système ultérieur. Cependant, si après le
PTRACE_EVENT_SECCOMP le numéro de l'appel système est négatif,
l'arrêt-entrée-appel-système et l'appel lui-même seront tous deux
sautés. Cela veut dire que si le numéro d'appel système est négatif après un
PTRACE_EVENT_SECCOMP et si l'observé est redémarré en utilisant
PTRACE_SYSCALL, le prochain arrêt observé sera un
arrêt-sortie-appel-système et non un arrêt-entrée-appel-système qui comme on
aurait pu s'y attendre.
Arrêts PTRACE_EVENT_SECCOMP (depuis Linux 4.8)
À partir de Linux 4.8, l'arrêt PTRACE_EVENT_SECCOMP a été réaménagé pour
intervenir entre l'arrêt-entrée-appel-système et
l'arrêt-sortie-appel-système. Remarquez que seccomp ne s'exécute plus (et
aucun PTRACE_EVENT_SECCOMP ne sera renvoyé) si l'appel système est sauté
du fait d'un PTRACE_SYSEMU.
Pratiquement, un arrêt PTRACE_EVENT_SECCOMP fonctionne comme un
arrêt-entrée-appel-système (à savoir que les reprises utilisant
PTRACE_SYSCALL provoqueront des arrêts-sortie-appel-système, le numéro de
l'appel système peut être modifié et tous les registres modifiés sont
visibles également à l'appel système à exécuter). Remarquez qu'il peut y
avoir, sans obligation qu'il y ait déjà eu, un arrêt-entrée-appel-système
précédent.
Après un arrêt PTRACE_EVENT_SECCOMP, seccomp sera réexécuté, avec une
règle SECCOMP_RET_TRACE qui fonctionne désormais de la même manière que
SECCOMP_RET_ALLOW. En particulier, cela veut dire que si les registres ne
sont pas modifiés lors d'un arrêt PTRACE_EVENT_SECCOMP, l'appel système
aura alors l'autorisation.
Arrêts PTRACE_SINGLESTEP
[Les précisions sur ces types d'arrêts sont encore à documenter.]
Commandes ptrace d'information et de redémarrage
La plupart des commandes ptrace (toutes sauf PTRACE_ATTACH,
PTRACE_SEIZE, PTRACE_TRACEME, PTRACE_INTERRUPT et PTRACE_KILL)
nécessitent que l'observé soit en arrêt-ptrace, sinon il échoue avec
ESRCH.
Quand l'observé est en arrêt-ptrace, l'observateur peut lire et écrire les
donnés sur l'observé en utilisant les commandes d'information. Ces commandes
laissent l'observé en état arrêté-ptrace :
ptrace(PTRACE_PEEKTEXT/PEEKDATA/PEEKUSER, pid, addr, 0);
ptrace(PTRACE_POKETEXT/POKEDATA/POKEUSER, pid, addr, long_val);
ptrace(PTRACE_GETREGS/GETFPREGS, pid, 0, &struct);
ptrace(PTRACE_SETREGS/SETFPREGS, pid, 0, &struct);
ptrace(PTRACE_GETREGSET, pid, NT_foo, &iov);
ptrace(PTRACE_SETREGSET, pid, NT_foo, &iov);
ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo);
ptrace(PTRACE_SETSIGINFO, pid, 0, &siginfo);
ptrace(PTRACE_GETEVENTMSG, pid, 0, &long_var);
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_flags);
Remarquez que certaines erreurs ne sont pas signalées. Par exemple, la
configuration d'informations de signal (siginfo) pourrait être sans effet
pour certains arrêts-ptrace, alors que l'appel pourrait-être réussi (en
renvoyant 0 et sans définir errno) ; la demande de
PTRACE_GETEVENTMSG pourrait réussir et renvoyer une quelconque valeur
aléatoire si l'arrêt-ptrace actuel n'est pas documenté comme renvoyant un
message d'événement significatif.
L'appel
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_flags);
ne concerne qu'un observé. Les attributs actuels de l'observé sont
remplacés. Les attributs sont hérités par les nouveaux observés créés et
« attachés automatiquement » à l'aide d'options PTRACE_O_TRACEFORK,
PTRACE_O_TRACEVFORK ou PTRACE_O_TRACECLONE actives.
Un autre groupe de commandes peut redémarrer l'observé arrêté-ptrace. Ils
sont de la forme :
ptrace(cmd, pid, 0, sig);
où cmd est PTRACE_CONT, PTRACE_LISTEN, PTRACE_DETACH,
PTRACE_SYSCALL, PTRACE_SINGLESTEP, PTRACE_SYSEMU ou
PTRACE_SYSEMU_SINGLESTEP. Si l'observé est en arrêt-distribution-signal,
sig est le signal à injecter (s'il est non nul). Sinon, sig pourrait
être ignoré (lors du redémarrage d'un observé depuis un arrêt-ptrace
différent d'un arrêt-distribution-signal, il est conseillé de toujours
passer 0 à sig).
Attachement et détachement
Un thread peut être attaché à l'observateur en utilisant l'appel
ptrace(PTRACE_ATTACH, pid, 0, 0);
ou
ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_flags);
PTRACE_ATTACH envoie aussi SIGSTOP à ce thread. Si l'observateur veut
que SIGSTOP soit sans effet, il doit le supprimer. Remarquez que si
d'autres signaux sont envoyés en même temps à ce thread pendant
l'attachement, l'observateur pourrait voir l'observé entrer en
arrêt-distribution-signal avec d'autres signaux d'abord ! Ces signaux sont
d'habitude réinjectés jusqu'à ce que SIGSTOP soit vu, puis l'injection
SIGSTOP est supprimée. Le bogue de conception ici est qu'un attachement
ptrace et un SIGSTOP distribués en même temps peuvent entrer en
compétition et le SIGSTOP risque d'être perdu.
Puisque l'attachement envoie SIGSTOP et que l'observateur le supprime
normalement, cela risque de forcer le retour d'un EINTR perdu de l'appel
système en cours d'exécution dans l'observé, tel que c'est décrit dans la
section Injection et suppression de signal.
Depuis Linux 3.4, PTRACE_SEIZE peut être utilisé à la place de
PTRACE_ATTACH. PTRACE_SEIZE n'arrête pas le processus attaché. Si vous
devez l'arrêter après attachement (ou à n'importe quel autre moment) sans
lui envoyer de signal du tout, utilisez la commande PTRACE_INTERRUPT.
L'opération
ptrace(PTRACE_TRACEME, 0, 0, 0);
transforme le thread appelant en observé. L'appel continue d'être exécuté
(n'entre pas en arrêt-ptrace). PTRACE_TRACEME est habituellement suivi
avec
raise(SIGSTOP);
et permet au parent (qui est maintenant l'observateur) de respecter
l'arrêt-distribution-signal.
Si les options PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK ou
PTRACE_O_TRACECLONE font effet, alors les enfants respectivement créés
par vfork(2) ou clone(2) avec l'attribut CLONE_VFORK, fork(2) ou
clone(2) avec le signal de sortie configuré à SIGCHLD, et d'autres
sortes de clone(2), sont automatiquement attachés au même observateur qui
à suivi leur parent. SIGSTOP est distribué aux enfants, les forçant à
entrer en arrêt-distribution-signal après être sortis de l'appel système
qu'ils ont créé.
Le détachement de l'observé est réalisé par :
ptrace(PTRACE_DETACH, pid, 0, sig);
PTRACE_DETACH est une opération de redémarrage ; par conséquent elle
nécessite que l'observé soit en arrêt-ptrace. Si l'observé est en
arrêt-distribution-signal, un signal peut être injecté. Sinon, le paramètre
sig pourrait être silencieusement ignoré.
Si l'observé est en cours d'exécution quand l'observateur veut le détacher,
la solution habituelle est d'envoyer SIGSTOP (en utilisant tgkill(2),
pour s'assurer qu'il va au bon thread), d'attendre que l'observé s'arrête en
arrêt-distribution-signal pour SIGSTOP et ensuite de le détacher (en
supprimant l'injection SIGSTOP). Un bogue de conception est que l'observé
pourrait entrer dans d'autres arrêts-ptrace et avoir besoin d'être redémarré
et attendre encore, jusqu'à ce que SIGSTOP soit vu. Encore une autre
complication est de s'assurer que l'observé n'est pas déjà arrêté-ptrace,
parce qu'aucune distribution de signal n'arrive tant qu'il l'est — pas même
SIGSTOP.
Si l'observateur meurt, tous les observés sont automatiquement détachés et
redémarrés, sauf s'il étaient en arrêt-groupe. Le gestion de redémarrage
depuis un arrêt-groupe est en ce moment dysfonctionnelle, mais le
comportement « prévu » est de laisser les observés arrêtés et d'attendre un
SIGCONT. Si l'observé est redémarré depuis un arrêt-distribution-signal,
le signal en attente est injecté.
execve(2) sous ptrace
Quand un thread de processus multithreadé appelle execve(2), le noyau
détruit tous les autres threads du processus, et réinitialise l'identifiant
de thread du thread exécuté à l'identifiant de groupe de threads (PID) (ou,
pour le présenter autrement, quand un processus multithreadé fait un
execve(2), à la fin de l'appel, il apparaît comme si l'execve(2)
s'était appliqué au leader de groupe de threads, quelque soit le thread qui
a fait execve(2)). Cette réinitialisation de l'identifiant de thread
semble est très déroutante pour les observateurs.
- -
-
Tous les autres threads s'arrêtent en arrêt PTRACE_EVENT_EXIT, si
l'option PTRACE_O_TRACEEXIT était activée. Alors tous les autres threads
sauf le leader de groupe de threads signalent leur mort comme s'il s'étaient
terminés par l'intermédiaire de _exit(2) avec un code de retour 0.
- -
-
L'observé en cours d'exécution modifie son identifiant de thread pendant
qu'il est dans l'execve(2) (rappelez-vous que, sous ptrace, le « pid »
renvoyé par waitpid(2) ou fourni dans les appels ptrace, est
l'identifiant de thread de l'observé). Ainsi, l'identifiant de thread de
l'observé est réinitialisé pour être le même que son identifiant de
processus (PID), qui est le même que l'identifiant de thread du leader de
groupe de threads.
- -
-
Ensuite un arrêt PTRACE_EVENT_EXEC arrive, si l'option
PTRACE_O_TRACEEXEC était activée.
- -
-
Si le leader de groupe de threads a signalé son arrêt PTRACE_EVENT_EXIT
pendant ce temps, le leader de thread mort à l'air de « revenir de nulle
part » du point de vue de l'observateur (remarque : le leader de groupe de
threads ne signale pas sa mort à l'aide de WIFEXITED(status) tant qu'au
moins un autre thread est en vie. Cela enlève la possibilité à l'observateur
de le voir mourir puis réapparaître). Si le leader de groupe de threads
était encore en vie, cela pourrait être vu par l'observateur comme si le
leader de groupe revenait d'un autre appel système que celui dans lequel il
était entré, ou même « revenait d'un appel système même s'il n'y avait pas
d'appel système ». Si le leader de groupe de threads n'était pas suivi (ou
était suivi par un autre observateur), alors pendant execve(2) il
apparaîtra comme s'il était devenu un observé de l'observateur de l'observé
en cours d'exécution.
Tous les effets précédents sont des artifices de la modification
d'identifiant de thread de l'observé.
L'option PTRACE_O_TRACEEXEC est l'outil conseillé pour s'occuper de cette
situation. D'abord, elle active l'arrêt PTRACE_EVENT_EXEC, qui arrive
avant le retour d'execve(2). Dans cet arrêt, l'observateur peut utiliser
PTRACE_GETEVENTMSG pour récupérer l'ancien identifiant de thread de
l'observé (cette fonctionnalité a été introduite avec Linux 3.0). Ensuite,
l'option PTRACE_O_TRACEEXEC désactive la création obsolète de SIGTRAP
dans execve(2).
Quand l'observé reçoit une notification d'arrêt PTRACE_EVENT_EXEC, il est
garanti qu'à part cet observé et le leader de groupe de threads, aucun autre
thread du processus n'est en vie.
Lors de la réception d'une notification d'arrêt PTRACE_EVENT_EXEC,
l'observateur devrait nettoyer toutes ses structures de données internes
décrivant les threads de ce processus et ne garder qu'une seule structure de
données — celle qui décrit l'unique observé en cours d'exécution, avec
identifiant de thread == identifiant de groupe de threads == identifiant de processus.
Par exemple, soient deux threads qui appellent execve(2) en même temps :
*** arrêt-entrée-appel-système obtenu dans le thread 1 : **
PID1 execve("/bin/truc", "truc" <pas terminé…>
*** PTRACE_SYSCALL émis pour le thread 1 **
*** arrêt-entrée-appel-système obtenu dans le thread 2 : **
PID2 execve("/bin/bidule", "bidule" <pas terminé…>
*** PTRACE_SYSCALL émis pour le thread 2 **
*** PTRACE_EVENT_EXEC obtenu pour PID0, PTRACE_SYSCALL émis **
*** arrêt-sortie-appel-système obtenu pour PID0 : **
PID0 <… retour d'execve> ) = 0
Si l'option PTRACE_O_TRACEEXEC n'est pas effective pour l'observé en
cours d'exécution et si l'observé a été PTRACE_ATTACHé et non
PTRACE_SEIZEé, le noyau distribue un SIGTRAP supplémentaire à
l'observé après le retour d'execve(2). C'est un signal normal (similaire
à celui qui peut être créé par kill -TRAP), pas une sorte spéciale
d'arrêt-ptrace. L'utilisation de PTRACE_GETSIGINFO pour ce signal renvoie
si_code configuré à 0 (SI_USER). Ce signal pourrait être bloqué par
un masque de signal et pourrait ainsi être distribué (bien) plus tard.
Normalement, l'observateur (par exemple strace(1)) ne voudrait pas
montrer ce signal SIGTRAP supplémentaire postérieur à execve à
l'utilisateur, et voudrait supprimer sa distribution à l'observé (si
SIGTRAP est configuré à SIG_DFL, c'est un signal tueur). Cependant,
déterminer quel est le SIGTRAP à supprimer n'est pas simple. La
configuration de l'option PTRACE_O_TRACEEXEC ou l'utilisation de
PTRACE_SEIZE et par conséquent la suppression du SIGTRAP
supplémentaire est l'approche conseillée.
Vrai parent
L'interface de programmation de ptrace utilise (parfois mal) la norme UNIX
de signalement de parent ou enfant par l'intermédiaire de
waitpid(2). Cela a régulièrement forcé le vrai parent du processus à
arrêter de recevoir plusieurs sortes de notifications de waitpid(2) quand
le processus enfant est suivi par un autre processus.
De nombreux bogues de ce type ont été corrigés, mais il en reste encore
beaucoup dans Linux 2.6.38. Consultez la section BOGUES ci dessous.
Depuis Linux 2.6.38, ce qui suit est censé fonctionner correctement :
- -
-
l'exécution ou la mort par signal sont d'abord signalées à l'observateur,
puis, quand l'observateur consomme le résultat de waitpid(2), au vrai
parent (au vrai parent seulement quand l'intégralité du processus
multithreadé se termine). Si l'observateur et le vrai parent sont le même
processus, le signalement n'est envoyé qu'une fois.
VALEUR RENVOYÉE
En cas de succès, l'opération PTRACE_PEEK* renvoie les données demandées
(mais consultez les NOTES), l'opération PTRACE_SECCOMP_GET_FILTER renvoie
le nombre d'instructions du programme BPF, l'opération
PTRACE_GET_SYSCALL_INFO renvoie le nombre d'octets disponibles disponible
pour être écrits par le noyau, alors que les autres opérations renvoient
zéro.
En cas d'erreur, toutes les opérations renvoient -1 et errno est
défini pour indiquer l'erreur. Comme la valeur renvoyée par une opération
PTRACE_PEEK* peut légitimement être -1, l'appelant doit effacer
errno avant l'appel, et ensuite le vérifier pour savoir si une erreur
s'est produite.
ERREURS
- EBUSY
-
(i386 seulement) Une erreur est survenue lors de l'allocation ou de la
libération d'un registre de débogage.
- EFAULT
-
Tentative de lire ou écrire dans une zone mémoire non valable de
l'observateur ou de l'observé, probablement parce que la zone n'était pas
projetée ou accessible. Malheureusement sous Linux, certaines variantes de
cette erreur déclencheront EIO ou EFAULT plus ou moins arbitrairement.
- EINVAL
-
Tentative d'utiliser une option non valable.
- EIO
-
L'opération op n'est pas valable ou une tentative de lecture ou
d'écriture dans une zone non valable de mémoire de l'observateur ou de
l'observé a eu lieu. Un problème d'alignement a aussi pu survenir sur une
frontière de mot, ou un signal non valable a été spécifié pendant une
opération de redémarrage.
- EPERM
-
Le processus indiqué ne peut pas être suivi. Cela peut être dû à un manque
de privilège de l'observateur (la capacité nécessaire est
CAP_SYS_PTRACE). Les processus non privilégiés ne peuvent pas suivre les
processus auxquels ils ne peuvent envoyer de signal, ou ceux qui s'exécutent
Set-UID/Set-GID, pour des raisons évidentes. En outre, le processus visé
peut être déjà suivi, ou (avant Linux 2.6.26) être init(8) (le processus
numéro 1).
- ESRCH
-
Le processus indiqué n'existe pas, ou n'est pas suivi par l'appelant, ou
n'est pas arrêté (pour les opérations qui ont besoin d'un observé arrêté).
STANDARDS
Aucun.
HISTORIQUE
SVr4, 4.3BSD.
Avant Linux 2.6.26, init(8), le processus numéro 1, ne peut pas être
suivi.
NOTES
Bien que les arguments de ptrace() soient interprétés comme dans le
prototype donné, la bibliothèque glibc déclare ptrace comme une fonction
variadique où seul l'argument op est corrigé. Il vaut mieux toujours
fournir quatre arguments, même si l'opération demandée ne les utilise pas,
en configurant les arguments non utilisés ou ignorés à 0L ou (void *) 0.
Le parent d'observés reste observateur même s'il appelle execve(2).
La disposition du contenu de la mémoire et de la zone USER dépendent du
système d'exploitation et de l'architecture. Le décalage fourni et les
données renvoyées peuvent ne pas correspondre entièrement avec la définition
d'une structure struct user.
La taille d'un mot (« word ») est déterminée par la version du système
d'exploitation (par exemple 32 bits pour Linux 32 bits).
Cette page documente le fonctionnement actuel de ptrace() sous
Linux. Celui-ci peut varier significativement d'autres types d'UNIX. De
toute façon, l'utilisation de ptrace() dépend fortement de l'architecture
et du système d'exploitation.
Vérification du mode d'accès ptrace
Divers endroits de l'API de l'espace utilisateur du noyau (pas seulement les
opérations ptrace()) exigent ce qu'on appelle des « vérifications de mode
d'accès ptrace », dont le résultat détermine si une opération est autorisée
(ou, dans certains cas, fait renvoyer à l'opération « read » des données
nettoyées). Ces vérifications sont effectuées dans les cas où un processus
peut accéder à des informations sensibles concernant un autre processus ou
modifier son état. Les vérifications s'opèrent sur la base de facteurs tels
que les droits et les capacités des deux processus, le fait que le processus
« cible » puisse générer un fichier core, et des résultats des vérifications
effectuées par un module de sécurité Linux activé (LSM) (par exemple
SELinux, Yama ou Smack) et par le LSM commoncap (qui est toujours appelé).
Avant Linux 2.6.27, toutes les vérifications d'accès étaient d'un seul
type. Depuis Linux 2.6.27, on distingue deux niveaux de modes d'accès :
- PTRACE_MODE_READ
-
Pour les opérations « read » ou d'autres moins dangereuses telles que :
get_robust_list(2) ; kcmp(2) ; la lecture de /proc/pid/auxv,
/proc/pid/environ ou de /proc/pid/stat ; ou le readlink(2)
d'un fichier /proc/pid/ns/*.
- PTRACE_MODE_ATTACH
-
Pour les opérations « write » ou d'autres plus dangereuses telles que : le
rattachement de ptrace (PTRACE_ATTACH) à un autre processus ou un appel
process_vm_writev(2) (PTRACE_MODE_ATTACH était celui par défaut avant
Linux 2.6.27).
Depuis Linux 4.5, les vérifications de mode d'accès ci-dessus sont combinées
(opération OU) avec un ou plusieurs des modificateurs suivants :
- PTRACE_MODE_FSCREDS
-
Utiliser l'identifiant de groupe ou d’utilisateur du système de fichiers de
l'appelant (voir credentials(7)) ou les capacités effectives pour les
vérifications LSM.
- PTRACE_MODE_REALCREDS
-
Utiliser l'identifiant de groupe ou d’utilisateur réel de l'appelant ou les
capacités autorisées pour les vérifications LSM. C'était l'action par défaut
avant Linux 4.5.
La combinaison d'un des modificateurs de droits avec un des modes d'accès
ci-dessus étant classique, certaines macros sont définies dans les sources
du noyau pour les combinaisons :
- PTRACE_MODE_READ_FSCREDS
-
Définie en tant que PTRACE_MODE_READ | PTRACE_MODE_FSCREDS.
- PTRACE_MODE_READ_REALCREDS
-
Définie en tant que PTRACE_MODE_READ | PTRACE_MODE_REALCREDS.
- PTRACE_MODE_ATTACH_FSCREDS
-
Définie en tant que PTRACE_MODE_ATTACH | PTRACE_MODE_FSCREDS.
- PTRACE_MODE_ATTACH_REALCREDS
-
Définie en tant que PTRACE_MODE_ATTACH | PTRACE_MODE_REALCREDS.
Un modificateur supplémentaire peut être lié (opération OU) au mode
d'accès :
- PTRACE_MODE_NOAUDIT (depuis Linux 3.3)
-
Ne pas effectuer la vérification de ce mode d'accès. Ce modificateur est
utilisé pour les vérifications de mode d'accès ptrace (telles que celles
faites lors de la lecture de /proc/pid/stat) qui filtrent ou nettoient
la sortie au lieu de renvoyer une erreur à l'appelant. Dans ces cas, l'accès
au fichier n'est pas une violation de sécurité et il n'y aucune raison de
générer un enregistrement de l'évaluation de la sécurité. Ce modificateur
supprime la génération d'un tel enregistrement pour cette vérification
d'accès particulière.
Remarquez que toutes les constantes PTRACE_MODE_* décrites dans cette
sous-section sont internes au noyau et invisibles à l'espace utilisateur. Le
nom des constantes est mentionné ici pour identifier les différents types de
vérification des modes d'accès ptrace effectuées pour divers appels système
et divers accès à des pseudofichiers (comme dans /proc). Ces noms sont
utilisés dans d'autres pages de manuel pour fournir un raccourci simple
d'identification des vérifications du noyau.
L'algorithme utilisé pour la vérification des modes d'accès ptrace détermine
si le processus appelant est autorisé à effectuer l'action correspondante
sur le processus cible (pour l'ouverture des fichiers /proc/pid, le
« processus appelant » est celui qui ouvre le fichier et le processus dont
le PID correspond est le « processus cible »). L’algorithme est comme suit :
- (1)
-
Si le thread appelant et cible sont dans le même groupe de threads, l'accès
est toujours autorisé.
- (2)
-
Si le mode d'accès indique PTRACE_MODE_FSCREDS, lors de la vérification
de la prochaine étape, utiliser l'identifiant d'utilisateur et de groupe du
système de fichiers appelant (comme indiqué dans credentials(7), les
identifiants d'utilisateur et de groupe du système de fichiers ont presque
toujours la même valeur que les identifiants effectifs correspondant).
-
Sinon le mode d'accès indique PTRACE_MODE_REALCREDS, donc utiliser
l'identifiant d'utilisateur et de groupe réels de l'appelant lors des
vérifications de la prochaine étape (la plupart des API qui vérifient les
identifiants d’utilisateur et de groupe utilisent les identifiants
effectifs. Pour des raisons historiques, la vérification
PTRACE_MODE_REALCREDS utilise plutôt ceux réels).
- (3)
-
Interdire l'accès si rien de ce qui suit n'est vrai :
-
- -
-
Les identifiants utilisateur réel, effectif et défini de la cible
correspondent à l'identifiant utilisateur de l'appelant et les
identifiants réels, effectifs et définis de groupe de la cible correspondent
à ceux de groupe de l'appelant.
- -
-
L'appelant a la capacité CAP_SYS_PTRACE dans l'espace de noms utilisateur
de la cible.
- (4)
-
Interdire l'accès si l'attribut « dumpable » du processus cible a une autre
valeur que 1 (SUID_DUMP_USER ; voir le point sur PR_SET_DUMPABLE
dans prctl(2)) et l'appelant n'a pas la capacité CAP_SYS_PTRACE dans
l'espace de noms de l'utilisateur du processus cible.
- (5)
-
L'interface security_ptrace_access_check() du LSM du noyau est appelée
pour voir si l'accès ptrace est autorisé. Le résultat dépend des
LSM. L'implémentation de cette interface dans le LSM commoncap suit les
étapes suivantes :
-
- (5.1)
-
Si le mode d'accès comprend PTRACE_MODE_FSCREDS, utiliser l’ensemble des
capacités effectives de l'appelant dans la prochaine vérification ; sinon
(si le mode d'accès indique PTRACE_MODE_REALCREDS), utiliser l’ensemble
des capacités autorisées de l'appelant.
- (5.2)
-
Interdire l'accès si rien de ce qui suit n'est vrai :
-
- -
-
Les processus appelant et cible sont dans le même espace de noms utilisateur
et les capacités de l'appelant sont un surensemble des capacités
autorisées du processus cible.
- -
-
L'appelant a la capacité CAP_SYS_PTRACE dans l'espace de noms utilisateur
du processus cible.
-
Remarquez que le LSM commoncap ne fait pas de différence entre
PTRACE_MODE_READ et PTRACE_MODE_ATTACH.
- (6)
-
Si l'accès n'a pas été interdit par une étape précédente, il est autorisé.
/proc/sys/kernel/yama/ptrace_scope
Sur des systèmes ayant un Yama Linux Security Module (LSM) installé (donc si
le noyau a été configuré avec CONFIG_SECURITY_YAMA), le fichier
/proc/sys/kernel/yama/ptrace_scope (disponible depuis Linux 3.4) peut
être utilisé pour restreindre la possibilité d'observer un processus avec
ptrace() (et ainsi, celle d'utiliser des outils tels que strace(1) et
gdb(1)). Le but de telles restrictions est d'empêcher des attaques en
cascade par lesquelles un processus infecté peut s'attacher avec un ptrace à
d'autres processus sensibles (comme un agent GPG ou une session SSH)
appartenant à l'utilisateur, afin d'obtenir d'autres droits qui pourraient
exister en mémoire et ainsi, élargir l'objectif de l'attaque.
Plus précisément, le Yama LSM limite deux types d'opérations :
- -
-
Toute opération qui effectue une vérification PTRACE_MODE_ATTACH de mode
d'accès (par exemple, PTRACE_ATTACH de ptrace(), voir le point sur les
« vérifications de mode d'accès ptrace » ci-dessus).
- -
-
PTRACE_TRACEME ptrace().
Un processus ayant la capacité CAP_SYS_PTRACE peut mettre à jour le
fichier /proc/sys/kernel/yama/ptrace_scope avec une ou plusieurs des
valeurs suivantes :
- 0 (droits ptrace « classiques »)
-
Aucune restriction supplémentaire sur les opérations qui effectuent des
vérifications PTRACE_MODE_ATTACH (au-delà de celles imposées par le LSM
commoncap et les autres).
-
L'utilisation de PTRACE_TRACEME ne change pas.
- 1 (« ptrace restreint » (valeur par défaut)
-
Lors d'une opération qui exige une vérification PTRACE_MODE_ATTACH, le
processus appelant doit avoir soit la capacité CAP_SYS_PTRACE dans
l'espace de noms utilisateur du processus cible, soit une relation
prédéfinie avec le processus cible. Par défaut, la relation prédéfinie est
que le processus cible doit être un descendant de l'appelant.
-
Un processus cible peut utiliser l'opération PR_SET_PTRACER de
prctl(2) pour déclarer un PID supplémentaire autorisé à effectuer des
opérations PTRACE_MODE_ATTACH sur la cible. Voir le fichier
Documentation/admin-guide/LSM/Yama.rst des sources du noyau (ou
Documentation/security/Yama.txt avant Linux 4.13) pour plus de détails.
-
L'utilisation de PTRACE_TRACEME ne change pas.
- 2 (attachement « admin-only »)
-
Seuls les processus ayant la capacité CAP_SYS_PTRACE dans l'espace de
noms de l'utilisateur du processus cible peuvent effectuer des opérations
PTRACE_MODE_ATTACH ou observer les enfants qui utilisent
PTRACE_TRACEME.
- 3 (« pas d'attachement »)
-
Aucun processus ne peut effectuer d'opération PTRACE_MODE_ATTACH ou
observer les enfants qui utilisent PTRACE_TRACEME.
-
Une fois que cette valeur a été écrite dans le fichier, elle ne peut pas
être modifiée.
Par rapport aux valeurs 1 et 2, remarquez que la création d'un nouvel
espace de noms utilisateur supprime de fait la protection apportée par
Yama. Cela parce qu'un processus dans l'espace de noms de l'utilisateur
parent dont l'identifiant utilisateur effectif correspond à celui de
l'espace de noms enfant a toutes les capacités (y compris CAP_SYS_PTRACE)
lorsqu'il effectue des opérations dans l'espace de noms utilisateur de
l'enfant (et les descendants supprimés plus tard de cet espace de noms). Par
conséquent, quand un processus essaie d'utiliser les espaces de noms
utilisateur pour s'isoler lui-même, il affaiblit à son insu les protections
apportées par le Yama LSM.
Différences entre bibliothèque C et noyau
L'interface de programmation de l'appel système est différente pour les
opérations PTRACE_PEEKTEXT, PTRACE_PEEKDATA et PTRACE_PEEKUSER :
elles stockent le résultat à l’adresse indiquée par le paramètre data, et
la valeur de retour est l’attribut d’erreur. La fonction glibc encapsulant
cet appel fournit une interface détaillée dans la section DESCRIPTION
ci-dessus, et le résultat qu'elle renvoie est le résultat de l'appel
système.
BOGUES
Sur les machines ayant des en-têtes du noyau Linux 2.6, PTRACE_SETOPTIONS
est déclaré avec une valeur différente de celle de Linux 2.4. De ce fait,
les applications compilées avec des en-têtes du noyau Linux 2.6 ne peuvent
pas s'exécuter sous Linux 2.4. Il est possible de contourner cette
difficulté en redéfinissant PTRACE_SETOPTIONS à PTRACE_OLDSETOPTIONS,
si cette dernière constante est définie.
Les notifications d'arrêt-groupe sont envoyées à l'observateur, mais pas au
vrai parent. C'était encore vrai sur 2.6.38.6.
Si un leader de groupe de threads est suivi et existe en appelant
_exit(2), un arrêt PTRACE_EVENT_EXIT lui arrivera (si réclamé), mais
la notification WIFEXITED suivante ne sera pas distribuée avant la fin de
tous les autres threads. Comme expliqué précédemment, si un des autres
threads appelle execve(2), la mort du leader de groupe de threads ne sera
jamais signalée. Si le thread exécuté n'est pas suivi par cet
observateur, l'observé ne saura jamais qu'execve(2) est arrivé. Un
contournement possible est de PTRACE_DETACHer le leader de groupe de
threads au lieu de le redémarrer dans ce cas. C'était encore vrai sur
2.6.38.6.
Un signal SIGKILL pourrait encore provoquer un arrêt PTRACE_EVENT_EXIT
avant une véritable mort du signal. Cela pourrait évoluer à
l'avenir. SIGKILL est supposé tuer immédiatement les tâches même sous
ptrace. C'était encore vrai sur Linux 3.13.
Certains appels système renvoient EINTR si un signal a été envoyé à
l'observé, mais que la distribution a été supprimée par l'observateur (c'est
une opération tout à fait caractéristique : elle est normalement réalisée
par les débogueurs sur tous les attachements, afin de ne pas introduire de
SIGSTOP défectueux). Depuis Linux 3.2.9, les appels système suivants sont
concernés (cette liste est sans doute incomplète) : epoll_wait(2) et
read(2) depuis un descripteur de fichier inotify(7). Le symptôme
classique de ce bogue est qu'en attachant à un processus quiescent avec la
commande
strace -p <process-ID>
alors, au lieu de la ligne de sortie habituelle attendue comme
restart_syscall(<... reprise de l'appel interrompu ...>_
ou
select(6, [5], NULL, [5], NULL_
(« _ » indique la position du curseur), plusieurs lignes sont affichées. Par
exemple :
clock_gettime(CLOCK_MONOTONIC, {15370, 690928118}) = 0
epoll_wait(4,_
Ce qui n'est pas visible ici est que le processus a été bloqué dans
epoll_wait(2) avant que strace(1) ne s'y soit attaché. L'attachement a
forcé epoll_wait(2) à revenir dans l'espace utilisateur avec l'erreur
EINTR. Dans ce cas particulier, le programme a réagit à EINTR en
vérifiant l'heure actuelle et en exécutant encore epoll_wait(2) (les
programmes qui ne s'attendent pas à de telles erreurs EINTR « perdue »
risquent de se comporter de façon inattendue sur une attache strace(1)).
Contrairement aux règles normales, l'enveloppe de la glibc pour ptrace()
peut positionner errno sur zéro.
VOIR AUSSI
gdb(1), ltrace(1), strace(1), clone(2), execve(2),
fork(2), gettid(2), prctl(2), seccomp(2), sigaction(2),
tgkill(2), vfork(2), waitpid(2), exec(3), capabilities(7),
signal(7)
TRADUCTION
La traduction française de cette page de manuel a été créée par
Christophe Blaess <https://www.blaess.fr/christophe/>,
Stéphan Rafin <stephan.rafin@laposte.net>,
Thierry Vignaud <tvignaud@mandriva.com>,
François Micaux,
Alain Portal <aportal@univ-montp2.fr>,
Jean-Philippe Guérard <fevrier@tigreraye.org>,
Jean-Luc Coulon (f5ibh) <jean-luc.coulon@wanadoo.fr>,
Julien Cristau <jcristau@debian.org>,
Thomas Huriaux <thomas.huriaux@gmail.com>,
Nicolas François <nicolas.francois@centraliens.net>,
Florentin Duneau <fduneau@gmail.com>,
Simon Paillard <simon.paillard@resel.enst-bretagne.fr>,
Denis Barbier <barbier@debian.org>,
David Prévot <david@tilapin.org>
et
Jean-Philippe MENGUAL <jpmengual@debian.org>
Cette traduction est une documentation libre ; veuillez vous reporter à la
GNU General Public License version 3
concernant les conditions de copie et
de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.
Si vous découvrez un bogue dans la traduction de cette page de manuel,
veuillez envoyer un message à
Index
- NOM
-
- BIBLIOTHÈQUE
-
- SYNOPSIS
-
- DESCRIPTION
-
- Mort sous ptrace
-
- États arrêtés
-
- Arrêt-distribution-signal
-
- Injection et suppression de signal
-
- Arrêt-groupe
-
- Arrêts PTRACE_EVENT
-
- Arrêts-appel-système
-
- Arrêts PTRACE_EVENT_SECCOMP (Linux 3.5 à Linux 4.7)
-
- Arrêts PTRACE_EVENT_SECCOMP (depuis Linux 4.8)
-
- Arrêts PTRACE_SINGLESTEP
-
- Commandes ptrace d'information et de redémarrage
-
- Attachement et détachement
-
- execve(2) sous ptrace
-
- Vrai parent
-
- VALEUR RENVOYÉE
-
- ERREURS
-
- STANDARDS
-
- HISTORIQUE
-
- NOTES
-
- Vérification du mode d'accès ptrace
-
- /proc/sys/kernel/yama/ptrace_scope
-
- Différences entre bibliothèque C et noyau
-
- BOGUES
-
- VOIR AUSSI
-
- TRADUCTION
-
This document was created by
man2html,
using the manual pages.
Time: 05:06:04 GMT, September 19, 2025