diff --git a/cmake/toolchain_features.cmake b/cmake/toolchain_features.cmake index 45463466..cfd6862d 100644 --- a/cmake/toolchain_features.cmake +++ b/cmake/toolchain_features.cmake @@ -65,6 +65,9 @@ check_c_compiler_flag(-Wno-c90-c99-compat HAS_NO9099) check_c_compiler_flag(-Wl,-nostdlib LINKER_HAS_NOSTDLIB) check_c_compiler_flag(-Wl,--fatal-warnings HAS_WLFATAL) check_c_compiler_flag(-Wno-unused-command-line-argument HAS_NOUNUSEDARG) +check_c_compiler_flag(-pie HAS_ARG_PIE) +check_c_compiler_flag(-nopie HAS_ARG_NOPIE) +check_c_compiler_flag(-no-pie HAS_ARG_NO_PIE) if(HAS_WERROR AND DEVELOPER_MODE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") diff --git a/src/intercept.c b/src/intercept.c index 2180f314..cbdb81d2 100644 --- a/src/intercept.c +++ b/src/intercept.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ #include #include #include +#include #include "intercept.h" #include "intercept_util.h" @@ -92,15 +94,6 @@ debug_dump(const char *fmt, ...) syscall_no_intercept(SYS_write, 2, buf, len); } -static Dl_info libc_dlinfo; -static Dl_info pthreads_dlinfo; - -static int find_glibc_dl(void); -static int find_libpthread_dl(void); - -static struct intercept_desc glibc_patches; -static struct intercept_desc pthreads_patches; - static void log_header(void); void __attribute__((noreturn)) xlongjmp(long rip, long rsp, long rax); @@ -118,138 +111,331 @@ intercept_routine(long nr, long arg0, long arg1, static void clone_child_intercept_routine(void); +/* Should all objects be patched, or only libc and libpthread? */ +static bool patch_all_objs; + /* - * intercept - This is where the highest level logic of hotpatching - * is described. Upon startup, this routine looks for libc, and libpthread. - * If these libraries are found in the process's address space, they are - * patched. - * The reason to look for these two libraries, is that these two are essential - * parts of the glibc implementation, containing a lot of syscall instructions - * most users would care to override. Some other parts of glibc (e.g.: libm) - * don't contain syscalls - at least not ones that many would care about. - * Other libraries are expected to never issue any syscalls directly, and are - * not patched here. + * Information collected during dissassembly phase, and anything else + * needed for hotpatching are stored in this dynamically allocated + * array of structs. + * The number currently allocated is in the objs_count variable. */ -void -intercept(void) +static struct intercept_desc *objs; +static unsigned objs_count; + +/* was libc found while looking for loaded objects? */ +static bool libc_found; + +/* address of [vdso] */ +static void *vdso_addr; + +/* + * allocate_next_obj_desc + * Handles the dynamic allocation of the struct intercept_desc array. + * Returns a pointer to a newly allocated item. + */ +static struct intercept_desc * +allocate_next_obj_desc(void) { - bool pthreads_available; + if (objs_count == 0) + objs = xmmap_anon(sizeof(objs[0])); + else + objs = xmremap(objs, objs_count * sizeof(objs[0]), + (objs_count + 1) * sizeof(objs[0])); + + ++objs_count; + return objs + objs_count - 1; +} - debug_dumps_on = getenv("INTERCEPT_DEBUG_DUMP") != NULL; +/* + * get_lib_short_name - find filename in path containing directories. + */ +static const char * +get_lib_short_name(const char *name) +{ + const char *slash = strrchr(name, '/'); + if (slash != NULL) + name = slash + 1; - glibc_patches.c_destination = - (void *)((uintptr_t)&intercept_routine); - glibc_patches.c_destination_clone_child = - (void *)((uintptr_t)&clone_child_intercept_routine); - pthreads_patches.c_destination = - (void *)((uintptr_t)&intercept_routine); - pthreads_patches.c_destination_clone_child = - (void *)((uintptr_t)&clone_child_intercept_routine); - intercept_setup_log(getenv("INTERCEPT_LOG"), - getenv("INTERCEPT_LOG_TRUNC")); + return name; +} - if (find_glibc_dl() != 0) { - intercept_logs("libc not found"); - intercept_log_close(); - return; - } +/* + * str_match - matching library names. + * The first string (name) is not null terminated, while + * the second string (expected) is null terminated. + * This allows matching e.g.: "libc-2.25.so\0" with "libc\0". + * If name_len is 4, the comparision is between: "libc" and "libc". + */ +static bool +str_match(const char *name, size_t name_len, + const char *expected) +{ + return name_len == strlen(expected) && + strncmp(name, expected, name_len) == 0; +} - init_patcher(); +/* + * get_name_from_proc_maps + * Tries to find the path of an object file loaded at a specific + * address. + * + * The paths found are stored in BSS, in the paths variable. The + * returned pointer points into this variable. The next_path + * pointer keeps track of the already "allocated" space inside + * the paths array. + */ +static const char * +get_name_from_proc_maps(uintptr_t addr) +{ + static char paths[0x10000]; + static char *next_path = paths; + const char *path = NULL; + + char line[0x2000]; + FILE *maps; + + if ((next_path >= paths + sizeof(paths) - sizeof(line))) + return NULL; /* No more space left */ + + if ((maps = fopen("/proc/self/maps", "r")) == NULL) + return NULL; + + while ((fgets(line, sizeof(line), maps)) != NULL) { + unsigned char *start; + unsigned char *end; + + /* Read the path into next_path */ + if (sscanf(line, "%p-%p %*s %*x %*x:%*x %*u %s", + (void **)&start, (void **)&end, next_path) != 3) + continue; + + if (addr >= (uintptr_t)end) + break; + + if ((uintptr_t)start <= addr && addr < (uintptr_t)end) { + /* + * Object found, settin the return value. + * Adjusting the next_path pointer to point past the + * string found just now, to the unused space behind it. + * The next string found (if this routine is called + * again) will be stored there. + */ + path = next_path; + next_path += strlen(next_path) + 1; + break; + } + } - log_header(); + fclose(maps); - glibc_patches.dlinfo = libc_dlinfo; - find_syscalls(&glibc_patches); - allocate_trampoline_table(&glibc_patches); - create_patch_wrappers(&glibc_patches); + return path; +} - pthreads_available = (find_libpthread_dl() == 0); +/* + * get_any_used_vaddr - find a virtual address that is expected to + * be a used for the object file mapped into memory. + * + * An Elf64_Phdr struct contains information about a segment in an on object + * file. This routine looks for a segment with type LOAD, that has a non-zero + * size in memory. The p_vaddr field contains the virtual address where this + * segment should be loaded to. This of course is relative to the base address. + * + * typedef struct + * { + * Elf64_Word p_type; Segment type + * Elf64_Word p_flags; Segment flags + * Elf64_Off p_offset; Segment file offset + * Elf64_Addr p_vaddr; Segment virtual address + * Elf64_Addr p_paddr; Segment physical address + * Elf64_Xword p_filesz; Segment size in file + * Elf64_Xword p_memsz; Segment size in memory + * Elf64_Xword p_align; Segment alignment + * } Elf64_Phdr; + * + * + */ +static uintptr_t +get_any_used_vaddr(const struct dl_phdr_info *info) +{ + const Elf64_Phdr *pheaders = info->dlpi_phdr; - if (pthreads_available) { - pthreads_patches.dlinfo = pthreads_dlinfo; - find_syscalls(&pthreads_patches); - allocate_trampoline_table(&pthreads_patches); - create_patch_wrappers(&pthreads_patches); - activate_patches(&pthreads_patches); - } else { - intercept_logs("libpthread not found"); + for (Elf64_Word i = 0; i < info->dlpi_phnum; ++i) { + if (pheaders[i].p_type == PT_LOAD && pheaders[i].p_memsz != 0) + return info->dlpi_addr + pheaders[i].p_vaddr; } - mprotect_asm_wrappers(); - activate_patches(&glibc_patches); - if (pthreads_available) - activate_patches(&pthreads_patches); + return 0; /* not found */ } /* - * log_header - part of logging - * This routine outputs some potentially useful information into the log - * file, which can be very useful during development. + * get_object_path - attempt to find the path of the object in the + * filesystem. + * + * This is usually supplied by dl_iterate_phdr the in the dl_phdr_info struct, + * but sometimes that does not conain it. */ -static void -log_header(void) +static const char * +get_object_path(const struct dl_phdr_info *info) { - static const char self_decoder[] = - "tempfile=$(mktemp) ; tempfile2=$(mktemp) ; " - "grep \"^/\" $0 | cut -d \" \" -f 1,2 | " - "sed \"s/^/addr2line -p -f -e /\" > $tempfile ; " - "{ echo ; . $tempfile ; echo ; } > $tempfile2 ; " - "paste $tempfile2 $0 ; exit 0\n"; + if (info->dlpi_name != NULL && info->dlpi_name[0] != '\0') { + return info->dlpi_name; + } else { + uintptr_t addr = get_any_used_vaddr(info); + if (addr == 0) + return NULL; + return get_name_from_proc_maps(addr); + } +} - intercept_log(self_decoder, sizeof(self_decoder) - 1); +static bool +is_vdso(uintptr_t addr, const char *path) +{ + return addr == (uintptr_t)vdso_addr || strstr(path, "vdso") != NULL; } /* - * find_glibc_dl - look for libc + * should_patch_object + * Decides whether a particular loaded object should should be targeted for + * hotpatching. + * Always skipped: [vdso], and the syscall_intercept library itself. + * Besides these two, if patch_all_objs is true, everything object is + * a target. When patch_all_objs is false, only libraries that are parts of + * the glibc implementation are targeted, i.e.: libc and libpthread. */ -static int -find_glibc_dl(void) +static bool +should_patch_object(uintptr_t addr, const char *path) { - /* Assume the library that provides fopen is glibc */ + static const char self[] = "libsyscall_intercept"; + static const char libc[] = "libc"; + static const char pthr[] = "libpthread"; + static const char caps[] = "libcapstone"; - if (!dladdr((void *)((uintptr_t)&fopen), &libc_dlinfo)) - return -1; + if (is_vdso(addr, path)) + return false; - if (libc_dlinfo.dli_fbase == NULL) - return -1; + const char *name = get_lib_short_name(path); + size_t len = strcspn(name, "-."); - if (libc_dlinfo.dli_fname == NULL) - return -1; + if (len == 0) + return false; - return 0; + if (str_match(name, len, self) || str_match(name, len, caps)) + return false; + + if (str_match(name, len, libc)) { + libc_found = true; + return true; + } + + if (patch_all_objs) + return true; + + if (str_match(name, len, pthr)) + return true; + + return false; } /* - * find_libpthread_dl - look for libpthread - * Returns zero if pthreads is found. It is required to have libpthread - * loaded in order to intercept syscalls. + * analyze_object + * Look at a library loaded into the current process, and determine as much as + * possible about it. The disassembling, allocations are initiated here. + * + * This is a callback function, passed to dl_iterate_phdr(3). + * data and size are just unused callback arguments. + * + * + * From dl_iterate_phdr(3) man page: + * + * struct dl_phdr_info + * { + * ElfW(Addr) dlpi_addr; Base address of object + * const char *dlpi_name; (Null-terminated) name of object + * const ElfW(Phdr) *dlpi_phdr; Pointer to array of ELF program headers + * ElfW(Half) dlpi_phnum; # of items in dlpi_phdr + * ... + * } + * */ static int -find_libpthread_dl(void) +analyze_object(struct dl_phdr_info *info, size_t size, void *data) { - /* - * Assume the library that provides pthread_create is libpthread. - * Use dlsym instead of &pthread_create, as that would abort the - * program if libpthread is not actually loaded. - */ + (void) data; + (void) size; + const char *path; + + debug_dump("analyze_object called on \"%s\" at 0x%016" PRIxPTR "\n", + info->dlpi_name, info->dlpi_addr); - void *pcreate_addr = dlsym(RTLD_DEFAULT, "pthread_create"); + if ((path = get_object_path(info)) == NULL) + return 0; - if (pcreate_addr == NULL) - return -1; + debug_dump("analyze %s\n", path); - if (!dladdr(pcreate_addr, &pthreads_dlinfo)) - return -1; + if (!should_patch_object(info->dlpi_addr, path)) + return 0; - if (pthreads_dlinfo.dli_fbase == NULL) - return -1; + struct intercept_desc *patches = allocate_next_obj_desc(); - if (pthreads_dlinfo.dli_fname == NULL) - return -1; + patches->base_addr = (unsigned char *)info->dlpi_addr; + patches->path = path; + patches->c_destination = (void *)((uintptr_t)&intercept_routine); + patches->c_destination_clone_child = + (void *)((uintptr_t)&clone_child_intercept_routine); + find_syscalls(patches); + allocate_trampoline_table(patches); + create_patch_wrappers(patches); return 0; } +/* + * intercept - This is where the highest level logic of hotpatching + * is described. Upon startup, this routine looks for libc, and libpthread. + * If these libraries are found in the process's address space, they are + * patched. + */ +void +intercept(void) +{ + vdso_addr = (void *)(uintptr_t)getauxval(AT_SYSINFO_EHDR); + debug_dumps_on = getenv("INTERCEPT_DEBUG_DUMP") != NULL; + patch_all_objs = (getenv("INTERCEPT_ALL_OBJS") != NULL); + intercept_setup_log(getenv("INTERCEPT_LOG"), + getenv("INTERCEPT_LOG_TRUNC")); + log_header(); + init_patcher(); + + dl_iterate_phdr(analyze_object, NULL); + if (!libc_found) { + intercept_logs("libc not found"); + intercept_log_close(); + return; + } + mprotect_asm_wrappers(); + for (unsigned i = 0; i < objs_count; ++i) + activate_patches(objs + i); +} + +/* + * log_header - part of logging + * This routine outputs some potentially useful information into the log + * file, which can be very useful during development. + */ +static void +log_header(void) +{ + static const char self_decoder[] = + "tempfile=$(mktemp) ; tempfile2=$(mktemp) ; " + "grep \"^/\" $0 | cut -d \" \" -f 1,2 | " + "sed \"s/^/addr2line -p -f -e /\" > $tempfile ; " + "{ echo ; . $tempfile ; echo ; } > $tempfile2 ; " + "paste $tempfile2 $0 ; exit 0\n"; + + intercept_log(self_decoder, sizeof(self_decoder) - 1); +} + /* * xabort - speaks for itself * Calling abort() in libc might result other syscalls being called diff --git a/src/intercept.h b/src/intercept.h index 9842fbb5..f85ca6c1 100644 --- a/src/intercept.h +++ b/src/intercept.h @@ -41,6 +41,7 @@ #include #include #include +#include #include "disasm_wrapper.h" @@ -131,8 +132,14 @@ struct intercept_desc { */ bool uses_trampoline_table; - /* Storing the Dl_info returned by dladdr(3) */ - Dl_info dlinfo; + /* + * delta between vmem addresses and addresses in symbol tables, + * non-zero for dynamic objects + */ + unsigned char *base_addr; + + /* where the object is in fs */ + const char *path; /* * Some sections of the library from which information diff --git a/src/intercept_desc.c b/src/intercept_desc.c index e3e4a993..8ba43734 100644 --- a/src/intercept_desc.c +++ b/src/intercept_desc.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -51,14 +52,17 @@ * all this information is read from the file, thus its original place, * the file where the library is in an FS. The loaded library is mmaped * already of course, but not necceseraly the whole file is mapped as one - * readable mem mapping. + * readable mem mapping -- only some segments are present in memory, but + * information about the file's sections, and the sections themselves might + * only be present in the original file. + * Note on naming: memory has segments, the object file has sections. */ static long open_orig_file(const struct intercept_desc *desc) { long fd; - fd = syscall_no_intercept(SYS_open, desc->dlinfo.dli_fname, O_RDONLY); + fd = syscall_no_intercept(SYS_open, desc->path, O_RDONLY); if (fd < 0) xabort(); @@ -86,8 +90,7 @@ add_text_info(struct intercept_desc *desc, const Elf64_Shdr *header, Elf64_Half index) { desc->text_offset = header->sh_offset; - desc->text_start = - (unsigned char *)(desc->dlinfo.dli_fbase) + header->sh_offset; + desc->text_start = desc->base_addr + header->sh_addr; desc->text_end = desc->text_start + header->sh_size - 1; desc->text_section_index = index; } @@ -100,27 +103,27 @@ add_text_info(struct intercept_desc *desc, const Elf64_Shdr *header, static void find_sections(struct intercept_desc *desc, long fd) { - const Elf64_Ehdr *elf_header; + Elf64_Ehdr elf_header; desc->symbol_tables.count = 0; desc->rela_tables.count = 0; - elf_header = (const Elf64_Ehdr *)(desc->dlinfo.dli_fbase); + xread(fd, &elf_header, sizeof(elf_header)); - Elf64_Shdr sec_headers[elf_header->e_shnum]; + Elf64_Shdr sec_headers[elf_header.e_shnum]; - xlseek(fd, elf_header->e_shoff, SEEK_SET); - xread(fd, sec_headers, elf_header->e_shnum * sizeof(Elf64_Shdr)); + xlseek(fd, elf_header.e_shoff, SEEK_SET); + xread(fd, sec_headers, elf_header.e_shnum * sizeof(Elf64_Shdr)); - char sec_string_table[sec_headers[elf_header->e_shstrndx].sh_size]; + char sec_string_table[sec_headers[elf_header.e_shstrndx].sh_size]; - xlseek(fd, sec_headers[elf_header->e_shstrndx].sh_offset, SEEK_SET); + xlseek(fd, sec_headers[elf_header.e_shstrndx].sh_offset, SEEK_SET); xread(fd, sec_string_table, - sec_headers[elf_header->e_shstrndx].sh_size); + sec_headers[elf_header.e_shstrndx].sh_size); bool text_section_found = false; - for (Elf64_Half i = 0; i < elf_header->e_shnum; ++i) { + for (Elf64_Half i = 0; i < elf_header.e_shnum; ++i) { const Elf64_Shdr *section = &sec_headers[i]; char *name = sec_string_table + section->sh_name; @@ -331,8 +334,9 @@ mark_jump(const struct intercept_desc *desc, const unsigned char *addr) * Read the .symtab or .dynsym section, which stores an array of Elf64_Sym * structs. Some of these symbols are functions in the .text section, * thus their entry points are jump destinations. - * A symbol starts at offset st_value in the file, and this is its - * exposed entry point as well. + * + * The st_value fields holds the virtual address of the symbol + * relative to the base address. * * The format of the entries: * @@ -372,8 +376,7 @@ find_jumps_in_section_syms(struct intercept_desc *desc, Elf64_Shdr *section, debug_dump("jump target: %lx\n", (unsigned long)syms[i].st_value); - unsigned char *address = - syms[i].st_value + (unsigned char *)desc->dlinfo.dli_fbase; + unsigned char *address = desc->base_addr + syms[i].st_value; /* a function entry point in .text, mark it */ mark_jump(desc, address); @@ -423,8 +426,7 @@ find_jumps_in_section_rela(struct intercept_desc *desc, Elf64_Shdr *section, (unsigned long)syms[i].r_addend); unsigned char *address = - (unsigned char *)desc->dlinfo.dli_fbase + - syms[i].r_addend; + desc->base_addr + syms[i].r_addend; mark_jump(desc, address); @@ -435,7 +437,7 @@ find_jumps_in_section_rela(struct intercept_desc *desc, Elf64_Shdr *section, /* * has_pow2_count - * Checks if the number of patches in a struct intercept_desc + * Checks if the positive number of patches in a struct intercept_desc * is a power of two or not. */ static bool @@ -774,8 +776,7 @@ dump_skip_ranges(const struct intercept_desc *desc) size_t text_size = desc->text_end + 1 - desc->text_start; for (const struct range *r = desc->skip_ranges; r->address; ++r) { - size_t offset = - r->address - (unsigned char *)(desc->dlinfo.dli_fbase); + size_t offset = r->address - desc->base_addr; if (r->size > 0) { debug_dump("skip range at: %zx - %zx\n", @@ -821,7 +822,7 @@ find_skip_ranges(struct intercept_desc *desc) unsigned char *address = desc->text_start + i; debug_dump("looking at jump at: %tx\n", - address - (unsigned char *)desc->dlinfo.dli_fbase); + address - desc->base_addr); size = i - range_start; @@ -855,13 +856,21 @@ find_skip_ranges(struct intercept_desc *desc) void find_syscalls(struct intercept_desc *desc) { - debug_dump("find_syscalls in %s\n", desc->dlinfo.dli_fname); + debug_dump("find_syscalls in %s " + "at base_addr 0x%016" PRIxPTR "\n", + desc->path, + (uintptr_t)desc->base_addr); desc->count = 0; long fd = open_orig_file(desc); find_sections(desc, fd); + debug_dump( + "%s .text mapped at 0x%016" PRIxPTR " - 0x%016" PRIxPTR " \n", + desc->path, + (uintptr_t)desc->text_start, + (uintptr_t)desc->text_end); allocate_jump_table(desc); allocate_nop_table(desc); allocate_skip_ranges(desc); diff --git a/src/patcher.c b/src/patcher.c index c01e55ed..0b2fcbd0 100644 --- a/src/patcher.c +++ b/src/patcher.c @@ -486,7 +486,7 @@ create_patch_wrappers(struct intercept_desc *desc) int l = snprintf(buffer, sizeof(buffer), "unintercepted syscall at: %s 0x%lx\n", - desc->dlinfo.dli_fname, + desc->path, patch->syscall_offset); intercept_log(buffer, (size_t)l); @@ -499,7 +499,7 @@ create_patch_wrappers(struct intercept_desc *desc) create_wrapper(patch, desc->c_destination, desc->c_destination_clone_child, desc->uses_trampoline_table, - desc->dlinfo.dli_fname); + desc->path); } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 34e3f348..a3d18c15 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -150,7 +150,7 @@ target_link_libraries(filter_test syscall_intercept_shared) add_test(NAME "filter_none" COMMAND ${CMAKE_COMMAND} -DTEST_PROG=$ - -P ${CMAKE_CURRENT_SOURCE_DIR}/check_filter.cmake) + -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake) set_tests_properties("filter_none" PROPERTIES PASS_REGULAR_EXPRESSION "hooked - allowed") @@ -160,7 +160,7 @@ add_test(NAME "filter_positive" COMMAND ${CMAKE_COMMAND} -DFILTER=${filter_test_filename} -DTEST_PROG=$ - -P ${CMAKE_CURRENT_SOURCE_DIR}/check_filter.cmake) + -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake) set_tests_properties("filter_positive" PROPERTIES PASS_REGULAR_EXPRESSION "hooked - allowed") @@ -168,7 +168,7 @@ add_test(NAME "filter_negative" COMMAND ${CMAKE_COMMAND} -DFILTER=non_matching_filter -DTEST_PROG=$ - -P ${CMAKE_CURRENT_SOURCE_DIR}/check_filter.cmake) + -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake) set_tests_properties("filter_negative" PROPERTIES PASS_REGULAR_EXPRESSION "disallowed") @@ -181,6 +181,74 @@ add_test(NAME "clone_thread" -DFILTER=${test_clone_thread_filename} -DTEST_PROG=$ -DLIB_FILE=$ - -P ${CMAKE_CURRENT_SOURCE_DIR}/check_filter.cmake) + -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake) set_tests_properties("clone_thread" PROPERTIES PASS_REGULAR_EXPRESSION "clone_hook_child called") + +add_library(intercept_sys_write SHARED intercept_sys_write.c) +target_link_libraries(intercept_sys_write PRIVATE syscall_intercept_shared) + +add_executable(executable_with_syscall_pie executable_with_syscall.s) +if(HAS_NOUNUSEDARG) + target_compile_options(executable_with_syscall_pie BEFORE + PRIVATE "-Wno-unused-command-line-argument") +endif() +set_target_properties(executable_with_syscall_pie + PROPERTIES POSITION_INDEPENDENT_CODE True) +if(HAS_ARG_PIE) + target_compile_options(executable_with_syscall_pie PRIVATE "-pie") + target_link_libraries(executable_with_syscall_pie PRIVATE "-pie") +endif() + +add_executable(executable_with_syscall_no_pie executable_with_syscall.s) +if(HAS_NOUNUSEDARG) + target_compile_options(executable_with_syscall_no_pie BEFORE + PRIVATE "-Wno-unused-command-line-argument") +endif() +set_target_properties(executable_with_syscall_no_pie + PROPERTIES POSITION_INDEPENDENT_CODE False) +if(HAS_ARG_NOPIE) + target_compile_options(executable_with_syscall_no_pie PRIVATE "-nopie") + target_link_libraries(executable_with_syscall_no_pie PRIVATE "-nopie") +elseif(HAS_ARG_NO_PIE) + target_compile_options(executable_with_syscall_no_pie PRIVATE "-no-pie") + target_link_libraries(executable_with_syscall_no_pie PRIVATE "-no-pie") +endif() + +add_test(NAME "prog_pie_intercept_libc_only" + COMMAND ${CMAKE_COMMAND} + -DTEST_PROG=$ + -DLIB_FILE=$ + -DTEST_PROG_ARGS=original_syscall + -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake) +set_tests_properties("prog_pie_intercept_libc_only" + PROPERTIES PASS_REGULAR_EXPRESSION "original_syscall") + +add_test(NAME "prog_no_pie_intercept_libc_only" + COMMAND ${CMAKE_COMMAND} + -DTEST_PROG=$ + -DLIB_FILE=$ + -DTEST_PROG_ARGS=original_syscall + -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake) +set_tests_properties("prog_no_pie_intercept_libc_only" + PROPERTIES PASS_REGULAR_EXPRESSION "original_syscall") + +add_test(NAME "prog_pie_intercept_all" + COMMAND ${CMAKE_COMMAND} + -DINTERCEPT_ALL=1 + -DTEST_PROG=$ + -DLIB_FILE=$ + -DTEST_PROG_ARGS=original_syscall + -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake) +set_tests_properties("prog_pie_intercept_all" + PROPERTIES PASS_REGULAR_EXPRESSION "intercepted_call") + +add_test(NAME "prog_no_pie_intercept_all" + COMMAND ${CMAKE_COMMAND} + -DINTERCEPT_ALL=1 + -DTEST_PROG=$ + -DLIB_FILE=$ + -DTEST_PROG_ARGS=original_syscall + -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake) +set_tests_properties("prog_no_pie_intercept_all" + PROPERTIES PASS_REGULAR_EXPRESSION "intercepted_call") diff --git a/test/asm_pattern.c b/test/asm_pattern.c index d6aae057..1b1902b7 100644 --- a/test/asm_pattern.c +++ b/test/asm_pattern.c @@ -216,7 +216,8 @@ main(int argc, char **argv) * Some more information about the library to be patched, normally * these variables would refer to libc. */ - patches.dlinfo = lib_in.info; + patches.base_addr = lib_in.info.dli_fbase; + patches.path = lib_in.info.dli_fname; patches.uses_trampoline_table = true; patches.trampoline_table = lib_in.mock_trampoline_table; patches.trampoline_table_size = lib_in.mock_trampoline_table_size; diff --git a/test/check_filter.cmake b/test/check.cmake similarity index 89% rename from test/check_filter.cmake rename to test/check.cmake index 5aa51442..e633b587 100644 --- a/test/check_filter.cmake +++ b/test/check.cmake @@ -30,11 +30,21 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +if(FILTER) set(ENV{INTERCEPT_HOOK_CMDLINE_FILTER} ${FILTER}) +endif() + +if(LIB_FILE) set(ENV{LD_PRELOAD} ${LIB_FILE}) -execute_process(COMMAND ${TEST_PROG} RESULT_VARIABLE HAD_ERROR) -set(ENV{LD_PRELOAD} "") -set(ENV{INTERCEPT_HOOK_CMDLINE_FILTER} "") +endif() + +if(INTERCEPT_ALL) +set(ENV{INTERCEPT_ALL_OBJS} 1) +endif() + +execute_process(COMMAND ${TEST_PROG} ${TEST_PROG_ARGS} RESULT_VARIABLE HAD_ERROR) + +unset(ENV{LD_PRELOAD}) if(HAD_ERROR) message(FATAL_ERROR "Error: ${HAD_ERROR}") diff --git a/test/executable_with_syscall.s b/test/executable_with_syscall.s new file mode 100644 index 00000000..a21fe668 --- /dev/null +++ b/test/executable_with_syscall.s @@ -0,0 +1,70 @@ +# +# Copyright 2017, Intel Corporation +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# A program with a syscall instruction. +# This only serves for testing syscall_intercept's ability to +# patch syscalls in the main executable object of a process. + +.intel_syntax noprefix + +.global main; + +.text + +main: + cmp rdi, 2 # cmp argc with 2 + jl 0f # jump if argc < 2 + add rsi, 8 # inc argv + mov rsi, [rsi] # syscall argument: argv[1] + mov rdi, rsi # copy argv[1] to rdi + xor rcx, rcx + not rcx + shr rcx, 1 # scan -- max iteration count: SSIZE_MAX + sub al, al # scan -- byte to look for: '\0' + cld # scan -- setup direction: forward +repne scasb # scan memory to find null terminator + sub rdi, rsi # compute strlen + mov rdx, rdi # syscall argument: buffer len + mov rdi, 1 # syscall argument: stdout + mov rax, 1 # syscall number: SYS_write + syscall + mov rdi, 1 # syscall argument: stdout + lea rsi, [rip + newline] # syscall argument: buffer + mov rdx, 1 # syscall argument: length + mov rax, 1 # syscall number: SYS_write + syscall + mov rax, 0 # return 0 + ret +0: mov rax, 1 # return 1 + ret + +.data +newline: .byte 0xa diff --git a/test/intercept_sys_write.c b/test/intercept_sys_write.c new file mode 100644 index 00000000..a786ad7b --- /dev/null +++ b/test/intercept_sys_write.c @@ -0,0 +1,71 @@ +/* + * Copyright 2017, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "libsyscall_intercept_hook_point.h" + +#include +#include + +static int +hook(long syscall_number, + long arg0, long arg1, + long arg2, long arg3, + long arg4, long arg5, + long *result) +{ + (void) arg0; + (void) arg2; + (void) arg3; + (void) arg4; + (void) arg5; + (void) result; + + if (syscall_number == SYS_write) { + const char interc[] = "intercepted_"; + size_t len = (size_t)arg2; + char *dst = (char *)arg1; + const char *src = interc; + + if (len > sizeof(interc)) { + while (*src != '\0') + *dst++ = *src++; + } + } + + return 1; +} + +static __attribute__((constructor)) void +init(void) +{ + intercept_hook_point = hook; +} diff --git a/test/libcintercept0.log.match b/test/libcintercept0.log.match index 36738394..b0ef37d2 100644 --- a/test/libcintercept0.log.match +++ b/test/libcintercept0.log.match @@ -4,19 +4,25 @@ $(S) $(XX) -- clone(CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID, 0x0, 0x0, $(XX), $(OPT)$(S) $(XX) -- clone(CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID, 0x0, 0x0, $(XX), $(XX)) = 0 $(S) $(XX) -- wait4(-1, 0x0, 0x0, 0x0) = ? $(OPT)$(S) $(XX) -- clone(CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID, 0x0, 0x0, $(XX), $(XX)) = 0 +$(OPT)$(S) $(XX) -- set_robust_list($(XX), $(N)) = ? +$(OPT)$(S) $(XX) -- set_robust_list($(XX), $(N)) = $(N) $(S) $(XX) -- wait4(-1, 0x0, 0x0, 0x0) = $(N) $(S) $(XX) -- open($(S), O_RDONLY, 0666) = ? $(S) $(XX) -- open($(S), O_RDONLY, 0666) = $(N) $(S) $(XX) -- fstat($(N), $(XX)) = ? $(S) $(XX) -- fstat($(N), $(XX)) = 0 -$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = ? -$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = $(N) +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = ? +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = $(N) +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = ? +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = $(N) $(S) $(XX) -- read($(N), "", $(N)) = ? $(S) $(XX) -- read($(N), "/*\n * Copyright 2016-2017, Intel Corporation\n *\n * Redistribution and use in source and binary forms, with or without\n...", $(N)) = $(N) $(S) $(XX) -- fstat(1, $(XX)) = ? $(S) $(XX) -- fstat(1, $(XX)) = 0 $(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = ? $(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = $(N) +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = ? +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = $(N) $(S) $(XX) -- write(1, "/", 1) = ? $(S) $(XX) -- write(1, "/", 1) = 1 $(S) $(XX) -- write(1, "/*", 2) = ? diff --git a/test/libcintercept0_child.log.match b/test/libcintercept0_child.log.match index ba88a45c..34003887 100644 --- a/test/libcintercept0_child.log.match +++ b/test/libcintercept0_child.log.match @@ -2,14 +2,18 @@ $(S) $(XX) -- open($(S), O_RDONLY, 0666) = ? $(S) $(XX) -- open($(S), O_RDONLY, 0666) = $(N) $(S) $(XX) -- fstat($(N), $(XX)) = ? $(S) $(XX) -- fstat($(N), $(XX)) = 0 -$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = ? -$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = $(N) +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = ? +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = $(N) +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = ? +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = $(N) $(S) $(XX) -- read($(N), "", $(N)) = ? $(S) $(XX) -- read($(N), "/*\n * Copyright 2016-2017, Intel Corporation\n *\n * Redistribution and use in source and binary forms, with or without\n...", $(N)) = $(N) $(S) $(XX) -- fstat(1, $(XX)) = ? $(S) $(XX) -- fstat(1, $(XX)) = 0 -$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = ? -$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), $(N), $(XX)) = $(N) +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = ? +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = $(N) +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = ? +$(OPT)$(S) $(XX) -- mmap($(XX), $(N), $(N), $(N), -1, $(XX)) = $(N) $(S) $(XX) -- write(1, "/", 1) = ? $(S) $(XX) -- write(1, "/", 1) = 1 $(S) $(XX) -- write(1, "/*", 2) = ?