ptrace(2) | System Calls Manual | ptrace(2) |
ptrace - Prozessverfolgung
Standard-C-Bibliothek (libc, -lc)
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request Anfrage, pid_t PID, void *Adresse, void *Daten);
Der Systemaufruf ptrace() stellt ein Mittel bereit, wodurch ein Prozess (der »Verfolger«) die Ausführung eines anderen Prozesses (des »verfolgten Prozesses«) beobachten und steuern kann und seinen Speicher sowie die Register untersuchen und ändern kann. Er wird in erster Linie benutzt, um Fehlersuche mittels Haltepunkten zu implementieren und Systemaufrufe zu verfolgen.
Ein verfolgter Prozess muss zuerst an den Verfolger angehängt werden. Anhängen und nachfolgende Befehle bestehen pro Thread: In einem Prozess mit mehreren Threads kann jeder Thread individuell an einen (möglicherweise unterschiedlichen) Verfolger angehängt werden oder nicht angehängt und folglich nicht auf Fehler untersucht werden. Daher bedeutet »verfolgter Prozess« immer »(ein) Thread«, niemals »ein Prozess (mit möglicherweise mehreren Threads)«. Ptrace-Befehle werden immer an einen bestimmten verfolgten Prozess gesandt. Der Aufruf hat folgende Form:
ptrace(PTRACE_foo, PID, …)
wobei PID die Thread-Kennung des zugehörigen Linux-Threads ist.
(Beachten Sie, dass auf dieser Seite ein »Prozess aus mehreren Threads« eine Thread-Gruppe bezeichnet, die aus Threads besteht, die mittels des clone(2)-Schalters CLONE_THREAD erzeugt wurde.)
Ein Prozess kann eine Verfolgung mittels fork(2) starten und als Ergebnis einen Kindprozess erhalten, der PTRACE_TRACEME ausführt, was (üblicherweise) von einem execve(2) gefolgt wird. Alternativ kann ein Prozess die Verfolgung eines anderen Prozesses mittels PTRACE_ATTACH oder PTRACE_SEIZE beginnen.
Während der Prozess verfolgt wird, wird er jedesmal stoppen, wenn ein Signal gesandt wird, sogar wenn das Signal ignoriert wird. (Eine Ausnahme ist SIGKILL, das seine normale Wirkung erzielt.) Der Verfolger wird bei seinem nächsten Aufruf von waitpid(2) (oder den zugehörigen »wait«-Systemaufrufen) benachrichtigt; dies wird einen Statuswert zurückgeben, der Informationen enthält, die den Grund angeben, weshalb der verfolgte Prozess stoppte. Während der verfolgte Prozess angehalten ist, kann der Verfolger verschiedene Ptrace-Anfragen verwenden, um den verfolgten Prozess zu untersuchen und zu verändern. Dann veranlasst der Verfolger den verfolgten Prozess fortzufahren und wahlweise das versandte Signal zu ignorieren (oder stattdessen sogar ein anderes Signal zu senden).
Falls die Option PTRACE_O_TRACEEXEC nicht in Kraft ist, werden alle erfolgreichen Aufrufe von execve(2) durch den verfolgten Prozess zum Senden eines SIGTRAP-Signals veranlassen, um dem Elternprozess die Möglichkeit zu geben, die Kontrolle zu erlangen, bevor die Ausführung des neuen Programms beginnt.
Wenn der Verfolger die Verfolgung beendet hat, kann er veranlassen, das der verfolgte Prozess mit PTRACE_DETACH in einem normal Modus ohne Verfolgung fortfährt.
Der Wert des Arguments Anfrage legt die Aktion des Systemaufrufs fest:
struct ptrace_peeksiginfo_args { u64 off; /* Ordnungsposition in der Warteschlange, an der mit dem Kopieren der Signale begonnen wird */ u32 flags; /* PTRACE_PEEKSIGINFO_SHARED oder 0 */ s32 nr; /* Anzahl zu kopierender Signale */ };
status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8))
status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
status>>8 == (SIGTRAP | (PTRACE_EVENT_EXIT<<8))
status>>8 == (SIGTRAP | (PTRACE_EVENT_FORK<<8))
status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK<<8))
status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK_DONE<<8))
status>>8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP<<8))
struct ptrace_syscall_info { __u8 op; /* Typ des Systemaufruf-Stopps */ __u32 arch; /* AUDIT_ARCH_*-Wert; siehe seccomp(2) */ __u64 instruction_pointer; /* CPU-Anweisungszeiger */ __u64 stack_pointer; /* CPU-Stack-Zeiger */ union { struct { /* op == PTRACE_SYSCALL_INFO_ENTRY */ __u64 nr; /* Systemaufrufnummer */ __u64 args[6]; /* Systemaufrufargumente */ } entry; struct { /* op == PTRACE_SYSCALL_INFO_EXIT */ __s64 rval; /* Systemaufruf-Rückgabewert */ __u8 is_error; /* Systemaufruf-Fehlerschalter; logisch: enthält rval einen Fehlerwert (-ERRCODE) oder einen nichtfehler-Rückgabewert? */ } exit; struct { /* op == PTRACE_SYSCALL_INFO_SECCOMP */ __u64 nr; /* Systemaufrufnummer */ __u64 args[6]; /* Systemaufrufargumente */ __u32 ret_data; /* SECCOMP_RET_DATA-Anteil vom SECCOMP_RET_TRACE- Rückgabewert */ } seccomp; }; };
Wenn ein Prozess (der möglicherweise aus mehreren Threads besteht) ein tötendes Signal erhält (eines, dessen Zuordnung auf SIG_DFL gesetzt ist und dessen Standardaktion das Töten des Prozesses ist) werden alle Threads beendet. Verfolgte Prozesse melden ihren Tod an ihre(n) Verfolger. Die Benachrichtigung über dieses Ereignis wird über waitpid(2) zugestellt.
Beachten Sie, dass das killende Signal zuerst einen Signallieferstopp (auf nur einen verfolgten Prozess) verursachen wird und nur nachdem es durch den Verfolger eingespeist wurde (oder nachdem es an einen nicht verfolgten Thread versandt wurde), wird der Tod von dem Signal auf alle verfolgten Prozesse innerhalb eines Prozesses mit mehreren Threads ausgehen. (Der Begriff »Signallieferstopp« wird nachfolgend erklärt.)
SIGKILL erzeugt keinen »Signallieferstopp«. Daher kann der Verfolger es nicht unterdrücken. SIGKILL killt sogar innerhalb von Systemaufrufen (der Systemaufrufbeendigungsstopp wird nicht vorrangig vor dem Tod durch SIGKILL erzeugt). Der reine Effekt besteht darin, dass SIGKILL den Prozess (all seine Threads) immer killt, sogar dann, wenn einige Threads des Prozesses verfolgt werden.
Wenn der verfolgte Prozess _exit(2) aufruft, meldet er seinem Verfolger seinen Tod. Andere Threads werden nicht beeinflusst.
Wenn irgendein Thread exit_group(2) ausführt, meldet jeder verfolgte Prozess in dessen Gruppe seinen Tod an den Verfolger.
Falls die Option PTRACE_O_TRACEEXIT aktiv ist, wird PTRACE_EVENT_EXIT vor dem tatsächlichen Tod auftreten. Dies wird angewandt, um mittels exit(2), exit_group(2) und Todessignalen (ausgenommen SIGKILL, abhängig von der Kernel-Version, siehe FEHLER unten) zu beenden und wenn Threads bei execve(2) in einem Prozess mit mehreren Threads zerrissen werden.
Der Verfolger kann nicht abschätzen, ob der von Ptrace gestoppte Prozess existiert. Es gibt mehrere Szenarien, in denen der verfolgte Prozess sterben kann, während er gestoppt ist (wie SIGKILL). Daher muss der Verfolger vorbereitet werden, bei allen Ptrace-Aktionen einen ESRCH-Fehler zu handhaben. Leider wird der gleiche Fehler zurückgegeben, falls der verfolgte Prozess existiert, aber nicht von Ptrace gestoppt wurde (für Befehle, die einen gestoppten, verfolgten Prozess erfordern) oder falls er nicht durch den Prozess verfolgt wird, der den Ptrace-Aufruf abschickte. Der Verfolger muss den Überblick über den »laufend«-/»gestoppt«-Status des verfolgten Prozesses behalten und ESRCH nur dann als »verfolgter Prozess starb unerwartet« interpretieren, falls er weiß, dass der verfolgte Prozess beobachtet wurde, um in einen Ptrace-Stopp einzutreten. Beachten Sie, dass es keine Garantie gibt, dass waitpid(WNOHANG) zuverlässig den Todesstatus des verfolgten Prozesses meldet, falls eine Ptrace-Aktion ESRCH zurückgibt. waitpid(WNOHANG) könnte stattdessen 0 zurückgeben. In anderen Worten kann es sein, dass der verfolgte Prozess »noch nicht vollständig tot« ist, aber bereits Ptrace-Anfragen ablehnt.
Der Verfolger kann nicht abschätzen, ob der verfolgte Prozess immer sein Leben durch Melden von WIFEXITED(status) oder WIFSIGNALED(status) aushaucht. Es gibt Fälle, in denen dies nicht geschieht. Falls ein Thread, der nicht führender Thread der Thread-Gruppe ist, zum Beispiel ein execve(2) ausführt, verschwindet er; seine PID wird nie wieder gesehen und alle nachfolgenden Ptrace-Stopps werden unter der PID des führenden Threads der Thread-Gruppe gemeldet.
Ein verfolgter Prozess kann zwei Status haben: laufend oder gestoppt. Für die Zwecke von Ptrace wird ein durch einen Systemaufruf (wie read(2), pause(2), etc.) blockierter verfolgter Prozess dennoch als laufend betrachtet, sogar, wenn der verfolgte Prozess für lange Zeit blockiert ist. Der Status des verfolgten Prozesses nach PTRACE_LISTEN ist eine Grauzone: Er ist nicht in einem Ptrace-Stopp (Ptrace-Befehle funktionieren nicht bei ihm und er wird waitpid(2)-Benachrichtigungen übermitteln), aber er kann auch als »gestoppt« angesehen werden, da er keine Anweisungen ausführt (ist nicht eingeplant) und falls er vor PTRACE_LISTEN in einem Gruppenstopp war, wird er nicht auf Signale antworten, bis er SIGCONT empfängt.
Es gibt viele Arten von Status, wenn ein verfolgter Prozess gestoppt wurde und in Ptrace-Diskussionen werden sie oft durcheinandergebracht. Daher ist es wichtig, präzise Begriffe zu verwenden.
In dieser Handbuchseite wird jeder Gestoppt-Status, in dem der verfolgte Prozess bereit ist, Ptrace-Befehle vom Verfolger zu akzeptieren, Ptrace-Stopp genannt. Ptrace-Stopps können weiter in Signallieferstopp, Gruppenstopp, PTRACE_EVENT stops und so fort unterteilt werden. Diese gestoppten Status werden nachfolgend im Detail beschrieben.
Wenn der laufende, verfolgte Prozess in Ptrace-Stopp eintritt, benachrichtigt er seinen Verfolger mittels waitpid(2) (oder einem anderen der »wait«-Systemaufrufe). Meistens geht diese Handbuchseite davon aus, dass der Verfolger wartet mit:
PID = waitpid(PID_ODER_Minus1, &status, __WALL);
Mit Ptrace-Stopp angehaltene, verfolgte Prozesse werden als Rückgaben mit PID größer als 0 und »WIFSTOPPED(status) true« gemeldet.
Der Schalter __WALL enthält nicht die Schalter WSTOPPED und WEXITED, impliziert aber ihre Funktionalität.
Es wird nicht empfohlen, den Schalter WCONTINUED zu setzen, wenn waitpid(2) aufgerufen wird: Der Status »continued« gilt pro Prozess und ihn zu verbrauchen, kann den echten Elternprozess des verfolgten Prozesses verwirren.
Die Benutzung des Schalters WNOHANG könnte waitpid(2) veranlassen, 0 zurückzugeben (»noch keine Warteergebnisse verfügbar«), sogar dann, wenn der Verfolger weiß, dass dort eine Benachrichtigung sein soll. Beispiel:
errno = 0; ptrace(PTRACE_CONT, pid, 0L, 0L); if (errno == ESRCH) { /* verfolgter Prozess ist tot */ r = waitpid(tracee, &status, __WALL | WNOHANG); /* r kann hier immer noch 0 sein! */ }
Die folgenden Arten von Ptrace-Stopps existieren: Signallieferstopps, Gruppenstopps, PTRACE_EVENT-Stopps und Systemaufrufstopps. Sie alle werden von waitpid(2) mit »WIFSTOPPED(status) true« gemeldet. Sie könnten durch Untersuchen des Wertes status>>8 unterschieden werden und, falls es eine Unklarheit im Wert gibt, durch Abfragen von PTRACE_GETSIGINFO. (Hinweis: Das Makro WSTOPSIG(status) kann nicht für diese Untersuchung verwandt werden, da es den Wert (status>>8) & 0xff zurückgibt.)
Wenn ein Prozess (möglicherweise mit mehreren Threads) ein Signal außer SIGKILL empfängt, wählt der Kernel einen beliebigen Thread aus, der das Signal handhabt. (Falls das Signal mit tgkill(2) erzeugt wurde, kann der Ziel-Thread explizit durch den Aufrufenden ausgewählt werden.) Falls der ausgewählte Thread verfolgt wird, tritt er in einen Signallieferstopp ein. An diesem Punkt wird das Signal noch nicht an den Prozess zugestellt und kann durch den Verfolger unterdrückt werden. Falls der Verfolger das Signal nicht unterdrückt, übergibt er das Signal bei der nächsten Ptrace-Neustartanfrage an den verfolgten Prozess. Dieser zweite Schritt der Signalzustellung wird in dieser Handbuchseite Signaleinspeisung genannt. Beachten Sie, dass, falls das Signal blockiert ist, der Signallieferstopp nicht auftritt, bis die Blockade des Signals aufgehoben wurde, mit der üblichen Ausnahme, dass SIGSTOP nicht blockiert werden kann.
Der Signallieferstopp wird vom Verfolger als waitpid(2) beobachtet und kehrt mit »WIFSTOPPED(status) true« mit dem Signal zurück, das von WSTOPSIG(status) zurückgegeben wurde. Falls das Signal SIGTRAP ist, könnte dies eine andere Art eines Ptrace-Stopps sein; Einzelheiten finden Sie in den Abschnitten »Systemaufrufstopps« und »execve« unterhalb. Falls WSTOPSIG(status) ein stoppendes Signal zurückgibt, könnte dies ein Gruppenstopp sein; siehe unten.
Nachdem der Signallieferstopp durch den Verfolger beobachtet wurde, sollte der Verfolger den verfolgten Prozess mit dem Aufruf
ptrace(PTRACE_restart, PID, 0, Signal)
neu starten, wobei PTRACE_restart einer der neu startenden Ptrace-Anfragen ist. Falls Signal 0 ist, wird das Signal nicht zugestellt. Andernfalls wird das Signal Signal zugestellt. Diese Aktion wird in dieser Handbuchseite Signaleinspeisung genannt, um sie vom Signallieferstopp zu unterscheiden.
Der Signalwert kann sich vom Wert WSTOPSIG(status) unterschieden: Der Verfolger kann veranlassen, dass ein anderes Signal eingespeist wird.
Beachten Sie, dass ein unterdrücktes Signal immer noch Systemaufrufe verursacht, um vorzeitig zurückzukehren. In diesem Fall werden Systemaufrufe neu gestartet: Der Verfolger wird den verfolgten Prozess beobachten, um den unterbrochenen Systemaufruf neu auszuführen (oder den Systemaufruf restart_syscall(2) für wenige Systemaufrufe, die unterschiedliche Mechanismen zum erneuten Starten verwenden), falls der Verfolger PTRACE_SYSCALL benutzt. Sogar Systemaufrufe (wie poll(2)), die nach einem Signal nicht mehr neu startbar sind, werden nach dem Unterdrücken des Signals neu gestartet werden. Es existieren jedoch einige Kernelfehler, die zum Fehlschlagen einiger Systemaufrufe mit EINTR führen, sogar, wenn kein beobachtbares Signal in den verfolgten Prozess eingespeist wurde.
Es wird nicht garantiert, dass beim Neustarten von Ptrace-Befehlen, die in anderen Ptrace-Stopps als Signallieferstopps angestoßen wurden,ein Signal eingespeist wird, nicht einmal, wenn Signal nicht Null ist. Es wird kein Fehler gemeldet; ein Signal ungleich Null könnte einfach ignoriert werden. Ptrace-Benutzer sollten nicht versuchen, auf diese Art »ein neues Signal zu erzeugen«: Benutzen Sie stattdessen tgkill(2).
Die Tatsache, dass Signaleinspeisungsanfragen beim erneuten Starten des verfolgten Prozesses nach Ptrace-Stopps, die keine Signallieferstopps sind, ignoriert werden können, ist ein Grund für Verwirrung bei Ptrace-Benutzern. Ein typisches Szenario ist, dass der Verfolger Gruppenstopps beobachtet, sie fälschlicherweise für Signallieferstopps hält und den verfolgen Prozess mit
ptrace(PTRACE_restart, PID, 0, Stoppsignal)
neu startet mit der Absicht Stoppsignal einzuspeisen, Stoppsignal aber ignoriert wird und der verfolgte Prozess weiter läuft.
Das Signal SIGCONT hat einen Seiteneffekt, dass es einen Prozess im Gruppenstopp (alle Threads davon) aufweckt. Dieser Seiteneffekt tritt vor dem Signallieferstopp auf. Der Verfolger kann diesen Seiteneffekt nicht unterdrücken (er kann nur Signaleinspeisung unterdrücken, was nur dazu führt, dass die SIGCONT-Handhabung nicht im verfolgten Prozess ausgeführt wird, falls eine solche Handhabung installiert ist). Tatsächlich könnte dem Aufwecken aus den Gruppenstopp ein Signallieferstopp für andere Signale als SIGCONT folgen, falls sie ausstehen, wenn SIGCONT gesandt wurde. In anderen Worten könnte es sein, dass SIGCONT nicht das erste durch den Verfolger beobachtete Signal ist, nachdem es gesandt wurde.
Stoppen von Signalen führt dazu, das ein Prozess (alle Threads davon) in einen Gruppenstopp eintritt. Dieser Seiteneffekt tritt nach der Signaleinspeisung auf und kann daher durch den Verfolger unterdrückt werden.
In Linux 2.4 und älter kann das Signal SIGSTOP nicht eingespeist werden.
PTRACE_GETSIGINFO kann benutzt werden, um eine siginfo_t-Struktur zu erhalten, die dem gesandten Signal entspricht. PTRACE_SETSIGINFO kann benutzt werden, um es zu verändern. Falls PTRACE_SETSIGINFO benutzt wurde, um siginfo_t zu verändern, müssen das Feld si_signo und der Parameter Signal im Befehl zum Neustart übereinstimmen, andernfalls ist das Ergebnis undefiniert.
Wenn ein Prozess (der möglicherweise aus mehreren Threads besteht) ein Stoppsignal empfängt, werden alle Threads gestoppt. Falls einige Threads verfolgt werden, treten sie in eine Thread-Gruppe ein. Beachten Sie, dass das Stoppsignal zuerst einen Signallieferstopp verursachen wird (nur auf den verfolgten Prozess) und nur, nachdem es durch den Verfolger eingespeist wurde (oder nachdem es an einen Thread geschickt wurde, der nicht verfolgt wird), wird der Gruppenstopp auf alle verfolgten Prozesse innerhalb eines Prozesses aus mehreren Threads eingeleitet. Wie üblich meldet jeder verfolgte Prozess seinen Gruppenstopp separat an den entsprechenden Verfolger.
Der Gruppenstopp wird vom Verfolger als waitpid(2) beobachtet und kehrt mit »WIFSTOPPED(status) true« mit dem Stoppsignal zurück, das über WSTOPSIG(status) verfügbar ist. Dasselbe Ergebnis wird von einigen anderen Klassen von Ptrace-Stopps zurückgegeben, daher ist die empfohlene Vorgehensweise, folgenden Aufruf zu tätigen:
ptrace(PTRACE_GETSIGINFO, PID, 0, &siginfo)
Der Aufruf kann vermieden werden, falls das Signal nicht SIGSTOP, SIGTSTP, SIGTTIN oder SIGTTOU ist. Nur diese vier Signale sind Stoppsignale. Falls der Verfolger etwas anderes sieht, kann es kein Gruppenstopp sein. Andernfalls benötigt der Verfolger den Aufruf PTRACE_GETSIGINFO. Falls PTRACE_GETSIGINFO mit EINVAL fehlschlägt, ist es definitiv ein Gruppenstopp. (Andere Fehlerkodes wie ESRCH (»kein derartiger Prozess«) sind möglich, falls ein SIGKILL den verfolgten Prozess gekillt hat.
Falls ein verfolgter Prozess mittels PTRACE_SEIZE angehängt wurde, wird ein Gruppenstopp durch PTRACE_EVENT_STOP angezeigt: Status>>16 == PTRACE_EVENT_STOP. Dies ermöglicht, einen Gruppenstopp festzustellen, ohne dass ein zusätzlicher PTRACE_GETSIGINFO-Aufruf erforderlich ist.
Ab Linux 2.6.38 wird der verfolgte Prozess, nachdem der Verfolger den Ptrace-Stopp des verfolgten Prozesses sieht und bis er neu startet oder ihn killt, nicht laufen und keine Benachrichtigungen an den Verfolger senden (außer dem Tod durch SIGKILL), nicht einmal, wenn der Verfolger in einen anderen waitpid(2)-Aufruf gelangt.
Das im vorhergehenden Absatz beschriebene Verhalten verursacht ein Problem bei transparenter Behandlung von Stoppsignalen. Falls der Verfolger den verfolgten Prozess nach einem Gruppenstopp neu startet, wird das Stoppsignal effektiv ignoriert – der verfolgte Prozess bleibt nicht gestoppt, er läuft. Falls der Verfolger den verfolgten Prozess neu startet, bevor er in das nächste waitpid(2) eintritt, werden zukünftige SIGCONT-Signale nicht an den Verfolger gemeldet. Dies würde dazu führen, dass die SIGCONT-Signale keine Auswirkungen auf den verfolgten Prozess haben.
Seit Linux 3.4 gibt es eine Methode, die dieses Problem bewältigt: Statt PTRACE_CONT kann ein PTRACE_LISTEN-Befehl zum Neustart eines verfolgten Prozesses auf eine Weise benutzt werden, die ihn nicht ausführt, aber auf ein neues Ereignis wartet, das er über waitpid(2) melden kann, wenn er zum Beispiel mit SIGCONT neu gestartet wird.
Falls der Verfolger PTRACE_O_TRACE_*-Optionen setzt, wird der verfolgte Prozess in PTRACE_EVENT-Stopps genannte Stopps gelangen.
PTRACE_EVENT-Stopps werden durch den Verfolger als waitpid(2) beobachtet, kehren mit WIFSTOPPED(status) zurück und WSTOPSIG(status) gibt SIGTRAP zurück (oder für PTRACE_EVENT_STOP: gibt das Stopp-Signal zurück, falls der verfolgte Prozess in einem Gruppen-Stopp ist). Es wird ein zusätzliches Bit in das höhere Bit des Status (Datentyp Word) gesetzt: Der Wert status>>8 wird wie folgt sein:
((PTRACE_EVENT_foo<<8) | SIGTRAP).
Es gibt die folgenden Ereignisse:
Für alle vier oben beschriebenen Stopps tritt der Stopp im Elternprozess auf (d.h. im verfolgenden Prozess), nicht im neu erstellten Thread. PTRACE_GETEVENTMSG kann benutzt werden, um die Kennung des neuen Threads zu erhalten.
PTRACE_GETSIGINFO auf PTRACE_EVENT-Stopps gibt SIGTRAP in si_signo zurück, wobei si_code auf (event<<8) | SIGTRAP gesetzt ist.
Falls der verfolgte Prozess durch PTRACE_SYSCALL oder PTRACE_SYSEMU neu gestartet wurde, gerät der verfolgte Prozess in einen Systemaufrufeintrittsstopp kurz vor dem Eintritt in irgendeinen Systemaufruf (der nicht ausgeführt wird, falls der Neustart PTRACE_SYSEMU verwandte, unabhängig von allen Änderungen, die zu diesem Zeitpunkt an den Registern vorgenommen wurden oder wie der verfolgte Prozess nach diesem Stopp neu gestartet wird). Unabhängig davon, welche Methode den Systemaufrufeintrittsstopp verursachte, falls der Verfolger den verfolgten Prozess mit PTRACE_SYSCALL neu startet, gerät der verfolgte Prozess in einen Systemaufrufbeendigungsstopp, wenn der Systemaufruf beendet ist oder falls er durch ein Signal unterbrochen wurde. (Sprich, der Signallieferstopp tritt nie zwischen Systemaufrufeintrittsstopp und Systemaufrufbeendigungsstopp auf; er findet nach dem Systemaufrufbeendigungsstopp statt.) Falls der verfolgte Prozess mittels irgendeiner anderen Methode fortgesetzt wird (einschließlich PTRACE_SYSEMU), erfolgt kein Systemaufrufbeendigungsstop. Beachten Sie, dass alle Erwähnungen von PTRACE_SYSEMU genauso auf PTRACE_SYSEMU_SINGLESTEP zutreffen.
Selbst falls der verfolgte Prozess mittels PTRACE_SYSCALL fortgesetzt wird, wird nicht garantiert, dass der nächste Stopp ein Systemaufrufbeendigungsstopp sein wird. Andere Möglichkeiten sind, dass der verfolgte Prozess in einem PTRACE_EVENT-Stopp (einschließlich Seccomp-Stopp) stoppen könnte, endet (falls er in _exit(2) oder exit_group(2) eintritt), durch SIGKILL gekillt wird oder leise stirbt (falls er die Thread-Gruppe anführt, kommt das execve(2) in einem anderen Thread vor und der Thread wird nicht vom selben Verfolger verfolgt; diese Situation wird später besprochen).
Systemaufrufeintrittsstopp und Systemaufrufbeendigungsstopp werden vom Verfolger als waitpid(2) beobachtet, kehren mit »WIFSTOPPED(status) true« zurück und WSTOPSIG(status) gibt SIGTRAP zurück. Falls die Option PTRACE_O_TRACESYSGOOD durch den Verfolger gesetzt wurde, wird WSTOPSIG(status) den Wert (SIGTRAP | 0x80) zurückgeben.
Systemaufrufstopps können von Signallieferstopps mit SIGTRAP durch Abfrage von PTRACE_GETSIGINFO für die folgenden Fälle unterschieden werden:
Systemaufrufstopps kommen jedoch sehr oft vor (zweimal pro Systemaufruf) und das Ausführen von PTRACE_GETSIGINFO für jeden Systemaufrufstopp könnte etwas aufwendig sein.
Einige Architekturen erlauben, die Fälle durch Untersuchen der Register zu unterscheiden. Zum Beispiel auf x86, rax == -ENOSYS im Systemaufrufeintrittsstopp. Da SIGTRAP (wie jedes andere Signal) immer nach dem Systemaufrufbeendigungsstopp auftritt und rax an diesem Punkt fast nie ENOSYS enthält, sieht das SIGTRAP aus wie ein »Systemaufrufstopp, der kein Systemaufrufeintrittsstopp ist«; in anderen Worten, er sieht aus wie ein »herrenloser Systemaufrufbeendigungsstopp« und kann auf diese Art erkannt werden. Aber eine solche Erkennung ist fragil und wird am besten vermieden.
Die Benutzung der Option PTRACE_O_TRACESYSGOOD ist die empfohlene Methode, um Systemaufrufstopps von anderen Arten der Ptrace-Stopps zu unterscheiden, da sie zuverlässig ist und sich keine Leistungseinbuße zuzieht.
Systemaufrufeintrittsstopp und Systemaufrufbeendigungsstopp sind für den Verfolger nicht voneinander zu unterscheiden. Der Verfolger muss den Überblick über die Abfolge der Ptrace-Stopps behalten, um nicht den Systemaufrufeintrittsstopp fälschlicherweise als Systemaufrufbeendigungsstopp oder umgekehrt zu interpretieren. Im Allgemeinen folgt diesem Systemaufrufeintrittsstopp immer ein Systemaufrufbeendigungsstopp, PTRACE_EVENT-Stopp oder der Tod des verfolgten Prozesses; dazwischen können keine anderen Arten von Ptrace-Stopps auftreten. Beachten Sie allerdings, dass Seccomp-Stopps (siehe unten) Systemaufrufbeendigungsstopps ohne vorhergehende Systemaufrufeintrittsstopps hervorrufen können. Falls Seccomp verwandt wird, muss Sorgfalt eingesetzt werden, um solche Stopps nicht als Systemaufrufeintrittsstopps misszuinterpretieren.
Falls der Verfolger nach dem Systemaufrufeintrittsstopp einen anderen Befehl zum Neustarten als PTRACE_SYSCALL verwendet, wird der Systemaufrufbeendigungsstopp nicht erzeugt.
PTRACE_GETSIGINFO auf Systemaufrufstopps gibt SIGTRAP in si_signo zurück, wobei si_code auf SIGTRAP oder (SIGTRAP|0x80) gesetzt ist.
Das Verhalten des PTRACE_EVENT_SECCOMP-Stopps und seiner Wechselwirkung mit anderen Arten von Ptrace-Stopps hat sich zwischen Kernel-Versionen geändert. Hier wird das Verhalten von seiner Einführung bis Linux 4.7 (einschließlich) beschrieben. Das Verhalten in neueren Kernelversionen wird im nächsten Abschnitt beschrieben.
Ein PTRACE_EVENT_SECCOMP-Stopp erfolgt, wann immer eine SECCOMP_RET_TRACE-Regel ausgelöst wird. Dies ist von der Methode, die zum Neustart des Systemaufrufes verwandt wurde, unabhängig. Insbesondere läuft Seccomp immer noch, selbst falls der verfolgte Prozess mittels PTRACE_SYSEMU neu gestartet wurde und dieser Systemaufruf wird bedingungslos übersprungen.
Neustarts aus diesem Stopp verhalten sich so, als ob der Stopp direkt vor dem in Frage stehenden Systemaufruf stattgefunden hätte. Insbesondere werden sowohl PTRACE_SYSCALL als auch PTRACE_SYSEMU normalerweise einen folgenden Systemaufrufeintrittsstopp auslösen. Falls allerdings nach dem PTRACE_EVENT_SECCOMP die Systemaufrufnummer negativ ist, werden sowohl der Systemaufrufeintrittsstopp als auch der Systemaufruf selbst übersprungen. Das bedeutet, dass falls die Systemaufrufnummer nach dem PTRACE_EVENT_SECCOMP negativ ist und der verfolgte Prozess mittels PTRACE_SYSCALL neu gestartet wird, der nächste beobachtete Stopp ein Systemaufrufbeendigungsstopp statt des vielleicht erwarteten Systemaufrufeintrittsstopps sein wird.
Beginnend mit Linux 4.8 wurde der Stopp PTRACE_EVENT_SECCOMP neu geordnet, so dass er zwischen Systemaufrufeintrittsstopp und Systemaufrufbeendigungsstopp auftritt. Beachten Sie, dass Seccomp nicht länger ausgeführt wird (und kein PTRACE_EVENT_SECCOMP berichtet wird) falls der Systemaufruf aufgrund PTRACE_SYSEMU übersprungen wird.
Funktional arbeitet ein PTRACE_EVENT_SECCOMP-Stopp vergleichbar mit einem Systemaufrufeintrittsstopp (d.h. Fortsetzungen mittels PTRACE_SYSCALL werden einen Systemaufrufbeendigungsstopp auslösen, die Systemaufrufnummer könnte sich ändern und alle anderen veränderten Register sind im gleich auszuführenden Systemaufruf ebenfalls sichtbar). Beachten Sie, dass es einen vorhergehenden Systemaufrufeintrittsstopp gegeben haben kann, aber nicht muss.
Nach einem Stopp PTRACE_EVENT_SECCOMP wird Seccomp mit einer Regel SECCOMP_RET_TRACE, die identisch zu einer SECCOMP_RET_ALLOW funktioniert, erneut ausgeführt. Insbesondere bedeutet dies, dass falls Register nicht während des Stopps PTRACE_EVENT_SECCOMP verändert wurden, der Systemaufruf dann erlaubt wird.
[Einzelheiten dieser Arten von Stopps sind noch nicht dokumentiert.]
Die meisten Ptrace-Befehle (alle außer PTRACE_ATTACH, PTRACE_SEIZE, PTRACE_TRACEME, PTRACE_INTERRUPT und PTRACE_KILL) erfordern, dass der verfolgte Prozess in einem Ptrace-Stopp ist, andernfalls scheitern sie mit ESRCH.
Wenn der verfolgte Prozess im Ptrace-Stopp ist, kann der Verfolger Daten des verfolgten Prozesses mittels benachrichtigenden Befehlen lesen und schreiben. Diese Befehle belassen den verfolgten Prozess im Status Ptrace-gestoppt:
ptrace(PTRACE_PEEKTEXT/PEEKDATA/PEEKUSER, PID, Adresse, 0); ptrace(PTRACE_POKETEXT/POKEDATA/POKEUSER, PID, Adresse, 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);
Beachten Sie, dass einige Fehler nicht gemeldet wurden. Das Setzen des Informationssignals (siginfo) hat zum Beispiel in einigen Ptrace-Stopps möglicherweise keine Auswirkungen, der Aufruf kann jedoch erfolgreich sein (0 zurückgeben und errno nicht setzen); Abfragen von PTRACE_GETEVENTMSG könnte erfolgreich sein und einen zufälligen Wert zurückgeben, falls der aktuelle Ptrace-Stopp nicht dokumentiert ist, um eine aussagekräftige Ereignisnachricht zurückzugeben.
Der Aufruf
ptrace(PTRACE_SETOPTIONS, PID, 0, PTRACE_O_flags);
beeinflusst einen verfolgten Prozess. Die aktuellen Schalter des verfolgten Prozesses werden ersetzt. Schalter werden geerbt durch neu erzeugte Prozesse und »automatisch angehängt« über aktive PTRACE_O_TRACEFORK-, PTRACE_O_TRACEVFORK- oder PTRACE_O_TRACECLONE-Optionen.
Eine weitere Gruppe von Befehlen lässt die per Ptrace gestoppten, verfolgten Prozesse laufen. Sie haben die Form:
ptrace(Befehl, PID, 0, Signal);
wobei Befehl PTRACE_CONT, PTRACE_LISTEN, PTRACE_DETACH, PTRACE_SYSCALL, PTRACE_SINGLESTEP, PTRACE_SYSEMU oder PTRACE_SYSEMU_SINGLESTEP ist. Falls der verfolgte Prozess sich im Signallieferstopp befindet, ist Signal das Signal, das eingespeist wird (falls es ungleich Null ist). Andernfalls kann Signal ignoriert werden. (Wenn ein verfolgter Prozess von einem anderen Ptrace-Stopp als dem Signallieferstopp neu gestartet wird, ist die empfohlene Vorgehensweise, 0 in Signal zu übergeben.)
Ein Thread kann an den Verfolger angehängt werden mit dem Aufruf
ptrace(PTRACE_ATTACH, PID, 0, 0);
oder
ptrace(PTRACE_SEIZE, PID, 0, PTRACE_O_flags);
PTRACE_ATTACH sendet außerdem SIGSTOP an diesen Thread. Falls der Verfolger möchte, dass dieser SIGSTOP keine Auswirkungen hat, muss er ihn unterdrücken. Beachten Sie, dass der Verfolger, falls während des Anhängens gleichzeitig weitere Signale an diesen Thread gesandt werden, den Eintritt in den Signallieferstopp mit anderen Signalen zuerst sieht! Die übliche Vorgehensweise ist, diese Signale neu einzuspeisen, bis SIGSTOP gesehen wird und dann die Einspeisung von SIGSTOP zu unterdrücken. Der Entwurfsfehler ist hierbei, dass sich ein Ptrace-Anhängen und ein gleichzeitig gesandtes SIGSTOP einen Wettlauf liefern und das gleichzeitige SIGSTOP verloren gegangen sein kann.
Da Anhängen SIGSTOP sendet und der Verfolger es üblicherweise unterdrückt, könnte dies zu einer herrenlosen EINTR-Rückgabe vom aktuell ausgeführten Systemaufruf in diesem verfolgten Prozess führen, wie er im Abschnitt »Signaleinspeisung und -unterdrückung« beschrieben wird.
Seit Linux 3.4 kann PTRACE_SEIZE anstelle von PTRACE_ATTACH benutzt werden. PTRACE_SEIZE stoppt nicht den angehängten Prozess. Falls Sie ihn nach dem Anhängen (oder zu einem anderen Zeitpunkt) stoppen wollen ohne irgendwelche Signale zu senden, verwenden Sie den Befehl PTRACE_INTERRUPT.
Die Anfrage
ptrace(PTRACE_TRACEME, 0, 0, 0);
verwandelt den aufrufenden Thread in einen verfolgten Prozess. Der Thread fährt mit der Ausführung fort (gerät nicht in den Ptrace-Stopp). Eine übliche Vorgehensweise besteht darin, PTRACE_TRACEME mit
raise(SIGSTOP);
zu folgen und dem Elternprozess (der nun der Verfolger ist) zu ermöglichen, den Signallieferstopp zu beobachten.
Falls die Optionen PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK oder PTRACE_O_TRACECLONE in Kraft sind, werden Kindprozesse mit vfork(2), beziehungsweise clone(2) mit dem Schalter CLONE_VFORK, fork(2) oder clone(2) mit auf SIGCHLD gesetztem Beendigungssignal und anderen Arten von clone(2) automatisch an den gleichen Verfolger angehängt, der ihren Elternprozess verfolgte. SIGSTOP wird an die Kindprozesse gesandt, was sie veranlasst, in einen Signallieferstopp zu gelangen, nachdem sie den Systemaufruf beenden, der sie erzeugt hat.
Loslösen des verfolgten Prozesses wird erreicht durch:
ptrace(PTRACE_DETACH, PID, 0, Signal);
PTRACE_DETACH ist eine Neustartaktion; daher erfordert sie, dass der verfolgte Prozess in einem Ptrace-Stopp ist. Falls der verfolgte Prozess in einem Signallieferstopp ist, kann ein Signal eingespeist werden. Andernfalls wird der Parameter Signal stillschweigend ignoriert.
Falls der verfolgte Prozess läuft, wenn der Verfolger ihn loslösen möchte, besteht die übliche Lösung darin, SIGSTOP zu senden (mittels tgkill(2), um sicherzustellen, dass es an den korrekten Thread geht), darauf zu warten, dass der verfolgte Prozess in einen Signallieferstopp für SIGSTOP stoppt und ihn dann loszulösen (SIGSTOP-Einspeisung wird unterdrückt). Ein Entwurfsfehler besteht darin, dass sich dies mit gleichzeitigen SIGSTOPs Ressourcenwettläufe liefern kann. Eine weitere Komplikation besteht darin, dass der verfolgte Prozess in andere Ptrace-Stopps geraten kann und neu gestartet werden muss und nochmals gewartet werden muss, bis SIGSTOP gesehen wird. Noch eine weitere Komplikation besteht darin, dass nicht sicher ist, ob der verfolgte Prozess nicht bereits mit Ptrace gestoppt wurde, da keine Signallieferung stattfindet, obwohl es noch nicht einmal SIGSTOP ist.
Falls der Verfolger stirbt, werden alle verfolgten Prozesse automatisch losgelöst und neu gestartet, es sei denn, sie sind im Gruppenstopp. Die Handhabung des Neustarts aus dem Gruppenstopp ist derzeit fehlerhaft, aber das »wie-geplant«-Verhalten ist, den verfolgten Prozess gestoppt zu lassen und auf SIGCONT zu warten. Falls der verfolgte Prozess neu vom Signallieferstopp gestartet wurde, wird das ausstehende Signal einspeist.
Wenn ein Thread in einem Prozess mit mehreren Threads execve(2) aufruft, zerstört der Kernel alle anderen Threads im Prozess und setzt die Thread-Kennung des ausführenden Threads auf die Gruppenkennung (Prozesskennung) zurück. (Oder anders ausgedrückt, wenn ein Prozess mit mehreren Threads ein execve(2) bei Vervollständigung des Aufrufs ausführt, scheint es durch das execve(2) im führenden Thread der Prozessgruppe aufzutreten, unabhängig davon, welcher Thread das execve(2) aufrief.) Dieses Zurücksetzen der Thread-Kennung sieht für Verfolger sehr verwirrend aus:
All die Auswirkungen oberhalb sind Artefakte des Thread-Kennungswechsels im verfolgten Prozess.
Die Option PTRACE_O_TRACEEXEC ist das empfohlene Werkzeug für den Umgang mit dieser Situation. Zuerst aktiviert es PTRACE_EVENT_EXEC-Stopp, der vor der Rückkehr von execve(2) auftritt. In diesem Stopp kann der Verfolger PTRACE_GETEVENTMSG verwenden, um die vorherige Thread-Kennung des verfolgten Prozesses zu erhalten. (Diese Funktion wurde in Lunux 3.0 eingeführt.) Als zweites deaktiviert die Option PTRACE_O_TRACEEXEC die alte SIGTRAP-Erzeugung auf execve(2).
Wenn der Verfolger die PTRACE_EVENT_EXEC-Stoppbenachrichtigung empfängt, ist garantiert, dass außer diesem verfolgten Prozess und dem führenden Thread der Gruppe keine anderen Threads des Prozesses lebendig sind.
Beim Empfang der PTRACE_EVENT_EXEC-Stoppbenachrichtigung sollte der Verfolger all seine internen Datenstrukturen aufräumen, die Threads dieses Prozesses beschreiben und nur eine Datenstruktur behalten, – eine, die den einzelnen, laufenden, verfolgten Prozess beschreibt mit
Thread-Kennung == Thread-Gruppenkennung == Prozesskennung.
Beispiel: Zwei Threads rufen zur gleichen Zeit execve(2) auf:
*** wir bekommen einen Systemaufrufeintrittsstopp in Thread 1: ** PID1 execve("/bin/foo", "foo" <nicht abgeschlossen …> *** wir liefern PTRACE_SYSCALL für Thread 1 ** *** wir bekommen einen Systemaufrufeintrittsstopp in Thread 2: ** PID2 execve("/bin/bar", "bar" <nicht abgeschlossen …> *** wir liefern PTRACE_SYSCALL für Thread 2 ** *** wir bekommen PTRACE_EVENT_EXEC für PID0, wir liefern PTRACE_SYSCALL ** *** wir bekommen Systemaufrufbeendigungsstopp für PID0: ** PID0 <… execve wieder aufgenommen> ) = 0
Falls die Option PTRACE_O_TRACEEXEC für den ausführenden, verfolgten Prozess nicht in Kraft ist und falls der verfolgte Prozess PTRACE_ATTACHed statt PTRACE_SEIZEd war, sendet der Kernel ein zusätzliches SIGTRAP an den verfolgten Prozess, nachdem execve(2) zurückgekehrt ist. Dies ist ein gewöhnliches Signal (ähnlich einem, das durch kill -TRAP erzeugt werden kann), keine Spezialart eines Ptrace-Stopps. Unter Einsatz von PTRACE_GETSIGINFO für dieses Signal gibt si_code auf 0 gesetzt (SI_USER) zurück. Dieses Signal kann durch die Signalmaske blockiert sein und könnte daher (viel) später gesandt werden.
Üblicherweise würde der Verfolger dem Anwender dieses zusätzliche SIGTRAP-Signal nach Execve nicht zeigen wollen und seinen Versand an den verfolgten Prozess unterdrücken (falls SIGTRAP auf SIGTRAP gesetzt ist, killt es das Signal). Es ist jedoch nicht einfach zu bestimmen, welches SIGTRAP zu unterdrücken ist. Die empfohlene Herangehensweise ist, die Option PTRACE_O_TRACEEXEC zu setzen oder PTRACE_SEIZE zu verwenden und damit dieses zusätzliche SIGTRAP zu unterdrücken.
Die Ptrace-Programmierschnittstelle (miss)braucht die Standard-UNIX-Eltern-/Kindprozess-Signalgebung über waitpid(2). Diese wird benutzt, um den echten Elternprozess zum Stopp des Empfangs mehrerer Arten von waitpid(2)-Benachrichtigungen zu veranlassen, wenn der Kindprozess durch einen anderen Prozess verfolgt wird.
Viele dieser Fehler wurden behoben, aber ab Linux 2.6.38 existieren etliche immer noch; siehe FEHLER oberhalb.
Ab Linux 2.6.38 wird davon ausgegangen, dass folgendes korrekt funktioniert:
Bei Erfolg geben die PTRACE_PEEK*-Anfragen die angeforderten Daten zurück (aber siehe die ANMERKUNGEN), die Anfrage PTRACE_SECCOMP_GET_FILTER liefert die Anzahl der Anweisungen in dem BPF-Programm zurück, die Anfrage PTRACE_GET_SYSCALL_INFO liefert die Anzahl der Byte zurück, die zum Schreiben durch den Kernel verfügbar sind und andere Anfragen geben Null zurück.
Bei einem Fehler geben alle Anfragen -1 zurück und errno wird gesetzt, um den Fehler anzuzeigen. Da der Wert, der von einer erfolgreichen PTRACE_PEEK*-Anfrage zurückgegeben wurde, -1 sein könnte, muss der Aufrufende vor dem Aufruf errno leeren und es dann hinterher untersuchen, um festzustellen, ob ein Fehler aufgetreten ist oder nicht.
SVr4, 4.3BSD.
Obwohl Argumente für ptrace() gemäß dem angegebenen Prototypen interpretiert werden, deklariert Glibc derzeit ptrace() als eine variable Funktion mit nur dem festen Anfrage-Argument. Es wird empfohlen, immer vier Argumente anzugeben, sogar dann, wenn die angeforderte Aktion sie nicht verwendet. Setzen Sie unbenutzte/ignorierte Argumente auf 0L oder (void *) 0.
In Linux-Versionen vor 2.6.26 kann init(1) den Prozess mit der Prozessnummer 1 nicht verfolgen.
Der Elternprozess des verfolgten Prozesses wird weiterhin der Verfolger sein, selbst wenn der Verfolger execve(2) aufruft.
Das Layout des Speicherinhalts und des BENUTZERbereichs sind ziemlich betriebsystem- und architekturspezifisch. Der mitgelieferte Versatz und die zurückgegebenen Daten passen möglicherweise nicht ganz zu der Definition von struct user.
Die Größe eines »word« wird durch die Betriebsystemvariante festgelegt (z.B. ist es für ein 32-Bit-Linux 32 Bit).
Diese Seite dokumentiert die Möglichkeit, wie der ptrace()-Aufruf derzeit in Linux arbeitet. Sein Verhalten unterscheidet sich auf anderen unixoiden Betriebssystemen deutlich. Auf jeden Fall ist die Benutzung von ptrace() in hohem Grad abhängig vom Betriebssystem und der Architektur.
Verschiedene Teile des Kernel-Benutzerraum-APIs (nicht nur ptrace()-Aktionen) benötigen sogenannte »Ptrace-Zugriffsmodusüberprüfungen«, deren Ergebnis bestimmt, ob eine Aktion erlaubt (oder, in wenigen Fällen, einer »Lese«-Aktion bereinigte Daten zurückliefern) wird. Diese Überprüfungen werden in Fällen durchgeführt, in denen ein Prozess vertrauliche Informationen über einen anderen Prozess einsehen könnte oder in einigen Fällen den Zustand eines anderen Prozesse verändern könnte. Die Überprüfungen basieren auf Faktoren wie den Berechtigungsnachweisen und den Capabilitys der zwei Prozesse, ob der Speicherinhalt des »Zielprozesses« ausgegeben werden kann und dem Ergebnis der Überprüfungen, die durch jedes aktivierte Linux-Sicherheitsmodul (LSM) – zum Beispiel SELinux, Yama oder Smack – und durch das Commoncap-LSM (das immer ausgeführt wird), ausgeführt wird.
Vor Linux 2.6.27 waren alle Zugriffsprüfungen von einem einzigen Typ. Seit Linux 2.6.27 werden zwei Zugriffsmodi unterschieden:
Seit Linux 4.5 sind die obigen Zugriffsmodusprüfungen mittels ODER mit einem der folgenden Modifikatoren verknüpft:
Da die Kombination eines der Berechtigungsnachweise-Modifikatoren mit einem der vorgenannten Zugriffsmodi typisch ist, sind ein paar Makros in den Kernelquellen für die Kombinationen definiert.
Ein weiterer Modifikator kann mit den Zugriffsmodus mittels ODER verknüpft werden:
Beachten Sie, dass alle in diesem Unterabschnitt beschriebenen Konstanten PTRACE_MODE_* kernelintern und nicht im Anwendungsraum sichtbar sind. Die Konstantennamen werden hier benannt, um den verschiedenen Arten von Ptrace-Zugriffsmodusprüfungen, die für verschiedene Systemaufrufe und Zugriff auf verschiedene Pseudodateien (z.B. unter /proc) durchgeführt werden, einen Namen zu geben. Diese Namen werden in anderen Handbuchseiten benutzt, um eine einfache Abkürzung für die Benennung der verschiedenen Kernelprüfungen bereitzustellen.
Der für Ptrace-Zugriffsmodusprüfungen eingesetzte Algorithmus bestimmt, ob dem aufrufenden Prozess erlaubt wird, die entsprechende Aktion auf dem Zielprozess durchzuführen. (Im Falle des Öffnens von /proc/[PID]-Dateien ist der »aufrufende Prozess« derjenige, der die Datei öffnet, und der Prozess mit der entsprechenden PID der »Zielprozess«). Der Algorithmus geht wie folgt:
Auf Systemen, auf denen das Yama Linux Security Module (LSM) installiert (d.h. der Kernel mit CONFIG_SECURITY_YAMA konfiguriert worden) ist, kann die Datei /proc/sys/kernel/yama/ptrace_scope (verfügbar seit Linux 3.4) zum Einschränken der Nachverfolgung von Prozessen mit ptrace() verwandt werden (und damit auch die Möglichkeit, Werkzeuge wie strace(1) und gdb(1) zu verwenden). Das Ziel einer solchen Einschränkung besteht darin, Angriffseskalationen zu vermeiden, bei denen ein kompromittierter Prozess sich mittels Ptrace-attach an andere sensitive Prozesse (z.B. einem GPG-Agenten oder einer SSH-Sitzung), die dem Benutzer gehören, anhängen könnte, um zusätzliche Berechtigungsnachweise zu erlangen, die im Speicher existieren, und damit den Umfang des Angriffs zu erhöhen.
Genauer gesagt begrenzt die Yama LSM zwei Arten von Aktionen:
Ein Prozess, der über die Capability CAP_SYS_PTRACE verfügt, kann die Datei /proc/sys/kernel/yama/ptrace_scope mit einem der folgenden Werte aktualisieren:
Beachten Sie im Hinblick auf die Werte 1 und 2, dass die Erstellung eines neuen Benutzernamensraums effektiv den durch Yama bereitgestellten Schutz entfernt. Dies rührt daher, dass der Prozess in dem Elternbenutzerraum, dessen effektive UID auf die UID des Erstellers des Kindnamensraums passt, über alle Capabilitys (einschließlich CAP_SYS_PTRACE) verfügt, wenn er Aktionen innerhalb des Kindnamensraums (und weiter entfernter Nachkommen dieses Namensraums) durchführt. Wenn ein Prozess versucht, einen Benutzernamensraum zu verwenden, um sich in eine Sandbox zu bringen, wird er konsequenterweise den durch das Yama LSM bereitgestellten Schutz schwächen.
Auf der Systemaufrufebene haben die Anfragen PTRACE_PEEKTEXT, PTRACE_PEEKDATA und PTRACE_PEEKUSER eine unterschiedliche Programmierschnittstelle: Sie speichern das Ergebnis an der durch den Parameter Daten angegebenen Adresse und der Rückgabewert ist ein Fehlercode. Die Glibc-Wrapper-Funktion stellt die oben in BESCHREIBUNG angegebene Programmierschnittstelle bereit. Ihr Ergebnis wird über den Rückgabewert der Funktion zurückgegeben.
Auf Rechnern mit 2.6 Linux-Headern ist PTRACE_SETOPTIONS mit einem anderen Wert deklariert, als auf einem für 2.4. Dies führt dazu, dass Anwendungen, die mit 2.6-Linux-Headern kompiliert wurden, bei der Ausführung auf 2.4er Kerneln scheitern. Dies kann durch Neudefinieren von PTRACE_SETOPTIONS zu PTRACE_OLDSETOPTIONS umgangen werden, wenn dies definiert ist.
Gruppenstoppbenachrichtigungen werden an der Verfolger gesandt, aber nicht an den echten Elternprozess. Zuletzt auf 2.6.38.6 bestätigt.
Falls ein führender Thread einer Gruppe verfolgt und durch den Aufruf von _exit(2) beendet wird, wird es für ihn zu einem PTRACE_EVENT_EXIT-Stopp kommen (falls angefordert), aber die nachfolgende WIFEXITED-Benachrichtigung wird nicht gesandt, bis alle anderen Threads beendet sind. Wie oben erklärt, wird der Tod des führenden Prozesses der Gruppe gemeldet, falls einer der anderen Threads execve(2) aufruft. Falls der ausgeführte Thread nicht durch den Verfolger verfolgt wird, wird der Verfolger niemals erfahren, dass execve(2) auftrat. Eine mögliche Notlösung ist ein PTRACE_DETACH für den führenden Thread der Gruppe, anstatt ihn in diesem Fall neu zu starten. Zuletzt auf 2.6.38.6 bestätigt.
Ein SIGKILL-Signal kann immer noch einen PTRACE_EVENT_EXIT-Stopp vor dem tatsächlichen Signaltod verursachen. Dies könnte in Zukunft geändert werden; SIGKILL ist dazu gedacht, Aufgaben immer sofort zu killen, sogar unter Ptrace. Zuletzt auf Linux 3.13 bestätigt.
Einige Systemaufrufe kehren mit EINTR zurück, falls ein Signal an den verfolgten Prozess gesandt, die Auslieferung aber durch den Verfolger unterdrückt wurde. (Dies ist eine ganz typische Aktion: Sie wird normalerweise von Fehlersuchprogrammen bei jedem Anhängen durchgeführt, um kein fingiertes SIGSTOP einzuleiten.) Ab Linux 3.2.9 werden die folgenden Systemaufrufe beeinflusst (diese Liste ist wahrscheinlich nicht vollständig): epoll_wait(2) und read(2) von einem inotify(7)-Dateideskriptor. Das übliche Anzeichen für diesen Fehler ist, falls Sie einen ruhenden Prozess mit dem Befehl
strace -p <Prozesskennung>
anhängen, dass Sie statt der erwarteten einzeiligen Ausgabe, wie
restart_syscall(<… resuming interrupted call …>_
oder
select(6, [5], NULL, [5], NULL_
('_' kennzeichnet die Cursor-Position) mehr als eine Zeile beobachten können, zum Beispiel:
clock_gettime(CLOCK_MONOTONIC, {15370, 690928118}) = 0 epoll_wait(4,_
Was hier nicht sichtbar ist, ist, dass der Prozess in epoll_wait(2) blockiert wurde, bevor strace(1) an ihn angehängt hat. Das Anhängen verursachte ein epoll_wait(2), um zum Anwendungsraum mit dem Fehler EINTR zurückzukehren. In diesem besonderen Fall reagiert das Programm auf EINTR, indem die aktuelle Zeit geprüft und dann epoll_wait(2) erneut ausgeführt wird. (Programme, die keine derartigen »verirrten« EINTR-Fehler erwarten, können sich bei einem strace(1)-Anhängen in unbeabsichtigter Weise verhalten.)
Entgegen den normalen Regeln kann der Glibc-Wrapper für ptrace() errno auf Null setzen.
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)
Die deutsche Übersetzung dieser Handbuchseite wurde von Patrick Rother <krd@gulu.net>, Chris Leick <c.leick@vollbio.de>, Mario Blättermann <mario.blaettermann@gmail.com> und Helge Kreutzmann <debian@helgefjell.de> erstellt.
Diese Übersetzung ist Freie Dokumentation; lesen Sie die GNU General Public License Version 3 oder neuer bezüglich der Copyright-Bedingungen. Es wird KEINE HAFTUNG übernommen.
Wenn Sie Fehler in der Übersetzung dieser Handbuchseite finden, schicken Sie bitte eine E-Mail an die Mailingliste der Übersetzer.
5. Februar 2023 | Linux man-pages 6.03 |