new(ptrace): Proof of concept of failed GETREGS.
authorsgf <sgf.dma@gmail.com>
Fri, 24 Nov 2023 16:30:56 +0000 (19:30 +0300)
committersgf <sgf.dma@gmail.com>
Tue, 5 Dec 2023 20:57:52 +0000 (23:57 +0300)
containers_from_scratch/guide_to_syscalls/traceme.c [new file with mode: 0644]
containers_from_scratch/guide_to_syscalls/traceme.go [new file with mode: 0644]

diff --git a/containers_from_scratch/guide_to_syscalls/traceme.c b/containers_from_scratch/guide_to_syscalls/traceme.c
new file mode 100644 (file)
index 0000000..a407abf
--- /dev/null
@@ -0,0 +1,122 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/ptrace.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdbool.h>
+
+int main(int  argc, char **argv){
+    setvbuf(stdout, NULL, _IONBF, 0);
+    if (argc < 2) {
+        printf("Too few arguments\n");
+        abort();
+    }
+
+    pid_t mypid = getpid();
+    printf("pidA = %d\n", mypid);
+
+    char *cmd = argv[1];
+    char **newargv = malloc(sizeof(char*) * argc);
+    for (int i = 1; i < argc; i++) {
+        newargv[i-1] = argv[i];
+    }
+    newargv[argc-1] = NULL;
+
+    pid_t child_pid = fork();
+    if (child_pid) {
+        printf("[%d]: pidB = %d\n", mypid, child_pid);
+
+        if (kill(child_pid, 0) == -1) {
+            perror("signal right after start: ");
+        } else {
+            printf("[%d]: Successfully sent a signal right after start\n", mypid);
+        }
+
+        struct user_regs_struct regs;
+        printf("[%d]: Get child registers\n", mypid);
+        // Following register quire will fail in any case, because child is
+        // not stopped!
+        if (ptrace(PTRACE_GETREGS, child_pid, NULL, &regs) == -1) {
+            perror("PTRACE_GETREGS:");
+        }
+        printf("[%d]: orig_rax = %d\n", mypid, regs.orig_rax);
+
+        printf("[%d]: waiting for child to stop\n", mypid);
+        int status;
+        while (waitpid(child_pid, &status, 0)) {
+            if (WIFEXITED(status)) {
+                printf("[%d]: Child exited with status %d\n", mypid, WEXITSTATUS(status));
+                return 0;
+
+            } else if (WIFSIGNALED(status)) {
+                int termsig = WTERMSIG(status);
+                printf("[%d]: Child killed by signal %d\n", mypid, termsig);
+                return 0;
+
+            } else if (WIFSTOPPED(status)) {
+                int csig = WSTOPSIG(status);
+                printf("[%d]: Child is traced and and is about to receive signal %d\n", mypid, csig);
+
+                if (kill(child_pid, 0) == -1) {
+                    perror("signal when stopped: ");
+                } else {
+                    printf("[%d]: Successfully sent a signal when stopped\n", mypid);
+                }
+
+                sleep(2);
+                struct user_regs_struct regs;
+                printf("[%d]: Get child registers\n", mypid);
+                // But this register will succeed, because now child is
+                // stopped.
+                if (ptrace(PTRACE_GETREGS, child_pid, NULL, &regs) == -1) {
+                    perror("PTRACE_GETREGS:");
+                }
+                printf("[%d]: orig_rax = %d\n", mypid, regs.orig_rax);
+
+                sleep(1);
+                // Suppress signal.
+                //if (ptrace(PTRACE_CONT, child_pid, 0, 0) == 1) {
+                //    perror("PTRACE_CONT:");
+                //}
+                if (ptrace(PTRACE_SYSCALL, child_pid, 0, 0) == 1) {
+                    perror("PTRACE_SYSCALL:");
+                }
+
+                if (kill(child_pid, 0) == -1) {
+                    perror("signal when running: ");
+                } else {
+                    printf("[%d]: Successfully sent a signal when running\n", mypid);
+                }
+
+
+            } else if (WIFCONTINUED(status)) {
+                printf("[%d]: Child continued\n", mypid);
+
+            } else {
+                printf("[%d]: Child is elsewhere\n", mypid);
+            }
+        }
+
+    } else {
+        mypid = getpid();
+        if ((ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) == -1) {
+            perror("PTRACE_TRACEME:");
+        }
+        // Child should normally raise SIGSTOP, so parent has a chance to
+        // inspect its state through ptrace.
+        // But go runtime does not raise such signal.
+        //raise(SIGSTOP);
+
+        printf("[%d]: Sleep after traceme and before execve..\n", mypid);
+        sleep(6);
+        printf("[%d]: execve '%s'\n", mypid, cmd);
+        execve(cmd, newargv, NULL);
+        perror("execve");
+    }
+    return 0;
+}
diff --git a/containers_from_scratch/guide_to_syscalls/traceme.go b/containers_from_scratch/guide_to_syscalls/traceme.go
new file mode 100644 (file)
index 0000000..f252bc1
--- /dev/null
@@ -0,0 +1,122 @@
+
+package main
+
+import (
+    "fmt"
+    "os"
+    "os/exec"
+    "syscall"
+    "time"
+)
+
+func main() {
+    mypid := os.Getpid()
+    fmt.Printf("pidA = %v\n", mypid);
+
+    fmt.Printf("[%v]: Run %v\n", mypid, os.Args[1:])
+
+    cmd := exec.Command(os.Args[1], os.Args[2:]...)
+    cmd.SysProcAttr = &syscall.SysProcAttr {
+        Ptrace: true,
+    }
+    cmd.Stderr = os.Stderr
+    cmd.Stdin = os.Stdin
+    cmd.Stdout = os.Stdout
+
+    cmd.Start()
+    child := cmd.Process
+    fmt.Printf("[%d]: pidB = %v\n", mypid, child.Pid)
+
+    sigZero := syscall.Signal(0)
+    if err := child.Signal(sigZero); err != nil {
+        fmt.Printf("[%d]: Error: signal right after start (go): %v\n", mypid, err)
+    } else {
+        fmt.Printf("[%d]: Successfully sent a signal right after start (go)\n", mypid)
+    }
+
+    if err := cmd.Wait(); err != nil {
+        fmt.Printf("[%v]: Wait returned: %v\n", mypid, err)
+    }
+
+    for {
+        time.Sleep(time.Second * 2)
+
+        var regs syscall.PtraceRegs
+        fmt.Printf("[%v]: Get child registers, when stopped\n", mypid)
+        if err := syscall.PtraceGetRegs(child.Pid, &regs); err != nil {
+            panic(err)
+        }
+        fmt.Printf("[%v]: Orig_rax = %v\n", mypid, regs.Orig_rax)
+
+        if err := child.Signal(syscall.Signal(0)); err != nil {
+            fmt.Printf("Error: signal when stopped (go): %v\n", err)
+            if err := syscall.Kill(child.Pid, sigZero); err != nil {
+                fmt.Printf("Error: signal when stopped (syscall): %v\n", err)
+            } else {
+                fmt.Printf("[%d]: Successfully sent a signal when stopped (syscall)\n", mypid)
+            }
+        } else {
+            fmt.Printf("[%d]: Successfully sent a signal when stopped (go)\n", mypid)
+        }
+
+        time.Sleep(time.Second)
+        //if err := syscall.PtraceCont(child.Pid, 0); err != nil {
+        if err := syscall.PtraceSyscall(child.Pid, 0); err != nil {
+            // Here the go program will frequently fail with 'no such
+            // process', though the child process is still alive (which is
+            // checked by sending sigZero below). This is quite reliable
+            // reproducible with sleep for at least 1 second (above). Without
+            // sleep all work fine. The reason is unknown.
+            if err := syscall.Kill(child.Pid, sigZero); err != nil {
+                fmt.Printf("Error: signal at failed ptrace_restart (syscall): %v\n", err)
+            } else {
+                fmt.Printf("[%d]: Successfully sent a signal at failed ptrace_restart (syscall)\n", mypid)
+            }
+            panic(err)
+        }
+
+        //time.Sleep(time.Second)
+        fmt.Printf("[%v]: Get child registers, when running\n", mypid)
+        // Following register quire will fail in any case, because child is
+        // not stopped!
+        if err := syscall.PtraceGetRegs(child.Pid, &regs); err != nil {
+            fmt.Printf("[%d]: Error: PTRACE_GETREGS: %v\n", mypid, err)
+        }
+        fmt.Printf("[%v]: Orig_rax = %v\n", mypid, regs.Orig_rax)
+
+        if err := child.Signal(syscall.Signal(0)); err != nil {
+            fmt.Printf("Error: signal when running (go): %v\n", err)
+            if err := syscall.Kill(child.Pid, sigZero); err != nil {
+                fmt.Printf("Error: signal when running (syscall): %v\n", err)
+            } else {
+                fmt.Printf("[%d]: Successfully sent a signal when running (syscall)\n", mypid)
+            }
+        } else {
+            fmt.Printf("[%d]: Successfully sent a signal when running (go)\n", mypid)
+        }
+
+
+        var ws syscall.WaitStatus
+        if _, err := syscall.Wait4(child.Pid, &ws, 0, nil); err != nil {
+            panic(err)
+        }
+
+        if ws.Exited() {
+            fmt.Printf("[%v]: Child exited with status %v\n", mypid, ws.ExitStatus())
+            break
+
+        } else if ws.Signaled() {
+            fmt.Printf("[%v]: Child killed by signal %v\n", mypid, ws.Signal().String())
+            break
+
+        } else if ws.Stopped() {
+            fmt.Printf("[%v]: Child is traced and and is about to receive %v\n", mypid, ws.StopSignal().String())
+
+        } else if ws.Continued() {
+            fmt.Printf("[%v]: Child continued\n", mypid)
+
+        } else {
+            fmt.Printf("[%d]: Child is elsewhere\n", mypid);
+        }
+    }
+}