Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > fc11cd6e1c513a17304da94a5390f3cd > files > 3692

kernel-2.6.18-194.11.1.el5.src.rpm

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;