Setzer
Setzer

Reputation: 745

SIGSEGV handler and mprotect and looping effect when injecting instructions at runtime. Handler can't get info->si_addr

I have looked at the various topics relating to this, but couldn't find this specific issue I am having.

Things I looked at: Injecting code into executable at runtime C SIGSEGV Handler & Mprotect Can I write-protect every page in the address space of a Linux process? How to write a signal handler to catch SIGSEGV?

I am able to handle SIGSEGV gracefully when the protection needs to be set to either PROT_READ or PROT_WRITE in the handler. However, when I try to inject instructions with mmap, and then use mprotect to set it to PROT_READ only, and then I execute the instructions via inline assembly, it causes a SIGSEGV as intended, but the handler is unable to get the originating address causing the signal, so I am unable to mprotect it to PROT_READ | PROT_EXEC.

Example:

void sigHandler(int signum, siginfo_t *info, void *ptr) {

    printf("Received signal number: %d\n", signum);
    printf("Signal originates from process %lu\n",
        (unsigned long)info->si_pid);

    printf("SIGSEGV caused by this address: ? %p\n", info->si_addr);

    char * alignedbaseAddr = (((unsigned int)(info->si_addr)) >> 12) * getPageSize(); 
    printf("Aligning to %p\n", alignedbaseAddr);
    //flip this page to be r+x
    mprotect(alignedbaseAddr, getPageSize(), PROT_READ | PROT_EXEC);
}
void setupSignalHandler() {
    action.sa_sigaction = sigHandler;
    action.sa_flags = SA_SIGINFO;
    sigemptyset(&action.sa_mask);
    sigaction(SIGSEGV, &action, NULL);
}

int main(int argc, char *argv[]) {
    char * baseAddr = (char*)mmap(NULL, getDiskSize(), PROT_READ | PROT_WRITE,    MAP_SHARED, fd, 0);
    if(baseAddr == MAP_FAILED) {
        perror("Unable to mmap.");
    }
    printf("Process address space is %d\n", getDiskSize());
    //no-op filler
    for(int i = 0; i < (getDiskSize()) - 1; i++) {
        baseAddr[i] = 0x90;
    }
    //ret instruction
    baseAddr[i] = 0xc3;

    if( mprotect(baseAddr, getDiskSize(), PROT_READ) == -1) {
        perror("mprotect");
        exit(1);
    }

    printf("Protecting addresses: %p to %p for READ_ONLY\n", baseAddr, baseAddr + getDiskSize() - 1);
    setupSignalHandler();


    __asm__
    (
     "call %%eax;"
     : "=a" (output)
     : "a" (baseAddr)
    );

    printf("Will this ever print?");
    //close fd, and unmap memory
    cleanUp();
    return EXIT_SUCCESS;
}

Here is the resulting output:

Received signal number: 11
Signal originates from process 0
SIGSEGV caused by this address: ? (nil)

//the above output repeatedly loops, since it fails to "re mprotect" that page.

Architecture: x86 32 bit OS: Ubuntu 11.04 - Linux version 2.6.38-12-generic (buildd@vernadsky) (gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4) )

Any ideas? The above logic works fine for simply read and writing into memory. Is there a better way to execute instructions at runtime as opposed to inline assembly?

Thanks in advance!

Upvotes: 2

Views: 1597

Answers (1)

In that case, the faulting address is the instruction pointer. Cast your third argument ptr (of your signal handler installed with SA_SIGINFO) to a ucontext_t, and retrieve the appropriate register, perhaps as (untested code!)

ucontext_t *uc = ptr;
void* faultyip = uc->uc_mcontext.gregs[REG_IP];

Read carefully /usr/include/sys/ucontext.h for more.

I'm interested to know why you are asking!!

Upvotes: 2

Related Questions