[PATCH] binfmt_misc: allow selecting the interpreter based on xattr keywords

From: Josh Max
Date: Mon Aug 22 2016 - 00:17:15 EST


This patch allows binfmt_misc to select the interpeter for arbitrary
binaries by comparing a specified registered keyword with the value
of a specified binary's extended attribute (user.binfmt.interp),
and then launching the program with the registered interpreter.

This is useful when wanting to launch a collection of binaries under
the same interpreter, even when they do not necessarily share a common
extension or magic bits, or when their magic conflics with the operation
of binfmt_elf. Some examples of its use would be to launch some executables
of various different architectures in a directory, or for running some
native binaries under a sandbox (like firejail) automatically during their
launch.

Signed-off-by: Josh Max <JMax@xxxxxxxxxxxxxxxxxxx>
---
fs/binfmt_misc.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 47 insertions(+), 4 deletions(-)

diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index 6103a63..86d93c7 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -24,6 +24,7 @@
#include <linux/mount.h>
#include <linux/syscalls.h>
#include <linux/fs.h>
+#include <linux/xattr.h>
#include <linux/uaccess.h>

#include "internal.h"
@@ -41,12 +42,17 @@ enum {
static LIST_HEAD(entries);
static int enabled = 1;

-enum {Enabled, Magic};
+enum {Enabled, Magic, Keyword};
#define MISC_FMT_PRESERVE_ARGV0 (1 << 31)
#define MISC_FMT_OPEN_BINARY (1 << 30)
#define MISC_FMT_CREDENTIALS (1 << 29)
#define MISC_FMT_OPEN_FILE (1 << 28)

+#define XATTR_BINFMT_PREFIX XATTR_USER_PREFIX "binfmt."
+#define XATTR_BINFMT_INTERPRETER_SUFFIX "interp"
+#define XATTR_NAME_BINFMT (XATTR_BINFMT_PREFIX XATTR_BINFMT_INTERPRETER_SUFFIX)
+#define XATTR_VALUE_MAX_LENGTH 128
+
typedef struct {
struct list_head list;
unsigned long flags; /* type, status, etc. */
@@ -61,6 +67,7 @@ typedef struct {
} Node;

static DEFINE_RWLOCK(entries_lock);
+static int get_xattr_interp_keyword(struct file *file, char *buf, size_t count);
static struct file_system_type bm_fs_type;
static struct vfsmount *bm_mnt;
static int entry_count;
@@ -87,6 +94,8 @@ static int entry_count;
*/
static Node *check_file(struct linux_binprm *bprm)
{
+ char k[XATTR_VALUE_MAX_LENGTH];
+ int k_len = get_xattr_interp_keyword(bprm->file, k, sizeof(k)-1);
char *p = strrchr(bprm->interp, '.');
struct list_head *l;

@@ -100,6 +109,16 @@ static Node *check_file(struct linux_binprm *bprm)
if (!test_bit(Enabled, &e->flags))
continue;

+ /* Do matching based on xattrs keyword */
+ if (test_bit(Keyword, &e->flags)) {
+ if (k_len <= 0)
+ continue;
+ k[k_len] = 0;
+ if (!strcmp(e->magic, k))
+ return e;
+ continue;
+ }
+
/* Do matching based on extension if applicable. */
if (!test_bit(Magic, &e->flags)) {
if (p && !strcmp(e->magic, p + 1))
@@ -309,6 +328,20 @@ static char *check_special_flags(char *sfs, Node *e)
}

/*
+ * Check to see if the filesystem supports xattrs
+ * and grab the value so it can be checked against
+ * the list of keywords in binfmt_misc for a match
+ */
+static int get_xattr_interp_keyword(struct file *file, char *buf, size_t count)
+{
+
+ if (unlikely(!file->f_inode->i_op->getxattr))
+ return -ENOENT;
+ return file->f_inode->i_op->getxattr(file->f_path.dentry, file->f_inode,
+ XATTR_NAME_BINFMT, buf, count);
+}
+
+/*
* 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
@@ -366,6 +399,10 @@ static Node *create_entry(const char __user *buffer, size_t count)
pr_debug("register: type: E (extension)\n");
e->flags = 1 << Enabled;
break;
+ case 'K':
+ pr_debug("register: type: K (xattrs keyword)\n");
+ e->flags = (1 << Enabled) | (1 << Keyword);
+ break;
case 'M':
pr_debug("register: type: M (magic)\n");
e->flags = (1 << Enabled) | (1 << Magic);
@@ -453,7 +490,7 @@ static Node *create_entry(const char __user *buffer, size_t count)
}
}
} else {
- /* Handle the 'E' (extension) format. */
+ /* Handle the 'E' (extension) and 'K' (keyword) format. */

/* Skip the 'offset' field. */
p = strchr(p, del);
@@ -469,7 +506,10 @@ static Node *create_entry(const char __user *buffer, size_t count)
*p++ = '\0';
if (!e->magic[0] || strchr(e->magic, '/'))
goto einval;
- pr_debug("register: extension: {%s}\n", e->magic);
+ if (test_bit(Keyword, &e->flags))
+ pr_debug("register: keyword: {%s}\n", e->magic);
+ else
+ pr_debug("register: extension: {%s}\n", e->magic);

/* Skip the 'mask' field. */
p = strchr(p, del);
@@ -563,7 +603,10 @@ static void entry_status(Node *e, char *page)
*dp++ = '\n';

if (!test_bit(Magic, &e->flags)) {
- sprintf(dp, "extension .%s\n", e->magic);
+ if (test_bit(Keyword, &e->flags))
+ sprintf(dp, "keyword %s\n", e->magic);
+ else
+ sprintf(dp, "extension .%s\n", e->magic);
} else {
dp += sprintf(dp, "offset %i\nmagic ", e->offset);
dp = bin2hex(dp, e->magic, e->size);
--
2.8.1