[RFC] LSM generic ioctl permissions patch (1/1)

From: Lorenzo Hernandez Garcia-Hierro (lorenzohgh@private)
Date: Tue Nov 01 2005 - 13:00:03 PST


This patch implements general ioctl permissions defined by LSM that
can be mapped by individual security modules like SELinux to their own
module-specific permission checks. Filesystem-specific ioctl commands are
mapped to generic ioctl permissions, and then let the security modules like
SELinux map the generic ioctl permissions to their own security module-specific
permission checks. If a capability is assigned to the ioctl command, there's an
additional task_has_capability() check.

Currently it's designed and implemented as follows:

 1) Each fs or driver provides a table mapping its ioctl commands to
    generic ioctl permissions defined by LSM framework, and a capability
    if needed.
 2) Each fs or driver looks up the generic ioctl permission from this
    table (using a common helper function defined in fs/ioctl_perm.c) and
    then calls new security_inode_checkioctl() LSM hook to allow the
    security module to check that generic permission prior to performing
    ioctl operations. A capability may be assigned to each mapped ioctl
    command for later check within the LSM hook.
 3) security_inode_checkioctl() LSM hook checks if a capability parameter
    is present. If positive, then calls task_has_capability() to check for
    such capability to be granted or denied to the task. If not present,
    check is ignored.
 4) Filesystem and driver code can be gradually instrumented in this
    manner to provide such controls over an ever larger set of ioctls.

  static struct ioctl_perm foofs_ioctl_perm[] =
  {
          { FOOFS_IOC_GETFLAGS, SECURITY_IOCTL_READ, -1 },
          { FOOFS_IOC_SETFLAGS, SECURITY_IOCTL_WRITE, -1 }
  };

Above is an example of the ioctl commands to generic ioctl permissions
mapping, as well as the capabilities assigned for each command (-1 for
no capability check at all).

The generic ioctl permissions defined within the LSM framework are:
	Permission		Type/Access	Mode
	---------------------------------------------------------
	SECURITY_IOCTL_READ		read		unprivileged
	SECURITY_IOCTL_WRITE		write		unprivileged
	SECURITY_IOCTL_READPRIV		read		privileged
	SECURITY_IOCTL_WRITEPRIV	write		privileged

Stephen Smalley provided great guidance, help and suggestions during the
development and James Morris suggested changes prior to release. 

---

 fs/Makefile                                  |    2 -
 fs/ext2/ioctl.c                              |   22 ++++++++++++
 fs/ext3/ioctl.c                              |   29 ++++++++++++++++
 fs/ioctl_perm.c                              |   47 +++++++++++++++++++++++++++
 include/linux/ioctl_perm.h                   |   40 ++++++++++++++++++++++
 include/linux/security.h                     |   14 ++++++++
 security/dummy.c                             |    6 +++
 security/selinux/hooks.c                     |   40 +++++++++++++++++-----
 security/selinux/include/av_perm_to_string.h |    2 +
 security/selinux/include/av_permissions.h    |    2 +
 10 files changed, 193 insertions(+), 11 deletions(-)

diff -puN include/linux/security.h~lsm-checkioctl-hook include/linux/security.h
--- linux-2.6.14-rc4-mm1/include/linux/security.h~lsm-checkioctl-hook	2005-10-20 18:40:06.000000000 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/include/linux/security.h	2005-10-20 22:00:29.000000000 +0200
@@ -31,6 +31,7 @@
 #include <linux/msg.h>
 #include <linux/sched.h>
 #include <linux/key.h>
+#include <linux/ioctl_perm.h>
 
 struct ctl_table;
 
@@ -1119,6 +1120,7 @@ struct security_operations {
   	int (*inode_getsecurity)(struct inode *inode, const char *name, void *buffer, size_t size, int err);
   	int (*inode_setsecurity)(struct inode *inode, const char *name, const void *value, size_t size, int flags);
   	int (*inode_listsecurity)(struct inode *inode, char *buffer, size_t buffer_size);
+  	int (*inode_checkioctl)(struct inode *inode, unsigned int perm, int cap);
 
 	int (*file_permission) (struct file * file, int mask);
 	int (*file_alloc_security) (struct file * file);
@@ -1637,6 +1639,13 @@ static inline int security_inode_listsec
 	return security_ops->inode_listsecurity(inode, buffer, buffer_size);
 }
 
+static inline int security_inode_checkioctl(struct inode *inode, unsigned int perm, int cap)
+{
+	if (unlikely (IS_PRIVATE (inode)))
+		return 0;
+	return security_ops->inode_checkioctl(inode, perm, cap);
+}
+
 static inline int security_file_permission (struct file *file, int mask)
 {
 	return security_ops->file_permission (file, mask);
@@ -2273,6 +2282,11 @@ static inline int security_inode_listsec
 	return 0;
 }
 
+static inline int security_inode_checkioctl(struct inode *inode, unsigned int perm, int cap)
+{
+	return 0;
+}
+
 static inline int security_file_permission (struct file *file, int mask)
 {
 	return 0;
diff -puN /dev/null include/linux/ioctl_perm.h
--- /dev/null	2005-10-26 17:37:55.408526824 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/include/linux/ioctl_perm.h	2005-10-26 20:35:13.000000000 +0200
@@ -0,0 +1,40 @@
+/* ioctl_perm.h -- Common ioctl permissions interface
+ *
+ * Copyright 2005 Lorenzo Hernández García-Hierro <lorenzo@private>
+ * All Rights Reserved.
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ */
+
+#ifndef _LINUX_IOCTL_PERM_H
+#define _LINUX_IOCTL_PERM_H
+
+#include <linux/types.h>
+
+/* Define the generic data structure for ioctl permissions */
+struct ioctl_perm
+{
+    unsigned int cmd;
+    unsigned int perm;
+    int cap;
+};
+
+/*
+ * Define generic ioctl permissions managed that can be managed by the Linux
+ * Security Modules framework.
+ */
+
+#define SECURITY_IOCTL_READ          1 /* read unprivileged state of inode */
+#define SECURITY_IOCTL_WRITE         2 /* write unprivileged state of inode */
+#define SECURITY_IOCTL_READPRIV      4 /* read privileged state of inode */
+#define SECURITY_IOCTL_WRITEPRIV     8 /* write privileged state of inode */
+
+/* Exported functions */
+extern int ioctl_perm(unsigned int cmd, unsigned int *perm, int *cap,
+                      struct ioctl_perm *tab, size_t tabsize);
+
+#endif /* _LINUX_IOCTL_PERM_H */
diff -puN fs/ext2/ioctl.c~lsm-checkioctl-hook fs/ext2/ioctl.c
--- linux-2.6.14-rc4-mm1/fs/ext2/ioctl.c~lsm-checkioctl-hook	2005-10-20 18:40:06.000000000 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/fs/ext2/ioctl.c	2005-10-26 21:56:14.000000000 +0200
@@ -12,16 +12,38 @@
 #include <linux/sched.h>
 #include <asm/current.h>
 #include <asm/uaccess.h>
+#include <linux/security.h>
+#include <linux/ioctl_perm.h>
 
+static struct ioctl_perm ext2_ioctl_perm[] =
+{
+        { EXT2_IOC_GETFLAGS,   SECURITY_IOCTL_READ,  -1 },
+        { EXT2_IOC_SETFLAGS,   SECURITY_IOCTL_WRITE, -1 },
+        { EXT2_IOC_GETVERSION, SECURITY_IOCTL_READ,  -1 },
+        { EXT2_IOC_SETVERSION, SECURITY_IOCTL_WRITE, -1 }
+};
 
 int ext2_ioctl (struct inode * inode, struct file * filp, unsigned int cmd,
 		unsigned long arg)
 {
 	struct ext2_inode_info *ei = EXT2_I(inode);
 	unsigned int flags;
+	int error;
+	unsigned int perm;
+	int cap;
 
 	ext2_debug ("cmd = %u, arg = %lu\n", cmd, arg);
 
+        /* Map ioctl command to generic permission */
+	error = ioctl_perm(cmd, &perm, &cap, ext2_ioctl_perm, sizeof(ext2_ioctl_perm));
+	if (error)
+	       return error;
+
+        /* Check permission for inode */
+	error = security_inode_checkioctl(inode, perm, cap);
+	if (error)
+	       return error;
+
 	switch (cmd) {
 	case EXT2_IOC_GETFLAGS:
 		flags = ei->i_flags & EXT2_FL_USER_VISIBLE;
diff -puN security/dummy.c~lsm-checkioctl-hook security/dummy.c
--- linux-2.6.14-rc4-mm1/security/dummy.c~lsm-checkioctl-hook	2005-10-20 18:40:06.000000000 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/security/dummy.c	2005-10-20 21:59:14.000000000 +0200
@@ -392,6 +392,11 @@ static int dummy_inode_listsecurity(stru
 	return 0;
 }
 
+static int dummy_inode_checkioctl(struct inode *inode, unsigned int perm, int cap)
+{
+	return 0;
+}
+
 static int dummy_file_permission (struct file *file, int mask)
 {
 	return 0;
@@ -893,6 +898,7 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, inode_getsecurity);
 	set_to_dummy_if_null(ops, inode_setsecurity);
 	set_to_dummy_if_null(ops, inode_listsecurity);
+	set_to_dummy_if_null(ops, inode_checkioctl);
 	set_to_dummy_if_null(ops, file_permission);
 	set_to_dummy_if_null(ops, file_alloc_security);
 	set_to_dummy_if_null(ops, file_free_security);
diff -puN /dev/null fs/ioctl_perm.c
--- /dev/null	2005-10-26 17:37:55.408526824 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/fs/ioctl_perm.c	2005-10-26 21:46:50.000000000 +0200
@@ -0,0 +1,47 @@
+/* linux/fs/ioctl_perm.c -- Common ioctl permissions interface
+ *
+ * Copyright 2005 Lorenzo Hernández García-Hierro <lorenzo@private>
+ * All Rights Reserved.
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/ioctl_perm.h>
+
+/**
+ * ioctl_perm - Maps an ioctl command value to a generic permission value.
+ * @cmd: fs-dependent command being checked for assignment.
+ * @perm: generic permission being checked for assignment.
+ * @cap: capability for assignment
+ * @tab: data structure with the permissions assignment.
+ * @tabsize: size of the data structure being used.
+ * @err: return value, if successful, it will be 0, if not, -EINVAL.
+ * Looks up @cmd to a generic ioctl permission defined in the @tab
+ * (ioctl_perm data structure) which is basically a matrix of the specific
+ * permissions/commands and the generic ones that apply to each one.
+ */
+int ioctl_perm(unsigned int cmd, unsigned int *perm, int *cap,
+               struct ioctl_perm *tab, size_t tabsize)
+{
+	int i, err = -EINVAL;
+
+	for (i = 0; i < tabsize/sizeof(struct ioctl_perm); i++)
+		if (cmd == tab[i].cmd) {
+			*perm = tab[i].perm;
+			*cap = tab[i].cap;
+			err = 0;
+			break;
+		}
+
+	return err;
+}
+
+EXPORT_SYMBOL(ioctl_perm);
diff -puN fs/Makefile~lsm-checkioctl-hook fs/Makefile
--- linux-2.6.14-rc4-mm1/fs/Makefile~lsm-checkioctl-hook	2005-10-20 18:40:06.000000000 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/fs/Makefile	2005-10-20 18:40:06.000000000 +0200
@@ -10,7 +10,7 @@ obj-y :=	open.o read_write.o file_table.
 		ioctl.o readdir.o select.o fifo.o locks.o dcache.o inode.o \
 		attr.o bad_inode.o file.o filesystems.o namespace.o aio.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \
-		ioprio.o
+		ioprio.o ioctl_perm.o
 
 obj-$(CONFIG_INOTIFY)		+= inotify.o
 obj-$(CONFIG_EPOLL)		+= eventpoll.o
diff -puN security/selinux/hooks.c~lsm-checkioctl-hook security/selinux/hooks.c
--- linux-2.6.14-rc4-mm1/security/selinux/hooks.c~lsm-checkioctl-hook	2005-10-20 18:40:06.000000000 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/security/selinux/hooks.c	2005-10-26 22:10:51.000000000 +0200
@@ -2314,6 +2314,35 @@ static int selinux_inode_listsecurity(st
 	return len;
 }
 
+static int selinux_inode_checkioctl(struct inode *inode, unsigned int perm, int cap)
+{
+	int rc;
+        u32 av = 0;
+
+	if (cap >= 0) {
+		rc = task_has_capability(current, cap);
+		if (rc)
+			return rc;
+	}
+
+        if (perm & (SECURITY_IOCTL_READ|SECURITY_IOCTL_READPRIV))
+        	av |= FILE__GETATTR;
+        if (perm & (SECURITY_IOCTL_WRITE|SECURITY_IOCTL_WRITEPRIV))
+        	av |= FILE__SETATTR;
+        rc = inode_has_perm(current, inode, av, NULL);
+        if (rc)
+        	return rc;
+        av = 0;
+        if (perm & SECURITY_IOCTL_READPRIV)
+        	av |= SYSTEM__IOCTL_READPRIV;
+        if (perm & SECURITY_IOCTL_WRITEPRIV)
+        	av |= SYSTEM__IOCTL_WRITEPRIV;
+        if (av)
+                rc = task_has_system(current, av);
+
+        return rc;
+}
+
 /* file security operations */
 
 static int selinux_file_permission(struct file *file, int mask)
@@ -2354,19 +2383,9 @@ static int selinux_file_ioctl(struct fil
 		case FIBMAP:
 		/* fall through */
 		case FIGETBSZ:
-		/* fall through */
-		case EXT2_IOC_GETFLAGS:
-		/* fall through */
-		case EXT2_IOC_GETVERSION:
 			error = file_has_perm(current, file, FILE__GETATTR);
 			break;
 
-		case EXT2_IOC_SETFLAGS:
-		/* fall through */
-		case EXT2_IOC_SETVERSION:
-			error = file_has_perm(current, file, FILE__SETATTR);
-			break;
-
 		/* sys_ioctl() checks */
 		case FIONBIO:
 		/* fall through */
@@ -4291,6 +4310,7 @@ static struct security_operations selinu
 	.inode_getsecurity =            selinux_inode_getsecurity,
 	.inode_setsecurity =            selinux_inode_setsecurity,
 	.inode_listsecurity =           selinux_inode_listsecurity,
+	.inode_checkioctl =             selinux_inode_checkioctl,
 
 	.file_permission =		selinux_file_permission,
 	.file_alloc_security =		selinux_file_alloc_security,
diff -puN security/selinux/include/av_permissions.h~lsm-checkioctl-hook security/selinux/include/av_permissions.h
--- linux-2.6.14-rc4-mm1/security/selinux/include/av_permissions.h~lsm-checkioctl-hook	2005-10-20 18:40:06.000000000 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/security/selinux/include/av_permissions.h	2005-10-20 18:40:06.000000000 +0200
@@ -531,6 +531,8 @@
 #define SYSTEM__SYSLOG_READ                       0x00000002UL
 #define SYSTEM__SYSLOG_MOD                        0x00000004UL
 #define SYSTEM__SYSLOG_CONSOLE                    0x00000008UL
+#define SYSTEM__IOCTL_READPRIV                    0x00000010UL
+#define SYSTEM__IOCTL_WRITEPRIV                   0x00000020UL
 
 #define CAPABILITY__CHOWN                         0x00000001UL
 #define CAPABILITY__DAC_OVERRIDE                  0x00000002UL
diff -puN security/selinux/include/av_perm_to_string.h~lsm-checkioctl-hook security/selinux/include/av_perm_to_string.h
--- linux-2.6.14-rc4-mm1/security/selinux/include/av_perm_to_string.h~lsm-checkioctl-hook	2005-10-20 18:40:06.000000000 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/security/selinux/include/av_perm_to_string.h	2005-10-20 18:40:06.000000000 +0200
@@ -91,6 +91,8 @@
    S_(SECCLASS_SYSTEM, SYSTEM__SYSLOG_READ, "syslog_read")
    S_(SECCLASS_SYSTEM, SYSTEM__SYSLOG_MOD, "syslog_mod")
    S_(SECCLASS_SYSTEM, SYSTEM__SYSLOG_CONSOLE, "syslog_console")
+   S_(SECCLASS_SYSTEM, SYSTEM__IOCTL_READPRIV, "ioctl_readpriv")
+   S_(SECCLASS_SYSTEM, SYSTEM__IOCTL_WRITEPRIV, "ioctl_writepriv")
    S_(SECCLASS_CAPABILITY, CAPABILITY__CHOWN, "chown")
    S_(SECCLASS_CAPABILITY, CAPABILITY__DAC_OVERRIDE, "dac_override")
    S_(SECCLASS_CAPABILITY, CAPABILITY__DAC_READ_SEARCH, "dac_read_search")
diff -puN fs/ext3/ioctl.c~lsm-checkioctl-hook fs/ext3/ioctl.c
--- linux-2.6.14-rc4-mm1/fs/ext3/ioctl.c~lsm-checkioctl-hook	2005-10-20 18:40:06.000000000 +0200
+++ linux-2.6.14-rc4-mm1-lorenzo/fs/ext3/ioctl.c	2005-10-26 21:55:38.000000000 +0200
@@ -13,6 +13,22 @@
 #include <linux/ext3_jbd.h>
 #include <linux/time.h>
 #include <asm/uaccess.h>
+#include <linux/security.h>
+#include <linux/ioctl_perm.h>
+
+static struct ioctl_perm ext3_ioctl_perm[] =
+{
+        { EXT3_IOC_GETFLAGS,       SECURITY_IOCTL_READ,      -1 },
+        { EXT3_IOC_SETFLAGS,       SECURITY_IOCTL_WRITE,     -1 },
+        { EXT3_IOC_GETVERSION,     SECURITY_IOCTL_READ,      -1 },
+        { EXT3_IOC_GETVERSION_OLD, SECURITY_IOCTL_READ,      -1 },
+        { EXT3_IOC_SETVERSION,     SECURITY_IOCTL_WRITE,     -1 },
+        { EXT3_IOC_SETVERSION_OLD, SECURITY_IOCTL_WRITE,     -1 },
+        { EXT3_IOC_GETRSVSZ,       SECURITY_IOCTL_READ,      -1 },
+        { EXT3_IOC_SETRSVSZ,       SECURITY_IOCTL_WRITE,     -1 },
+        { EXT3_IOC_GROUP_EXTEND,   SECURITY_IOCTL_WRITEPRIV, -1 },
+        { EXT3_IOC_GROUP_ADD,      SECURITY_IOCTL_WRITEPRIV, -1 }
+};
 
 
 int ext3_ioctl (struct inode * inode, struct file * filp, unsigned int cmd,
@@ -21,9 +37,22 @@ int ext3_ioctl (struct inode * inode, st
 	struct ext3_inode_info *ei = EXT3_I(inode);
 	unsigned int flags;
 	unsigned short rsv_window_size;
+	int error;
+	unsigned int perm;
+	int cap;
 
 	ext3_debug ("cmd = %u, arg = %lu\n", cmd, arg);
 
+        /* Map ioctl command to generic permission */
+	error = ioctl_perm(cmd, &perm, &cap, ext3_ioctl_perm, sizeof(ext3_ioctl_perm));
+	if (error)
+	       return error;
+
+        /* Check permission for inode */
+	error = security_inode_checkioctl(inode, perm, cap);
+	if (error)
+	       return error;
+
 	switch (cmd) {
 	case EXT3_IOC_GETFLAGS:
 		flags = ei->i_flags & EXT3_FL_USER_VISIBLE;
_

-- 
Lorenzo Hernández García-Hierro <lorenzo@private> 
[1024D/6F2B2DEC] & [2048g/9AE91A22][http://tuxedo-es.org]





This archive was generated by hypermail 2.1.3 : Tue Nov 01 2005 - 14:19:25 PST