While experimenting with editing opcodes of running process on Linux with C language, I found out that I can't to edit program opcodes by PID with process_vm_writev
, but using ptrace
and pwrite
I can do it. process_vm_writev
returns -1 and errno
says "Bad address". But when I use ptrace with same address it successes. I run program as root. Is it an issue of GNU/Linux and should I open an issue on GitHub? Code for process_vm_writev
(without includes to be short):
ssize_t write_pmem(pid_t pid, off_t addr, void* value, size_t size) {
struct iovec local[1];
local[0].iov_base = value;
local[0].iov_len = size;
struct iovec remote[1];
remote[0].iov_base = (void*)addr;
remote[0].iov_len = size;
return process_vm_writev(pid, local, 1, remote, 1, 0);
}
int main() {
pid_t pid;
off_t opcode_ptr = 0x45B5A7;
printf("PID: ");
scanf("%d", &pid);
char nops[5] = {0x90, 0x90, 0x90, 0x90, 0x90};
ssize_t res = write_pmem(pid, opcode_ptr, &nops, sizeof(nops));
printf("%ld\n%s", res, strerror(errno));
/* and it prints:
-1
Bad address */
return 0;
}
/proc/PID/maps
says that 0x45B5A7
is 00406000-004f0000 r-xp 00006000
for that process (no write rights) but I think it's still writable for the root.
I want to use new process_vm Linux API, so I want to fix that code. Any ideas?
Both `process_vm_writev` and `PTRACE_POKETEXT` ultimately rely on `get_user_pages_remote` at the lower level. However, they use different flags: `process_vm_writev` uses `FOLL_WRITE` as the flag, while `PTRACE_POKETEXT` uses `FOLL_WRITE | FOLL_FORCE`. The `FOLL_FORCE` flag allows the `can_follow_write_pte` function to bypass the permission check on the page table entry (PTE), enabling writes to a read-only address.
Linux kernel 5.10 source code mm/gup.c
/*
* FOLL_FORCE can write to even unwritable pte's, but only
* after we've gone through a COW cycle and they are dirty.
*/
static inline bool can_follow_write_pte(pte_t pte, unsigned int flags)
{
return pte_write(pte) ||
((flags & FOLL_FORCE) && (flags & FOLL_COW) && pte_dirty(pte));
}