Skip to content
Snippets Groups Projects
binfmt_misc.c 15.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *  binfmt_misc.c
     *
     *  Copyright (C) 1997 Richard Gnther
     *
     *  binfmt_misc detects binaries via a magic or filename extension and invokes
     *  a specified wrapper. This should obsolete binfmt_java, binfmt_em86 and
     *  binfmt_mz.
     *
     *  1997-04-25 first version
     *  [...]
     *  1997-05-19 cleanup
     *  1997-06-26 hpa: pass the real filename rather than argv[0]
     *  1997-06-30 minor cleanup
     *  1997-08-09 removed extension stripping, locking cleanup
     *  2001-02-28 AV: rewritten into something that resembles C. Original didn't.
     */
    
    #include <linux/module.h>
    #include <linux/init.h>
    
    #include <linux/binfmts.h>
    #include <linux/slab.h>
    #include <linux/ctype.h>
    #include <linux/file.h>
    #include <linux/pagemap.h>
    #include <linux/namei.h>
    #include <linux/mount.h>
    #include <linux/syscalls.h>
    
    #include <asm/uaccess.h>
    
    enum {
    	VERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */
    };
    
    static LIST_HEAD(entries);
    static int enabled = 1;
    
    enum {Enabled, Magic};
    #define MISC_FMT_PRESERVE_ARGV0 (1<<31)
    #define MISC_FMT_OPEN_BINARY (1<<30)
    #define MISC_FMT_CREDENTIALS (1<<29)
    
    typedef struct {
    	struct list_head list;
    	unsigned long flags;		/* type, status, etc. */
    	int offset;			/* offset of magic */
    	int size;			/* size of magic/mask */
    	char *magic;			/* magic or filename extension */
    	char *mask;			/* mask, NULL for exact match */
    	char *interpreter;		/* filename of interpreter */
    	char *name;
    	struct dentry *dentry;
    } Node;
    
    static DEFINE_RWLOCK(entries_lock);
    
    static struct file_system_type bm_fs_type;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static struct vfsmount *bm_mnt;
    static int entry_count;
    
    /* 
     * Check if we support the binfmt
     * if we do, return the node, else NULL
     * locking is done in load_misc_binary
     */
    static Node *check_file(struct linux_binprm *bprm)
    {
    	char *p = strrchr(bprm->interp, '.');
    	struct list_head *l;
    
    	list_for_each(l, &entries) {
    		Node *e = list_entry(l, Node, list);
    		char *s;
    		int j;
    
    		if (!test_bit(Enabled, &e->flags))
    			continue;
    
    		if (!test_bit(Magic, &e->flags)) {
    			if (p && !strcmp(e->magic, p + 1))
    				return e;
    			continue;
    		}
    
    		s = bprm->buf + e->offset;
    		if (e->mask) {
    			for (j = 0; j < e->size; j++)
    				if ((*s++ ^ e->magic[j]) & e->mask[j])
    					break;
    		} else {
    			for (j = 0; j < e->size; j++)
    				if ((*s++ ^ e->magic[j]))
    					break;
    		}
    		if (j == e->size)
    			return e;
    	}
    	return NULL;
    }
    
    /*
     * the loader itself
     */
    static int load_misc_binary(struct linux_binprm *bprm, struct pt_regs *regs)
    {
    	Node *fmt;
    	struct file * interp_file = NULL;
    	char iname[BINPRM_BUF_SIZE];
    	char *iname_addr = iname;
    	int retval;
    	int fd_binary = -1;
    	struct files_struct *files = NULL;
    
    	retval = -ENOEXEC;
    	if (!enabled)
    		goto _ret;
    
    	/* to keep locking time low, we copy the interpreter string */
    	read_lock(&entries_lock);
    	fmt = check_file(bprm);
    	if (fmt)
    		strlcpy(iname, fmt->interpreter, BINPRM_BUF_SIZE);
    	read_unlock(&entries_lock);
    	if (!fmt)
    		goto _ret;
    
    	if (!(fmt->flags & MISC_FMT_PRESERVE_ARGV0)) {
    		remove_arg_zero(bprm);
    	}
    
    	if (fmt->flags & MISC_FMT_OPEN_BINARY) {
    
    		files = current->files;
    		retval = unshare_files();
    		if (retval < 0)
    			goto _ret;
    		if (files == current->files) {
    			put_files_struct(files);
    			files = NULL;
    		}
    		/* if the binary should be opened on behalf of the
    		 * interpreter than keep it open and assign descriptor
    		 * to it */
     		fd_binary = get_unused_fd();
     		if (fd_binary < 0) {
     			retval = fd_binary;
     			goto _unshare;
     		}
     		fd_install(fd_binary, bprm->file);
    
    		/* if the binary is not readable than enforce mm->dumpable=0
    		   regardless of the interpreter's permissions */
    
    		if (file_permission(bprm->file, MAY_READ))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			bprm->interp_flags |= BINPRM_FLAGS_ENFORCE_NONDUMP;
    
    		allow_write_access(bprm->file);
    		bprm->file = NULL;
    
    		/* mark the bprm that fd should be passed to interp */
    		bprm->interp_flags |= BINPRM_FLAGS_EXECFD;
    		bprm->interp_data = fd_binary;
    
     	} else {
     		allow_write_access(bprm->file);
     		fput(bprm->file);
     		bprm->file = NULL;
     	}
    	/* make argv[1] be the path to the binary */
    	retval = copy_strings_kernel (1, &bprm->interp, bprm);
    	if (retval < 0)
    		goto _error;
    	bprm->argc++;
    
    	/* add the interp as argv[0] */
    	retval = copy_strings_kernel (1, &iname_addr, bprm);
    	if (retval < 0)
    		goto _error;
    	bprm->argc ++;
    
    	bprm->interp = iname;	/* for binfmt_script */
    
    	interp_file = open_exec (iname);
    	retval = PTR_ERR (interp_file);
    	if (IS_ERR (interp_file))
    		goto _error;
    
    	bprm->file = interp_file;
    	if (fmt->flags & MISC_FMT_CREDENTIALS) {
    		/*
    		 * No need to call prepare_binprm(), it's already been
    		 * done.  bprm->buf is stale, update from interp_file.
    		 */
    		memset(bprm->buf, 0, BINPRM_BUF_SIZE);
    		retval = kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
    	} else
    		retval = prepare_binprm (bprm);
    
    	if (retval < 0)
    		goto _error;
    
    	retval = search_binary_handler (bprm, regs);
    	if (retval < 0)
    		goto _error;
    
    	if (files) {
    		put_files_struct(files);
    		files = NULL;
    	}
    _ret:
    	return retval;
    _error:
    	if (fd_binary > 0)
    		sys_close(fd_binary);
    	bprm->interp_flags = 0;
    	bprm->interp_data = 0;
    _unshare:
    
    	if (files)
    		reset_files_struct(current, files);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	goto _ret;
    }
    
    /* Command parsers */
    
    /*
     * parses and copies one argument enclosed in del from *sp to *dp,
     * recognising the \x special.
     * returns pointer to the copied argument or NULL in case of an
     * error (and sets err) or null argument length.
     */
    static char *scanarg(char *s, char del)
    {
    	char c;
    
    	while ((c = *s++) != del) {
    		if (c == '\\' && *s == 'x') {
    			s++;
    			if (!isxdigit(*s++))
    				return NULL;
    			if (!isxdigit(*s++))
    				return NULL;
    		}
    	}
    	return s;
    }
    
    static int unquote(char *from)
    {
    	char c = 0, *s = from, *p = from;
    
    	while ((c = *s++) != '\0') {
    		if (c == '\\' && *s == 'x') {
    			s++;
    			c = toupper(*s++);
    			*p = (c - (isdigit(c) ? '0' : 'A' - 10)) << 4;
    			c = toupper(*s++);
    			*p++ |= c - (isdigit(c) ? '0' : 'A' - 10);
    			continue;
    		}
    		*p++ = c;
    	}
    	return p - from;
    }
    
    
    static char * check_special_flags (char * sfs, Node * e)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	char * p = sfs;
    	int cont = 1;
    
    	/* special flags */
    	while (cont) {
    		switch (*p) {
    			case 'P':
    				p++;
    				e->flags |= MISC_FMT_PRESERVE_ARGV0;
    				break;
    			case 'O':
    				p++;
    				e->flags |= MISC_FMT_OPEN_BINARY;
    				break;
    			case 'C':
    				p++;
    				/* this flags also implies the
    				   open-binary flag */
    				e->flags |= (MISC_FMT_CREDENTIALS |
    						MISC_FMT_OPEN_BINARY);
    				break;
    			default:
    				cont = 0;
    		}
    	}
    
    	return p;
    }
    /*
     * This registers a new binary format, it recognises the syntax
     * ':name:type:offset:magic:mask:interpreter:flags'
     * where the ':' is the IFS, that can be chosen with the first char
     */
    static Node *create_entry(const char __user *buffer, size_t count)
    {
    	Node *e;
    	int memsize, err;
    	char *buf, *p;
    	char del;
    
    	/* some sanity checks */
    	err = -EINVAL;
    	if ((count < 11) || (count > 256))
    		goto out;
    
    	err = -ENOMEM;
    	memsize = sizeof(Node) + count + 8;
    	e = (Node *) kmalloc(memsize, GFP_USER);
    	if (!e)
    		goto out;
    
    	p = buf = (char *)e + sizeof(Node);
    
    	memset(e, 0, sizeof(Node));
    	if (copy_from_user(buf, buffer, count))
    		goto Efault;
    
    	del = *p++;	/* delimeter */
    
    	memset(buf+count, del, 8);
    
    	e->name = p;
    	p = strchr(p, del);
    	if (!p)
    		goto Einval;
    	*p++ = '\0';
    	if (!e->name[0] ||
    	    !strcmp(e->name, ".") ||
    	    !strcmp(e->name, "..") ||
    	    strchr(e->name, '/'))
    		goto Einval;
    	switch (*p++) {
    		case 'E': e->flags = 1<<Enabled; break;
    		case 'M': e->flags = (1<<Enabled) | (1<<Magic); break;
    		default: goto Einval;
    	}
    	if (*p++ != del)
    		goto Einval;
    	if (test_bit(Magic, &e->flags)) {
    		char *s = strchr(p, del);
    		if (!s)
    			goto Einval;
    		*s++ = '\0';
    		e->offset = simple_strtoul(p, &p, 10);
    		if (*p++)
    			goto Einval;
    		e->magic = p;
    		p = scanarg(p, del);
    		if (!p)
    			goto Einval;
    		p[-1] = '\0';
    		if (!e->magic[0])
    			goto Einval;
    		e->mask = p;
    		p = scanarg(p, del);
    		if (!p)
    			goto Einval;
    		p[-1] = '\0';
    		if (!e->mask[0])
    			e->mask = NULL;
    		e->size = unquote(e->magic);
    		if (e->mask && unquote(e->mask) != e->size)
    			goto Einval;
    		if (e->size + e->offset > BINPRM_BUF_SIZE)
    			goto Einval;
    	} else {
    		p = strchr(p, del);
    		if (!p)
    			goto Einval;
    		*p++ = '\0';
    		e->magic = p;
    		p = strchr(p, del);
    		if (!p)
    			goto Einval;
    		*p++ = '\0';
    		if (!e->magic[0] || strchr(e->magic, '/'))
    			goto Einval;
    		p = strchr(p, del);
    		if (!p)
    			goto Einval;
    		*p++ = '\0';
    	}
    	e->interpreter = p;
    	p = strchr(p, del);
    	if (!p)
    		goto Einval;
    	*p++ = '\0';
    	if (!e->interpreter[0])
    		goto Einval;
    
    
    	p = check_special_flags (p, e);
    
    	if (*p == '\n')
    		p++;
    	if (p != buf + count)
    		goto Einval;
    	return e;
    
    out:
    	return ERR_PTR(err);
    
    Efault:
    	kfree(e);
    	return ERR_PTR(-EFAULT);
    Einval:
    	kfree(e);
    	return ERR_PTR(-EINVAL);
    }
    
    /*
     * Set status of entry/binfmt_misc:
     * '1' enables, '0' disables and '-1' clears entry/binfmt_misc
     */
    static int parse_command(const char __user *buffer, size_t count)
    {
    	char s[4];
    
    	if (!count)
    		return 0;
    	if (count > 3)
    		return -EINVAL;
    	if (copy_from_user(s, buffer, count))
    		return -EFAULT;
    	if (s[count-1] == '\n')
    		count--;
    	if (count == 1 && s[0] == '0')
    		return 1;
    	if (count == 1 && s[0] == '1')
    		return 2;
    	if (count == 2 && s[0] == '-' && s[1] == '1')
    		return 3;
    	return -EINVAL;
    }
    
    /* generic stuff */
    
    static void entry_status(Node *e, char *page)
    {
    	char *dp;
    	char *status = "disabled";
    	const char * flags = "flags: ";
    
    	if (test_bit(Enabled, &e->flags))
    		status = "enabled";
    
    	if (!VERBOSE_STATUS) {
    		sprintf(page, "%s\n", status);
    		return;
    	}
    
    	sprintf(page, "%s\ninterpreter %s\n", status, e->interpreter);
    	dp = page + strlen(page);
    
    	/* print the special flags */
    	sprintf (dp, "%s", flags);
    	dp += strlen (flags);
    	if (e->flags & MISC_FMT_PRESERVE_ARGV0) {
    		*dp ++ = 'P';
    	}
    	if (e->flags & MISC_FMT_OPEN_BINARY) {
    		*dp ++ = 'O';
    	}
    	if (e->flags & MISC_FMT_CREDENTIALS) {
    		*dp ++ = 'C';
    	}
    	*dp ++ = '\n';
    
    
    	if (!test_bit(Magic, &e->flags)) {
    		sprintf(dp, "extension .%s\n", e->magic);
    	} else {
    		int i;
    
    		sprintf(dp, "offset %i\nmagic ", e->offset);
    		dp = page + strlen(page);
    		for (i = 0; i < e->size; i++) {
    			sprintf(dp, "%02x", 0xff & (int) (e->magic[i]));
    			dp += 2;
    		}
    		if (e->mask) {
    			sprintf(dp, "\nmask ");
    			dp += 6;
    			for (i = 0; i < e->size; i++) {
    				sprintf(dp, "%02x", 0xff & (int) (e->mask[i]));
    				dp += 2;
    			}
    		}
    		*dp++ = '\n';
    		*dp = '\0';
    	}
    }
    
    static struct inode *bm_get_inode(struct super_block *sb, int mode)
    {
    	struct inode * inode = new_inode(sb);
    
    	if (inode) {
    		inode->i_mode = mode;
    		inode->i_uid = 0;
    		inode->i_gid = 0;
    		inode->i_blocks = 0;
    		inode->i_atime = inode->i_mtime = inode->i_ctime =
    			current_fs_time(inode->i_sb);
    	}
    	return inode;
    }
    
    static void bm_clear_inode(struct inode *inode)
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static void kill_node(Node *e)
    {
    	struct dentry *dentry;
    
    	write_lock(&entries_lock);
    	dentry = e->dentry;
    	if (dentry) {
    		list_del_init(&e->list);
    		e->dentry = NULL;
    	}
    	write_unlock(&entries_lock);
    
    	if (dentry) {
    		dentry->d_inode->i_nlink--;
    		d_drop(dentry);
    		dput(dentry);
    		simple_release_fs(&bm_mnt, &entry_count);
    	}
    }
    
    /* /<entry> */
    
    static ssize_t
    bm_entry_read(struct file * file, char __user * buf, size_t nbytes, loff_t *ppos)
    {
    
    	Node *e = file->f_dentry->d_inode->i_private;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	loff_t pos = *ppos;
    	ssize_t res;
    	char *page;
    	int len;
    
    	if (!(page = (char*) __get_free_page(GFP_KERNEL)))
    		return -ENOMEM;
    
    	entry_status(e, page);
    	len = strlen(page);
    
    	res = -EINVAL;
    	if (pos < 0)
    		goto out;
    	res = 0;
    	if (pos >= len)
    		goto out;
    	if (len < pos + nbytes)
    		nbytes = len - pos;
    	res = -EFAULT;
    	if (copy_to_user(buf, page + pos, nbytes))
    		goto out;
    	*ppos = pos + nbytes;
    	res = nbytes;
    out:
    	free_page((unsigned long) page);
    	return res;
    }
    
    static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
    				size_t count, loff_t *ppos)
    {
    	struct dentry *root;
    
    	Node *e = file->f_dentry->d_inode->i_private;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int res = parse_command(buffer, count);
    
    	switch (res) {
    		case 1: clear_bit(Enabled, &e->flags);
    			break;
    		case 2: set_bit(Enabled, &e->flags);
    			break;
    		case 3: root = dget(file->f_vfsmnt->mnt_sb->s_root);
    
    			mutex_lock(&root->d_inode->i_mutex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    			kill_node(e);
    
    
    			mutex_unlock(&root->d_inode->i_mutex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			dput(root);
    			break;
    		default: return res;
    	}
    	return count;
    }
    
    
    static const struct file_operations bm_entry_operations = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.read		= bm_entry_read,
    	.write		= bm_entry_write,
    };
    
    /* /register */
    
    static ssize_t bm_register_write(struct file *file, const char __user *buffer,
    			       size_t count, loff_t *ppos)
    {
    	Node *e;
    	struct inode *inode;
    	struct dentry *root, *dentry;
    	struct super_block *sb = file->f_vfsmnt->mnt_sb;
    	int err = 0;
    
    	e = create_entry(buffer, count);
    
    	if (IS_ERR(e))
    		return PTR_ERR(e);
    
    	root = dget(sb->s_root);
    
    	mutex_lock(&root->d_inode->i_mutex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dentry = lookup_one_len(e->name, root, strlen(e->name));
    	err = PTR_ERR(dentry);
    	if (IS_ERR(dentry))
    		goto out;
    
    	err = -EEXIST;
    	if (dentry->d_inode)
    		goto out2;
    
    	inode = bm_get_inode(sb, S_IFREG | 0644);
    
    	err = -ENOMEM;
    	if (!inode)
    		goto out2;
    
    
    	err = simple_pin_fs(&bm_fs_type, &bm_mnt, &entry_count);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (err) {
    		iput(inode);
    		inode = NULL;
    		goto out2;
    	}
    
    	e->dentry = dget(dentry);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	inode->i_fop = &bm_entry_operations;
    
    	d_instantiate(dentry, inode);
    	write_lock(&entries_lock);
    	list_add(&e->list, &entries);
    	write_unlock(&entries_lock);
    
    	err = 0;
    out2:
    	dput(dentry);
    out:
    
    	mutex_unlock(&root->d_inode->i_mutex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dput(root);
    
    	if (err) {
    		kfree(e);
    		return -EINVAL;
    	}
    	return count;
    }
    
    
    static const struct file_operations bm_register_operations = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.write		= bm_register_write,
    };
    
    /* /status */
    
    static ssize_t
    bm_status_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
    {
    	char *s = enabled ? "enabled" : "disabled";
    	int len = strlen(s);
    	loff_t pos = *ppos;
    
    	if (pos < 0)
    		return -EINVAL;
    	if (pos >= len)
    		return 0;
    	if (len < pos + nbytes)
    		nbytes = len - pos;
    	if (copy_to_user(buf, s + pos, nbytes))
    		return -EFAULT;
    	*ppos = pos + nbytes;
    	return nbytes;
    }
    
    static ssize_t bm_status_write(struct file * file, const char __user * buffer,
    		size_t count, loff_t *ppos)
    {
    	int res = parse_command(buffer, count);
    	struct dentry *root;
    
    	switch (res) {
    		case 1: enabled = 0; break;
    		case 2: enabled = 1; break;
    		case 3: root = dget(file->f_vfsmnt->mnt_sb->s_root);
    
    			mutex_lock(&root->d_inode->i_mutex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    			while (!list_empty(&entries))
    				kill_node(list_entry(entries.next, Node, list));
    
    
    			mutex_unlock(&root->d_inode->i_mutex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			dput(root);
    		default: return res;
    	}
    	return count;
    }
    
    
    static const struct file_operations bm_status_operations = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.read		= bm_status_read,
    	.write		= bm_status_write,
    };
    
    /* Superblock handling */
    
    static struct super_operations s_ops = {
    	.statfs		= simple_statfs,
    	.clear_inode	= bm_clear_inode,
    };
    
    static int bm_fill_super(struct super_block * sb, void * data, int silent)
    {
    	static struct tree_descr bm_files[] = {
    		[1] = {"status", &bm_status_operations, S_IWUSR|S_IRUGO},
    		[2] = {"register", &bm_register_operations, S_IWUSR},
    		/* last one */ {""}
    	};
    	int err = simple_fill_super(sb, 0x42494e4d, bm_files);
    	if (!err)
    		sb->s_op = &s_ops;
    	return err;
    }
    
    
    static int bm_get_sb(struct file_system_type *fs_type,
    	int flags, const char *dev_name, void *data, struct vfsmount *mnt)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	return get_sb_single(fs_type, flags, data, bm_fill_super, mnt);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static struct linux_binfmt misc_format = {
    	.module = THIS_MODULE,
    	.load_binary = load_misc_binary,
    };
    
    static struct file_system_type bm_fs_type = {
    	.owner		= THIS_MODULE,
    	.name		= "binfmt_misc",
    	.get_sb		= bm_get_sb,
    	.kill_sb	= kill_litter_super,
    };
    
    static int __init init_misc_binfmt(void)
    {
    	int err = register_filesystem(&bm_fs_type);
    	if (!err) {
    		err = register_binfmt(&misc_format);
    		if (err)
    			unregister_filesystem(&bm_fs_type);
    	}
    	return err;
    }
    
    static void __exit exit_misc_binfmt(void)
    {
    	unregister_binfmt(&misc_format);
    	unregister_filesystem(&bm_fs_type);
    }
    
    core_initcall(init_misc_binfmt);
    module_exit(exit_misc_binfmt);
    MODULE_LICENSE("GPL");