#define _GNU_SOURCE #include #include #include #include #include #include #include #include #define MAX_TRIPS 64 typedef struct { uintptr_t start; uintptr_t length; uint8_t tripped[0]; } tripwire_t; static tripwire_t* Trips[MAX_TRIPS]; static int HandlerInstalled; static char altstack[SIGSTKSZ]; static struct sigaction sa_old; static void SignalHandler(int sig, siginfo_t* info, void* ucontext) { uintptr_t faultAddress = (uintptr_t)info->si_addr; for (int i = 0; i < MAX_TRIPS; i++) { if (Trips[i] && faultAddress >= Trips[i]->start && faultAddress < Trips[i]->start + Trips[i]->length) { uintptr_t page = (faultAddress - Trips[i]->start) >> 12; if (Trips[i]->tripped[page] & 1) // should change { if (mprotect((void*)(faultAddress & ~0xffful), 0x1000, PROT_READ | PROT_WRITE) != 0) { abort(); while (1) ; } Trips[i]->tripped[page] = 3; // did change return; } else { break; } } } if (sa_old.sa_flags & SA_SIGINFO) sa_old.sa_sigaction(sig, info, ucontext); else sa_old.sa_handler(sig); } static int InstallHandler() { stack_t ss; ss.ss_flags = 0; ss.ss_sp = altstack; ss.ss_size = sizeof(altstack); if (sigaltstack(&ss, NULL) != 0) { fprintf(stderr, "sigaltstack: %i\n", errno); return 0; } struct sigaction sa; sa.sa_sigaction = SignalHandler; sa.sa_flags = SA_ONSTACK | SA_SIGINFO; sigfillset(&sa.sa_mask); if (sigaction(SIGSEGV, &sa, &sa_old) != 0) { fprintf(stderr, "sigaction: %i\n", errno); return 0; } return 1; } uint8_t* AddTripGuard(uintptr_t start, uintptr_t length) { if (!HandlerInstalled) { if (!InstallHandler()) return NULL; HandlerInstalled = 1; } uintptr_t npage = length >> 12; for (int i = 0; i < MAX_TRIPS; i++) { if (!Trips[i]) { Trips[i] = calloc(1, sizeof(*Trips[i]) + npage); if (!Trips[i]) return NULL; Trips[i]->start = start; Trips[i]->length = length; return &Trips[i]->tripped[0]; } } return NULL; } int64_t RemoveTripGuard(uintptr_t start, uintptr_t length) { for (int i = 0; i < MAX_TRIPS; i++) { if (Trips[i] && Trips[i]->start == start && Trips[i]->length == length) { free(Trips[i]); Trips[i] = NULL; return 1; } } return 0; } uint8_t* ExamineTripGuard(uintptr_t start, uintptr_t length) { for (int i = 0; i < MAX_TRIPS; i++) { if (Trips[i] && Trips[i]->start == start && Trips[i]->length == length) return &Trips[i]->tripped[0]; } return NULL; } /* #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct uffdio_writeprotect wp; wp.range.start = start; wp.range.len = length; wp.mode = 0; if (ioctl(fd, UFFDIO_WRITEPROTECT, &wp) == -1) { free(Trips[i]); Trips[i] = NULL; return NULL; } static void v(const char* msg, int value) { if (v < 0) { printf("ERROR: %s %i\n", msg, errno); exit(1); } } const uint64_t addr = 0x36f00000000ul; const uint64_t size = 0x100000ul; static void* threadproc(void* arg) { int wpfd = (int)(long)arg; // should be able to register once for a large range, // then wp smaller ranges struct uffdio_register uffdio_register; uffdio_register.range.start = addr; uffdio_register.range.len = size; uffdio_register.mode = UFFDIO_REGISTER_MODE_WP; v("ioctl:UFFDIO_REGISTER", ioctl(wpfd, UFFDIO_REGISTER, &uffdio_register)); v("uffdio_register.ioctls", (uffdio_register.ioctls & UFFD_API_RANGE_IOCTLS) == UFFD_API_RANGE_IOCTLS ? 0 : -1); // wp { struct uffdio_writeprotect wp; wp.range.start = addr; wp.range.len = size; wp.mode = UFFDIO_WRITEPROTECT_MODE_WP; v("ioctl:UFFDIO_WRITEPROTECT", ioctl(wpfd, UFFDIO_WRITEPROTECT, &wp)); } while (1) { struct uffd_msg msg; int nb = read(wpfd, &msg, sizeof(msg)); if (nb == -1) { if (errno == EAGAIN) continue; v("read", errno); } if (nb != sizeof(msg)) v("sizeof(msg)", -1); if (msg.event & UFFD_EVENT_PAGEFAULT) { printf("==> Event is pagefault on %p flags 0x%llx write? 0x%llx wp? 0x%llx\n" , (void *)msg.arg.pagefault.address , msg.arg.pagefault.flags , msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE , msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP ); } if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) { // send write unlock struct uffdio_writeprotect wp; wp.range.start = addr; wp.range.len = size; wp.mode = 0; printf("sending !UFFDIO_WRITEPROTECT event to userfaultfd\n"); v("ioctl:UFFDIO_WRITEPROTECT", ioctl(wpfd, UFFDIO_WRITEPROTECT, &wp)); } } return NULL; } int main(void) { int fd = memfd_create("pewps", MFD_CLOEXEC); v("memfd_create", fd); printf("fd: %i\n", fd); v("ftruncate", ftruncate(fd, size)); char* ptr = mmap((void*)addr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED, fd, 0); if (ptr == MAP_FAILED || ptr != (void*)addr) v("mmap", (int)(long)ptr); v("mprotect", mprotect(ptr, size, PROT_READ | PROT_WRITE)); int wpfd = syscall(SYS_userfaultfd, O_CLOEXEC); v("SYS_userfaultfd", wpfd); struct uffdio_api uffdio_api; uffdio_api.api = UFFD_API; uffdio_api.features = 0; v("ioctl:UFFDIO_API", ioctl(wpfd, UFFDIO_API, &uffdio_api)); v("uffdio_api.api", uffdio_api.api == UFFD_API ? 0 : -1); pthread_t thr; v("pthread_create", pthread_create(&thr, NULL, threadproc, (void*)(long)wpfd)); for (uint64_t a = addr; a < addr + size; a += 4096) { sleep(1); printf("Gonna read"); if (*(uint64_t*)a == 77777) { printf("Lucky!"); } sleep(1); printf("Gonna write"); strcpy((void*)a, "string in mem area"); printf("%s\n", (char*)a); } } */