diff --git a/NEWS b/NEWS index ab093142..c97e6898 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,13 @@ # @markup markdown +2.2.14 (2024-08-XX) +=================== + +Fixed issues +------------ + +- Fix SIGSEGV when nokogiri is loaded before oci8 (GH-263 - Linux only) + 2.2.13 (2024-07-27) =================== diff --git a/ext/oci8/oci8lib.c b/ext/oci8/oci8lib.c index 406738ee..14dde9a0 100644 --- a/ext/oci8/oci8lib.c +++ b/ext/oci8/oci8lib.c @@ -9,6 +9,7 @@ #endif #if defined(HAVE_PLTHOOK) && !defined(WIN32) #include +#include #include "plthook.h" #endif @@ -135,6 +136,8 @@ static void rebind_internal_symbols(void) void **addr; const char *prefix; size_t prefix_len; + int prot; + size_t page_size = sysconf(_SC_PAGESIZE); #ifdef RTLD_FIRST flags |= RTLD_FIRST; /* for macOS */ @@ -161,7 +164,7 @@ static void rebind_internal_symbols(void) plthook_close(ph); return; } - while (plthook_enum(ph, &pos, &name, &addr) == 0) { + while (plthook_enum_with_prot(ph, &pos, &name, &addr, &prot) == 0) { void *funcaddr; if (prefix_len != 0) { if (strncmp(name, prefix, prefix_len) != 0) { @@ -179,7 +182,17 @@ static void rebind_internal_symbols(void) * PLT entries are forcedly modified to point to itself not * to use functions in other libraries. */ +#define ALIGN_ADDR(addr) ((void*)((size_t)(addr) & ~(page_size - 1))) + if ((prot & PROT_WRITE) == 0) { + /* when the region containing addr isn't writable, make it writable temporarily */ + if (mprotect(ALIGN_ADDR(addr), page_size, PROT_READ | PROT_WRITE) != 0) { + continue; + } + } *addr = funcaddr; + if ((prot & PROT_WRITE) == 0) { + mprotect(ALIGN_ADDR(addr), page_size, prot); + } } } plthook_close(ph); diff --git a/ext/oci8/plthook.h b/ext/oci8/plthook.h index 2ef827ed..94d09796 100644 --- a/ext/oci8/plthook.h +++ b/ext/oci8/plthook.h @@ -59,8 +59,14 @@ int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, vo void plthook_close(plthook_t *plthook); const char *plthook_error(void); +/* enumerate entries with memory protection information (bitwise-OR of PROT_READ, PROT_WRITE and PROT_EXEC) + * + * source: plthook_elf.c and plthook_osx.c + */ +int plthook_enum_with_prot(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, int *prot); + #ifdef __cplusplus -}; /* extern "C" */ +} /* extern "C" */ #endif #endif diff --git a/ext/oci8/plthook_elf.c b/ext/oci8/plthook_elf.c index 28eaf59a..0be2d8b2 100644 --- a/ext/oci8/plthook_elf.c +++ b/ext/oci8/plthook_elf.c @@ -187,6 +187,14 @@ #endif #endif /* __LP64__ */ +typedef struct mem_prot { + size_t start; + size_t end; + int prot; +} mem_prot_t; + +#define NUM_MEM_PROT 20 + struct plthook { const Elf_Sym *dynsym; const char *dynstr; @@ -198,6 +206,7 @@ struct plthook { const Elf_Plt_Rel *rela_dyn; size_t rela_dyn_cnt; #endif + mem_prot_t mem_prot[NUM_MEM_PROT]; }; static char errmsg[512]; @@ -207,7 +216,15 @@ static size_t page_size; static int plthook_open_executable(plthook_t **plthook_out); static int plthook_open_shared_library(plthook_t **plthook_out, const char *filename); static const Elf_Dyn *find_dyn_by_tag(const Elf_Dyn *dyn, Elf_Sxword tag); + +typedef struct mem_prot_iter mem_prot_iter_t; +static int mem_prot_begin(mem_prot_iter_t *iter); +static int mem_prot_next(mem_prot_iter_t *iter, mem_prot_t *mem_prot); +static void mem_prot_end(mem_prot_iter_t *iter); + static int plthook_open_real(plthook_t **plthook_out, struct link_map *lmap); +static int plthook_set_mem_prot(plthook_t *plthook); +static int plthook_get_mem_prot(plthook_t *plthook, void *addr); #if defined __FreeBSD__ || defined __sun static int check_elf_header(const Elf_Ehdr *ehdr); #endif @@ -397,22 +414,29 @@ static const Elf_Dyn *find_dyn_by_tag(const Elf_Dyn *dyn, Elf_Sxword tag) } #ifdef __linux__ -static int get_memory_permission(void *address) -{ - unsigned long addr = (unsigned long)address; +struct mem_prot_iter { FILE *fp; - char buf[PATH_MAX]; - char perms[5]; - int bol = 1; +}; - fp = fopen("/proc/self/maps", "r"); - if (fp == NULL) { +static int mem_prot_begin(mem_prot_iter_t *iter) +{ + iter->fp = fopen("/proc/self/maps", "r"); + if (iter->fp == NULL) { set_errmsg("failed to open /proc/self/maps"); - return 0; + return -1; } - while (fgets(buf, PATH_MAX, fp) != NULL) { + return 0; +} + +static int mem_prot_next(mem_prot_iter_t *iter, mem_prot_t *mem_prot) +{ + char buf[PATH_MAX]; + char perms[5]; + int bol = 1; /* beginnng of line */ + + while (fgets(buf, PATH_MAX, iter->fp) != NULL) { unsigned long start, end; - int eol = (strchr(buf, '\n') != NULL); + int eol = (strchr(buf, '\n') != NULL); /* end of line */ if (bol) { /* The fgets reads from the beginning of a line. */ if (!eol) { @@ -431,121 +455,126 @@ static int get_memory_permission(void *address) if (sscanf(buf, "%lx-%lx %4s", &start, &end, perms) != 3) { continue; } - if (start <= addr && addr < end) { - int prot = 0; - if (perms[0] == 'r') { - prot |= PROT_READ; - } else if (perms[0] != '-') { - goto unknown_perms; - } - if (perms[1] == 'w') { - prot |= PROT_WRITE; - } else if (perms[1] != '-') { - goto unknown_perms; - } - if (perms[2] == 'x') { - prot |= PROT_EXEC; - } else if (perms[2] != '-') { - goto unknown_perms; - } - if (perms[3] != 'p') { - goto unknown_perms; - } - if (perms[4] != '\0') { - perms[4] = '\0'; - goto unknown_perms; - } - fclose(fp); - return prot; + mem_prot->start = start; + mem_prot->end = end; + mem_prot->prot = 0; + if (perms[0] == 'r') { + mem_prot->prot |= PROT_READ; + } + if (perms[1] == 'w') { + mem_prot->prot |= PROT_WRITE; } + if (perms[2] == 'x') { + mem_prot->prot |= PROT_EXEC; + } + return 0; } - fclose(fp); - set_errmsg("Could not find memory region containing %p", (void*)addr); - return 0; -unknown_perms: - fclose(fp); - set_errmsg("Unexcepted memory permission %s at %p", perms, (void*)addr); - return 0; + return -1; } -#elif defined __FreeBSD__ -static int get_memory_permission(void *address) + +static void mem_prot_end(mem_prot_iter_t *iter) { - uint64_t addr = (uint64_t)address; - struct kinfo_vmentry *top; - int i, cnt; + if (iter->fp != NULL) { + fclose(iter->fp); + } +} +#elif defined __FreeBSD__ +struct mem_prot_iter { + struct kinfo_vmentry *kve; + int idx; + int num; +}; - top = kinfo_getvmmap(getpid(), &cnt); - if (top == NULL) { +static int mem_prot_begin(mem_prot_iter_t *iter) +{ + iter->kve = kinfo_getvmmap(getpid(), &iter->num); + if (iter->kve == NULL) { set_errmsg("failed to call kinfo_getvmmap()\n"); - return 0; + return -1; } - for (i = 0; i < cnt; i++) { - struct kinfo_vmentry *kve = top + i; + iter->idx = 0; + return 0; +} - if (kve->kve_start <= addr && addr < kve->kve_end) { - int prot = 0; - if (kve->kve_protection & KVME_PROT_READ) { - prot |= PROT_READ; - } - if (kve->kve_protection & KVME_PROT_WRITE) { - prot |= PROT_WRITE; - } - if (kve->kve_protection & KVME_PROT_EXEC) { - prot |= PROT_EXEC; - } - if (prot == 0) { - set_errmsg("Unknown kve_protection 0x%x at %p", kve->kve_protection, (void*)addr); - } - free(top); - return prot; - } +static int mem_prot_next(mem_prot_iter_t *iter, mem_prot_t *mem_prot) +{ + if (iter->idx >= iter->num) { + return -1; + } + struct kinfo_vmentry *kve = &iter->kve[iter->idx++]; + mem_prot->start = kve->kve_start; + mem_prot->end = kve->kve_end; + mem_prot->prot = 0; + if (kve->kve_protection & KVME_PROT_READ) { + mem_prot->prot |= PROT_READ; + } + if (kve->kve_protection & KVME_PROT_WRITE) { + mem_prot->prot |= PROT_WRITE; + } + if (kve->kve_protection & KVME_PROT_EXEC) { + mem_prot->prot |= PROT_EXEC; } - free(top); - set_errmsg("Could not find memory region containing %p", (void*)addr); return 0; } -#elif defined(__sun) -#define NUM_MAPS 20 -static int get_memory_permission(void *address) + +static void mem_prot_end(mem_prot_iter_t *iter) { - unsigned long addr = (unsigned long)address; + if (iter->kve != NULL) { + free(iter->kve); + } +} +#elif defined(__sun) +struct mem_prot_iter { FILE *fp; - prmap_t maps[NUM_MAPS]; + prmap_t maps[20]; + size_t idx; size_t num; +}; - fp = fopen("/proc/self/map", "r"); - if (fp == NULL) { +static int mem_prot_begin(mem_prot_iter_t *iter) +{ + iter->fp = fopen("/proc/self/map", "r"); + if (iter->fp == NULL) { set_errmsg("failed to open /proc/self/map"); - return 0; + return -1; } - while ((num = fread(maps, sizeof(prmap_t), NUM_MAPS, fp)) > 0) { - size_t i; - for (i = 0; i < num; i++) { - prmap_t *map = &maps[i]; + iter->idx = iter->num = 0; + return 0; +} - if (map->pr_vaddr <= addr && addr < map->pr_vaddr + map->pr_size) { - int prot = 0; - if (map->pr_mflags & MA_READ) { - prot |= PROT_READ; - } - if (map->pr_mflags & MA_WRITE) { - prot |= PROT_WRITE; - } - if (map->pr_mflags & MA_EXEC) { - prot |= PROT_EXEC; - } - if (prot == 0) { - set_errmsg("Unknown pr_mflags 0x%x at %p", map->pr_mflags, (void*)addr); - } - fclose(fp); - return prot; - } +static int mem_prot_next(mem_prot_iter_t *iter, mem_prot_t *mem_prot) +{ + prmap_t *map; + + if (iter->idx == iter->num) { + iter->num = fread(iter->maps, sizeof(iter->maps[0]), sizeof(iter->maps) / sizeof(iter->maps[0]), iter->fp); + if (iter->num == 0) { + return -1; } + iter->idx = 0; + } + map = &iter->maps[iter->idx++]; + mem_prot->start = map->pr_vaddr; + mem_prot->end = map->pr_vaddr + map->pr_size; + mem_prot->prot = 0; + if (map->pr_mflags & MA_READ) { + mem_prot->prot |= PROT_READ; + } + if (map->pr_mflags & MA_WRITE) { + mem_prot->prot |= PROT_WRITE; + } + if (map->pr_mflags & MA_EXEC) { + mem_prot->prot |= PROT_EXEC; } - fclose(fp); - set_errmsg("Could not find memory region containing %p", (void*)addr); return 0; } + +static void mem_prot_end(mem_prot_iter_t *iter) +{ + if (iter->fp != NULL) { + fclose(iter->fp); + } +} #else #error Unsupported platform #endif @@ -572,7 +601,11 @@ static int plthook_open_real(plthook_t **plthook_out, struct link_map *lmap) dyn_addr_base = (const char*)lmap->l_addr; #endif #elif defined __FreeBSD__ || defined __sun +#if __FreeBSD__ >= 13 + const Elf_Ehdr *ehdr = (const Elf_Ehdr*)lmap->l_base; +#else const Elf_Ehdr *ehdr = (const Elf_Ehdr*)lmap->l_addr; +#endif int rv_ = check_elf_header(ehdr); if (rv_ != 0) { return rv_; @@ -666,6 +699,9 @@ static int plthook_open_real(plthook_t **plthook_out, struct link_map *lmap) return PLTHOOK_INTERNAL_ERROR; } #endif + if (plthook_set_mem_prot(&plthook)) { + return PLTHOOK_INTERNAL_ERROR; + } *plthook_out = malloc(sizeof(plthook_t)); if (*plthook_out == NULL) { @@ -676,6 +712,53 @@ static int plthook_open_real(plthook_t **plthook_out, struct link_map *lmap) return 0; } +static int plthook_set_mem_prot(plthook_t *plthook) +{ + unsigned int pos = 0; + const char *name; + void **addr; + size_t start = (size_t)-1; + size_t end = 0; + mem_prot_iter_t iter; + mem_prot_t mem_prot; + int idx = 0; + + while (plthook_enum(plthook, &pos, &name, &addr) == 0) { + if (start > (size_t)addr) { + start = (size_t)addr; + } + if (end < (size_t)addr) { + end = (size_t)addr; + } + } + end++; + + if (mem_prot_begin(&iter) != 0) { + return PLTHOOK_INTERNAL_ERROR; + } + while (mem_prot_next(&iter, &mem_prot) == 0 && idx < NUM_MEM_PROT) { + if (mem_prot.prot != 0 && mem_prot.start < end && start < mem_prot.end) { + plthook->mem_prot[idx++] = mem_prot; + } + } + mem_prot_end(&iter); + return 0; +} + +static int plthook_get_mem_prot(plthook_t *plthook, void *addr) +{ + mem_prot_t *ptr = plthook->mem_prot; + mem_prot_t *end = ptr + NUM_MEM_PROT; + + while (ptr < end && ptr->prot != 0) { + if (ptr->start <= (size_t)addr && (size_t)addr < ptr->end) { + return ptr->prot; + } + ++ptr; + } + return 0; +} + #if defined __FreeBSD__ || defined __sun static int check_elf_header(const Elf_Ehdr *ehdr) { @@ -742,12 +825,20 @@ static int check_rel(const plthook_t *plthook, const Elf_Plt_Rel *plt, Elf_Xword } int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out) +{ + return plthook_enum_with_prot(plthook, pos, name_out, addr_out, NULL); +} + +int plthook_enum_with_prot(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, int *prot) { while (*pos < plthook->rela_plt_cnt) { const Elf_Plt_Rel *plt = plthook->rela_plt + *pos; int rv = check_rel(plthook, plt, R_JUMP_SLOT, name_out, addr_out); (*pos)++; if (rv >= 0) { + if (rv == 0 && prot != NULL) { + *prot = plthook_get_mem_prot(plthook, *addr_out); + } return rv; } } @@ -757,6 +848,9 @@ int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, v int rv = check_rel(plthook, plt, R_GLOBAL_DATA, name_out, addr_out); (*pos)++; if (rv >= 0) { + if (rv == 0 && prot != NULL) { + *prot = plthook_get_mem_prot(plthook, *addr_out); + } return rv; } } @@ -781,8 +875,10 @@ int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, vo while ((rv = plthook_enum(plthook, &pos, &name, &addr)) == 0) { if (strncmp(name, funcname, funcnamelen) == 0) { if (name[funcnamelen] == '\0' || name[funcnamelen] == '@') { - int prot = get_memory_permission(addr); + int prot = plthook_get_mem_prot(plthook, addr); if (prot == 0) { + set_errmsg("Could not get the process memory permission at %p", + ALIGN_ADDR(addr)); return PLTHOOK_INTERNAL_ERROR; } if (!(prot & PROT_WRITE)) { diff --git a/ext/oci8/plthook_osx.c b/ext/oci8/plthook_osx.c index 4b4a5aab..e6af2732 100644 --- a/ext/oci8/plthook_osx.c +++ b/ext/oci8/plthook_osx.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -152,9 +153,17 @@ typedef struct { void **addr; } bind_address_t; +typedef struct mem_prot { + size_t start; + size_t end; + int prot; +} mem_prot_t; + +#define NUM_MEM_PROT 100 + struct plthook { unsigned int num_entries; - int readonly_segment; + mem_prot_t mem_prot[NUM_MEM_PROT]; bind_address_t entries[1]; /* This must be the last. */ }; @@ -174,6 +183,8 @@ static int plthook_open_real(plthook_t **plthook_out, uint32_t image_idx, const static unsigned int set_bind_addrs(data_t *d, uint32_t lazy_bind_off, uint32_t lazy_bind_size); static void set_bind_addr(data_t *d, unsigned int *idx, const char *sym_name, int seg_index, int seg_offset); static int read_chained_fixups(data_t *d, const struct mach_header *mh, const char *image_name); +static int set_mem_prot(plthook_t *plthook); +static int get_mem_prot(plthook_t *plthook, void *addr); static void set_errmsg(const char *fmt, ...) __attribute__((__format__ (__printf__, 1, 2))); @@ -511,6 +522,7 @@ static int plthook_open_real(plthook_t **plthook_out, uint32_t image_idx, const data.plthook->num_entries = nbind; set_bind_addrs(&data, lazy_bind_off, lazy_bind_size); } + set_mem_prot(data.plthook); *plthook_out = data.plthook; return 0; @@ -666,7 +678,6 @@ static int read_chained_fixups(data_t *d, const struct mach_header *mh, const ch goto cleanup; } d->plthook->num_entries = header->imports_count; - d->plthook->readonly_segment = 1; switch (header->imports_format) { case DYLD_CHAINED_IMPORT: @@ -967,7 +978,64 @@ static int read_chained_fixups(data_t *d, const struct mach_header *mh, const ch return rv; } +static int set_mem_prot(plthook_t *plthook) +{ + unsigned int pos = 0; + const char *name; + void **addr; + size_t start = (size_t)-1; + size_t end = 0; + mach_port_t task = mach_task_self(); + vm_address_t vm_addr = 0; + vm_size_t vm_size; + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + memory_object_name_t object = 0; + int idx = 0; + + while (plthook_enum(plthook, &pos, &name, &addr) == 0) { + if (start > (size_t)addr) { + start = (size_t)addr; + } + if (end < (size_t)addr) { + end = (size_t)addr; + } + } + end++; + + while (vm_region_64(task, &vm_addr, &vm_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &info_count, &object) == KERN_SUCCESS) { + mem_prot_t mem_prot = {vm_addr, vm_addr + vm_size, info.protection & (PROT_READ | PROT_WRITE | PROT_EXEC)}; + if (mem_prot.prot != 0 && mem_prot.start < end && start < mem_prot.end) { + plthook->mem_prot[idx++] = mem_prot; + if (idx == NUM_MEM_PROT) { + break; + } + } + vm_addr += vm_size; + } + return 0; +} + +static int get_mem_prot(plthook_t *plthook, void *addr) +{ + mem_prot_t *ptr = plthook->mem_prot; + mem_prot_t *end = ptr + NUM_MEM_PROT; + + while (ptr < end && ptr->prot != 0) { + if (ptr->start <= (size_t)addr && (size_t)addr < ptr->end) { + return ptr->prot; + } + ++ptr; + } + return 0; +} + int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out) +{ + return plthook_enum_with_prot(plthook, pos, name_out, addr_out, NULL); +} + +int plthook_enum_with_prot(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, int *prot) { if (*pos >= plthook->num_entries) { *name_out = NULL; @@ -977,6 +1045,9 @@ int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, v *name_out = plthook->entries[*pos].name; *addr_out = plthook->entries[*pos].addr; (*pos)++; + if (prot != NULL) { + *prot = get_mem_prot(plthook, *addr_out); + } return 0; } @@ -1020,7 +1091,12 @@ int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, vo if (oldfunc) { *oldfunc = *addr; } - if (plthook->readonly_segment) { + int prot = get_mem_prot(plthook, addr); + if (prot == 0) { + set_errmsg("Could not get the process memory permission at %p", addr); + return PLTHOOK_INTERNAL_ERROR; + } + if (!(prot & PROT_WRITE)) { size_t page_size = sysconf(_SC_PAGESIZE); void *base = (void*)((size_t)addr & ~(page_size - 1)); if (mprotect(base, page_size, PROT_READ | PROT_WRITE) != 0) { @@ -1028,7 +1104,7 @@ int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, vo return PLTHOOK_INTERNAL_ERROR; } *addr = funcaddr; - mprotect(base, page_size, PROT_READ); + mprotect(base, page_size, prot); } else { *addr = funcaddr; }