/* * linux/fs/binfmt_flat.c * * Copyright (C) 1998 Kenneth Albanowski * * This is a relatively simple binary format, intended solely to contain * the bare minimum needed to load and execute simple binaries, with * special attention to executing from ROM, when possible. * * Originally based on: * * linux/fs/binfmt_aout.c * * Copyright (C) 1991, 1992, 1996 Linus Torvalds */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX(a,b) ((a) <= (b) ? (b) : (a)) #undef DEBUG #ifdef DEBUG #define DBG_FLT(a...) printk(##a) #else #define DBG_FLT(a...) #endif static int load_flat_binary(struct linux_binprm *, struct pt_regs * regs); extern void dump_thread(struct pt_regs *, struct user *); static struct linux_binfmt flat_format = { #ifndef MODULE NULL, NULL, load_flat_binary, NULL, NULL #else NULL, &mod_use_count_, load_flat_binary, NULL, NULL #endif }; static unsigned long putstring(unsigned long p, char * string) { unsigned long l = strlen(string)+1; DBG_FLT("put_string '%s'\n", string); p -= l; memcpy((void*)p, string, l); return p; } static unsigned long putstringarray(unsigned long p, int count, char ** array) { DBG_FLT("putstringarray(%d)\n", count); while(count) { p=putstring(p, array[--count]); DBG_FLT("p2=%x\n", (unsigned int)p); } return p; } static unsigned long stringarraylen(int count, char ** array) { int l = 4; while(count) { l += strlen(array[--count]); l++; l+=4; } return l; } #ifdef CONFIG_BINFMT_ZFLAT /* * this is fairly harmless unless you use it. It hasn't had a lot * of testing but I have run systems with every binary compressed (davidm) * * here are the zlib hacks - to replace globals with locals */ typedef unsigned char uch; typedef unsigned short ush; typedef unsigned long ulg; #define INBUFSIZ 4096 #define WSIZE 0x8000 /* window size--must be a power of two, and */ /* at least 32K for zip's deflate method */ struct s_zloc { struct inode *inode; unsigned long start_pos; unsigned long bytes2read; char *data_pointer, *out_pointer; uch *inbuf; uch *window; unsigned insize; /* valid bytes in inbuf */ unsigned inptr; /* index of next byte to be processed in inbuf */ unsigned outcnt; /* bytes in output buffer */ int exit_code; long bytes_out; int crd_infp, crd_outfp; ulg bb; /* bit buffer */ unsigned bk; /* bits in bit buffer */ ulg crc_32_tab[256]; ulg crc; /* shift register contents */ unsigned hufts; }; static int fill_inbuf(struct s_zloc *zloc) { int i; if(zloc->exit_code) return -1; i = (zloc->bytes2read > INBUFSIZ) ? INBUFSIZ : zloc->bytes2read; if((i = read_exec(zloc->inode, zloc->start_pos, zloc->data_pointer, i, 0)) >= (unsigned long)-4096) return -1; zloc->bytes2read -= i; zloc->start_pos += i; zloc->insize = i; zloc->inptr = 1; return zloc->inbuf[0]; } static void flush_window(struct s_zloc *zloc) { ulg c = zloc->crc; unsigned n; uch *in, ch; memcpy(zloc->out_pointer, zloc->window, zloc->outcnt); in = zloc->window; for(n = 0; n < zloc->outcnt; n++) { ch = *in++; c = zloc->crc_32_tab[((int)c ^ch) & 0xff] ^(c >> 8); } zloc->crc = c; zloc->out_pointer += zloc->outcnt; zloc->bytes_out += (ulg)zloc->outcnt; zloc->outcnt = 0; } #define inbuf (zloc->inbuf) #define window (zloc->window) #define insize (zloc->insize) #define inptr (zloc->inptr) #define outcnt (zloc->outcnt) #define exit_code (zloc->exit_code) #define bytes_out (zloc->bytes_out) #define crd_infp (zloc->crd_infp) #define crd_infp (zloc->crd_infp) #define bb (zloc->bb) #define bk (zloc->bk) #define crc (zloc->crc) #define crc_32_tab (zloc->crc_32_tab) #define hufts (zloc->hufts) #define get_byte() (inptr < insize ? inbuf[inptr++] : fill_inbuf(zloc)) #define memzero(s, n) memset ((s), 0, (n)) #define OF(args) args #define Assert(cond,msg) #define Trace(x) #define Tracev(x) #define Tracevv(x) #define Tracec(c,x) #define Tracecv(c,x) #define STATIC static #define malloc(arg) kmalloc(arg, GFP_KERNEL) #define free(arg) kfree(arg) #define error(arg) printk("zflat:" arg "\n") #include "../lib/inflate2.c" #undef error #undef malloc #undef free #undef inbuf #undef window #undef insize #undef inptr #undef outcnt #undef exit_code #undef bytes_out #undef crd_infp #undef crd_outp #undef bb #undef bk #undef crc #undef crc_32_tab #undef get_byte #undef memzero static int decompress_exec(struct inode *inode, unsigned long offset, char * buffer, long len, int fd) { struct s_zloc *zloc; int res; zloc = kmalloc(sizeof(*zloc), GFP_KERNEL); if(!zloc) { return -ENOMEM; } memset(zloc, 0, sizeof(*zloc)); zloc->inode = inode; zloc->out_pointer = buffer; zloc->inbuf = kmalloc(INBUFSIZ, GFP_KERNEL); if(!zloc->inbuf) { kfree(zloc); return -ENOMEM; } zloc->window = kmalloc(WSIZE, GFP_KERNEL); if(!zloc->window) { kfree(zloc->inbuf); kfree(zloc); return -ENOMEM; } zloc->data_pointer = zloc->inbuf; zloc->bytes2read = len; zloc->start_pos = offset; zloc->crc = (ulg)0xffffffffL; makecrc(zloc); res = gunzip(zloc); kfree(zloc->window); kfree(zloc->inbuf); kfree(zloc); return res ? -ENOMEM : 0; } #endif /* CONFIG_BINFMT_ZFLAT */ /* * create_flat_tables() parses the env- and arg-strings in new user * memory and creates the pointer tables from them, and puts their * addresses on the "stack", returning the new stack pointer value. */ static unsigned long create_flat_tables(unsigned long pp, struct linux_binprm * bprm) { unsigned long *argv,*envp; unsigned long * sp; char * p = (char*)pp; int argc = bprm->argc; int envc = bprm->envc; sp = (unsigned long *) ((-(unsigned long)sizeof(char *)) & (unsigned long) p); #ifdef __alpha__ /* whee.. test-programs are so much fun. */ put_user(0, --sp); put_user(0, --sp); if (bprm->loader) { put_user(0, --sp); put_user(0x3eb, --sp); put_user(bprm->loader, --sp); put_user(0x3ea, --sp); } put_user(bprm->exec, --sp); put_user(0x3e9, --sp); #endif sp -= envc+1; envp = sp; sp -= argc+1; argv = sp; #if defined(__i386__) || defined(__mc68000__) || defined(__arm__) put_user(envp,--sp); put_user(argv,--sp); #endif put_user(argc,--sp); current->mm->arg_start = (unsigned long) p; while (argc-->0) { put_user(p,argv++); while (get_user(p++)) /* nothing */ ; } put_user(NULL,argv); current->mm->arg_end = current->mm->env_start = (unsigned long) p; while (envc-->0) { put_user(p,envp++); while (get_user(p++)) /* nothing */ ; } put_user(NULL,envp); current->mm->env_end = (unsigned long) p; return (unsigned long)sp; } static unsigned long calc_reloc(unsigned long r, unsigned long text_len) { if (r < text_len) { /* In text segment */ return r + current->mm->start_code; } /* In data segment */ return r - text_len + current->mm->start_data; } void old_reloc(unsigned long rl) { #ifdef DEBUG char *segment[] = { "TEXT", "DATA", "BSS", "*UNKNOWN*" }; #endif flat_v2_reloc_t r; unsigned long *ptr; r.value = rl; #if defined(CONFIG_COLDFIRE) ptr = (unsigned long *) (current->mm->start_code + r.reloc.offset); #else ptr = (unsigned long *) (current->mm->start_data + r.reloc.offset); #endif #ifdef DEBUG printk("Relocation of variable at DATASEG+%x " "(address %p, currently %x) into segment %s\n", r.reloc.offset, ptr, (int)*ptr, segment[r.reloc.type]); #endif switch (r.reloc.type) { case OLD_FLAT_RELOC_TYPE_TEXT: *ptr += current->mm->start_code; break; case OLD_FLAT_RELOC_TYPE_DATA: *ptr += current->mm->start_data; break; case OLD_FLAT_RELOC_TYPE_BSS: *ptr += current->mm->end_data; break; default: printk("BINFMT_FLAT: Unknown relocation type=%x\n", r.reloc.type); break; } #ifdef DEBUG printk("Relocation became %x\n", (int)*ptr); #endif } /* * These are the functions used to load flat style executables and shared * libraries. There is no binary dependent code anywhere else. */ inline int do_load_flat_binary(struct linux_binprm * bprm, struct pt_regs * regs) { struct flat_hdr * hdr; struct file *file; unsigned long textpos = 0, datapos = 0, result; unsigned long text_len, data_len, bss_len, stack_len, flags; unsigned long memp = 0, memkasked = 0; /* for finding the brk area */ unsigned long extra; unsigned long p = bprm->p; unsigned long *reloc = 0, *rp; int rev, i, relocs = 0; current->personality = PER_LINUX; file = current->files->fd[open_inode(bprm->inode, O_RDONLY)]; DBG_FLT("BINFMT_FLAT: Loading file: %x\n", (int)file); #ifdef DEBUG show_free_areas(); #endif hdr = (struct flat_hdr*)bprm->buf; if (flush_old_exec(bprm)) { printk("unable to flush\n"); return -ENOMEM; } text_len = ntohl(hdr->data_start); data_len = ntohl(hdr->data_end) - ntohl(hdr->data_start); bss_len = ntohl(hdr->bss_end) - ntohl(hdr->data_end); stack_len = ntohl(hdr->stack_size); relocs = ntohl(hdr->reloc_count); flags = ntohl(hdr->flags); rev = ntohl(hdr->rev); if (strncmp(hdr->magic, "bFLT", 4) || (rev != FLAT_VERSION && rev != OLD_FLAT_VERSION)) { printk("BINFMT_FLAT: bad magic/rev (%d, need %d)\n", rev, (int) FLAT_VERSION); return -ENOEXEC; } /* * fix up the flags for the older format, there were all kinds * of endian hacks, this only works for the simple cases */ if (rev == OLD_FLAT_VERSION && flags) flags = FLAT_FLAG_RAM; #ifndef CONFIG_BINFMT_ZFLAT if (flags & FLAT_FLAG_GZIP) { printk("Support for ZFLAT executables is not enabled.\n"); return -ENOEXEC; } #endif /* Make room on stack for arguments & environment */ stack_len += strlen(bprm->filename) + 1; stack_len += stringarraylen(bprm->envc, bprm->envp); stack_len += stringarraylen(bprm->argc, bprm->argv); /* * there are a couple of cases here, the seperate code/data * case, and then the fully copied to RAM case which lumps * it all together. */ if ((flags & (FLAT_FLAG_RAM|FLAT_FLAG_GZIP)) == 0) { /* * this should give us a ROM ptr, but if it doesn't we don't * really care */ DBG_FLT("BINFMT_FLAT: ROM mapping of file (we hope)\n"); textpos = do_mmap(file, 0, text_len, PROT_READ | PROT_EXEC, 0, 0); if (textpos >= (unsigned long) -4096) return(textpos); extra = MAX(bss_len + stack_len, relocs * sizeof(unsigned long)), datapos = do_mmap(0, 0, data_len + extra, PROT_READ|PROT_WRITE|PROT_EXEC, 0, 0); if (datapos >= (unsigned long)-4096) { printk("Unable to allocate RAM for process data, errno %d\n", (int)-datapos); do_munmap(textpos, 0); return datapos; } DBG_FLT("BINFMT_FLAT: Allocated data+bss+stack (%d bytes): %x\n", data_len + bss_len + stack_len, datapos); result = read_exec(bprm->inode, ntohl(hdr->data_start), (char *)datapos, data_len + extra, 0); if (result >= (unsigned long)-4096) { printk("Unable to read data+bss, errno %d\n", (int)-result); do_munmap(textpos, 0); do_munmap(datapos, 0); return result; } reloc = (unsigned long *) (datapos+(ntohl(hdr->reloc_start)-text_len)); memp = datapos; memkasked = data_len + extra; } else { /* * calculate the extra space we need to map in */ extra = MAX(bss_len + stack_len, relocs * sizeof(unsigned long)), textpos = do_mmap(0, 0, text_len + data_len + extra, PROT_READ | PROT_EXEC | PROT_WRITE, 0, 0); if (textpos >= (unsigned long)-4096) return textpos; #ifdef CONFIG_BINFMT_ZFLAT /* * load it all in and treat it like a RAM load from now on */ if (flags & FLAT_FLAG_GZIP) { result = decompress_exec( bprm->inode, ntohl(hdr->entry), ((char *) textpos) + ntohl(hdr->entry), text_len + data_len + (relocs * sizeof(unsigned long)) - ntohl(hdr->entry), 0); if (result >= (unsigned long) -4096) { printk("Unable to decompress data+bss, errno %d\n", (int)-result); do_munmap(textpos, 0); return(result); } } else #endif { result = read_exec(bprm->inode, 0, (char *)textpos, text_len + data_len + extra, 0); if (result >= (unsigned long)-4096) { printk("Unable to read data+bss, errno %d\n", (int)-result); do_munmap(textpos, 0); return result; } } datapos = textpos + text_len; reloc = (unsigned long *) (textpos + ntohl(hdr->reloc_start)); memp = textpos; memkasked = text_len + data_len + extra; } DBG_FLT("BINFMT_FLAT: Allocated:\n" "code\t0x%x\tOx%x\n" "data\t0x%x\tOx%x\n" "bss\t-\tOx%x\n" "stack\t-\tOx%x\n", (int)textpos, (int)text_len, (int)datapos, (int)data_len, (int)bss_len, (int)stack_len); #ifdef DEBUG show_free_areas(); #endif current->mm->start_code = textpos + htonl(hdr->entry); current->mm->end_code = textpos + text_len; current->mm->start_data = datapos; current->mm->end_data = datapos + data_len; #ifdef NO_MM current->mm->brk = current->mm->start_brk = memp + ((memkasked + 3) & ~3); current->mm->end_brk = memp + ksize((void *) memp); #else current->mm->brk = datapos + data_len + bss_len; #endif /* * not sure if we should on do this for XIP ? * the coldfire code never did this and was plenty stable */ if (bprm->inode->i_sb->s_flags & MS_SYNCHRONOUS) { DBG_FLT("Retaining inode\n"); current->mm->executable = bprm->inode; bprm->inode->i_count++; } else current->mm->executable = 0; DBG_FLT("Load %s: TEXT=%x-%x DATA=%x-%x BSS=%x-%x\n", bprm->argv[0], (int) current->mm->start_code, (int) current->mm->end_code, (int) current->mm->start_data, (int) current->mm->end_data, (int) current->mm->end_data, (int) current->mm->brk); text_len -= sizeof(struct flat_hdr); /* the real code len */ /* * We've got two different sections of relocation entries. * The first is the GOT which resides at the begining of the data segment * and is terminated with a -1. This one can be relocated in place. * The second is the extra relocation entries tacked after the image's * data segment. These require a little more processing as the entry is * really an offset into the image which contains an offset into the * image. */ if (flags & FLAT_FLAG_GOTPIC) { for (rp = (unsigned long *)datapos; *rp != 0xffffffff; rp++) *rp = calc_reloc(ntohl(*rp), text_len); } /* * Now run through the relocation entries. * We've got to be careful here as C++ produces relocatable zero * entries in the constructor and destructor tables which are then * tested for being not zero (which will always occur unless we're * based from address zero). This causes an endless loop as __start * is at zero. The solution used is to not relocate zero addresses. * This has the negative side effect of not allowing a global data * reference to be statically initialised to _stext (I've moved * __start to address 4 so that is okay). */ if (rev > OLD_FLAT_VERSION) { for (i=0; i < relocs; i++) { rp = (unsigned long *) calc_reloc(ntohl(reloc[i]), text_len); if (*rp != 0) *rp = calc_reloc(ntohl(*rp), text_len); /* And relocate it */ } } else { for (i=0; i < relocs; i++) old_reloc(ntohl(reloc[i])); } /* * we have to clear the BSS/STACK after the relocation otherwise * we trash the relocations that lived in that space in the filesystem */ memset((char *) (datapos + data_len), '\0', bss_len + stack_len); current->mm->rss = 0; current->suid = current->euid = current->fsuid = bprm->e_uid; current->sgid = current->egid = current->fsgid = bprm->e_gid; current->flags &= ~PF_FORKNOEXEC; if (current->exec_domain && current->exec_domain->use_count) (*current->exec_domain->use_count)--; if (current->binfmt && current->binfmt->use_count) (*current->binfmt->use_count)--; current->exec_domain = lookup_exec_domain(current->personality); current->binfmt = &flat_format; if (current->exec_domain && current->exec_domain->use_count) (*current->exec_domain->use_count)++; if (current->binfmt && current->binfmt->use_count) (*current->binfmt->use_count)++; #ifndef NO_MM /*set_brk(current->mm->start_brk, current->mm->brk);*/ #endif p = ((datapos + data_len + bss_len + stack_len + 0x3) & ~0x3) - 4; DBG_FLT("p=%x\n", (int)p); p = putstringarray(p, 1, &bprm->filename); DBG_FLT("p(filename)=%x\n", (int)p); p = putstringarray(p, bprm->envc, bprm->envp); DBG_FLT("p(envp)=%x\n", (int)p); p = putstringarray(p, bprm->argc, bprm->argv); DBG_FLT("p(argv)=%x\n", (int)p); p = create_flat_tables(p, bprm); DBG_FLT("p(create_flat_tables)=%x\n", (int)p); DBG_FLT("arg_start = %x\n", (int)current->mm->arg_start); DBG_FLT("arg_end = %x\n", (int)current->mm->arg_end); DBG_FLT("env_start = %x\n", (int)current->mm->env_start); DBG_FLT("env_end = %x\n", (int)current->mm->env_end); current->mm->start_stack = p; #ifdef DEBUG show_free_areas(); #endif flush_cache_mm(current->mm); start_thread(regs, current->mm->start_code, current->mm->start_data, p); if (current->flags & PF_PTRACED) send_sig(SIGTRAP, current, 0); return 0; } static int load_flat_binary(struct linux_binprm * bprm, struct pt_regs * regs) { int retval; MOD_INC_USE_COUNT; retval = do_load_flat_binary(bprm, regs); MOD_DEC_USE_COUNT; return retval; } int init_flat_binfmt(void) { return register_binfmt(&flat_format); } #ifdef MODULE int init_module(void) { return init_flat_binfmt(); } void cleanup_module( void) { unregister_binfmt(&flat_format); } #endif