From 9ba59e4988b7fa84bb5fd3d52ab8c6482a98a108 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Fri, 28 Aug 2015 18:59:45 -0700 Subject: [PATCH] Step one towards uc_mem_protect, uc_mem_unmap, and support for UC_PROT_EXEC and NX regions --- include/unicorn/unicorn.h | 25 ++++++++++- qemu/softmmu_template.h | 88 ++++++++++++++++++++++++--------------- uc.c | 88 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 163 insertions(+), 38 deletions(-) diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 556b6c17..ce887aa3 100755 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -151,6 +151,7 @@ typedef enum uc_mem_type { UC_MEM_READ_WRITE, // Memory is accessed (either READ or WRITE) UC_MEM_WRITE_NW, // write to non-writable UC_MEM_READ_NR, // read from non-readable + UC_MEM_NX, // read from non-readable } uc_mem_type; // All type of hooks for uc_hook_add() API. @@ -391,12 +392,13 @@ uc_err uc_hook_del(uch handle, uch *h2); typedef enum uc_prot { UC_PROT_READ = 1, UC_PROT_WRITE = 2, + UC_PROT_EXEC = 4, } uc_prot; /* Map memory in for emulation. This API adds a memory region that can be used by emulation. The region is mapped - with permissions UC_PROT_READ | UC_PROT_WRITE. + with permissions UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC. @handle: handle returned by uc_open() @address: starting address of the new memory region to be mapped in. @@ -420,7 +422,7 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size); @size: size of the new memory region to be mapped in. This size must be multiple of 4KB, or this will return with UC_ERR_MAP error. @perms: Permissions for the newly mapped region. - This must be some combination of UC_PROT_READ | UC_PROT_WRITE, + This must be some combination of UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, or this will return with UC_ERR_MAP error. @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum @@ -429,6 +431,25 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size); UNICORN_EXPORT uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms); +/* + Set memory permissions for emulation memory. + This API changes permissions on an existing memory region. + + @handle: handle returned by uc_open() + @start: starting address of the memory region to be modified. + This address must be aligned to 4KB, or this will return with UC_ERR_MAP error. + @block_size: size of the memory region to be modified. + This size must be multiple of 4KB, or this will return with UC_ERR_MAP error. + @perms: New permissions for the mapped region. + This must be some combination of UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, + or this will return with UC_ERR_MAP error. + + @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum + for detailed error). +*/ +UNICORN_EXPORT +uc_err uc_mem_protect(uch handle, uint64_t start, size_t block_size, uint32_t perms); + #ifdef __cplusplus } #endif diff --git a/qemu/softmmu_template.h b/qemu/softmmu_template.h index 56f657a4..8c181824 100755 --- a/qemu/softmmu_template.h +++ b/qemu/softmmu_template.h @@ -181,6 +181,24 @@ WORD_TYPE helper_le_ld_name(CPUArchState *env, target_ulong addr, int mmu_idx, struct uc_struct *uc = env->uc; MemoryRegion *mr = memory_mapping(uc, addr); +#if defined(SOFTMMU_CODE_ACCESS) + // Unicorn: callback on fetch from NX + if (mr != NULL && !(mr->perms & UC_PROT_EXEC)) { //non-executable + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_NX, addr, DATA_SIZE, 0, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { + env->invalid_error = UC_ERR_OK; + } + else { + env->invalid_addr = addr; + env->invalid_error = UC_ERR_MEM_READ; + // printf("***** Invalid memory read (non-readable) at " TARGET_FMT_lx "\n", addr); + cpu_exit(uc->current_cpu); + return 0; + } + } +#endif + // Unicorn: callback on memory read if (env->uc->hook_mem_read && READ_ACCESS_TYPE == MMU_DATA_LOAD) { struct hook_struct *trace = hook_find((uch)env->uc, UC_MEM_READ, addr); @@ -206,15 +224,11 @@ WORD_TYPE helper_le_ld_name(CPUArchState *env, target_ulong addr, int mmu_idx, } } - // Unicorn: callback on read only memory + // Unicorn: callback on non-readable memory if (mr != NULL && !(mr->perms & UC_PROT_READ)) { //non-readable - bool result = false; - if (uc->hook_mem_idx) { - result = ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( - (uch)uc, UC_MEM_READ_NR, addr, DATA_SIZE, 0, - uc->hook_callbacks[uc->hook_mem_idx].user_data); - } - if (result) { + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_READ_NR, addr, DATA_SIZE, 0, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { env->invalid_error = UC_ERR_OK; } else { @@ -326,6 +340,24 @@ WORD_TYPE helper_be_ld_name(CPUArchState *env, target_ulong addr, int mmu_idx, struct uc_struct *uc = env->uc; MemoryRegion *mr = memory_mapping(uc, addr); +#if defined(SOFTMMU_CODE_ACCESS) + // Unicorn: callback on fetch from NX + if (mr != NULL && !(mr->perms & UC_PROT_EXEC)) { //non-executable + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_NX, addr, DATA_SIZE, 0, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { + env->invalid_error = UC_ERR_OK; + } + else { + env->invalid_addr = addr; + env->invalid_error = UC_ERR_MEM_READ; + // printf("***** Invalid memory read (non-readable) at " TARGET_FMT_lx "\n", addr); + cpu_exit(uc->current_cpu); + return 0; + } + } +#endif + // Unicorn: callback on memory read if (env->uc->hook_mem_read && READ_ACCESS_TYPE == MMU_DATA_LOAD) { struct hook_struct *trace = hook_find((uch)env->uc, UC_MEM_READ, addr); @@ -351,15 +383,11 @@ WORD_TYPE helper_be_ld_name(CPUArchState *env, target_ulong addr, int mmu_idx, } } - // Unicorn: callback on read only memory + // Unicorn: callback on non-readable memory if (mr != NULL && !(mr->perms & UC_PROT_READ)) { //non-readable - bool result = false; - if (uc->hook_mem_idx) { - result = ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( - (uch)uc, UC_MEM_READ_NR, addr, DATA_SIZE, 0, - uc->hook_callbacks[uc->hook_mem_idx].user_data); - } - if (result) { + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_READ_NR, addr, DATA_SIZE, 0, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { env->invalid_error = UC_ERR_OK; } else { @@ -534,15 +562,11 @@ void helper_le_st_name(CPUArchState *env, target_ulong addr, DATA_TYPE val, } } - // Unicorn: callback on read only memory - if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //read only memory - bool result = false; - if (uc->hook_mem_idx) { - result = ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( - (uch)uc, UC_MEM_WRITE_NW, addr, DATA_SIZE, (int64_t)val, - uc->hook_callbacks[uc->hook_mem_idx].user_data); - } - if (result) { + // Unicorn: callback on non-writable memory + if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //non-writable + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_WRITE_NW, addr, DATA_SIZE, (int64_t)val, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { env->invalid_error = UC_ERR_OK; } else { @@ -672,15 +696,11 @@ void helper_be_st_name(CPUArchState *env, target_ulong addr, DATA_TYPE val, } } - // Unicorn: callback on read only memory - if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //read only memory - bool result = false; - if (uc->hook_mem_idx) { - result = ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( - (uch)uc, UC_MEM_WRITE_NW, addr, DATA_SIZE, (int64_t)val, - uc->hook_callbacks[uc->hook_mem_idx].user_data); - } - if (result) { + // Unicorn: callback on non-writable memory + if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //non-writable + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_WRITE_NW, addr, DATA_SIZE, (int64_t)val, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { env->invalid_error = UC_ERR_OK; } else { diff --git a/uc.c b/uc.c index 3192c627..18d70b63 100755 --- a/uc.c +++ b/uc.c @@ -568,7 +568,7 @@ uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms) return UC_ERR_MAP; // check for only valid permissions - if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE)) != 0) + if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC)) != 0) return UC_ERR_MAP; if ((uc->mapped_block_count & (MEM_BLOCK_INCR - 1)) == 0) { //time to grow @@ -588,7 +588,91 @@ UNICORN_EXPORT uc_err uc_mem_map(uch handle, uint64_t address, size_t size) { //old api, maps RW by default - return uc_mem_map_ex(handle, address, size, UC_PROT_READ | UC_PROT_WRITE); + return uc_mem_map_ex(handle, address, size, UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC); +} + +UNICORN_EXPORT +uc_err uc_mem_protect(uch handle, uint64_t start, size_t block_size, uint32_t perms) +{ + uint64_t address; + uint64_t size; + struct uc_struct* uc = (struct uc_struct *)handle; + + if (handle == 0) + // invalid handle + return UC_ERR_UCH; + + if (block_size == 0) + // invalid memory mapping + return UC_ERR_MAP; + + // address must be aligned to 4KB + if ((start & (4*1024 - 1)) != 0) + return UC_ERR_MAP; + + // size must be multiple of 4KB + if ((block_size & (4*1024 - 1)) != 0) + return UC_ERR_MAP; + + // check for only valid permissions + if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC)) != 0) + return UC_ERR_MAP; + + //check that users entire requested block is mapped + address = start; + size = block_size; + while (size > 0) { + uint64_t region_size; + MemoryRegion *mr = memory_mapping(uc, address); + if (mr == NULL) { + return UC_ERR_MAP; + } + region_size = int128_get64(mr->size); + if (address > mr->addr) { + //in case start address is not aligned with start of region + region_size -= address - mr->addr; + } + if (size < region_size) { + //entire region is covered + break; + } + size -= region_size; + address += region_size; + } + + //Now we know entire region is mapped, so change permissions + address = start; + size = block_size; + while (size > 0) { + MemoryRegion *mr = memory_mapping(uc, address); + uint64_t region_size = int128_get64(mr->size); + if (address > mr->addr) { + //in case start address is not aligned with start of region + region_size -= address - mr->addr; + //TODO Learn how to split regions + //In this case some proper subset of the region is having it's permissions changed + //need to split region and add new portions into uc->mapped_blocks list + //In this case, there is a portion of the region with original perms: mr->addr..start + //and a portion getting new perms: start..start+block_size + + //split the block and stay in the loop + } + if (size < int128_get64(mr->size)) { + //TODO Learn how to split regions + //In this case some proper subset of the region is having it's permissions changed + //need to split region and add new portions into uc->mapped_blocks list + //In this case, there is a portion of the region with new perms: start..start+block_size + //and a portion getting new perms: mr->addr+size..mr->addr+mr->size + + //split the block and break + break; + } + size -= int128_get64(mr->size); + address += int128_get64(mr->size); + mr->perms = perms; + uc->readonly_mem(mr, (perms & UC_PROT_WRITE) == 0); + } + return UC_ERR_OK; } MemoryRegion *memory_mapping(struct uc_struct* uc, uint64_t address)