From: Stephen Smalley This patch adds a reqprot parameter to the security_file_mmap and security_file_mprotect hooks that is the original requested protection value prior to any modification for read-implies-exec, and changes the SELinux module to allow a mode of operation (controllable via a checkreqprot setting) where it applies checks based on that protection value rather than the protection that will be applied by the kernel, effectively restoring SELinux's original behavior prior to the introduction of the read-implies-exec logic in the mainline kernel. The patch also disables execmem and execmod checking entirely on PPC32, as the PPC32 ELF ABI presently requires RWE segments per Ulrich Drepper. At present, the read-implies-exec logic causes SELinux to see every mmap/mprotect read request by legacy binaries or binaries marked with PT_GNU_STACK RWE as a read|execute request, which tends to distort policy even if it reflects what is ultimately possible. The checkreqprot setting allows one to set the desired behavior for SELinux, so either the current behavior or the original behavior is possible. The checkreqprot value has a compile-time configurable default value and can also be set via boot parameter or at runtime via /selinux/checkreqprot if allowed by policy. Thanks to Chris Wright, James Morris, and Colin Walters for comments on an earlier version of the patch. Signed-off-by: Stephen Smalley Signed-off-by: James Morris Signed-off-by: Andrew Morton --- 25-akpm/include/linux/security.h | 23 +++++-- 25-akpm/mm/mmap.c | 4 - 25-akpm/mm/mprotect.c | 6 + 25-akpm/security/dummy.c | 7 +- 25-akpm/security/selinux/Kconfig | 19 ++++++ 25-akpm/security/selinux/hooks.c | 18 ++++- 25-akpm/security/selinux/include/av_perm_to_string.h | 1 25-akpm/security/selinux/include/av_permissions.h | 1 25-akpm/security/selinux/include/objsec.h | 2 25-akpm/security/selinux/selinuxfs.c | 60 +++++++++++++++++++ 10 files changed, 125 insertions(+), 16 deletions(-) diff -puN include/linux/security.h~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks include/linux/security.h --- 25/include/linux/security.h~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/include/linux/security.h Mon Mar 7 16:10:38 2005 @@ -458,13 +458,15 @@ struct swap_info_struct; * Check permissions for a mmap operation. The @file may be NULL, e.g. * if mapping anonymous memory. * @file contains the file structure for file to map (may be NULL). - * @prot contains the requested permissions. + * @reqprot contains the protection requested by the application. + * @prot contains the protection that will be applied by the kernel. * @flags contains the operational flags. * Return 0 if permission is granted. * @file_mprotect: * Check permissions before changing memory access permissions. * @vma contains the memory region to modify. - * @prot contains the requested permissions. + * @reqprot contains the protection requested by the application. + * @prot contains the protection that will be applied by the kernel. * Return 0 if permission is granted. * @file_lock: * Check permission before performing file locking operations. @@ -1129,8 +1131,11 @@ struct security_operations { int (*file_ioctl) (struct file * file, unsigned int cmd, unsigned long arg); int (*file_mmap) (struct file * file, + unsigned long reqprot, unsigned long prot, unsigned long flags); - int (*file_mprotect) (struct vm_area_struct * vma, unsigned long prot); + int (*file_mprotect) (struct vm_area_struct * vma, + unsigned long reqprot, + unsigned long prot); int (*file_lock) (struct file * file, unsigned int cmd); int (*file_fcntl) (struct file * file, unsigned int cmd, unsigned long arg); @@ -1693,16 +1698,18 @@ static inline int security_file_ioctl (s return security_ops->file_ioctl (file, cmd, arg); } -static inline int security_file_mmap (struct file *file, unsigned long prot, +static inline int security_file_mmap (struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) { - return security_ops->file_mmap (file, prot, flags); + return security_ops->file_mmap (file, reqprot, prot, flags); } static inline int security_file_mprotect (struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) { - return security_ops->file_mprotect (vma, prot); + return security_ops->file_mprotect (vma, reqprot, prot); } static inline int security_file_lock (struct file *file, unsigned int cmd) @@ -2342,13 +2349,15 @@ static inline int security_file_ioctl (s return 0; } -static inline int security_file_mmap (struct file *file, unsigned long prot, +static inline int security_file_mmap (struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) { return 0; } static inline int security_file_mprotect (struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) { return 0; diff -puN mm/mmap.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks mm/mmap.c --- 25/mm/mmap.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/mm/mmap.c Mon Mar 7 16:10:38 2005 @@ -872,7 +872,7 @@ unsigned long do_mmap_pgoff(struct file int error; struct rb_node ** rb_link, * rb_parent; int accountable = 1; - unsigned long charged = 0; + unsigned long charged = 0, reqprot = prot; if (file) { if (is_file_hugepages(file)) @@ -990,7 +990,7 @@ unsigned long do_mmap_pgoff(struct file } } - error = security_file_mmap(file, prot, flags); + error = security_file_mmap(file, reqprot, prot, flags); if (error) return error; diff -puN mm/mprotect.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks mm/mprotect.c --- 25/mm/mprotect.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/mm/mprotect.c Mon Mar 7 16:10:38 2005 @@ -222,7 +222,7 @@ fail: asmlinkage long sys_mprotect(unsigned long start, size_t len, unsigned long prot) { - unsigned long vm_flags, nstart, end, tmp; + unsigned long vm_flags, nstart, end, tmp, reqprot; struct vm_area_struct *vma, *prev; int error = -EINVAL; const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP); @@ -240,6 +240,8 @@ sys_mprotect(unsigned long start, size_t return -EINVAL; if (end == start) return 0; + + reqprot = prot; /* * Does the application expect PROT_READ to imply PROT_EXEC: */ @@ -293,7 +295,7 @@ sys_mprotect(unsigned long start, size_t goto out; } - error = security_file_mprotect(vma, prot); + error = security_file_mprotect(vma, reqprot, prot); if (error) goto out; diff -puN security/dummy.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks security/dummy.c --- 25/security/dummy.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/security/dummy.c Mon Mar 7 16:10:38 2005 @@ -446,13 +446,16 @@ static int dummy_file_ioctl (struct file return 0; } -static int dummy_file_mmap (struct file *file, unsigned long prot, +static int dummy_file_mmap (struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) { return 0; } -static int dummy_file_mprotect (struct vm_area_struct *vma, unsigned long prot) +static int dummy_file_mprotect (struct vm_area_struct *vma, + unsigned long reqprot, + unsigned long prot) { return 0; } diff -puN security/selinux/hooks.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks security/selinux/hooks.c --- 25/security/selinux/hooks.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/security/selinux/hooks.c Mon Mar 7 16:10:38 2005 @@ -2415,6 +2415,7 @@ static int selinux_file_ioctl(struct fil static int file_map_prot_check(struct file *file, unsigned long prot, int shared) { +#ifndef CONFIG_PPC32 if ((prot & PROT_EXEC) && (!file || (!shared && (prot & PROT_WRITE)))) { /* * We are making executable an anonymous mapping or a @@ -2425,6 +2426,7 @@ static int file_map_prot_check(struct fi if (rc) return rc; } +#endif if (file) { /* read access is always possible with a mapping */ @@ -2442,27 +2444,36 @@ static int file_map_prot_check(struct fi return 0; } -static int selinux_file_mmap(struct file *file, unsigned long prot, unsigned long flags) +static int selinux_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) { int rc; - rc = secondary_ops->file_mmap(file, prot, flags); + rc = secondary_ops->file_mmap(file, reqprot, prot, flags); if (rc) return rc; + if (selinux_checkreqprot) + prot = reqprot; + return file_map_prot_check(file, prot, (flags & MAP_TYPE) == MAP_SHARED); } static int selinux_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) { int rc; - rc = secondary_ops->file_mprotect(vma, prot); + rc = secondary_ops->file_mprotect(vma, reqprot, prot); if (rc) return rc; + if (selinux_checkreqprot) + prot = reqprot; + +#ifndef CONFIG_PPC32 if (vma->vm_file != NULL && vma->anon_vma != NULL && (prot & PROT_EXEC)) { /* * We are making executable a file mapping that has @@ -2474,6 +2485,7 @@ static int selinux_file_mprotect(struct if (rc) return rc; } +#endif return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED); } diff -puN security/selinux/include/av_permissions.h~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks security/selinux/include/av_permissions.h --- 25/security/selinux/include/av_permissions.h~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/security/selinux/include/av_permissions.h Mon Mar 7 16:10:38 2005 @@ -522,6 +522,7 @@ #define SECURITY__SETENFORCE 0x00000080UL #define SECURITY__SETBOOL 0x00000100UL #define SECURITY__SETSECPARAM 0x00000200UL +#define SECURITY__SETCHECKREQPROT 0x00000400UL #define SYSTEM__IPC_INFO 0x00000001UL #define SYSTEM__SYSLOG_READ 0x00000002UL diff -puN security/selinux/include/av_perm_to_string.h~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks security/selinux/include/av_perm_to_string.h --- 25/security/selinux/include/av_perm_to_string.h~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/security/selinux/include/av_perm_to_string.h Mon Mar 7 16:10:38 2005 @@ -83,6 +83,7 @@ S_(SECCLASS_SECURITY, SECURITY__SETENFORCE, "setenforce") S_(SECCLASS_SECURITY, SECURITY__SETBOOL, "setbool") S_(SECCLASS_SECURITY, SECURITY__SETSECPARAM, "setsecparam") + S_(SECCLASS_SECURITY, SECURITY__SETCHECKREQPROT, "setcheckreqprot") S_(SECCLASS_SYSTEM, SYSTEM__IPC_INFO, "ipc_info") S_(SECCLASS_SYSTEM, SYSTEM__SYSLOG_READ, "syslog_read") S_(SECCLASS_SYSTEM, SYSTEM__SYSLOG_MOD, "syslog_mod") diff -puN security/selinux/include/objsec.h~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks security/selinux/include/objsec.h --- 25/security/selinux/include/objsec.h~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/security/selinux/include/objsec.h Mon Mar 7 16:10:38 2005 @@ -109,4 +109,6 @@ struct sk_security_struct { extern int inode_security_set_sid(struct inode *inode, u32 sid); +extern unsigned int selinux_checkreqprot; + #endif /* _SELINUX_OBJSEC_H_ */ diff -puN security/selinux/Kconfig~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks security/selinux/Kconfig --- 25/security/selinux/Kconfig~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/security/selinux/Kconfig Mon Mar 7 16:10:38 2005 @@ -76,3 +76,22 @@ config SECURITY_SELINUX_AVC_STATS /selinux/avc/cache_stats, which may be monitored via tools such as avcstat. +config SECURITY_SELINUX_CHECKREQPROT_VALUE + int "NSA SELinux checkreqprot default value" + depends on SECURITY_SELINUX + range 0 1 + default 1 + help + This option sets the default value for the 'checkreqprot' flag + that determines whether SELinux checks the protection requested + by the application or the protection that will be applied by the + kernel (including any implied execute for read-implies-exec) for + mmap and mprotect calls. If this option is set to 0 (zero), + SELinux will default to checking the protection that will be applied + by the kernel. If this option is set to 1 (one), SELinux will + default to checking the protection requested by the application. + The checkreqprot flag may be changed from the default via the + 'checkreqprot=' boot parameter. It may also be changed at runtime + via /selinux/checkreqprot if authorized by policy. + + If you are unsure how to answer this question, answer 1. diff -puN security/selinux/selinuxfs.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks security/selinux/selinuxfs.c --- 25/security/selinux/selinuxfs.c~selinux-pass-requested-protection-to-security_file_mmap-mprotect-hooks Mon Mar 7 16:10:38 2005 +++ 25-akpm/security/selinux/selinuxfs.c Mon Mar 7 16:10:38 2005 @@ -34,6 +34,16 @@ #include "objsec.h" #include "conditional.h" +unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; + +static int __init checkreqprot_setup(char *str) +{ + selinux_checkreqprot = simple_strtoul(str,NULL,0) ? 1 : 0; + return 1; +} +__setup("checkreqprot=", checkreqprot_setup); + + static DECLARE_MUTEX(sel_sem); /* global data for booleans */ @@ -72,6 +82,7 @@ enum sel_inos { SEL_DISABLE, /* disable SELinux until next reboot */ SEL_AVC, /* AVC management directory */ SEL_MEMBER, /* compute polyinstantiation membership decision */ + SEL_CHECKREQPROT, /* check requested protection, not kernel-applied one */ }; #define TMPBUFLEN 12 @@ -300,6 +311,54 @@ static struct file_operations sel_contex .write = sel_write_context, }; +static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", selinux_checkreqprot); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static ssize_t sel_write_checkreqprot(struct file * file, const char __user * buf, + size_t count, loff_t *ppos) +{ + char *page; + ssize_t length; + unsigned int new_value; + + length = task_has_security(current, SECURITY__SETCHECKREQPROT); + if (length) + return length; + + if (count < 0 || count >= PAGE_SIZE) + return -ENOMEM; + if (*ppos != 0) { + /* No partial writes. */ + return -EINVAL; + } + page = (char*)get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%u", &new_value) != 1) + goto out; + + selinux_checkreqprot = new_value ? 1 : 0; + length = count; +out: + free_page((unsigned long) page); + return length; +} +static struct file_operations sel_checkreqprot_ops = { + .read = sel_read_checkreqprot, + .write = sel_write_checkreqprot, +}; /* * Remaining nodes use transaction based IO methods like nfsd/nfsctl.c @@ -1182,6 +1241,7 @@ static int sel_fill_super(struct super_b [SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO}, [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR}, [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR}, /* last one */ {""} }; ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files); _