Index: sys/conf/files =================================================================== --- sys/conf/files +++ sys/conf/files @@ -3318,7 +3318,8 @@ net/vnet.c optional vimage net/zlib.c optional crypto | geom_uzip | ipsec | \ mxge | netgraph_deflate | \ - ddb_ctf | gzio | geom_uncompress + ddb_ctf | gzio | \ + geom_uncompress | kldgzelf net80211/ieee80211.c optional wlan net80211/ieee80211_acl.c optional wlan wlan_acl net80211/ieee80211_action.c optional wlan Index: sys/conf/options =================================================================== --- sys/conf/options +++ sys/conf/options @@ -130,6 +130,7 @@ GEOM_VOL opt_geom.h GEOM_ZERO opt_geom.h KDTRACE_HOOKS opt_kdtrace.h +KLDGZELF opt_kldgzelf.h KSTACK_MAX_PAGES KSTACK_PAGES KSTACK_USAGE_PROF Index: sys/kern/link_elf.c =================================================================== --- sys/kern/link_elf.c +++ sys/kern/link_elf.c @@ -29,7 +29,9 @@ #include "opt_ddb.h" #include "opt_gdb.h" +#include "opt_kldgzelf.h" +#include #include #include #ifdef GPROF @@ -64,8 +66,9 @@ #include #include +#include -#ifdef DDB_CTF +#if defined(DDB_CTF) || defined(KLDGZELF) #include #endif @@ -174,7 +177,7 @@ KOBJMETHOD(linker_ctf_get, link_elf_ctf_get), KOBJMETHOD(linker_symtab_get, link_elf_symtab_get), KOBJMETHOD(linker_strtab_get, link_elf_strtab_get), - { 0, 0 } + KOBJMETHOD_END }; static struct linker_class link_elf_class = { @@ -186,6 +189,65 @@ link_elf_methods, sizeof(struct elf_file) }; +#ifdef KLDGZELF +#define GZ_ID1 31 +#define GZ_ID2 139 +/* Default ocmpression */ +#define GZ_CM 8 +/* Header flags */ +#define GZ_FLG_FTEXT 1 +#define GZ_FLG_FHCRC 2 +#define GZ_FLG_FEXTRA 4 +#define GZ_FLG_FNAME 8 +#define GZ_FLG_FCOMMENT 16 + +#define GZ_EXTRA_F_SIZE_SIZE 2 +#define GZ_INFLATED_SIZE_SIZE 4 + +static int link_gzelf_link_preload(linker_class_t, + const char *, linker_file_t *); +static int link_gzelf_link_preload_finish(linker_file_t); +static int link_gzelf_load_file(linker_class_t, const char *, + linker_file_t *); + +static kobj_method_t link_gzelf_methods[] = { + KOBJMETHOD(linker_load_file, link_gzelf_load_file), + KOBJMETHOD(linker_link_preload, link_gzelf_link_preload), + KOBJMETHOD(linker_link_preload_finish, link_gzelf_link_preload_finish), + KOBJMETHOD_END +}; + +static kobj_class_t link_gzelf_baseclasses[] = { + (kobj_class_t)&link_elf_class, NULL +}; + +static struct linker_class link_gzelf_class = { +#if ELF_TARG_CLASS == ELFCLASS32 + "gzelf32", +#else + "gzelf64", +#endif + link_gzelf_methods, sizeof(struct elf_file), link_gzelf_baseclasses +}; + +static int +link_gzelf_link_preload(linker_class_t cls, + const char* filename, linker_file_t *result) +{ + + return ENOENT; +} + +static int +link_gzelf_link_preload_finish(linker_file_t lf) +{ + + return ENOENT; +} + +#endif + + static int parse_dynamic(elf_file_t); static int relocate_file(elf_file_t); static int link_elf_preload_parse_symbols(elf_file_t); @@ -376,6 +438,9 @@ char *modname; linker_add_class(&link_elf_class); +#ifdef KLDGZELF + linker_add_class(&link_gzelf_class); +#endif dp = (Elf_Dyn *)&_DYNAMIC; modname = NULL; @@ -914,7 +979,7 @@ &resid, td); if (error != 0) goto out; - bzero(segbase + segs[i]->p_filesz, + memset(segbase + segs[i]->p_filesz, 0, segs[i]->p_memsz - segs[i]->p_filesz); #ifdef SPARSE_MAPPING @@ -1035,6 +1100,562 @@ return (error); } +#ifdef KLDGZELF + +typedef struct gzcontext { + u_char *out_p; + u_int inflated_size; + struct vnode *vd; + struct thread *td; + int error; + off_t skip; +} *gzcontext_t; + +typedef struct gz_hdr { + u_char id1; + u_char id2; + u_char cm; + u_char flags; + u_int mtime; + u_char xflags; + u_char os; +}__attribute__((__packed__)) *gz_hdr_t; + +static long gzhdr(u_char *, int); +static int gzelf_inflate(struct gzcontext *); +static void *zalloc(void *, u_int, u_int); +static void zfree(void *, void *); + +/* + * Checks buffer for GZIP header. + * + * Returns: number of bytes skipped while processing header (> 0) + * or Z_DATA_ERROR (< 0) in case of failure + */ +static long +gzhdr(u_char *p, int len) +{ + struct gz_hdr *hdr = (struct gz_hdr *)p; + u_short size = 0; + u_long total = sizeof(*hdr); + + if (len < total) + return (Z_DATA_ERROR); + + if (hdr->id1 != GZ_ID1 || hdr->id2 != GZ_ID2) { + printf("Not a GZIP format\n"); + return (Z_DATA_ERROR); + } + + if (hdr->cm != GZ_CM) { + printf("Unsupported compression\n"); + return (Z_DATA_ERROR); + } + + p += sizeof(*hdr); + + /* Process optional fields */ + /* Extra field */ + if (hdr->flags & GZ_FLG_FEXTRA) { + if (total + GZ_EXTRA_F_SIZE_SIZE > len) + return (Z_DATA_ERROR); + + /* + * Extra field length, 2 bytes, does not include size + * itself. + */ + size = *(u_short *)p; + + total += GZ_EXTRA_F_SIZE_SIZE; + p += GZ_EXTRA_F_SIZE_SIZE; + + if (total + size > len) + return (Z_DATA_ERROR); + + total += size; + p += size; + } + + /* Skip over the file name */ + if (hdr->flags & GZ_FLG_FNAME) { + do { + if (total >= len) + return (Z_DATA_ERROR); + total++; + } while (*p++ != 0); + } + + /* Skip comment */ + if (hdr->flags & GZ_FLG_FCOMMENT) { + do { + if (total >= len) + return (Z_DATA_ERROR); + total++; + } while (*p++ != 0); + } + + /* Header crc */ + if (hdr->flags & GZ_FLG_FHCRC) { + total += 2; + if (total > len) + return (Z_DATA_ERROR); + } + + /* + * Successfully went through gzip header, return number of + * octets processed + */ + return (total); +} + + +static void * +zalloc(void *_up __unused, u_int items, u_int size) +{ + + return (malloc(size*items, M_LINKER, M_WAITOK)); +} + +static void +zfree(void *_up __unused, void *p) +{ + + free(p, M_LINKER); +} + +static int +gzelf_inflate(struct gzcontext *gzc) +{ + z_stream strm; + u_char *in_p = NULL; + int ret = Z_OK; + struct stat sb; + off_t off = 0; + ssize_t resid; + ssize_t loaded_bytes; + + gzc->error = 0; + + memset(&strm, 0, sizeof(strm)); + strm.zalloc = zalloc; + strm.zfree = zfree; + + ret = inflateInit2(&strm, -15); + if (ret != Z_OK) + goto out; + + ret = Z_ERRNO; + + /* Alloc one page for purpose of checking if given file is + gzip file indeed and getting inflated file size */ + in_p = malloc(PAGE_SIZE, M_LINKER, M_WAITOK); + + gzc->error = vn_rdwr(UIO_READ, gzc->vd, in_p, PAGE_SIZE, 0, + UIO_SYSSPACE, IO_NODELOCKED, gzc->td->td_ucred, NOCRED, + &resid, gzc->td); + if (gzc->error != 0) + goto out; + + ret = gzhdr(in_p, PAGE_SIZE - resid); + + if (ret < 0) { + ret = Z_DATA_ERROR; + goto out; + } + + gzc->skip = ret; + + gzc->error = vn_stat(gzc->vd, &sb, gzc->td->td_ucred, NOCRED, gzc->td); + if (gzc->error != 0) + goto out; + + gzc->error = vn_rdwr(UIO_READ, gzc->vd, in_p, PAGE_SIZE, + MAX(0, sb.st_size - PAGE_SIZE), UIO_SYSSPACE, IO_NODELOCKED, + gzc->td->td_ucred, NOCRED, &resid, gzc->td); + if (gzc->error != 0) + goto out; + + /* Last four bytes of GZIPed file store size of inlated data */ + loaded_bytes = PAGE_SIZE - resid; + gzc->inflated_size = *(u_int *)(in_p + loaded_bytes - GZ_INFLATED_SIZE_SIZE); + + /* Be reasonable, 100MB of module is enough */ + if (gzc->inflated_size > (100 * 1024 * 1024)) { + gzc->error = E2BIG; + goto out; + } + + gzc->out_p = realloc(in_p, gzc->inflated_size, M_LINKER, M_WAITOK); + + strm.avail_out = gzc->inflated_size; + strm.next_out = gzc->out_p; + + do { + gzc->error = vn_rdwr(UIO_READ, gzc->vd, in_p, PAGE_SIZE, + off + gzc->skip, UIO_SYSSPACE, IO_NODELOCKED, + gzc->td->td_ucred, NOCRED, &resid, gzc->td); + if (gzc->error) { + ret = Z_ERRNO; + goto out; + } + + strm.avail_in = PAGE_SIZE - resid; + strm.next_in = in_p; + off += strm.avail_in; + + ret = inflate(&strm, Z_NO_FLUSH); + if (ret == Z_NEED_DICT) + ret = Z_DATA_ERROR; + + /* All negative return codes mean error */ + if (ret < 0) + goto out; + + } while (strm.avail_out > 0 && ret != Z_STREAM_END); + + /* + * We run out of buffer space for data and still have not inflated + * all of data. + */ + if (strm.avail_out == 0 && ret == Z_OK) + ret = Z_DATA_ERROR; + + /* + * Expected inflated size differs from obtained, something + * went wrong. + */ + if (strm.avail_out != 0 && ret == Z_STREAM_END) + ret = Z_DATA_ERROR; + + /* Return Z_OK if stream fully decompressed to avoid confusion */ + if (ret == Z_STREAM_END) + ret = Z_OK; +out: + /* Close file and release buffers */ + inflateEnd(&strm); + free(in_p, M_LINKER); + if (ret != Z_OK) { + free(gzc->out_p, M_LINKER); + gzc->out_p = NULL; + } + + if (gzc->error != 0) + return (Z_ERRNO); + + return (ret); +} + + +static int +link_gzelf_load_file(linker_class_t cls, const char* filename, + linker_file_t* result) +{ + struct nameidata nd; + struct thread* td = curthread; /* XXX */ + Elf_Ehdr *hdr; + int nbytes, i; + Elf_Phdr *phdr; + Elf_Phdr *phlimit; + Elf_Phdr *segs[MAXSEGS]; + int nsegs; + Elf_Phdr *phdyn; + Elf_Phdr *phphdr; + caddr_t mapbase; + size_t mapsize; + Elf_Off base_offset; + Elf_Addr base_vaddr; + Elf_Addr base_vlimit; + int error = 0; + int flags; + elf_file_t ef; + linker_file_t lf; + Elf_Shdr *shdr; + int symtabindex; + int symstrindex; + int symcnt; + int strcnt; + struct gzcontext gzc; + + shdr = NULL; + lf = NULL; + + NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, filename, td); + flags = FREAD; + error = vn_open(&nd, &flags, 0, NULL); + if (error != 0) + return (error); + NDFREE(&nd, NDF_ONLY_PNBUF); + if (nd.ni_vp->v_type != VREG) { + error = ENOEXEC; + goto out; + } +#ifdef MAC + error = mac_kld_check_load(curthread->td_ucred, nd.ni_vp); + if (error != 0) + goto out; +#endif + + memset(&gzc, 0, sizeof(struct gzcontext)); + gzc.td = td; + gzc.vd = nd.ni_vp; + if (gzelf_inflate(&gzc) != Z_OK) { + if (gzc.error != 0) + error = gzc.error; + else + error = EIO; + } + nbytes = gzc.inflated_size; + + /* File is no longer needed to be open */ + VOP_UNLOCK(nd.ni_vp, 0); + vn_close(nd.ni_vp, FREAD, td->td_ucred, td); + + hdr = (Elf_Ehdr *)gzc.out_p; + + if (!IS_ELF(*hdr)) { + error = ENOEXEC; + goto out; + } + + if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS || + hdr->e_ident[EI_DATA] != ELF_TARG_DATA) { + link_elf_error(filename, "Unsupported file layout"); + error = ENOEXEC; + goto out; + } + if (hdr->e_ident[EI_VERSION] != EV_CURRENT || + hdr->e_version != EV_CURRENT) { + link_elf_error(filename, "Unsupported file version"); + error = ENOEXEC; + goto out; + } + if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) { + error = ENOSYS; + goto out; + } + if (hdr->e_machine != ELF_TARG_MACH) { + link_elf_error(filename, "Unsupported machine"); + error = ENOEXEC; + goto out; + } + + /* + * We rely on the program header being in the first page. + * This is not strictly required by the ABI specification, but + * it seems to always true in practice. And, it simplifies + * things considerably. + */ + if (!((hdr->e_phentsize == sizeof(Elf_Phdr)) && + (hdr->e_phoff + hdr->e_phnum*sizeof(Elf_Phdr) <= PAGE_SIZE) && + (hdr->e_phoff + hdr->e_phnum*sizeof(Elf_Phdr) <= nbytes))) + link_elf_error(filename, "Unreadable program headers"); + + /* + * Scan the program header entries, and save key information. + * + * We rely on there being exactly two load segments, text and data, + * in that order. + */ + phdr = (Elf_Phdr *) (gzc.out_p + hdr->e_phoff); + phlimit = phdr + hdr->e_phnum; + nsegs = 0; + phdyn = NULL; + phphdr = NULL; + while (phdr < phlimit) { + switch (phdr->p_type) { + case PT_LOAD: + if (nsegs == MAXSEGS) { + link_elf_error(filename, "Too many sections"); + error = ENOEXEC; + goto out; + } + /* + * XXX: We just trust they come in right order ?? + */ + segs[nsegs] = phdr; + ++nsegs; + break; + + case PT_PHDR: + phphdr = phdr; + break; + + case PT_DYNAMIC: + phdyn = phdr; + break; + + case PT_INTERP: + error = ENOSYS; + goto out; + } + + ++phdr; + } + if (phdyn == NULL) { + link_elf_error(filename, "Object is not dynamically-linked"); + error = ENOEXEC; + goto out; + } + if (nsegs == 0) { + link_elf_error(filename, "No sections"); + error = ENOEXEC; + goto out; + } + + /* + * Allocate the entire address space of the object, to stake + * out our contiguous region, and to establish the base + * address for relocation. + */ + base_offset = trunc_page(segs[0]->p_offset); + base_vaddr = trunc_page(segs[0]->p_vaddr); + base_vlimit = round_page(segs[nsegs - 1]->p_vaddr + + segs[nsegs - 1]->p_memsz); + mapsize = base_vlimit - base_vaddr; + + lf = linker_make_file(filename, &link_elf_class); + if (lf == NULL) { + error = ENOMEM; + goto out; + } + + ef = (elf_file_t) lf; +#ifdef SPARSE_MAPPING + ef->object = vm_object_allocate(OBJT_DEFAULT, mapsize >> PAGE_SHIFT); + if (ef->object == NULL) { + error = ENOMEM; + goto out; + } + ef->address = (caddr_t) vm_map_min(kernel_map); + error = vm_map_find(kernel_map, ef->object, 0, + (vm_offset_t *) &ef->address, mapsize, 0, VMFS_OPTIMAL_SPACE, + VM_PROT_ALL, VM_PROT_ALL, 0); + if (error != 0) { + vm_object_deallocate(ef->object); + ef->object = 0; + goto out; + } +#else + ef->address = malloc(mapsize, M_LINKER, M_WAITOK); +#endif + mapbase = ef->address; + + /* + * Read the text and data sections and zero the bss. + */ + for (i = 0; i < nsegs; i++) { + caddr_t segbase = mapbase + segs[i]->p_vaddr - base_vaddr; + + memcpy(segbase, gzc.out_p + segs[i]->p_offset, segs[i]->p_filesz); + memset(segbase + segs[i]->p_filesz, 0, + segs[i]->p_memsz - segs[i]->p_filesz); + +#ifdef SPARSE_MAPPING + /* + * Wire down the pages + */ + error = vm_map_wire(kernel_map, + (vm_offset_t) segbase, + (vm_offset_t) segbase + segs[i]->p_memsz, + VM_MAP_WIRE_SYSTEM|VM_MAP_WIRE_NOHOLES); + if (error != KERN_SUCCESS) { + error = ENOMEM; + goto out; + } +#endif + } + +#ifdef GPROF + /* Update profiling information with the new text segment. */ + mtx_lock(&Giant); + kmupetext((uintfptr_t)(mapbase + segs[0]->p_vaddr - base_vaddr + + segs[0]->p_memsz)); + mtx_unlock(&Giant); +#endif + + ef->dynamic = (Elf_Dyn *) (mapbase + phdyn->p_vaddr - base_vaddr); + + lf->address = ef->address; + lf->size = mapsize; + + error = parse_dynamic(ef); + if (error != 0) + goto out; + error = parse_dpcpu(ef); + if (error != 0) + goto out; +#ifdef VIMAGE + error = parse_vnet(ef); + if (error != 0) + goto out; +#endif + link_elf_reloc_local(lf); + + error = linker_load_dependencies(lf); + + if (error != 0) + goto out; + error = relocate_file(ef); + if (error != 0) + goto out; + + /* + * Try and load the symbol table if it's present. (you can + * strip it!) + */ + nbytes = hdr->e_shnum * hdr->e_shentsize; + if (nbytes == 0 || hdr->e_shoff == 0) + goto nosyms; + shdr = malloc(nbytes, M_LINKER, M_WAITOK | M_ZERO); + if (shdr == NULL) { + error = ENOMEM; + goto out; + } + + memcpy(shdr, gzc.out_p + hdr->e_shoff, nbytes); + symtabindex = -1; + symstrindex = -1; + for (i = 0; i < hdr->e_shnum; i++) { + if (shdr[i].sh_type == SHT_SYMTAB) { + symtabindex = i; + symstrindex = shdr[i].sh_link; + } + } + if (symtabindex < 0 || symstrindex < 0) + goto nosyms; + + symcnt = shdr[symtabindex].sh_size; + ef->symbase = malloc(symcnt, M_LINKER, M_WAITOK); + strcnt = shdr[symstrindex].sh_size; + ef->strbase = malloc(strcnt, M_LINKER, M_WAITOK); + + memcpy(ef->symbase, gzc.out_p + shdr[symtabindex].sh_offset, symcnt); + + memcpy(ef->strbase, gzc.out_p + shdr[symstrindex].sh_offset, strcnt); + ef->ddbsymcnt = symcnt / sizeof(Elf_Sym); + ef->ddbsymtab = (const Elf_Sym *)ef->symbase; + ef->ddbstrcnt = strcnt; + ef->ddbstrtab = ef->strbase; + +nosyms: + error = link_elf_link_common_finish(lf); + if (error != 0) + goto out; + + *result = lf; + +out: + if (error != 0 && lf != NULL) + linker_file_unload(lf, LINKER_UNLOAD_FORCE); + if (shdr != NULL) + free(shdr, M_LINKER); + + free(gzc.out_p, M_LINKER); + + return (error); +} +#endif + Elf_Addr elf_relocaddr(linker_file_t lf, Elf_Addr x) {