(re-sent due to patch line-wrapping problems in earlier message, added Chris to CC list) 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 : Wed Nov 09 2005 - 08:25:34 PST