Search code examples
cmultiprocessingsegmentation-faultsignalsfork

WIFSIGNALED not catching SEGFAULT signal


Im fairly certain of what WIFEXITED and WIFSIGNALED are and how they work. I dont know why its not working since the program does a segfault, and I can very well see(debugged) that that signal on the third while iteration is a SEGFAULT. Thanks for any info.

segfault_prog

#include <stdlib.h>
#include <stdio.h>
int main() {
        int *i = 0x478734;
        printf("%d", *i);
}
int main(void) {
    int status;
    int ret = fork();

    if (ret == 0) {
        ptrace(PTRACE_TRACEME, ret, NULL, NULL);
        raise(SIGSTOP);
        execve(argv[1], &argv[1], NULL);
        return 1;
    }
    else {
        while (1) {
            pid_t val = waitpid(ret, &status, 0);
            ptrace(PTRACE_SETOPTIONS, ret, 0,PTRACE_O_EXITKILL | PTRACE_O_TRACEEXEC);

            if (WIFEXITED(status) && val == ret){
                printf("exited normally");
                return WEXITSTATUS (status);
            }
            else if (WIFSIGNALED(status)){
//if I change for WEXITSTATUS(status) == SIGSEGV. then it works. First while iteration wIFEXITVAL(SIGSTOP/19), 2nd (SIGTRAP/5), 3rd (SIGSEGV 11);
                printf("signal error");
                return WEXITSTATUS (status);
            }

            ptrace(PTRACE_CONT, val, 0, 0);
        }
    }
    return (0);
}

Solution

  • A few issues ...

    1. When tracee/child is stopped (i.e. WIFSTOPPED), the signal (i.e. WSTOPSIG) must be sent down to the tracee. This is the last arg for PTRACE_CONT

    2. Doing PTRACE_SETOPTIONS is only needed after the first waitpid

    3. waitpid should have a first arg of -1 to catch all children

    By not sending the signal down via the PTRACE_CONT [AFAICT], the signal is deferred [until the tracer elects to send it down]. So, the tracee/child generated the signal but it was never sent to the tracee/child.

    Also, as I mentioned, you may want to have a look at my recent answer: ptrace options not working in parent process


    Here is the refactored code. It is annotated with the bugs and fixes:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/ptrace.h>
    #include <sys/wait.h>
    
    int
    main(int argc,char **argv)
    {
        int status;
        int ret = fork();
    
        if (ret == 0) {
            ptrace(PTRACE_TRACEME, ret, NULL, NULL);
    // NOTE/BUG: not needed
    #if 0
            raise(SIGSTOP);
    #endif
            execve(argv[1], &argv[1], NULL);
    #if 0
            return 1;
    #else
            exit(1);
    #endif
        }
    
    #if TEST
        int iter = 20;
    #else
        int iter = 0x7FFFFFFF;
    #endif
    
        int first = 1;
    
        for (;  iter > 0;  --iter) {
    // NOTE/BUG: we should catch all children (tracee might do fork but not wait
    // and orphan/zombie its child (our grandchild) and we'd want to see that)
    #if 0
            pid_t val = waitpid(ret, &status, 0);
    #else
            pid_t val = waitpid(-1, &status, 0);
    #endif
    
    #if 1
            if (first) {
                ptrace(PTRACE_SETOPTIONS, ret, 0,
                    PTRACE_O_EXITKILL | PTRACE_O_TRACEEXEC);
                first = 0;
            }
    #endif
    
            printf("pt: status=%8.8X\n",status);
    
            int signo = 0;
    
            if (WIFEXITED(status)) {
                printf("pt: WIFEXITED %d\n",WEXITSTATUS(status));
            }
    
            if (WIFSTOPPED(status)) {
                signo = WSTOPSIG(status);
                printf("pt: WIFSTOPPED %d\n",signo);
                if (signo == SIGTRAP)
                    signo = 0;
            }
    
            if (WIFSIGNALED(status)) {
                printf("pt: WIFSIGNALED %d\n",WTERMSIG(status));
            }
    
            if (WIFEXITED(status) && val == ret) {
                printf("exited normally");
                return WEXITSTATUS(status);
            }
            else if (WIFSIGNALED(status)) {
                // if I change for WEXITSTATUS(status) == SIGSEGV. then it works.
                // First while iteration wIFEXITVAL(SIGSTOP/19), 2nd (SIGTRAP/5),
                // 3rd (SIGSEGV 11);
    #if 0
                printf("signal error");
    #else
                printf("signal error -- %d\n",WTERMSIG(status));
    #endif
                return WEXITSTATUS(status);
            }
    
    // NOTE/BUG: must send signo from WIFSTOPPED/WSTOPSIG to tracee
    #if SHOWBUG
            ptrace(PTRACE_CONT, val, 0, 0);
    #else
            ptrace(PTRACE_CONT, val, 0, signo);
    #endif
        }
    
        if (iter <= 0)
            printf("pt: fault -- not seen\n");
    
        return (0);
    }
    

    In the above code, I've used cpp conditionals to denote old vs new code:

    #if 0
    // old code
    #else
    // new code
    #endif
    
    #if 1
    // new code
    #endif
    

    Here is the original behavior (compiled with -DTEST -DSHOWBUG):

    pt: status=0000057F
    pt: WIFSTOPPED 5
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: fault -- not seen
    

    Here is the output with the fix:

    pt: status=0000057F
    pt: WIFSTOPPED 5
    pt: status=00000B7F
    pt: WIFSTOPPED 11
    pt: status=0000008B
    pt: WIFSIGNALED 11
    signal error -- 11