From 9530b2daff2f46be0ee6c1ac8561da262b7e4db9 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Thu, 27 Aug 2015 23:19:32 -0700 Subject: [PATCH] Remove MemoryBlock struct by consolidating in MemoryRegion. add new API uc_mem_protect. Add regress/mem_protect.c. Drop UC_PROT_EXEC for time being --- include/uc_priv.h | 7 +- include/unicorn/unicorn.h | 24 +++- qemu/include/exec/memory.h | 1 + qemu/memory.c | 13 +- regress/Makefile | 2 +- regress/mem_protect.c | 237 +++++++++++++++++++++++++++++++++++++ regress/ro_mem_test.c | 2 +- uc.c | 129 ++++++++++++++++---- 8 files changed, 379 insertions(+), 36 deletions(-) create mode 100755 regress/mem_protect.c diff --git a/include/uc_priv.h b/include/uc_priv.h index a8d601be..686ab9e5 100755 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -15,11 +15,6 @@ QTAILQ_HEAD(CPUTailQ, CPUState); -typedef struct MemoryBlock { - MemoryRegion *region; //inclusive begin - uint64_t end; //exclusive -} MemoryBlock; - typedef struct ModuleEntry { void (*init)(void); QTAILQ_ENTRY(ModuleEntry) node; @@ -176,7 +171,7 @@ struct uc_struct { int thumb; // thumb mode for ARM // full TCG cache leads to middle-block break in the last translation? bool block_full; - MemoryBlock *mapped_blocks; + MemoryRegion **mapped_blocks; uint32_t mapped_block_count; }; diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 862382a8..dd4acbee 100755 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -389,13 +389,12 @@ 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 | UC_PROT_EXEC. + with permissions UC_PROT_READ | UC_PROT_WRITE. @handle: handle returned by uc_open() @address: starting address of the new memory region to be mapped in. @@ -419,7 +418,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 | UC_PROT_EXEC, + This must be some combination of UC_PROT_READ | UC_PROT_WRITE, 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 @@ -428,6 +427,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() + @address: 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. + @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, + 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/include/exec/memory.h b/qemu/include/exec/memory.h index 7604e8ae..4df8bd85 100755 --- a/qemu/include/exec/memory.h +++ b/qemu/include/exec/memory.h @@ -171,6 +171,7 @@ struct MemoryRegion { NotifierList iommu_notify; struct uc_struct *uc; uint32_t perms; //all perms, partially redundant with readonly + uint64_t end; }; /** diff --git a/qemu/memory.c b/qemu/memory.c index a1d668da..3f8169d9 100755 --- a/qemu/memory.c +++ b/qemu/memory.c @@ -50,9 +50,9 @@ int memory_free(struct uc_struct *uc) int i; get_system_memory(uc)->enabled = false; for (i = 0; i < uc->mapped_block_count; i++) { - uc->mapped_blocks[i].region->enabled = false; - memory_region_del_subregion(get_system_memory(uc), uc->mapped_blocks[i].region); - g_free(uc->mapped_blocks[i].region); + uc->mapped_blocks[i]->enabled = false; + memory_region_del_subregion(get_system_memory(uc), uc->mapped_blocks[i]); + g_free(uc->mapped_blocks[i]); } return 0; } @@ -1315,6 +1315,12 @@ void memory_region_set_readonly(MemoryRegion *mr, bool readonly) if (mr->readonly != readonly) { memory_region_transaction_begin(mr->uc); mr->readonly = readonly; + if (readonly) { + mr->perms &= ~UC_PROT_WRITE; + } + else { + mr->perms |= UC_PROT_WRITE; + } mr->uc->memory_region_update_pending |= mr->enabled; memory_region_transaction_commit(mr->uc); } @@ -1534,6 +1540,7 @@ static void memory_region_add_subregion_common(MemoryRegion *mr, assert(!subregion->container); subregion->container = mr; subregion->addr = offset; + subregion->end = offset + int128_get64(subregion->size); memory_region_update_container_subregions(subregion); } diff --git a/regress/Makefile b/regress/Makefile index ed82f3aa..970f2957 100755 --- a/regress/Makefile +++ b/regress/Makefile @@ -4,7 +4,7 @@ LDFLAGS = -L.. -lunicorn TESTS = map_crash map_write TESTS += sigill sigill2 TESTS += block_test -TESTS += ro_mem_test +TESTS += ro_mem_test mem_protect all: $(TESTS) diff --git a/regress/mem_protect.c b/regress/mem_protect.c new file mode 100755 index 00000000..183a388c --- /dev/null +++ b/regress/mem_protect.c @@ -0,0 +1,237 @@ +#define __STDC_FORMAT_MACROS +#include +#include +#include + +#include + +unsigned char PROGRAM[] = + "\xc7\x05\x00\x00\x40\x00\x41\x41\x41\x41\x90\xc7\x05\x00\x00\x40" + "\x00\x42\x42\x42\x42\x90\xc7\x05\x01\x00\x40\x00\x43\x43\x43\x43" + "\x90\xc7\x05\x01\x00\x40\x00\x44\x44\x44\x44\x90\x66\xc7\x05\x02" + "\x00\x40\x00\x45\x45\x90\x66\xc7\x05\x02\x00\x40\x00\x46\x46\x90" + "\x66\xc7\x05\x01\x00\x40\x00\x47\x47\x90\x66\xc7\x05\x01\x00\x40" + "\x00\x48\x48\x90\xc6\x05\x03\x00\x40\x00\x49\x90\xc6\x05\x03\x00" + "\x40\x00\x4a\x90\xf4"; +// total size: 101 bytes + +/* +bits 32 + +; assumes code section at 0x100000 +; assumes data section at 0x400000, initially rw? + +; with installed hooks toggles UC_PROT_WRITE on each nop + + mov dword [0x400000], 0x41414141 ; aligned + nop + mov dword [0x400000], 0x42424242 ; aligned + nop + mov dword [0x400001], 0x43434343 ; unaligned + nop + mov dword [0x400001], 0x44444444 ; unaligned + nop + mov word [0x400002], 0x4545 ; aligned + nop + mov word [0x400002], 0x4646 ; aligned + nop + mov word [0x400001], 0x4747 ; unaligned + nop + mov word [0x400001], 0x4848 ; unaligned + nop + mov byte [0x400003], 0x49 ; unaligned + nop + mov byte [0x400003], 0x4A ; unaligned + nop + hlt ; tell hook function we are done +*/ + +int test_num = 0; +const uint8_t *tests[] = { + (uint8_t*)"\x41\x41\x41\x41\x00", + (uint8_t*)"\x42\x42\x42\x42\x00", + (uint8_t*)"\x42\x43\x43\x43\x43", + (uint8_t*)"\x42\x44\x44\x44\x44", + (uint8_t*)"\x42\x44\x45\x45\x44", + (uint8_t*)"\x42\x44\x46\x46\x44", + (uint8_t*)"\x42\x47\x47\x46\x44", + (uint8_t*)"\x42\x48\x48\x46\x44", + (uint8_t*)"\x42\x48\x48\x49\x44", + (uint8_t*)"\x42\x48\x48\x4A\x44", +}; + +static int log_num = 1; + +#define CODE_SECTION 0x100000 +#define CODE_SIZE 0x1000 + +#define DATA_SECTION 0x400000 +#define DATA_SIZE 0x1000 + +static uint32_t current_perms = UC_PROT_READ | UC_PROT_WRITE; + +static void hexdump(const char *prefix, const uint8_t *bytes, uint32_t len) { + uint32_t i; + printf("%s", prefix); + for (i = 0; i < len; i++) { + printf("%02hhx", bytes[i]); + } + printf("\n"); +} + +// callback for tracing instruction +static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) { + uint8_t opcode; + uint8_t bytes[5]; + if (uc_mem_read(handle, addr, &opcode, 1) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail during hook_code callback, addr: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + printf("ok %d - uc_mem_read for opcode\n", log_num++); + switch (opcode) { + case 0x90: //nop + if (uc_mem_read(handle, DATA_SECTION, bytes, sizeof(bytes)) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail for address: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + printf("ok %d - uc_mem_read for test %d\n", log_num++, test_num); + + if (memcmp(bytes, tests[test_num], sizeof(bytes)) == 0) { + printf("ok %d - passed test %d\n", log_num++, test_num); + } + else { + printf("not ok %d - failed test %d\n", log_num++, test_num); + hexdump("# Expected: ", tests[test_num], sizeof(bytes)); + hexdump("# Received: ", bytes, sizeof(bytes)); + } + test_num++; + current_perms ^= UC_PROT_WRITE; + if (uc_mem_protect(handle, DATA_SECTION, DATA_SIZE, current_perms) != UC_ERR_OK) { + printf("not ok %d - uc_mem_protect fail during hook_code callback, addr: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + else { + printf("ok %d - uc_mem_protect UC_PROT_WRITE toggled\n", log_num++); + } + break; + case 0xf4: //hlt + if (uc_emu_stop(handle) != UC_ERR_OK) { + printf("not ok %d - uc_emu_stop fail during hook_code callback, addr: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + else { + printf("ok %d - hlt encountered, uc_emu_stop called\n", log_num++); + } + break; + default: //all others + break; + } +} + +// callback for tracing memory access (READ or WRITE) +static bool hook_mem_invalid(uch handle, uc_mem_type type, + uint64_t addr, int size, int64_t value, void *user_data) { + uint8_t bytes[5]; + switch(type) { + default: + printf("not ok %d - UC_HOOK_MEM_INVALID type: %d at 0x%" PRIu64 "\n", log_num++, type, addr); + return false; + case UC_MEM_WRITE_RO: + printf("# RO memory is being WRITTEN at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); + + if (uc_mem_read(handle, DATA_SECTION, bytes, sizeof(bytes)) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail for address: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + printf("ok %d - uc_mem_read for ro side of test %d\n", log_num++, test_num - 1); + + if (memcmp(bytes, tests[test_num - 1], sizeof(bytes)) == 0) { + printf("ok %d - passed ro side of test %d\n", log_num++, test_num - 1); + } + else { + printf("ok %d - failed ro side of test %d\n", log_num++, test_num - 1); + hexdump("# Expected: ", tests[test_num - 1], sizeof(bytes)); + hexdump("# Received: ", bytes, sizeof(bytes)); + } + + current_perms |= UC_PROT_WRITE; + if (uc_mem_protect(handle, DATA_SECTION, DATA_SIZE, current_perms) != UC_ERR_OK) { + printf("not ok %d - uc_mem_protect fail during hook_mem_invalid callback, addr: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + else { + printf("ok %d - uc_mem_protect UC_PROT_WRITE toggled\n", log_num++); + } + return true; + } +} + +int main(int argc, char **argv, char **envp) { + uch handle, trace1, trace2; + uc_err err; + uint8_t bytes[8]; + uint32_t esp; + int result; + + printf("# Memory protect test\n"); + + // Initialize emulator in X86-32bit mode + err = uc_open(UC_ARCH_X86, UC_MODE_32, &handle); + if (err) { + printf("not ok %d - Failed on uc_open() with error returned: %u\n", log_num++, err); + return 1; + } + else { + printf("ok %d - uc_open() success\n", log_num++); + } + + uc_mem_map_ex(handle, CODE_SECTION, CODE_SIZE, UC_PROT_READ); + uc_mem_map_ex(handle, DATA_SECTION, DATA_SIZE, current_perms); + + // write machine code to be emulated to memory + if (uc_mem_write(handle, CODE_SECTION, PROGRAM, sizeof(PROGRAM))) { + printf("not ok %d - Failed to write emulation code to memory, quit!\n", log_num++); + return 2; + } + else { + printf("ok %d - Program written to memory\n", log_num++); + } + + if (uc_hook_add(handle, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_CODE handler\n", log_num++); + return 3; + } + else { + printf("ok %d - UC_HOOK_CODE installed\n", log_num++); + } + + // intercept invalid memory events + if (uc_hook_add(handle, &trace1, UC_HOOK_MEM_INVALID, hook_mem_invalid, NULL) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_MEM_INVALID handler\n", log_num++); + return 4; + } + else { + printf("ok %d - UC_HOOK_MEM_INVALID installed\n", log_num++); + } + + // emulate machine code until told to stop by hook_code + printf("# BEGIN execution\n"); + err = uc_emu_start(handle, CODE_SECTION, CODE_SECTION + CODE_SIZE, 0, 100); + if (err != UC_ERR_OK) { + printf("not ok %d - Failure on uc_emu_start() with error %u:%s\n", log_num++, err, uc_strerror(err)); + return 5; + } + else { + printf("ok %d - uc_emu_start complete\n", log_num++); + } + printf("# END execution\n"); + + if (uc_close(&handle) == UC_ERR_OK) { + printf("ok %d - uc_close complete\n", log_num++); + } + else { + printf("not ok %d - uc_close complete\n", log_num++); + } + + return 0; +} diff --git a/regress/ro_mem_test.c b/regress/ro_mem_test.c index bacd5b3a..56a04586 100755 --- a/regress/ro_mem_test.c +++ b/regress/ro_mem_test.c @@ -96,7 +96,7 @@ int main(int argc, char **argv, char **envp) { uc_mem_map(handle, 0x100000, 0x1000); uc_mem_map(handle, 0x200000, 0x2000); uc_mem_map(handle, 0x300000, 0x3000); - uc_mem_map_ex(handle, 0x400000, 0x4000, UC_PROT_READ | UC_PROT_EXEC); + uc_mem_map_ex(handle, 0x400000, 0x4000, UC_PROT_READ); if (map_stack) { printf("Pre-mapping stack\n"); diff --git a/uc.c b/uc.c index 2db8461c..fdd968a9 100755 --- a/uc.c +++ b/uc.c @@ -352,13 +352,14 @@ uc_err uc_mem_read(uch handle, uint64_t address, uint8_t *bytes, size_t size) return UC_ERR_OK; } -static const MemoryBlock *getMemoryBlock(struct uc_struct *uc, uint64_t address); -static const MemoryBlock *getMemoryBlock(struct uc_struct *uc, uint64_t address) { + +static MemoryRegion *getMemoryBlock(struct uc_struct *uc, uint64_t address); +static MemoryRegion *getMemoryBlock(struct uc_struct *uc, uint64_t address) { unsigned int i; for(i = 0; i < uc->mapped_block_count; i++) { - if (address >= uc->mapped_blocks[i].region->addr && address < uc->mapped_blocks[i].end) - return &uc->mapped_blocks[i]; + if (address >= uc->mapped_blocks[i]->addr && address < uc->mapped_blocks[i]->end) + return uc->mapped_blocks[i]; } // not found @@ -369,25 +370,27 @@ UNICORN_EXPORT uc_err uc_mem_write(uch handle, uint64_t address, const uint8_t *bytes, size_t size) { struct uc_struct *uc = (struct uc_struct *)(uintptr_t)handle; + uint32_t operms; if (handle == 0) // invalid handle return UC_ERR_UCH; - const MemoryBlock *mb = getMemoryBlock(uc, address); - if (mb == NULL) + MemoryRegion *mr = getMemoryBlock(uc, address); + if (mr == NULL) return UC_ERR_MEM_WRITE; - if (!(mb->region->perms & UC_PROT_WRITE)) //write protected + operms = mr->perms; + if (!(operms & UC_PROT_WRITE)) //write protected //but this is not the program accessing memory, so temporarily mark writable - uc->readonly_mem(mb->region, false); + uc->readonly_mem(mr, false); if (uc->write_mem(&uc->as, address, bytes, size) == false) return UC_ERR_MEM_WRITE; - if (!(mb->region->perms & UC_PROT_WRITE)) //write protected + if (!(operms & UC_PROT_WRITE)) //write protected //now write protect it again - uc->readonly_mem(mb->region, true); + uc->readonly_mem(mr, true); return UC_ERR_OK; } @@ -556,7 +559,7 @@ static uc_err _hook_mem_access(uch handle, uc_mem_type type, UNICORN_EXPORT uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms) { - MemoryBlock *blocks; + MemoryRegion **regions; struct uc_struct* uc = (struct uc_struct *)handle; if (handle == 0) @@ -576,19 +579,17 @@ 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 | UC_PROT_EXEC)) != 0) + if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE)) != 0) return UC_ERR_MAP; if ((uc->mapped_block_count & (MEM_BLOCK_INCR - 1)) == 0) { //time to grow - blocks = realloc(uc->mapped_blocks, sizeof(MemoryBlock) * (uc->mapped_block_count + MEM_BLOCK_INCR)); - if (blocks == NULL) { + regions = (MemoryRegion**)realloc(uc->mapped_blocks, sizeof(MemoryRegion*) * (uc->mapped_block_count + MEM_BLOCK_INCR)); + if (regions == NULL) { return UC_ERR_OOM; } - uc->mapped_blocks = blocks; + uc->mapped_blocks = regions; } - uc->mapped_blocks[uc->mapped_block_count].end = address + size; - //TODO extend uc_mem_map to accept permissions, figure out how to pass this down to qemu - uc->mapped_blocks[uc->mapped_block_count].region = uc->memory_map(uc, address, size, perms); + uc->mapped_blocks[uc->mapped_block_count] = uc->memory_map(uc, address, size, perms); uc->mapped_block_count++; return UC_ERR_OK; @@ -597,8 +598,92 @@ uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms) UNICORN_EXPORT uc_err uc_mem_map(uch handle, uint64_t address, size_t size) { - //old api, maps RWX by default - return uc_mem_map_ex(handle, address, size, UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC); + //old api, maps RW by default + return uc_mem_map_ex(handle, address, size, UC_PROT_READ | UC_PROT_WRITE); +} + +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)) != 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) @@ -606,8 +691,8 @@ MemoryRegion *memory_mapping(struct uc_struct* uc, uint64_t address) unsigned int i; for(i = 0; i < uc->mapped_block_count; i++) { - if (address >= uc->mapped_blocks[i].region->addr && address < uc->mapped_blocks[i].end) - return uc->mapped_blocks[i].region; + if (address >= uc->mapped_blocks[i]->addr && address < uc->mapped_blocks[i]->end) + return uc->mapped_blocks[i]; } // not found