From: Eric Paris <eparis@redhat.com> Date: Fri, 4 Sep 2009 07:47:01 -0400 Subject: [security] require root for mmap_min_addr Message-id: 1252064821.2425.46.camel@dhcp231-106.rdu.redhat.com O-Subject: Re: [RHEL 5.5] require root for mmap_min_addr Bugzilla: 518143 CVE: CVE-2009-2695 BZ 518143 This is a mashup of upstream: http://git.kernel.org/linus/9c0d90103c7e0eb6e638e5b649e9f6d8d9c1b4b3 http://git.kernel.org/linus/8cf948e744e0218af604c32edecde10006dc8e9e http://git.kernel.org/linus/788084aba2ab7348257597496befcbccabdc98a3 http://git.kernel.org/linus/1d9959734a1949ea4f2427bd2d8b21ede6b2441c The basic idea is that when selinux=0 the ability to map vm space less than mmap_min_addr is based on CAP_SYS_RAWIO (really this mean root) When selinux=1 that same ability is based on your type. Since users who log into a box are unconfined they have permission to map such address space. There is no uid=0 test when selinux=1. This patch does 2 things. 1) It splits the checking into a dac_mmap_min_addr and an lsm mmap_min_addr. The lsm mmap_min_addr is decided at compile time (Don 4096 please, or help me know how to patch that correctly) the dac_mmap_min_addr is still controlled by /proc/sys/vm/mmap_min_addr. 2) We now check the uid=0 code path even when selinux=1. So disabling the CAP_SYS_RAWIO check via /proc will not disable the SELinux mmap_min_addr checks. This should help mitigate the exploitability of future NULL pntr bugs... diff --git a/include/linux/mm.h b/include/linux/mm.h index b8a0d95..452bbae 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -39,10 +39,6 @@ extern int sysctl_legacy_va_layout; #include <asm/pgtable.h> #include <asm/processor.h> -#ifdef CONFIG_SECURITY -extern unsigned long mmap_min_addr; -#endif - #define nth_page(page,n) pfn_to_page(page_to_pfn((page)) + (n)) /* @@ -567,21 +563,6 @@ static inline void set_page_links(struct page *page, unsigned long zone, } /* - * If a hint addr is less than mmap_min_addr change hint to be as - * low as possible but still greater than mmap_min_addr - */ -static inline unsigned long round_hint_to_min(unsigned long hint) -{ -#ifdef CONFIG_SECURITY - hint &= PAGE_MASK; - if (((void *)hint != NULL) && - (hint < mmap_min_addr)) - return PAGE_ALIGN(mmap_min_addr); -#endif - return hint; -} - -/* * Some inline functions in vmstat.h depend on page_zone() */ #include <linux/vmstat.h> diff --git a/include/linux/security.h b/include/linux/security.h index a80f8f4..ba252dc 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -28,6 +28,9 @@ #include <linux/resource.h> #include <linux/sem.h> #include <linux/shm.h> +#ifndef __GENKSYMS__ +#include <linux/mm.h> /* PAGE_ALIGN */ +#endif #include <linux/msg.h> #include <linux/sched.h> #include <linux/key.h> @@ -73,6 +76,8 @@ extern int cap_netlink_send(struct sock *sk, struct sk_buff *skb); extern int cap_netlink_recv(struct sk_buff *skb, int cap); extern unsigned long mmap_min_addr; +extern unsigned long dac_mmap_min_addr; + /* * Values used in the task_security_ops calls */ @@ -99,6 +104,22 @@ struct request_sock; #define LSM_UNSAFE_PTRACE 2 #define LSM_UNSAFE_PTRACE_CAP 4 +/* + * If a hint addr is less than mmap_min_addr change hint to be as + * low as possible but still greater than mmap_min_addr + */ +static inline unsigned long round_hint_to_min(unsigned long hint) +{ + hint &= PAGE_MASK; + if (((void *)hint != NULL) && + (hint < mmap_min_addr)) + return PAGE_ALIGN(mmap_min_addr); + return hint; +} + +extern int mmap_min_addr_handler(struct ctl_table *table, int write, struct file *filp, + void __user *buffer, size_t *lenp, loff_t *ppos); + #ifdef CONFIG_SECURITY /** diff --git a/kernel/sysctl.c b/kernel/sysctl.c index c70404f..8f8dbb4 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -899,16 +899,14 @@ static ctl_table vm_table[] = { .extra1 = &zero, .extra2 = &one_hundred, }, -#ifdef CONFIG_SECURITY { .ctl_name = VM_MMAP_MIN_ADDR, .procname = "mmap_min_addr", - .data = &mmap_min_addr, - .maxlen = sizeof(unsigned long), + .data = &dac_mmap_min_addr, + .maxlen = sizeof(unsigned long), .mode = 0644, - .proc_handler = &proc_doulongvec_minmax, + .proc_handler = &mmap_min_addr_handler, }, -#endif { .ctl_name = VM_DIRTY_WB_CS, .procname = "dirty_writeback_centisecs", diff --git a/security/Kconfig b/security/Kconfig index 67785df..59f332d 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -105,6 +105,22 @@ config SECURITY_SECLVL If you are unsure how to answer this question, answer N. +config LSM_MMAP_MIN_ADDR + int "Low address space for LSM to from user allocation" + depends on SECURITY && SECURITY_SELINUX + default 65536 + help + This is the portion of low virtual memory which should be protected + from userspace allocation. Keeping a user from writing to low pages + can help reduce the impact of kernel NULL pointer bugs. + + For most ia64, ppc64 and x86 users with lots of address space + a value of 65536 is reasonable and should cause no problems. + On arm and other archs it should not be higher than 32768. + Programs which use vm86 functionality or have some need to map + this low address space will need the permission specific to the + systems running LSM. + source security/selinux/Kconfig endmenu diff --git a/security/Makefile b/security/Makefile index 8cbbf2f..674d1f5 100644 --- a/security/Makefile +++ b/security/Makefile @@ -5,6 +5,8 @@ obj-$(CONFIG_KEYS) += keys/ subdir-$(CONFIG_SECURITY_SELINUX) += selinux +obj-y += min_addr.o + # if we don't select a security model, use the default capabilities ifneq ($(CONFIG_SECURITY),y) obj-y += commoncap.o diff --git a/security/commoncap.c b/security/commoncap.c index e6b6736..8d217b6 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -277,7 +277,7 @@ int cap_task_post_setuid (uid_t old_ruid, uid_t old_euid, uid_t old_suid, /* * FIXME - is fsuser used for all CAP_FS_MASK capabilities? - * if not, we might be a bit too harsh here. + * if not, we might be a bit too harsh here. */ if (!issecure (SECURE_NO_SETUID_FIXUP)) { @@ -330,6 +330,35 @@ int cap_vm_enough_memory(long pages) return cap_vm_enough_memory_mm(current->mm, pages); } +/* + * cap_file_mmap - check if able to map given addr + * @file: unused + * @reqprot: unused + * @prot: unused + * @flags: unused + * @addr: address attempting to be mapped + * @addr_only: unused + * + * If the process is attempting to map memory below mmap_min_addr they need + * CAP_SYS_RAWIO. The other parameters to this function are unused by the + * capability security module. Returns 0 if this mapping should be allowed + * -EPERM if not. + */ +int cap_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags, + unsigned long addr, unsigned long addr_only) +{ + int ret = 0; + + if (addr < dac_mmap_min_addr) { + ret = cap_capable(current, CAP_SYS_RAWIO); + /* set PF_SUPERPRIV if it turns out we allow the low mmap */ + if (ret == 0) + current->flags |= PF_SUPERPRIV; + } + return ret; +} + EXPORT_SYMBOL(cap_capable); EXPORT_SYMBOL(cap_settime); EXPORT_SYMBOL(cap_ptrace); @@ -346,6 +375,7 @@ EXPORT_SYMBOL(cap_task_reparent_to_init); EXPORT_SYMBOL(cap_syslog); EXPORT_SYMBOL(cap_vm_enough_memory); EXPORT_SYMBOL(cap_vm_enough_memory_mm); +EXPORT_SYMBOL(cap_file_mmap); MODULE_DESCRIPTION("Standard Linux Common Capabilities Security Module"); MODULE_LICENSE("GPL"); diff --git a/security/dummy.c b/security/dummy.c index 966262e..a17d35e 100644 --- a/security/dummy.c +++ b/security/dummy.c @@ -700,9 +700,14 @@ static int dummy_file_mmap_addr (struct file *file, unsigned long reqprot, unsigned long addr, unsigned long addr_only) { - if ((addr < mmap_min_addr) && !capable(CAP_SYS_RAWIO)) - return -EACCES; - return 0; + int ret = 0; + + if (addr < dac_mmap_min_addr) { + ret = dummy_capable(current, CAP_SYS_RAWIO); + if (ret == 0) + current->flags |= PF_SUPERPRIV; + } + return ret; } #ifdef CONFIG_SECURITY_NETWORK diff --git a/security/min_addr.c b/security/min_addr.c new file mode 100644 index 0000000..5e3a07d --- /dev/null +++ b/security/min_addr.c @@ -0,0 +1,52 @@ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/security.h> +#include <linux/sysctl.h> + +/* amount of vm to protect from userspace access by both DAC and the LSM*/ +unsigned long mmap_min_addr; +/* amount of vm to protect from userspace using CAP_SYS_RAWIO (DAC) */ +unsigned long dac_mmap_min_addr = 4096; +/* amount of vm to protect from userspace using the LSM = CONFIG_LSM_MMAP_MIN_ADDR */ + +/* + * Update mmap_min_addr = max(dac_mmap_min_addr, CONFIG_LSM_MMAP_MIN_ADDR) + */ +static void update_mmap_min_addr(void) +{ +#ifdef CONFIG_LSM_MMAP_MIN_ADDR + if (dac_mmap_min_addr > CONFIG_LSM_MMAP_MIN_ADDR) + mmap_min_addr = dac_mmap_min_addr; + else + mmap_min_addr = CONFIG_LSM_MMAP_MIN_ADDR; +#else + mmap_min_addr = dac_mmap_min_addr; +#endif +} + +/* + * sysctl handler which just sets dac_mmap_min_addr = the new value and then + * calls update_mmap_min_addr() so non MAP_FIXED hints get rounded properly + */ +int mmap_min_addr_handler(struct ctl_table *table, int write, struct file *filp, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int ret; + + ret = proc_doulongvec_minmax(table, write, filp, buffer, lenp, ppos); + + update_mmap_min_addr(); + + return ret; +} + +int __init init_mmap_min_addr(void) +{ + update_mmap_min_addr(); + + return 0; +} +core_initcall(init_mmap_min_addr); + +EXPORT_SYMBOL_GPL(mmap_min_addr); diff --git a/security/security.c b/security/security.c index 0228305..ee4e070 100644 --- a/security/security.c +++ b/security/security.c @@ -25,7 +25,6 @@ extern struct security_operations dummy_security_ops; extern void security_fixup_ops(struct security_operations *ops); struct security_operations *security_ops; /* Initialized to NULL */ -unsigned long mmap_min_addr = 4096; /* 0 means no protection */ static inline int verify(struct security_operations *ops) { @@ -178,5 +177,4 @@ EXPORT_SYMBOL_GPL(register_security); EXPORT_SYMBOL_GPL(unregister_security); EXPORT_SYMBOL_GPL(mod_reg_security); EXPORT_SYMBOL_GPL(mod_unreg_security); -EXPORT_SYMBOL_GPL(mmap_min_addr); EXPORT_SYMBOL(security_ops); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 2af7bca..4719175 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2684,9 +2684,22 @@ static int selinux_file_mmap_addr(struct file *file, unsigned long reqprot, int rc = 0; u32 sid = ((struct task_security_struct*)(current->security))->sid; - if (addr < mmap_min_addr) + /* + * notice that we are intentionally putting the SELinux check before + * the secondary cap_file_mmap check. This is such a likely attempt + * at bad behaviour/exploit that we always want to get the AVC, even + * if DAC would have also denied the operation. + */ + if (addr < CONFIG_LSM_MMAP_MIN_ADDR) { rc = avc_has_perm(sid, sid, SECCLASS_MEMPROTECT, MEMPROTECT__MMAP_ZERO, NULL); + if (rc) + return rc; + } + + /* do DAC check on address space usage */ + rc = secondary_ops->file_mmap_addr(file, reqprot, prot, flags, addr, + addr_only); if (rc || addr_only) return rc;