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, ®s);
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, ®s);
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.