C – Inject the mprotect system call to trace process failures with EFAULT

Inject the mprotect system call to trace process failures with EFAULT… here is a solution to the problem.

Inject the mprotect system call to trace process failures with EFAULT

I’m tracking the process with the mprotect call injection (inject:

).

static int inject_mprotect(pid_t child, void *addr, size_t len, int prot)
{
     Machine code:
      int $0x80       (system call)
      int3            (trap)
    char code[] = {0xcd,0x80,0xcc,0};
    char orig_code[3];
    struct user_regs_struct regs;
    struct user_regs_struct orig_regs;

 Take a copy of current state
    __check_ptrace(PTRACE_GETREGS, child, NULL, &orig_regs);
    getdata(child, INSTRUCTION_POINTER(regs), orig_code, 3);

 Inject the code, update registers
    putdata(child, INSTRUCTION_POINTER(regs), code, 3);
    __check_ptrace(PTRACE_GETREGS, child, NULL, &regs);
    XAX_REGISTER(regs) = MPROTECT_SYSCALL;
    MPROTECT_ARG_START(regs) = (unsigned long)addr;
    MPROTECT_ARG_LEN(regs) = len;
    MPROTECT_ARG_PROT(regs) = prot;   
    __check_ptrace(PTRACE_SETREGS, child, NULL, &regs);

 Snip

However, the call fails and returns -14 (EFAULT). I looked at the mprotect source code (kernel 3.13) and don’t understand why my system call returns this.

If I trace the call to inject and print out the register, I see the following:

SIGTRAP: eip: 0x34646ef8d4, syscall 10, rc = -38
PARENT 10 MPROTECT(start: 0x00007f45b9611000, len: 4096, prot: 0)
EIP: 0x00000034646ef8d4 AX: 0xffffffffffffffda  BX: 0x0000000000000005  CX: 0xffffffffffffffff
 DX: 0x0000000000000000 DI: 0x00007f45b9611000  BP: 0x00007fffcb93bc20  SI: 0x0000000000001000
 R8: 0x0000000000000000 R9: 0x0000000000000000  R10: 0x0000000000000000
SIGTRAP: eip: 0x34646ef8d4, syscall 10, rc = -14 Bad address (trap after system call exit)

To verify the system call format, I added an mprotect call to child and dumped its parameters and registers:

SIGTRAP: eip: 0x34646ef927, syscall 10, rc = -38
CHILD  10 MPROTECT(start: 0x00007f45b9611000, len: 4096, prot: 0)
EIP: 0x00000034646ef927 AX: 0xffffffffffffffda  BX: 0x0000000000000005  CX: 0xffffffffffffffff
 DX: 0x0000000000000000 DI: 0x00007f45b9611000  BP: 0x00007fffcb93bc20  SI: 0x0000000000001000
 R8: 0x000000000000004e R9: 0x746f72706d206c6c  R10: 0x00007fffcb93b9a0
SIGTRAP (child return): eip: 0x34646ef927, syscall 10, rc = 0

The child process call succeeded. So let’s say I’m making the same system call (10) with the same parameters, why does the call to inject fail with EFAULT, but the call from child succeeds?

The only difference between the calls is some garbage in regs.r8, regs.r9, and regs.r10, but based on this table of system calls on X86_64 Do not trust the contents of these registers to affect system calls.

Solution

The problem is related to this question: i386 and x86_64 use different calling conventions for system calls. Your sample code uses int 0x80, the i386 variant, but syscall_number = 10, mprotect 64-bit system call number. According to this list, in a 32-bit environment, the system call 10 corresponds to unlink, it EFAULT (error address) can be returned.

On 64-bit

platforms, using 32-bit or 64-bit variants in a consistent manner can solve the problem.

Related Problems and Solutions