kernel-hardening.lists.openwall.com archive mirror
 help / color / mirror / Atom feed
* [kernel-hardening] [PATCH] Subject: [RFC PATCH] mm: Hardened usercopy
@ 2016-05-19 16:14 casey.schaufler
  2016-05-19 19:15 ` Rik van Riel
  2016-05-20 18:22 ` Kees Cook
  0 siblings, 2 replies; 3+ messages in thread
From: casey.schaufler @ 2016-05-19 16:14 UTC (permalink / raw)
  To: kernel-hardening; +Cc: Casey Schaufler

From: Casey Schaufler <casey.schaufler@intel.com>

This is a work in progress, first stage of porting
the PAX_USERCOPY feature for upstream consumption.
The work isn't complete, but this should provide the
basic structure and initial range checking. There
are known gaps:

	- The SLOB memory allocator does not work
	  on x86, however the grsecurity version does
	  not work there either
	- This is relative to the 4.3.3 kernel, same
	  as the publicly released version of
	  grsecurity that serves as its model.
	- I don't have a good set of test cases.
	  Any pointers would be greatly appreciated.
	  I am not myself especially clever, and the
	  sort of things you have to do to generate
	  exploits do not come naturally to me.

Further, there are questions about the way I've done
the port.

	- The hard usercopy implementation is fully
	  ifdefed, whereas the grsecurity version makes
	  wholesale changes to the base code. I'm open
	  to opinions regarding which would be preferred
	  upstream.
	- In part because of the above, it's possible
	  that I've missed important bits. Reports of
	  glaring ommissions (or sins of commission)
	  are always welcome.
	- Attestation. How best to ensure the the original
	  authors get what they deserve.

This is definitely RFC territory. Please C away.

Signed-off-by: Casey Schaufler <casey.schaufler@intel.com>
---
 arch/arm/include/asm/uaccess.h      |   6 ++
 arch/ia64/include/asm/uaccess.h     |  25 +++++++
 arch/powerpc/include/asm/uaccess.h  |  35 ++++++++++
 arch/sparc/include/asm/uaccess_32.h |  20 ++++++
 arch/sparc/include/asm/uaccess_64.h |  19 ++++++
 arch/x86/include/asm/uaccess_32.h   |   6 ++
 arch/x86/include/asm/uaccess_64.h   |   3 +
 fs/cifs/cifsfs.c                    |  13 ++++
 fs/dcache.c                         |   5 ++
 fs/exec.c                           | 125 +++++++++++++++++++++++++++++++++++
 fs/jfs/super.c                      |   7 ++
 fs/seq_file.c                       |   5 ++
 include/linux/gfp.h                 |  15 +++++
 include/linux/sched.h               |   3 +
 include/linux/slab.h                |  11 ++++
 include/linux/thread_info.h         |  11 ++++
 ipc/msgutil.c                       |   8 +++
 kernel/fork.c                       |   5 ++
 mm/slab.c                           |  39 +++++++++++
 mm/slab.h                           |   6 ++
 mm/slab_common.c                    |  42 ++++++++++++
 mm/slob.c                           | 126 ++++++++++++++++++++++++++++++++++++
 mm/slub.c                           |  42 ++++++++++++
 net/decnet/af_decnet.c              |   3 +
 security/Kconfig                    |  15 +++++
 virt/kvm/kvm_main.c                 |   5 ++
 26 files changed, 600 insertions(+)

diff --git a/arch/arm/include/asm/uaccess.h b/arch/arm/include/asm/uaccess.h
index 8cc85a4..dcb71c4 100644
--- a/arch/arm/include/asm/uaccess.h
+++ b/arch/arm/include/asm/uaccess.h
@@ -497,6 +497,9 @@ static inline unsigned long __must_check
 __copy_from_user(void *to, const void __user *from, unsigned long n)
 {
 	unsigned int __ua_flags = uaccess_save_and_enable();
+#ifdef CONFIG_HARDUSERCOPY
+	check_object_size(to, n, false);
+#endif
 	n = arm_copy_from_user(to, from, n);
 	uaccess_restore(__ua_flags);
 	return n;
@@ -511,6 +514,9 @@ static inline unsigned long __must_check
 __copy_to_user(void __user *to, const void *from, unsigned long n)
 {
 	unsigned int __ua_flags = uaccess_save_and_enable();
+#ifdef CONFIG_HARDUSERCOPY
+	check_object_size(to, n, false);
+#endif
 	n = arm_copy_to_user(to, from, n);
 	uaccess_restore(__ua_flags);
 	return n;
diff --git a/arch/ia64/include/asm/uaccess.h b/arch/ia64/include/asm/uaccess.h
index 4f3fb6cc..e5ddd11 100644
--- a/arch/ia64/include/asm/uaccess.h
+++ b/arch/ia64/include/asm/uaccess.h
@@ -241,12 +241,21 @@ extern unsigned long __must_check __copy_user (void __user *to, const void __use
 static inline unsigned long
 __copy_to_user (void __user *to, const void *from, unsigned long count)
 {
+#ifdef CONFIG_HARDUSERCOPY
+	if (!__builtin_constant_p(count))
+		check_object_size(from, count, true);
+#endif
+
 	return __copy_user(to, (__force void __user *) from, count);
 }
 
 static inline unsigned long
 __copy_from_user (void *to, const void __user *from, unsigned long count)
 {
+#ifdef CONFIG_HARDUSERCOPY
+	if (!__builtin_constant_p(count))
+		check_object_size(from, count, true);
+#endif
 	return __copy_user((__force void __user *) to, from, count);
 }
 
@@ -258,8 +267,16 @@ __copy_from_user (void *to, const void __user *from, unsigned long count)
 	const void *__cu_from = (from);							\
 	long __cu_len = (n);								\
 											\
+#ifdef CONFIG_HARDUSERCOPY								\
+	if (__access_ok(__cu_from, __cu_len, get_fs())) {				\
+		if (!__builtin_constant_p(n))						\
+			check_object_size(__cu_from, __cu_len, true);			\
+		__cu_len = __copy_user((__force void __user *) __cu_to, __cu_from, __cu_len);	\
+	}										\
+#else											\
 	if (__access_ok(__cu_to, __cu_len, get_fs()))					\
 		__cu_len = __copy_user(__cu_to, (__force void __user *) __cu_from, __cu_len);	\
+#endif											\
 	__cu_len;									\
 })
 
@@ -270,8 +287,16 @@ __copy_from_user (void *to, const void __user *from, unsigned long count)
 	long __cu_len = (n);								\
 											\
 	__chk_user_ptr(__cu_from);							\
+#ifdef CONFIG_HARDUSERCOPY								\
+	if (__access_ok(__cu_from, __cu_len, get_fs())) {				\
+		if (!__builtin_constant_p(n))						\
+			check_object_size(__cu_to, __cu_len, false);			\
+		__cu_len = __copy_user((__force void __user *) __cu_to, __cu_from, __cu_len);	\
+	}										\
+#else											\
 	if (__access_ok(__cu_from, __cu_len, get_fs()))					\
 		__cu_len = __copy_user((__force void __user *) __cu_to, __cu_from, __cu_len);	\
+#endif											\
 	__cu_len;									\
 })
 
diff --git a/arch/powerpc/include/asm/uaccess.h b/arch/powerpc/include/asm/uaccess.h
index 2a8ebae..08f83c9 100644
--- a/arch/powerpc/include/asm/uaccess.h
+++ b/arch/powerpc/include/asm/uaccess.h
@@ -325,10 +325,22 @@ static inline unsigned long copy_from_user(void *to,
 {
 	unsigned long over;
 
+#ifdef CONFIG_HARDUSERCOPY
+	if (access_ok(VERIFY_READ, from, n)) {
+		if (!__builtin_constant_p(n))
+			check_object_size(to, n, false);
+		return __copy_tofrom_user((__force void __user *)to, from, n);
+	}
+#else
 	if (access_ok(VERIFY_READ, from, n))
 		return __copy_tofrom_user((__force void __user *)to, from, n);
+#endif
 	if ((unsigned long)from < TASK_SIZE) {
 		over = (unsigned long)from + n - TASK_SIZE;
+#ifdef CONFIG_HARDUSERCOPY
+		if (!__builtin_constant_p(n - over))
+			check_object_size(to, n - over, false);
+#endif
 		return __copy_tofrom_user((__force void __user *)to, from,
 				n - over) + over;
 	}
@@ -340,10 +352,22 @@ static inline unsigned long copy_to_user(void __user *to,
 {
 	unsigned long over;
 
+#ifdef CONFIG_HARDUSERCOPY
+	if (access_ok(VERIFY_WRITE, to, n)) {
+		if (!__builtin_constant_p(n))
+			check_object_size(from, n, true);
+		return __copy_tofrom_user(to, (__force void __user *)from, n);
+	}
+#else
 	if (access_ok(VERIFY_WRITE, to, n))
 		return __copy_tofrom_user(to, (__force void __user *)from, n);
+#endif
 	if ((unsigned long)to < TASK_SIZE) {
 		over = (unsigned long)to + n - TASK_SIZE;
+#ifdef CONFIG_HARDUSERCOPY
+		if (!__builtin_constant_p(n))
+			check_object_size(from, n - over, true);
+#endif
 		return __copy_tofrom_user(to, (__force void __user *)from,
 				n - over) + over;
 	}
@@ -387,6 +411,12 @@ static inline unsigned long __copy_from_user_inatomic(void *to,
 		if (ret == 0)
 			return 0;
 	}
+
+#ifdef CONFIG_HARDUSERCOPY
+	if (!__builtin_constant_p(n))
+		check_object_size(to, n, false);
+#endif
+
 	return __copy_tofrom_user((__force void __user *)to, from, n);
 }
 
@@ -413,6 +443,11 @@ static inline unsigned long __copy_to_user_inatomic(void __user *to,
 		if (ret == 0)
 			return 0;
 	}
+#ifdef CONFIG_HARDUSERCOPY
+	if (!__builtin_constant_p(n))
+		check_object_size(from, n, true);
+#endif
+
 	return __copy_tofrom_user(to, (__force const void __user *)from, n);
 }
 
diff --git a/arch/sparc/include/asm/uaccess_32.h b/arch/sparc/include/asm/uaccess_32.h
index 64ee103..a8c10ad 100644
--- a/arch/sparc/include/asm/uaccess_32.h
+++ b/arch/sparc/include/asm/uaccess_32.h
@@ -313,22 +313,42 @@ unsigned long __copy_user(void __user *to, const void __user *from, unsigned lon
 
 static inline unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
 {
+#ifdef CONFIG_HARDUSERCOPY
+	if (n && __access_ok((unsigned long) to, n)) {
+		if (!__builtin_constant_p(n))
+			check_object_size(from, n, true);
+		return __copy_user(to, (__force void __user *) from, n);
+	} else
+#else
 	if (n && __access_ok((unsigned long) to, n))
 		return __copy_user(to, (__force void __user *) from, n);
 	else
+#endif
 		return n;
 }
 
 static inline unsigned long __copy_to_user(void __user *to, const void *from, unsigned long n)
 {
+#ifdef CONFIG_HARDUSERCOPY
+	if (!__builtin_constant_p(n))
+		check_object_size(from, n, true);
+#endif
 	return __copy_user(to, (__force void __user *) from, n);
 }
 
 static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
 {
+#ifdef CONFIG_HARDUSERCOPY
+	if (n && __access_ok((unsigned long) from, n)) {
+		if (!__builtin_constant_p(n))
+			check_object_size(to, n, false);
+		return __copy_user((__force void __user *) to, from, n);
+	} else
+#else
 	if (n && __access_ok((unsigned long) from, n))
 		return __copy_user((__force void __user *) to, from, n);
 	else
+#endif
 		return n;
 }
 
diff --git a/arch/sparc/include/asm/uaccess_64.h b/arch/sparc/include/asm/uaccess_64.h
index ea6e9a2..58d5e0a 100644
--- a/arch/sparc/include/asm/uaccess_64.h
+++ b/arch/sparc/include/asm/uaccess_64.h
@@ -250,8 +250,18 @@ unsigned long copy_from_user_fixup(void *to, const void __user *from,
 static inline unsigned long __must_check
 copy_from_user(void *to, const void __user *from, unsigned long size)
 {
+#ifdef CONFIG_HARDUSERCOPY
+	unsigned long ret;
+#else
 	unsigned long ret = ___copy_from_user(to, from, size);
+#endif
+
+#ifdef CONFIG_HARDUSERCOPY
+	if (!__builtin_constant_p(size))
+		check_object_size(to, size, false);
 
+	ret = ___copy_from_user(to, from, size);
+#endif
 	if (unlikely(ret))
 		ret = copy_from_user_fixup(to, from, size);
 
@@ -267,8 +277,17 @@ unsigned long copy_to_user_fixup(void __user *to, const void *from,
 static inline unsigned long __must_check
 copy_to_user(void __user *to, const void *from, unsigned long size)
 {
+#ifdef CONFIG_HARDUSERCOPY
+	unsigned long ret;
+#else
 	unsigned long ret = ___copy_to_user(to, from, size);
+#endif
 
+#ifdef CONFIG_HARDUSERCOPY
+	if (!__builtin_constant_p(size))
+		check_object_size(from, size, true);
+	ret = ___copy_to_user(to, from, size);
+#endif
 	if (unlikely(ret))
 		ret = copy_to_user_fixup(to, from, size);
 	return ret;
diff --git a/arch/x86/include/asm/uaccess_32.h b/arch/x86/include/asm/uaccess_32.h
index f5dcb52..8b6a3b6 100644
--- a/arch/x86/include/asm/uaccess_32.h
+++ b/arch/x86/include/asm/uaccess_32.h
@@ -43,6 +43,9 @@ unsigned long __must_check __copy_from_user_ll_nocache_nozero
 static __always_inline unsigned long __must_check
 __copy_to_user_inatomic(void __user *to, const void *from, unsigned long n)
 {
+#ifdef CONFIG_HARDUSERCOPY
+	check_object_size(from, n, true);
+#endif
 	if (__builtin_constant_p(n)) {
 		unsigned long ret;
 
@@ -143,6 +146,9 @@ static __always_inline unsigned long
 __copy_from_user(void *to, const void __user *from, unsigned long n)
 {
 	might_fault();
+#ifdef CONFIG_HARDUSERCOPY
+	check_object_size(to, n, false);
+#endif
 	if (__builtin_constant_p(n)) {
 		unsigned long ret;
 
diff --git a/arch/x86/include/asm/uaccess_64.h b/arch/x86/include/asm/uaccess_64.h
index f2f9b39..673dcef 100644
--- a/arch/x86/include/asm/uaccess_64.h
+++ b/arch/x86/include/asm/uaccess_64.h
@@ -53,6 +53,9 @@ int __copy_from_user_nocheck(void *dst, const void __user *src, unsigned size)
 {
 	int ret = 0;
 
+#ifdef CONFIG_HARDUSERCOPY
+	check_object_size(dst, size, false);
+#endif
 	if (!__builtin_constant_p(size))
 		return copy_user_generic(dst, (__force void *)src, size);
 	switch (size) {
diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index e739950..cf3133e 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -1083,9 +1083,16 @@ cifs_init_request_bufs(void)
 	cifs_dbg(VFS, "CIFSMaxBufSize %d 0x%x\n",
 		 CIFSMaxBufSize, CIFSMaxBufSize);
 */
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	cifs_req_cachep = kmem_cache_create("cifs_request",
+					    CIFSMaxBufSize + max_hdr_size, 0,
+					    SLAB_HWCACHE_ALIGN|SLAB_USERCOPY,
+					    NULL);
+#else
 	cifs_req_cachep = kmem_cache_create("cifs_request",
 					    CIFSMaxBufSize + max_hdr_size, 0,
 					    SLAB_HWCACHE_ALIGN, NULL);
+#endif
 	if (cifs_req_cachep == NULL)
 		return -ENOMEM;
 
@@ -1111,9 +1118,15 @@ cifs_init_request_bufs(void)
 	more SMBs to use small buffer alloc and is still much more
 	efficient to alloc 1 per page off the slab compared to 17K (5page)
 	alloc of large cifs buffers even when page debugging is on */
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	cifs_sm_req_cachep = kmem_cache_create("cifs_small_rq",
+			MAX_CIFS_SMALL_BUFFER_SIZE, 0,
+			SLAB_USERCOPY | SLAB_HWCACHE_ALIGN, NULL);
+#else
 	cifs_sm_req_cachep = kmem_cache_create("cifs_small_rq",
 			MAX_CIFS_SMALL_BUFFER_SIZE, 0, SLAB_HWCACHE_ALIGN,
 			NULL);
+#endif
 	if (cifs_sm_req_cachep == NULL) {
 		mempool_destroy(cifs_req_poolp);
 		kmem_cache_destroy(cifs_req_cachep);
diff --git a/fs/dcache.c b/fs/dcache.c
index 5c33aeb..f6e0f58 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -3450,8 +3450,13 @@ void __init vfs_caches_init_early(void)
 
 void __init vfs_caches_init(void)
 {
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
+			SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_USERCOPY, NULL);
+#else /* CONFIG_HARDUSERCOPY_SLABS */
 	names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
 			SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
 
 	dcache_init();
 	inode_init();
diff --git a/fs/exec.c b/fs/exec.c
index b06623a..0e67eb2 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1749,3 +1749,128 @@ COMPAT_SYSCALL_DEFINE5(execveat, int, fd,
 				  argv, envp, flags);
 }
 #endif
+
+#ifdef CONFIG_HARDUSERCOPY
+/*
+ *	0: not at all,
+ *	1: fully,
+ *	2: fully inside frame,
+ *	-1: partially (implies an error)
+ */
+static noinline int check_stack_object(const void *obj, unsigned long len)
+{
+	const void * const stack = task_stack_page(current);
+	const void * const stackend = stack + THREAD_SIZE;
+
+#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_X86)
+	const void *frame = NULL;
+	const void *oldframe;
+#endif
+
+	if (obj + len < obj)
+		return -1;
+
+	if (obj + len <= stack || stackend <= obj)
+		return 0;
+
+	if (obj < stack || stackend < obj + len)
+		return -1;
+
+#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_X86)
+	oldframe = __builtin_frame_address(1);
+	if (oldframe)
+		frame = __builtin_frame_address(2);
+	/*
+	  low ----------------------------------------------> high
+	  [saved bp][saved ip][args][local vars][saved bp][saved ip]
+			      ^----------------^
+			  allow copies only within here
+	*/
+	while (stack <= frame && frame < stackend) {
+		/* if obj + len extends past the last frame, this
+		   check won't pass and the next frame will be 0,
+		   causing us to bail out and correctly report
+		   the copy as invalid
+		*/
+		if (obj + len <= frame)
+			return obj >= oldframe + 2 * sizeof(void *) ? 2 : -1;
+		oldframe = frame;
+		frame = *(const void * const *)frame;
+	}
+	return -1;
+#else
+	return 1;
+#endif
+}
+
+static inline void gr_handle_kernel_exploit(void) {}
+
+static __noreturn void report_hardusercopy(const void *ptr, unsigned long len,
+						bool to_user, const char *type)
+{
+	if (current->signal->curr_ip)
+		printk(KERN_EMERG "Hard user copy: From %pI4: kernel memory %s attempt detected %s %p (%s) (%lu bytes)\n",
+			&current->signal->curr_ip, to_user ? "leak" : "overwrite", to_user ? "from" : "to", ptr, type ? : "unknown", len);
+	else
+		printk(KERN_EMERG "Hard user copy: kernel memory %s attempt detected %s %p (%s) (%lu bytes)\n",
+			to_user ? "leak" : "overwrite", to_user ? "from" : "to", ptr, type ? : "unknown", len);
+	dump_stack();
+	gr_handle_kernel_exploit();
+	do_group_exit(SIGKILL);
+}
+
+static inline bool check_kernel_text_object(unsigned long low,
+						unsigned long high)
+{
+	unsigned long textlow = (unsigned long)_stext;
+	unsigned long texthigh = (unsigned long)_etext;
+
+#ifdef CONFIG_X86_64
+	/* check against linear mapping as well */
+	if (high > (unsigned long)__va(__pa(textlow)) &&
+	    low < (unsigned long)__va(__pa(texthigh)))
+		return true;
+#endif
+
+	if (high <= textlow || low >= texthigh)
+		return false;
+	else
+		return true;
+}
+
+void __check_object_size(const void *ptr, unsigned long n, bool to_user,
+				bool const_size)
+{
+	const char *type;
+
+#if !defined(CONFIG_STACK_GROWSUP) && !defined(CONFIG_X86_64)
+	unsigned long stackstart = (unsigned long)task_stack_page(current);
+	unsigned long currentsp = (unsigned long)&stackstart;
+	if (unlikely((currentsp < stackstart + 512 ||
+		     currentsp >= stackstart + THREAD_SIZE) && !in_interrupt()))
+		BUG();
+#endif
+
+	if (!n)
+		return;
+
+	type = check_heap_object(ptr, n);
+	if (!type) {
+		int ret = check_stack_object(ptr, n);
+		if (ret == 1 || ret == 2)
+			return;
+		if (ret == 0) {
+			if (check_kernel_text_object((unsigned long)ptr,
+						(unsigned long)ptr + n))
+				type = "<kernel text>";
+			else
+				return;
+		} else
+			type = "<process stack>";
+	}
+
+	report_hardusercopy(ptr, n, to_user, type);
+
+}
+EXPORT_SYMBOL(__check_object_size);
+#endif /* CONFIG_HARDUSERCOPY */
diff --git a/fs/jfs/super.c b/fs/jfs/super.c
index 4cd9798..493668e 100644
--- a/fs/jfs/super.c
+++ b/fs/jfs/super.c
@@ -899,10 +899,17 @@ static int __init init_jfs_fs(void)
 	int i;
 	int rc;
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	jfs_inode_cachep =
+	    kmem_cache_create("jfs_ip", sizeof(struct jfs_inode_info), 0,
+			    SLAB_USERCOPY|SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD,
+			    init_once);
+#else
 	jfs_inode_cachep =
 	    kmem_cache_create("jfs_ip", sizeof(struct jfs_inode_info), 0,
 			    SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD,
 			    init_once);
+#endif
 	if (jfs_inode_cachep == NULL)
 		return -ENOMEM;
 
diff --git a/fs/seq_file.c b/fs/seq_file.c
index 225586e..a468a16 100644
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -30,7 +30,12 @@ static void *seq_buf_alloc(unsigned long size)
 	 * __GFP_NORETRY to avoid oom-killings with high-order allocations -
 	 * it's better to fall back to vmalloc() than to kill things.
 	 */
+#ifdef CONFIG_HARDUSERCOPY
+	buf = kmalloc(size, GFP_KERNEL | GFP_USERCOPY | __GFP_NORETRY |
+				__GFP_NOWARN);
+#else
 	buf = kmalloc(size, GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN);
+#endif
 	if (!buf && size > PAGE_SIZE)
 		buf = vmalloc(size);
 	return buf;
diff --git a/include/linux/gfp.h b/include/linux/gfp.h
index f92cbd2..5807645 100644
--- a/include/linux/gfp.h
+++ b/include/linux/gfp.h
@@ -35,6 +35,10 @@ struct vm_area_struct;
 #define ___GFP_NO_KSWAPD	0x400000u
 #define ___GFP_OTHER_NODE	0x800000u
 #define ___GFP_WRITE		0x1000000u
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+#define ___GFP_USERCOPY         0x2000000u
+#endif
+
 /* If the above are modified, __GFP_BITS_SHIFT may need updating */
 
 /*
@@ -97,6 +101,9 @@ struct vm_area_struct;
 #define __GFP_NO_KSWAPD	((__force gfp_t)___GFP_NO_KSWAPD)
 #define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE) /* On behalf of other node */
 #define __GFP_WRITE	((__force gfp_t)___GFP_WRITE)	/* Allocator intends to dirty page */
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+#define __GFP_USERCOPY	((__force gfp_t)___GFP_USERCOPY)/* Allocator intends to copy page to/from userland */
+#endif
 
 /*
  * This may seem redundant, but it's a way of annotating false positives vs.
@@ -104,7 +111,11 @@ struct vm_area_struct;
  */
 #define __GFP_NOTRACK_FALSE_POSITIVE (__GFP_NOTRACK)
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+#define __GFP_BITS_SHIFT 26	/* Room for N __GFP_FOO bits */
+#else
 #define __GFP_BITS_SHIFT 25	/* Room for N __GFP_FOO bits */
+#endif
 #define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
 
 /* This equals 0, but use constants in case they ever change */
@@ -149,6 +160,10 @@ struct vm_area_struct;
 /* 4GB DMA on some platforms */
 #define GFP_DMA32	__GFP_DMA32
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+#define GFP_USERCOPY         __GFP_USERCOPY
+#endif
+
 /* Convert GFP flags to their corresponding migrate type */
 static inline int gfpflags_to_migratetype(const gfp_t gfp_flags)
 {
diff --git a/include/linux/sched.h b/include/linux/sched.h
index b7b9501..048eea8 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -757,6 +757,9 @@ struct signal_struct {
 #ifdef CONFIG_TASKSTATS
 	struct taskstats *stats;
 #endif
+#ifdef CONFIG_HARDUSERCOPY
+	u32 curr_ip;
+#endif
 #ifdef CONFIG_AUDIT
 	unsigned audit_tty;
 	unsigned audit_tty_log_passwd;
diff --git a/include/linux/slab.h b/include/linux/slab.h
index 7e37d44..b6d311c 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -21,6 +21,9 @@
  * The ones marked DEBUG are only valid if CONFIG_DEBUG_SLAB is set.
  */
 #define SLAB_DEBUG_FREE		0x00000100UL	/* DEBUG: Perform (expensive) checks on free */
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+#define SLAB_USERCOPY           0x00000200UL    /* HARD: Allow copying objs to/from userland */
+#endif
 #define SLAB_RED_ZONE		0x00000400UL	/* DEBUG: Red zone objs in a cache */
 #define SLAB_POISON		0x00000800UL	/* DEBUG: Poison objects */
 #define SLAB_HWCACHE_ALIGN	0x00002000UL	/* Align objs on cache lines */
@@ -144,6 +147,10 @@ void kfree(const void *);
 void kzfree(const void *);
 size_t ksize(const void *);
 
+#ifdef CONFIG_HARDUSERCOPY
+const char *check_heap_object(const void *ptr, unsigned long n);
+#endif
+
 /*
  * Some archs want to perform DMA into kmalloc caches and need a guaranteed
  * alignment larger than the alignment of a 64-bit integer.
@@ -235,6 +242,10 @@ extern struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];
 extern struct kmem_cache *kmalloc_dma_caches[KMALLOC_SHIFT_HIGH + 1];
 #endif
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+extern struct kmem_cache *kmalloc_usercopy_caches[KMALLOC_SHIFT_HIGH + 1];
+#endif
+
 /*
  * Figure out which kmalloc slab an allocation of a certain size
  * belongs to.
diff --git a/include/linux/thread_info.h b/include/linux/thread_info.h
index ff307b5..3449364 100644
--- a/include/linux/thread_info.h
+++ b/include/linux/thread_info.h
@@ -145,6 +145,17 @@ static inline bool test_and_clear_restore_sigmask(void)
 #error "no set_restore_sigmask() provided and default one won't work"
 #endif
 
+#ifdef CONFIG_HARDUSERCOPY
+extern void __check_object_size(const void *ptr, unsigned long n,
+					bool to_user, bool const_size);
+
+static inline void check_object_size(const void *ptr, unsigned long n,
+					bool to_user)
+{
+	__check_object_size(ptr, n, to_user, __builtin_constant_p(n));
+}
+#endif /* CONFIG_HARDUSERCOPY */
+
 #endif	/* __KERNEL__ */
 
 #endif /* _LINUX_THREAD_INFO_H */
diff --git a/ipc/msgutil.c b/ipc/msgutil.c
index 71f448e..818bdd3 100644
--- a/ipc/msgutil.c
+++ b/ipc/msgutil.c
@@ -55,7 +55,11 @@ static struct msg_msg *alloc_msg(size_t len)
 	size_t alen;
 
 	alen = min(len, DATALEN_MSG);
+#ifdef CONFIG_HARDUSERCOPY
+	msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL|GFP_USERCOPY);
+#else
 	msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL);
+#endif
 	if (msg == NULL)
 		return NULL;
 
@@ -67,7 +71,11 @@ static struct msg_msg *alloc_msg(size_t len)
 	while (len > 0) {
 		struct msg_msgseg *seg;
 		alen = min(len, DATALEN_SEG);
+#ifdef CONFIG_HARDUSERCOPY
+		seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL|GFP_USERCOPY);
+#else
 		seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL);
+#endif
 		if (seg == NULL)
 			goto out_err;
 		*pseg = seg;
diff --git a/kernel/fork.c b/kernel/fork.c
index 2845623..519d6b6 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -187,8 +187,13 @@ static void free_thread_info(struct thread_info *ti)
 
 void thread_info_cache_init(void)
 {
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	thread_info_cache = kmem_cache_create("thread_info", THREAD_SIZE,
+					      THREAD_SIZE, SLAB_USERCOPY, NULL);
+#else /* CONFIG_HARDUSERCOPY_SLABS */
 	thread_info_cache = kmem_cache_create("thread_info", THREAD_SIZE,
 					      THREAD_SIZE, 0, NULL);
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
 	BUG_ON(thread_info_cache == NULL);
 }
 # endif
diff --git a/mm/slab.c b/mm/slab.c
index 4fcc5dd..4207a19 100644
--- a/mm/slab.c
+++ b/mm/slab.c
@@ -1451,8 +1451,14 @@ void __init kmem_cache_init(void)
 	 * Initialize the caches that provide memory for the  kmem_cache_node
 	 * structures first.  Without this, further allocations will bug.
 	 */
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",
+				kmalloc_size(INDEX_NODE),
+				SLAB_USERCOPY | ARCH_KMALLOC_FLAGS);
+#else /* CONFIG_HARDUSERCOPY_SLABS */
 	kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",
 				kmalloc_size(INDEX_NODE), ARCH_KMALLOC_FLAGS);
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
 	slab_state = PARTIAL_NODE;
 	setup_kmalloc_cache_index_table();
 
@@ -4238,6 +4244,39 @@ static int __init slab_proc_init(void)
 module_init(slab_proc_init);
 #endif
 
+#ifdef CONFIG_HARDUSERCOPY
+const char *check_heap_object(const void *ptr, unsigned long n)
+{
+	struct page *page;
+	struct kmem_cache *cachep;
+	unsigned int objnr;
+	unsigned long offset;
+
+	if (ZERO_OR_NULL_PTR(ptr))
+		return "<null>";
+
+	if (!virt_addr_valid(ptr))
+		return NULL;
+
+	page = virt_to_head_page(ptr);
+
+	if (!PageSlab(page))
+		return NULL;
+
+	cachep = page->slab_cache;
+	if (!(cachep->flags & SLAB_USERCOPY))
+		return cachep->name;
+
+	objnr = obj_to_index(cachep, page, ptr);
+	BUG_ON(objnr >= cachep->num);
+	offset = ptr - index_to_obj(cachep, page, objnr) - obj_offset(cachep);
+	if (offset <= cachep->object_size && n <= cachep->object_size - offset)
+		return NULL;
+
+	return cachep->name;
+}
+#endif /* CONFIG_HARDUSERCOPY */
+
 /**
  * ksize - get the actual amount of memory allocated for a given object
  * @objp: Pointer to the object
diff --git a/mm/slab.h b/mm/slab.h
index a3a967d..d5307a8 100644
--- a/mm/slab.h
+++ b/mm/slab.h
@@ -114,8 +114,14 @@ static inline unsigned long kmem_cache_flags(unsigned long object_size,
 
 
 /* Legal flag mask for kmem_cache_create(), for various configurations */
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+#define SLAB_CORE_FLAGS (SLAB_HWCACHE_ALIGN | SLAB_CACHE_DMA | SLAB_PANIC | \
+			 SLAB_DESTROY_BY_RCU | SLAB_DEBUG_OBJECTS | \
+			 SLAB_USERCOPY)
+#else
 #define SLAB_CORE_FLAGS (SLAB_HWCACHE_ALIGN | SLAB_CACHE_DMA | SLAB_PANIC | \
 			 SLAB_DESTROY_BY_RCU | SLAB_DEBUG_OBJECTS )
+#endif
 
 #if defined(CONFIG_DEBUG_SLAB)
 #define SLAB_DEBUG_FLAGS (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER)
diff --git a/mm/slab_common.c b/mm/slab_common.c
index 5ce4fae..655cc17d 100644
--- a/mm/slab_common.c
+++ b/mm/slab_common.c
@@ -43,7 +43,11 @@ struct kmem_cache *kmem_cache;
  * Merge control. If this is set then no merging of slab caches will occur.
  * (Could be removed. This was introduced to pacify the merge skeptics.)
  */
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+static int slab_nomerge = 1;
+#else
 static int slab_nomerge;
+#endif
 
 static int __init setup_slab_nomerge(char *str)
 {
@@ -741,6 +745,11 @@ struct kmem_cache *kmalloc_dma_caches[KMALLOC_SHIFT_HIGH + 1];
 EXPORT_SYMBOL(kmalloc_dma_caches);
 #endif
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+struct kmem_cache *kmalloc_usercopy_caches[KMALLOC_SHIFT_HIGH + 1];
+EXPORT_SYMBOL(kmalloc_usercopy_caches);
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
+
 /*
  * Conversion table for small slabs sizes / 8 to the index in the
  * kmalloc array. This is necessary for slabs < 192 since we have non power
@@ -805,6 +814,11 @@ struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
 		return kmalloc_dma_caches[index];
 
 #endif
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	if (unlikely((flags & GFP_USERCOPY)))
+		return kmalloc_usercopy_caches[index];
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
+
 	return kmalloc_caches[index];
 }
 
@@ -897,7 +911,11 @@ void __init create_kmalloc_caches(unsigned long flags)
 
 	for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
 		if (!kmalloc_caches[i])
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+			new_kmalloc_cache(i, SLAB_USERCOPY | flags);
+#else
 			new_kmalloc_cache(i, flags);
+#endif
 
 		/*
 		 * Caches that are not of the two-to-the-power-of size.
@@ -905,9 +923,17 @@ void __init create_kmalloc_caches(unsigned long flags)
 		 * earlier power of two caches
 		 */
 		if (KMALLOC_MIN_SIZE <= 32 && !kmalloc_caches[1] && i == 6)
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+			new_kmalloc_cache(1, SLAB_USERCOPY | flags);
+#else
 			new_kmalloc_cache(1, flags);
+#endif
 		if (KMALLOC_MIN_SIZE <= 64 && !kmalloc_caches[2] && i == 7)
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+			new_kmalloc_cache(2, SLAB_USERCOPY | flags);
+#else
 			new_kmalloc_cache(2, flags);
+#endif
 	}
 
 	/* Kmalloc array is now usable */
@@ -928,6 +954,22 @@ void __init create_kmalloc_caches(unsigned long flags)
 		}
 	}
 #endif
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
+		struct kmem_cache *s = kmalloc_caches[i];
+
+		if (s) {
+			int size = kmalloc_size(i);
+			char *n = kasprintf(GFP_NOWAIT,
+				 "usercopy-kmalloc-%d", size);
+
+			BUG_ON(!n);
+			kmalloc_usercopy_caches[i] = create_kmalloc_cache(n,
+				size, SLAB_USERCOPY | flags);
+		}
+        }
+#endif /* CONFIG_HARDUSERCOPY_SLABS*/
+
 }
 #endif /* !CONFIG_SLOB */
 
diff --git a/mm/slob.c b/mm/slob.c
index 0d7e5df..554cc69 100644
--- a/mm/slob.c
+++ b/mm/slob.c
@@ -501,6 +501,67 @@ void kfree(const void *block)
 }
 EXPORT_SYMBOL(kfree);
 
+#ifdef CONFIG_HARDUSERCOPY
+const char *check_heap_object(const void *ptr, unsigned long n)
+{
+	struct page *page;
+	const slob_t *free;
+	const void *base;
+	unsigned long flags;
+
+	if (ZERO_OR_NULL_PTR(ptr))
+		return "<null>";
+
+	if (!virt_addr_valid(ptr))
+		return NULL;
+
+	page = virt_to_head_page(ptr);
+	if (!PageSlab(page))
+		return NULL;
+
+	if (page->private) {
+		base = page;
+		if (base <= ptr && n <= page->private - (ptr - base))
+			return NULL;
+		return "<slob>";
+	}
+
+	/* some tricky double walking to find the chunk */
+	spin_lock_irqsave(&slob_lock, flags);
+	base = (void *)((unsigned long)ptr & PAGE_MASK);
+	free = page->freelist;
+
+	while (!slob_last(free) && (void *)free <= ptr) {
+		base = free + slob_units(free);
+		free = slob_next(free);
+	}
+
+	while (base < (void *)free) {
+		slobidx_t m = ((slob_t *)base)[0].units, align = ((slob_t *)base)[1].units;
+		int size = SLOB_UNIT * SLOB_UNITS(m + align);
+		int offset;
+
+		if (ptr < base + align)
+			break;
+
+		offset = ptr - base - align;
+		if (offset >= m) {
+			base += size;
+			continue;
+		}
+
+		if (n > m - offset)
+			break;
+
+		spin_unlock_irqrestore(&slob_lock, flags);
+		return NULL;
+	}
+
+	spin_unlock_irqrestore(&slob_lock, flags);
+	return "<slob>";
+}
+#endif /* CONFIG_HARDUSERCOPY */
+
 /* can't use ksize for kmem_cache_alloc memory, only kmalloc */
 size_t ksize(const void *block)
 {
@@ -532,6 +593,53 @@ int __kmem_cache_create(struct kmem_cache *c, unsigned long flags)
 	return 0;
 }
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+static __always_inline void *
+__do_kmalloc_node_align(size_t size, gfp_t gfp, int node, unsigned long caller, int align)
+{
+	slob_t *m;
+	void *ret = NULL;
+
+	gfp &= gfp_allowed_mask;
+
+	lockdep_trace_alloc(gfp);
+
+	if (size < PAGE_SIZE - align) {
+		if (!size)
+			return ZERO_SIZE_PTR;
+
+		m = slob_alloc(size + align, gfp, align, node);
+
+		if (!m)
+			return NULL;
+		BUILD_BUG_ON(ARCH_KMALLOC_MINALIGN < 2 * SLOB_UNIT);
+		BUILD_BUG_ON(ARCH_SLAB_MINALIGN < 2 * SLOB_UNIT);
+		m[0].units = size;
+		m[1].units = align;
+		ret = (void *)m + align;
+
+		trace_kmalloc_node(caller, ret,
+				   size, size + align, gfp, node);
+	} else {
+		unsigned int order = get_order(size);
+		struct page *page;
+
+		if (likely(order))
+			gfp |= __GFP_COMP;
+		page = slob_new_pages(gfp, order, node);
+		if (page) {
+			ret = page_address(page);
+			page->private = size;
+		}
+
+		trace_kmalloc_node(caller, ret,
+				   size, PAGE_SIZE << order, gfp, node);
+	}
+
+	return ret;
+}
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
+
 static void *slob_alloc_node(struct kmem_cache *c, gfp_t flags, int node)
 {
 	void *b;
@@ -540,6 +648,10 @@ static void *slob_alloc_node(struct kmem_cache *c, gfp_t flags, int node)
 
 	lockdep_trace_alloc(flags);
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	b = __do_kmalloc_node_align(c->size, flags, node, _RET_IP_, c->align);
+#else /* CONFIG_HARDUSERCOPY_SLABS */
+
 	if (c->size < PAGE_SIZE) {
 		b = slob_alloc(c->size, flags, c->align, node);
 		trace_kmem_cache_alloc_node(_RET_IP_, b, c->object_size,
@@ -551,6 +663,7 @@ static void *slob_alloc_node(struct kmem_cache *c, gfp_t flags, int node)
 					    PAGE_SIZE << get_order(c->size),
 					    flags, node);
 	}
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
 
 	if (b && c->ctor)
 		c->ctor(b);
@@ -597,6 +710,15 @@ static void kmem_rcu_free(struct rcu_head *head)
 
 void kmem_cache_free(struct kmem_cache *c, void *b)
 {
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	int size = c->size;
+
+	if (size + c->align < PAGE_SIZE) {
+		size += c->align;
+		b -= c->align;
+	}
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
+
 	kmemleak_free_recursive(b, c->flags);
 	if (unlikely(c->flags & SLAB_DESTROY_BY_RCU)) {
 		struct slob_rcu *slob_rcu;
@@ -607,7 +729,11 @@ void kmem_cache_free(struct kmem_cache *c, void *b)
 		__kmem_cache_free(b, c->size);
 	}
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	trace_kfree(_RET_IP_, b);
+#else /* CONFIG_HARDUSERCOPY_SLABS */
 	trace_kmem_cache_free(_RET_IP_, b);
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
 }
 EXPORT_SYMBOL(kmem_cache_free);
 
diff --git a/mm/slub.c b/mm/slub.c
index f614b5d..4ed0635 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -3475,6 +3475,36 @@ void *__kmalloc_node(size_t size, gfp_t flags, int node)
 EXPORT_SYMBOL(__kmalloc_node);
 #endif
 
+#ifdef CONFIG_HARDUSERCOPY
+const char *check_heap_object(const void *ptr, unsigned long n)
+{
+	struct page *page;
+	struct kmem_cache *s;
+	unsigned long offset;
+
+	if (ZERO_OR_NULL_PTR(ptr))
+		return "<null>";
+
+	if (!virt_addr_valid(ptr))
+		return NULL;
+
+	page = virt_to_head_page(ptr);
+
+	if (!PageSlab(page))
+		return NULL;
+
+	s = page->slab_cache;
+	if (!(s->flags & SLAB_USERCOPY))
+		return s->name;
+
+	offset = (ptr - page_address(page)) % s->size;
+	if (offset <= s->object_size && n <= s->object_size - offset)
+		return NULL;
+
+	return s->name;
+}
+#endif /* CONFIG_HARDUSERCOPY */
+
 static size_t __ksize(const void *object)
 {
 	struct page *page;
@@ -4677,6 +4707,14 @@ static ssize_t cache_dma_show(struct kmem_cache *s, char *buf)
 SLAB_ATTR_RO(cache_dma);
 #endif
 
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+static ssize_t usercopy_show(struct kmem_cache *s, char *buf)
+{
+	return sprintf(buf, "%d\n", !!(s->flags & SLAB_USERCOPY));
+}
+SLAB_ATTR_RO(usercopy);
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
+
 static ssize_t destroy_by_rcu_show(struct kmem_cache *s, char *buf)
 {
 	return sprintf(buf, "%d\n", !!(s->flags & SLAB_DESTROY_BY_RCU));
@@ -5019,6 +5057,9 @@ static struct attribute *slab_attrs[] = {
 #ifdef CONFIG_ZONE_DMA
 	&cache_dma_attr.attr,
 #endif
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	&usercopy_attr.attr,
+#endif /* CONFIG_HARDUSERCOPY_SLABS */
 #ifdef CONFIG_NUMA
 	&remote_node_defrag_ratio_attr.attr,
 #endif
@@ -5284,6 +5325,7 @@ static int sysfs_slab_add(struct kmem_cache *s)
 
 	s->kobj.kset = cache_kset(s);
 	err = kobject_init_and_add(&s->kobj, &slab_ktype, NULL, "%s", name);
+
 	if (err)
 		goto out;
 
diff --git a/net/decnet/af_decnet.c b/net/decnet/af_decnet.c
index 675cf94..f8d2803 100644
--- a/net/decnet/af_decnet.c
+++ b/net/decnet/af_decnet.c
@@ -466,6 +466,9 @@ static struct proto dn_proto = {
 	.sysctl_rmem		= sysctl_decnet_rmem,
 	.max_header		= DN_MAX_NSP_DATA_HEADER + 64,
 	.obj_size		= sizeof(struct dn_sock),
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	.slab_flags		= SLAB_USERCOPY,
+#endif
 };
 
 static struct sock *dn_alloc_sock(struct net *net, struct socket *sock, gfp_t gfp, int kern)
diff --git a/security/Kconfig b/security/Kconfig
index e452378..476f203 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -118,6 +118,21 @@ config LSM_MMAP_MIN_ADDR
 	  this low address space will need the permission specific to the
 	  systems running LSM.
 
+config HARDUSERCOPY_SLABS
+	bool "Memory set correctly for harden user copy"
+	default n
+	help
+	  Just for debug now.
+	  If you are unsure as to whether this is required, answer N.
+
+config HARDUSERCOPY
+	bool "Harden user copy"
+	select HARDUSERCOPY_SLABS
+	default n
+	help
+	  Just for debug now.
+	  If you are unsure as to whether this is required, answer N.
+
 source security/selinux/Kconfig
 source security/smack/Kconfig
 source security/tomoyo/Kconfig
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 8db1d93..6d479c1 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -3560,8 +3560,13 @@ int kvm_init(void *opaque, unsigned vcpu_size, unsigned vcpu_align,
 	/* A kmem cache lets us meet the alignment requirements of fx_save. */
 	if (!vcpu_align)
 		vcpu_align = __alignof__(struct kvm_vcpu);
+#ifdef CONFIG_HARDUSERCOPY_SLABS
+	kvm_vcpu_cache = kmem_cache_create("kvm_vcpu", vcpu_size, vcpu_align,
+					   SLAB_USERCOPY, NULL);
+#else
 	kvm_vcpu_cache = kmem_cache_create("kvm_vcpu", vcpu_size, vcpu_align,
 					   0, NULL);
+#endif
 	if (!kvm_vcpu_cache) {
 		r = -ENOMEM;
 		goto out_free_3;
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [kernel-hardening] [PATCH] Subject: [RFC PATCH] mm: Hardened usercopy
  2016-05-19 16:14 [kernel-hardening] [PATCH] Subject: [RFC PATCH] mm: Hardened usercopy casey.schaufler
@ 2016-05-19 19:15 ` Rik van Riel
  2016-05-20 18:22 ` Kees Cook
  1 sibling, 0 replies; 3+ messages in thread
From: Rik van Riel @ 2016-05-19 19:15 UTC (permalink / raw)
  To: kernel-hardening; +Cc: Casey Schaufler

[-- Attachment #1: Type: text/plain, Size: 11320 bytes --]

On Thu, 2016-05-19 at 09:14 -0700, casey.schaufler@intel.com wrote:

I have a bunch of nitpicks for this patch, mostly
to do with code readability and documentation.

> +++ b/arch/x86/include/asm/uaccess_32.h
> @@ -43,6 +43,9 @@ unsigned long __must_check
> __copy_from_user_ll_nocache_nozero
>  static __always_inline unsigned long __must_check
>  __copy_to_user_inatomic(void __user *to, const void *from, unsigned
> long n)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       check_object_size(from, n, true);
> +#endif
>         if (__builtin_constant_p(n)) {
>                 unsigned long ret;

Would it be possible to leave out those ifdefs,
and move all the ifdeffery to the include file?

+++ b/include/linux/something.h
#ifdef CONFIG_HARDUSERCOPY
... what you have now
#else
static inline void check_object_size(const void *ptr, unsigned long n,
bool to_user) { }
#endif


> +++ b/fs/exec.c
> @@ -1749,3 +1749,128 @@ COMPAT_SYSCALL_DEFINE5(execveat, int, fd,
>  				  argv, envp, flags);
>  }
>  #endif
> +
> +#ifdef CONFIG_HARDUSERCOPY
> +/*

Maybe add "Check whether an object is inside the stack" ?

> + *	0: not at all,
> + *	1: fully,
> + *	2: fully inside frame,
> + *	-1: partially (implies an error)
> + */
> +static noinline int check_stack_object(const void *obj, unsigned
> long len)
> +{
> +	const void * const stack = task_stack_page(current);
> +	const void * const stackend = stack + THREAD_SIZE;
> +
> +#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_X86)
> +	const void *frame = NULL;
> +	const void *oldframe;
> +#endif
> +
> +	if (obj + len < obj)
> +		return -1;
> +
> +	if (obj + len <= stack || stackend <= obj)
> +		return 0;
> +
> +	if (obj < stack || stackend < obj + len)
> +		return -1;
> +
> +#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_X86)
> +	oldframe = __builtin_frame_address(1);
> +	if (oldframe)
> +		frame = __builtin_frame_address(2);
> +	/*
> +	  low ----------------------------------------------> high
> +	  [saved bp][saved ip][args][local vars][saved bp][saved ip]
> +			      ^----------------^
> +			  allow copies only within here
> +	*/
> +	while (stack <= frame && frame < stackend) {
> +		/* if obj + len extends past the last frame, this
> +		   check won't pass and the next frame will be 0,
> +		   causing us to bail out and correctly report
> +		   the copy as invalid
> +		*/
> +		if (obj + len <= frame)
> +			return obj >= oldframe + 2 * sizeof(void *)
> ? 2 : -1;
> +		oldframe = frame;
> +		frame = *(const void * const *)frame;
> +	}
> +	return -1;
> +#else
> +	return 1;
> +#endif
> +}



> +static inline bool check_kernel_text_object(unsigned long low,
> +						unsigned long high)
> +{
> +	unsigned long textlow = (unsigned long)_stext;
> +	unsigned long texthigh = (unsigned long)_etext;
> +
> +#ifdef CONFIG_X86_64
> +	/* check against linear mapping as well */
> +	if (high > (unsigned long)__va(__pa(textlow)) &&
> +	    low < (unsigned long)__va(__pa(texthigh)))
> +		return true;
> +#endif
> +
> +	if (high <= textlow || low >= texthigh)
> +		return false;
> +	else
> +		return true;

Could this be simplified to the following?

	/* Object overlaps with kernel text */
	return (high > textlow && low < texthigh);


The following function could use a bunch of documentation,
too.

   /* Is ptr a valid location for copy_from_user of size n? */

> +void __check_object_size(const void *ptr, unsigned long n, bool
> to_user,
> +				bool const_size)
> +{
> +	const char *type;
> +
> +#if !defined(CONFIG_STACK_GROWSUP) && !defined(CONFIG_X86_64)
> +	unsigned long stackstart = (unsigned
> long)task_stack_page(current);
> +	unsigned long currentsp = (unsigned long)&stackstart;
> +	if (unlikely((currentsp < stackstart + 512 ||
> +		     currentsp >= stackstart + THREAD_SIZE) &&
> !in_interrupt()))
> +		BUG();
> +#endif
> +
> +	if (!n)
> +		return;
> +
> +	type = check_heap_object(ptr, n);

Maybe rename "type" to "err", to indicate that if
check_heap_object returned non-zero, there already
is an error detected, and it makes no sense to check
the rest?

The following seems a little convoluted. This appears
to be for good reasons, but could use some comments.

> +	if (!type) {
> +		int ret = check_stack_object(ptr, n);
> +		if (ret == 1 || ret == 2)
> +			return;
> +		if (ret == 0) {
			/*
			 * Object is not on the stack. Make sure
			 * it is not inside the kernel text.
			 */
> +			if (check_kernel_text_object((unsigned
> long)ptr,
> +						(unsigned long)ptr +
> n))

Why does check_kernel_text_object() not take "ptr, n" as
its arguments, like all the other functions here?

Some consistency may make it easier for people to see
what is going on.

> +				type = "<kernel text>";
> +			else
				/* Valid copy_from_user object */
> +				return;
> +		} else
> +			type = "<process stack>";
> +	}
> +
> +	report_hardusercopy(ptr, n, to_user, type);
> +
> +}

> 
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index b7b9501..048eea8 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -757,6 +757,9 @@ struct signal_struct {
>  #ifdef CONFIG_TASKSTATS
>  	struct taskstats *stats;
>  #endif
> +#ifdef CONFIG_HARDUSERCOPY
> +	u32 curr_ip;
> +#endif

This is not big enough to hold an instruction
pointer on a 64 bit architecture.

However, it also appears to be unused in your
patch, so you may be able to just get rid of it,
and the code that checks for it in report_hardusercopy()

> +++ b/include/linux/thread_info.h
> @@ -145,6 +145,17 @@ static inline bool
> test_and_clear_restore_sigmask(void)
>  #error "no set_restore_sigmask() provided and default one won't
> work"
>  #endif
>  
> +#ifdef CONFIG_HARDUSERCOPY
> +extern void __check_object_size(const void *ptr, unsigned long n,
> +					bool to_user, bool
> const_size);
> +
> +static inline void check_object_size(const void *ptr, unsigned long
> n,
> +					bool to_user)
> +{
> +	__check_object_size(ptr, n, to_user,
> __builtin_constant_p(n));
> +}
  #else
  static inline void check_object_size(const void *ptr, unsigned long
n,
					bool to_user) {}
> +#endif /* CONFIG_HARDUSERCOPY */
> +
>  #endif	/* __KERNEL__ */
>  
>  #endif /* _LINUX_THREAD_INFO_H */

Here is another function that could use some documentation.

I wrote up a comment documenting what it does, but I am not
sure why copy_from_user is not allowed to vmalloced kernel
objects.  Is there a policy reason for this, or is it just a
happy coincidence that it does not break anything currently?

> +#ifdef CONFIG_HARDUSERCOPY
  /*
   * Ensure that ptr is a valid copy_to_user destination.
   * Disallow copies to slab caches without SLAB_USERCOPY
   * set, that overflow slab objects, or to vmalloc areas.
   * There are additional checks in __check_object_size for
   * errors not detected here.
   */
> +const char *check_heap_object(const void *ptr, unsigned long n)
> +{
> +	struct page *page;
> +	struct kmem_cache *cachep;
> +	unsigned int objnr;
> +	unsigned long offset;
> +
> +	if (ZERO_OR_NULL_PTR(ptr))
> +		return "<null>";
> +
> +	if (!virt_addr_valid(ptr))
> +		return NULL;
> +
> +	page = virt_to_head_page(ptr);
> +
> +	if (!PageSlab(page))
> +		return NULL;
> +
> +	cachep = page->slab_cache;
> +	if (!(cachep->flags & SLAB_USERCOPY))
> +		return cachep->name;
> +
> +	objnr = obj_to_index(cachep, page, ptr);
> +	BUG_ON(objnr >= cachep->num);
> +	offset = ptr - index_to_obj(cachep, page, objnr) -
> obj_offset(cachep);
> +	if (offset <= cachep->object_size && n <= cachep-
> >object_size - offset)
> +		return NULL;
> +
> +	return cachep->name;
> +}
> +#endif /* CONFIG_HARDUSERCOPY */
> +
>  /**
>   * ksize - get the actual amount of memory allocated for a given
> object
>   * @objp: Pointer to the object


> diff --git a/mm/slab_common.c b/mm/slab_common.c
> index 5ce4fae..655cc17d 100644
> --- a/mm/slab_common.c
> +++ b/mm/slab_common.c
> @@ -43,7 +43,11 @@ struct kmem_cache *kmem_cache;
>   * Merge control. If this is set then no merging of slab caches will
> occur.
>   * (Could be removed. This was introduced to pacify the merge
> skeptics.)
>   */
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +static int slab_nomerge = 1;
> +#else
>  static int slab_nomerge;
> +#endif

This disables merging of all slabs just because there
may be a few slabs with SLAB_USERCOPY set. This seems
like overkill.

Simply adding SLAB_USERCOPY to "SLAB_MERGE_SAME" should
prevent the merging of slabs with SLAB_USERCOPY set with
slab caches that do not have that flag set, and should
result in the same security benefits without losing
functionality.


The following could use a comment:

> @@ -805,6 +814,11 @@ struct kmem_cache *kmalloc_slab(size_t size,
> gfp_t flags)
>  		return kmalloc_dma_caches[index];
>  
>  #endif
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
        /* Separate slab caches for copy_from_user */
> +	if (unlikely((flags & GFP_USERCOPY)))
> +		return kmalloc_usercopy_caches[index];
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
> +
>  	return kmalloc_caches[index];
>  }

The code added to slub.c and slob.c could use similar
commenting to the slab.c comments above.

> +++ b/mm/slub.c
> @@ -3475,6 +3475,36 @@ void *__kmalloc_node(size_t size, gfp_t flags,
> int node)
>  EXPORT_SYMBOL(__kmalloc_node);
>  #endif
>  
> +#ifdef CONFIG_HARDUSERCOPY
> +const char *check_heap_object(const void *ptr, unsigned long n)
> +{
> +	struct page *page;
> +	struct kmem_cache *s;
> +	unsigned long offset;
> +
> +	if (ZERO_OR_NULL_PTR(ptr))
> +		return "<null>";
> +
> +	if (!virt_addr_valid(ptr))
> +		return NULL;
> +
> +	page = virt_to_head_page(ptr);

Wait a moment.  Everything above the
if (!PageSlab(page)) code is identical between
slab, slob, and slub.

Would it make sense to move that common code into
__check_object_size, and have a check_slab_object
check that is called if (PageSlab(page)) ?

That way slab.c, slob.c, and slub.c only have their
own unique code, and not this replication between them.

Having the common code in __check_object_size is likely
to end up more readable, too.

> +	if (!PageSlab(page))
> +		return NULL;
> +
> +	s = page->slab_cache;

Additionally, having the SLAB_USERCOPY flag set on any
slab caches at all is a function of CONFIG_HARDUSERCOPY_SLABS.

By not having the check below under an ifdef, does the code
always disallow copy_from_user to slab caches when that
config option is not set?

In other words, does disabling CONFIG_HARDUSERCOPY_SLABS
result in stricter controls (and possibly a brokne system)?

> +	if (!(s->flags & SLAB_USERCOPY))
> +		return s->name;
> +
> +	offset = (ptr - page_address(page)) % s->size;
> +	if (offset <= s->object_size && n <= s->object_size -
> offset)
> +		return NULL;
> +
> +	return s->name;
> +}
> +#endif /* CONFIG_HARDUSERCOPY */
> +
>  static size_t __ksize(const void *object)
>  {
>  	struct page *page;

-- 
All rights reversed

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 473 bytes --]

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [kernel-hardening] [PATCH] Subject: [RFC PATCH] mm: Hardened usercopy
  2016-05-19 16:14 [kernel-hardening] [PATCH] Subject: [RFC PATCH] mm: Hardened usercopy casey.schaufler
  2016-05-19 19:15 ` Rik van Riel
@ 2016-05-20 18:22 ` Kees Cook
  1 sibling, 0 replies; 3+ messages in thread
From: Kees Cook @ 2016-05-20 18:22 UTC (permalink / raw)
  To: kernel-hardening; +Cc: Casey Schaufler

On Thu, May 19, 2016 at 9:14 AM,  <casey.schaufler@intel.com> wrote:
> From: Casey Schaufler <casey.schaufler@intel.com>
>
> This is a work in progress, first stage of porting
> the PAX_USERCOPY feature for upstream consumption.
> The work isn't complete, but this should provide the
> basic structure and initial range checking. There
> are known gaps:
>
>         - The SLOB memory allocator does not work
>           on x86, however the grsecurity version does
>           not work there either
>         - This is relative to the 4.3.3 kernel, same
>           as the publicly released version of
>           grsecurity that serves as its model.
>         - I don't have a good set of test cases.
>           Any pointers would be greatly appreciated.
>           I am not myself especially clever, and the
>           sort of things you have to do to generate
>           exploits do not come naturally to me.

So, we just need to add some tests to lkdtm to try stuff like:

user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
                          PROT_READ | PROT_WRITE | PROT_EXEC,
                          MAP_ANONYMOUS | MAP_PRIVATE, 0);
one = kmalloc(1024, GFP_KERNEL);
two = kmalloc(1024, GFP_KERNEL);

memset(one, 'A', 1024);
memset(two, 'B', 1024);

copy_to_user(user_addr, one, 2048);

This should explode, since "one" isn't 2048 long.

And for stack frames, we could construct a call chain like:

noinline int callee(char * __user buf, unsigned long junk)
{
    unsigned long stuff = junk * junk;

    return copy_to_user(buf, &stuff, 1024);
}

int caller(void)
{
    user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
                          PROT_READ | PROT_WRITE | PROT_EXEC,
                          MAP_ANONYMOUS | MAP_PRIVATE, 0);
    callee(user_addr, (unsigned long)user_addr);
}

The copy_to_user exceeds the size of the stack frame on callee and
should explode.

>
> Further, there are questions about the way I've done
> the port.
>
?>         - The hard usercopy implementation is fully
>           ifdefed, whereas the grsecurity version makes
>           wholesale changes to the base code. I'm open
>           to opinions regarding which would be preferred
>           upstream.

As already suggested, I think it's better to hide the ifdefs in the
header files and leave the new function calls in the routines.

>         - In part because of the above, it's possible
>           that I've missed important bits. Reports of
>           glaring ommissions (or sins of commission)
>           are always welcome.

Were there any thing you had to leave out, or were otherwise tricky to extract?

>         - Attestation. How best to ensure the the original
>           authors get what they deserve.

I tend to keep it simple and direct. I usually end up with things that
would read like this:

Based on PAX_USERCOPY from PaX Team.

>
> This is definitely RFC territory. Please C away.
>
> Signed-off-by: Casey Schaufler <casey.schaufler@intel.com>
> ---
>  arch/arm/include/asm/uaccess.h      |   6 ++
>  arch/ia64/include/asm/uaccess.h     |  25 +++++++
>  arch/powerpc/include/asm/uaccess.h  |  35 ++++++++++
>  arch/sparc/include/asm/uaccess_32.h |  20 ++++++
>  arch/sparc/include/asm/uaccess_64.h |  19 ++++++
>  arch/x86/include/asm/uaccess_32.h   |   6 ++
>  arch/x86/include/asm/uaccess_64.h   |   3 +
>  fs/cifs/cifsfs.c                    |  13 ++++
>  fs/dcache.c                         |   5 ++
>  fs/exec.c                           | 125 +++++++++++++++++++++++++++++++++++
>  fs/jfs/super.c                      |   7 ++
>  fs/seq_file.c                       |   5 ++
>  include/linux/gfp.h                 |  15 +++++
>  include/linux/sched.h               |   3 +
>  include/linux/slab.h                |  11 ++++
>  include/linux/thread_info.h         |  11 ++++
>  ipc/msgutil.c                       |   8 +++
>  kernel/fork.c                       |   5 ++
>  mm/slab.c                           |  39 +++++++++++
>  mm/slab.h                           |   6 ++
>  mm/slab_common.c                    |  42 ++++++++++++
>  mm/slob.c                           | 126 ++++++++++++++++++++++++++++++++++++
>  mm/slub.c                           |  42 ++++++++++++
>  net/decnet/af_decnet.c              |   3 +
>  security/Kconfig                    |  15 +++++
>  virt/kvm/kvm_main.c                 |   5 ++
>  26 files changed, 600 insertions(+)
>
> diff --git a/arch/arm/include/asm/uaccess.h b/arch/arm/include/asm/uaccess.h
> index 8cc85a4..dcb71c4 100644
> --- a/arch/arm/include/asm/uaccess.h
> +++ b/arch/arm/include/asm/uaccess.h
> @@ -497,6 +497,9 @@ static inline unsigned long __must_check
>  __copy_from_user(void *to, const void __user *from, unsigned long n)
>  {
>         unsigned int __ua_flags = uaccess_save_and_enable();
> +#ifdef CONFIG_HARDUSERCOPY
> +       check_object_size(to, n, false);
> +#endif
>         n = arm_copy_from_user(to, from, n);
>         uaccess_restore(__ua_flags);
>         return n;
> @@ -511,6 +514,9 @@ static inline unsigned long __must_check
>  __copy_to_user(void __user *to, const void *from, unsigned long n)
>  {
>         unsigned int __ua_flags = uaccess_save_and_enable();
> +#ifdef CONFIG_HARDUSERCOPY
> +       check_object_size(to, n, false);
> +#endif
>         n = arm_copy_to_user(to, from, n);
>         uaccess_restore(__ua_flags);
>         return n;
> diff --git a/arch/ia64/include/asm/uaccess.h b/arch/ia64/include/asm/uaccess.h
> index 4f3fb6cc..e5ddd11 100644
> --- a/arch/ia64/include/asm/uaccess.h
> +++ b/arch/ia64/include/asm/uaccess.h
> @@ -241,12 +241,21 @@ extern unsigned long __must_check __copy_user (void __user *to, const void __use
>  static inline unsigned long
>  __copy_to_user (void __user *to, const void *from, unsigned long count)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (!__builtin_constant_p(count))
> +               check_object_size(from, count, true);
> +#endif
> +
>         return __copy_user(to, (__force void __user *) from, count);
>  }
>
>  static inline unsigned long
>  __copy_from_user (void *to, const void __user *from, unsigned long count)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (!__builtin_constant_p(count))
> +               check_object_size(from, count, true);
> +#endif
>         return __copy_user((__force void __user *) to, from, count);
>  }
>
> @@ -258,8 +267,16 @@ __copy_from_user (void *to, const void __user *from, unsigned long count)
>         const void *__cu_from = (from);                                                 \
>         long __cu_len = (n);                                                            \
>                                                                                         \
> +#ifdef CONFIG_HARDUSERCOPY                                                             \
> +       if (__access_ok(__cu_from, __cu_len, get_fs())) {                               \
> +               if (!__builtin_constant_p(n))                                           \
> +                       check_object_size(__cu_from, __cu_len, true);                   \
> +               __cu_len = __copy_user((__force void __user *) __cu_to, __cu_from, __cu_len);   \
> +       }                                                                               \
> +#else                                                                                  \
>         if (__access_ok(__cu_to, __cu_len, get_fs()))                                   \
>                 __cu_len = __copy_user(__cu_to, (__force void __user *) __cu_from, __cu_len);   \
> +#endif                                                                                 \
>         __cu_len;                                                                       \
>  })
>
> @@ -270,8 +287,16 @@ __copy_from_user (void *to, const void __user *from, unsigned long count)
>         long __cu_len = (n);                                                            \
>                                                                                         \
>         __chk_user_ptr(__cu_from);                                                      \
> +#ifdef CONFIG_HARDUSERCOPY                                                             \
> +       if (__access_ok(__cu_from, __cu_len, get_fs())) {                               \
> +               if (!__builtin_constant_p(n))                                           \
> +                       check_object_size(__cu_to, __cu_len, false);                    \
> +               __cu_len = __copy_user((__force void __user *) __cu_to, __cu_from, __cu_len);   \
> +       }                                                                               \
> +#else                                                                                  \
>         if (__access_ok(__cu_from, __cu_len, get_fs()))                                 \
>                 __cu_len = __copy_user((__force void __user *) __cu_to, __cu_from, __cu_len);   \
> +#endif                                                                                 \
>         __cu_len;                                                                       \
>  })
>
> diff --git a/arch/powerpc/include/asm/uaccess.h b/arch/powerpc/include/asm/uaccess.h
> index 2a8ebae..08f83c9 100644
> --- a/arch/powerpc/include/asm/uaccess.h
> +++ b/arch/powerpc/include/asm/uaccess.h
> @@ -325,10 +325,22 @@ static inline unsigned long copy_from_user(void *to,
>  {
>         unsigned long over;
>
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (access_ok(VERIFY_READ, from, n)) {
> +               if (!__builtin_constant_p(n))
> +                       check_object_size(to, n, false);

Are these safe from the gcc bug that has wrecked
CONFIG_DEBUG_STRICT_USER_COPY_CHECKS?

http://www.openwall.com/lists/kernel-hardening/2015/11/20/2

> +               return __copy_tofrom_user((__force void __user *)to, from, n);
> +       }
> +#else
>         if (access_ok(VERIFY_READ, from, n))
>                 return __copy_tofrom_user((__force void __user *)to, from, n);
> +#endif
>         if ((unsigned long)from < TASK_SIZE) {
>                 over = (unsigned long)from + n - TASK_SIZE;
> +#ifdef CONFIG_HARDUSERCOPY
> +               if (!__builtin_constant_p(n - over))
> +                       check_object_size(to, n - over, false);
> +#endif
>                 return __copy_tofrom_user((__force void __user *)to, from,
>                                 n - over) + over;
>         }
> @@ -340,10 +352,22 @@ static inline unsigned long copy_to_user(void __user *to,
>  {
>         unsigned long over;
>
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (access_ok(VERIFY_WRITE, to, n)) {
> +               if (!__builtin_constant_p(n))
> +                       check_object_size(from, n, true);
> +               return __copy_tofrom_user(to, (__force void __user *)from, n);
> +       }
> +#else
>         if (access_ok(VERIFY_WRITE, to, n))
>                 return __copy_tofrom_user(to, (__force void __user *)from, n);
> +#endif
>         if ((unsigned long)to < TASK_SIZE) {
>                 over = (unsigned long)to + n - TASK_SIZE;
> +#ifdef CONFIG_HARDUSERCOPY
> +               if (!__builtin_constant_p(n))
> +                       check_object_size(from, n - over, true);
> +#endif
>                 return __copy_tofrom_user(to, (__force void __user *)from,
>                                 n - over) + over;
>         }
> @@ -387,6 +411,12 @@ static inline unsigned long __copy_from_user_inatomic(void *to,
>                 if (ret == 0)
>                         return 0;
>         }
> +
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (!__builtin_constant_p(n))
> +               check_object_size(to, n, false);
> +#endif
> +
>         return __copy_tofrom_user((__force void __user *)to, from, n);
>  }
>
> @@ -413,6 +443,11 @@ static inline unsigned long __copy_to_user_inatomic(void __user *to,
>                 if (ret == 0)
>                         return 0;
>         }
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (!__builtin_constant_p(n))
> +               check_object_size(from, n, true);
> +#endif
> +
>         return __copy_tofrom_user(to, (__force const void __user *)from, n);
>  }
>
> diff --git a/arch/sparc/include/asm/uaccess_32.h b/arch/sparc/include/asm/uaccess_32.h
> index 64ee103..a8c10ad 100644
> --- a/arch/sparc/include/asm/uaccess_32.h
> +++ b/arch/sparc/include/asm/uaccess_32.h
> @@ -313,22 +313,42 @@ unsigned long __copy_user(void __user *to, const void __user *from, unsigned lon
>
>  static inline unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (n && __access_ok((unsigned long) to, n)) {
> +               if (!__builtin_constant_p(n))
> +                       check_object_size(from, n, true);
> +               return __copy_user(to, (__force void __user *) from, n);
> +       } else
> +#else
>         if (n && __access_ok((unsigned long) to, n))
>                 return __copy_user(to, (__force void __user *) from, n);
>         else
> +#endif
>                 return n;
>  }
>
>  static inline unsigned long __copy_to_user(void __user *to, const void *from, unsigned long n)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (!__builtin_constant_p(n))
> +               check_object_size(from, n, true);
> +#endif
>         return __copy_user(to, (__force void __user *) from, n);
>  }
>
>  static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (n && __access_ok((unsigned long) from, n)) {
> +               if (!__builtin_constant_p(n))
> +                       check_object_size(to, n, false);
> +               return __copy_user((__force void __user *) to, from, n);
> +       } else
> +#else
>         if (n && __access_ok((unsigned long) from, n))
>                 return __copy_user((__force void __user *) to, from, n);
>         else
> +#endif
>                 return n;
>  }
>
> diff --git a/arch/sparc/include/asm/uaccess_64.h b/arch/sparc/include/asm/uaccess_64.h
> index ea6e9a2..58d5e0a 100644
> --- a/arch/sparc/include/asm/uaccess_64.h
> +++ b/arch/sparc/include/asm/uaccess_64.h
> @@ -250,8 +250,18 @@ unsigned long copy_from_user_fixup(void *to, const void __user *from,
>  static inline unsigned long __must_check
>  copy_from_user(void *to, const void __user *from, unsigned long size)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       unsigned long ret;
> +#else
>         unsigned long ret = ___copy_from_user(to, from, size);
> +#endif
> +
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (!__builtin_constant_p(size))
> +               check_object_size(to, size, false);
>
> +       ret = ___copy_from_user(to, from, size);
> +#endif
>         if (unlikely(ret))
>                 ret = copy_from_user_fixup(to, from, size);
>
> @@ -267,8 +277,17 @@ unsigned long copy_to_user_fixup(void __user *to, const void *from,
>  static inline unsigned long __must_check
>  copy_to_user(void __user *to, const void *from, unsigned long size)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       unsigned long ret;
> +#else
>         unsigned long ret = ___copy_to_user(to, from, size);
> +#endif
>
> +#ifdef CONFIG_HARDUSERCOPY
> +       if (!__builtin_constant_p(size))
> +               check_object_size(from, size, true);
> +       ret = ___copy_to_user(to, from, size);
> +#endif
>         if (unlikely(ret))
>                 ret = copy_to_user_fixup(to, from, size);
>         return ret;
> diff --git a/arch/x86/include/asm/uaccess_32.h b/arch/x86/include/asm/uaccess_32.h
> index f5dcb52..8b6a3b6 100644
> --- a/arch/x86/include/asm/uaccess_32.h
> +++ b/arch/x86/include/asm/uaccess_32.h
> @@ -43,6 +43,9 @@ unsigned long __must_check __copy_from_user_ll_nocache_nozero
>  static __always_inline unsigned long __must_check
>  __copy_to_user_inatomic(void __user *to, const void *from, unsigned long n)
>  {
> +#ifdef CONFIG_HARDUSERCOPY
> +       check_object_size(from, n, true);
> +#endif
>         if (__builtin_constant_p(n)) {
>                 unsigned long ret;
>
> @@ -143,6 +146,9 @@ static __always_inline unsigned long
>  __copy_from_user(void *to, const void __user *from, unsigned long n)
>  {
>         might_fault();
> +#ifdef CONFIG_HARDUSERCOPY
> +       check_object_size(to, n, false);
> +#endif
>         if (__builtin_constant_p(n)) {
>                 unsigned long ret;
>
> diff --git a/arch/x86/include/asm/uaccess_64.h b/arch/x86/include/asm/uaccess_64.h
> index f2f9b39..673dcef 100644
> --- a/arch/x86/include/asm/uaccess_64.h
> +++ b/arch/x86/include/asm/uaccess_64.h
> @@ -53,6 +53,9 @@ int __copy_from_user_nocheck(void *dst, const void __user *src, unsigned size)
>  {
>         int ret = 0;
>
> +#ifdef CONFIG_HARDUSERCOPY
> +       check_object_size(dst, size, false);
> +#endif
>         if (!__builtin_constant_p(size))
>                 return copy_user_generic(dst, (__force void *)src, size);
>         switch (size) {
> diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
> index e739950..cf3133e 100644
> --- a/fs/cifs/cifsfs.c
> +++ b/fs/cifs/cifsfs.c
> @@ -1083,9 +1083,16 @@ cifs_init_request_bufs(void)
>         cifs_dbg(VFS, "CIFSMaxBufSize %d 0x%x\n",
>                  CIFSMaxBufSize, CIFSMaxBufSize);
>  */
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       cifs_req_cachep = kmem_cache_create("cifs_request",
> +                                           CIFSMaxBufSize + max_hdr_size, 0,
> +                                           SLAB_HWCACHE_ALIGN|SLAB_USERCOPY,
> +                                           NULL);
> +#else
>         cifs_req_cachep = kmem_cache_create("cifs_request",
>                                             CIFSMaxBufSize + max_hdr_size, 0,
>                                             SLAB_HWCACHE_ALIGN, NULL);
> +#endif
>         if (cifs_req_cachep == NULL)
>                 return -ENOMEM;
>
> @@ -1111,9 +1118,15 @@ cifs_init_request_bufs(void)
>         more SMBs to use small buffer alloc and is still much more
>         efficient to alloc 1 per page off the slab compared to 17K (5page)
>         alloc of large cifs buffers even when page debugging is on */
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       cifs_sm_req_cachep = kmem_cache_create("cifs_small_rq",
> +                       MAX_CIFS_SMALL_BUFFER_SIZE, 0,
> +                       SLAB_USERCOPY | SLAB_HWCACHE_ALIGN, NULL);
> +#else
>         cifs_sm_req_cachep = kmem_cache_create("cifs_small_rq",
>                         MAX_CIFS_SMALL_BUFFER_SIZE, 0, SLAB_HWCACHE_ALIGN,
>                         NULL);
> +#endif
>         if (cifs_sm_req_cachep == NULL) {
>                 mempool_destroy(cifs_req_poolp);
>                 kmem_cache_destroy(cifs_req_cachep);
> diff --git a/fs/dcache.c b/fs/dcache.c
> index 5c33aeb..f6e0f58 100644
> --- a/fs/dcache.c
> +++ b/fs/dcache.c
> @@ -3450,8 +3450,13 @@ void __init vfs_caches_init_early(void)
>
>  void __init vfs_caches_init(void)
>  {
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
> +                       SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_USERCOPY, NULL);
> +#else /* CONFIG_HARDUSERCOPY_SLABS */
>         names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
>                         SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
>
>         dcache_init();
>         inode_init();
> diff --git a/fs/exec.c b/fs/exec.c
> index b06623a..0e67eb2 100644
> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -1749,3 +1749,128 @@ COMPAT_SYSCALL_DEFINE5(execveat, int, fd,
>                                   argv, envp, flags);
>  }
>  #endif
> +
> +#ifdef CONFIG_HARDUSERCOPY
> +/*
> + *     0: not at all,
> + *     1: fully,
> + *     2: fully inside frame,
> + *     -1: partially (implies an error)
> + */
> +static noinline int check_stack_object(const void *obj, unsigned long len)
> +{
> +       const void * const stack = task_stack_page(current);
> +       const void * const stackend = stack + THREAD_SIZE;
> +
> +#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_X86)
> +       const void *frame = NULL;
> +       const void *oldframe;
> +#endif
> +
> +       if (obj + len < obj)
> +               return -1;
> +
> +       if (obj + len <= stack || stackend <= obj)
> +               return 0;
> +
> +       if (obj < stack || stackend < obj + len)
> +               return -1;
> +
> +#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_X86)
> +       oldframe = __builtin_frame_address(1);
> +       if (oldframe)
> +               frame = __builtin_frame_address(2);
> +       /*
> +         low ----------------------------------------------> high
> +         [saved bp][saved ip][args][local vars][saved bp][saved ip]
> +                             ^----------------^
> +                         allow copies only within here
> +       */
> +       while (stack <= frame && frame < stackend) {
> +               /* if obj + len extends past the last frame, this
> +                  check won't pass and the next frame will be 0,
> +                  causing us to bail out and correctly report
> +                  the copy as invalid
> +               */
> +               if (obj + len <= frame)
> +                       return obj >= oldframe + 2 * sizeof(void *) ? 2 : -1;
> +               oldframe = frame;
> +               frame = *(const void * const *)frame;
> +       }
> +       return -1;
> +#else
> +       return 1;
> +#endif
> +}
> +
> +static inline void gr_handle_kernel_exploit(void) {}
> +
> +static __noreturn void report_hardusercopy(const void *ptr, unsigned long len,
> +                                               bool to_user, const char *type)
> +{
> +       if (current->signal->curr_ip)
> +               printk(KERN_EMERG "Hard user copy: From %pI4: kernel memory %s attempt detected %s %p (%s) (%lu bytes)\n",
> +                       &current->signal->curr_ip, to_user ? "leak" : "overwrite", to_user ? "from" : "to", ptr, type ? : "unknown", len);
> +       else
> +               printk(KERN_EMERG "Hard user copy: kernel memory %s attempt detected %s %p (%s) (%lu bytes)\n",
> +                       to_user ? "leak" : "overwrite", to_user ? "from" : "to", ptr, type ? : "unknown", len);
> +       dump_stack();
> +       gr_handle_kernel_exploit();
> +       do_group_exit(SIGKILL);
> +}
> +
> +static inline bool check_kernel_text_object(unsigned long low,
> +                                               unsigned long high)
> +{
> +       unsigned long textlow = (unsigned long)_stext;
> +       unsigned long texthigh = (unsigned long)_etext;
> +
> +#ifdef CONFIG_X86_64
> +       /* check against linear mapping as well */
> +       if (high > (unsigned long)__va(__pa(textlow)) &&
> +           low < (unsigned long)__va(__pa(texthigh)))
> +               return true;
> +#endif
> +
> +       if (high <= textlow || low >= texthigh)
> +               return false;
> +       else
> +               return true;
> +}
> +
> +void __check_object_size(const void *ptr, unsigned long n, bool to_user,
> +                               bool const_size)
> +{
> +       const char *type;
> +
> +#if !defined(CONFIG_STACK_GROWSUP) && !defined(CONFIG_X86_64)
> +       unsigned long stackstart = (unsigned long)task_stack_page(current);
> +       unsigned long currentsp = (unsigned long)&stackstart;
> +       if (unlikely((currentsp < stackstart + 512 ||
> +                    currentsp >= stackstart + THREAD_SIZE) && !in_interrupt()))
> +               BUG();
> +#endif
> +
> +       if (!n)
> +               return;
> +
> +       type = check_heap_object(ptr, n);
> +       if (!type) {
> +               int ret = check_stack_object(ptr, n);
> +               if (ret == 1 || ret == 2)
> +                       return;
> +               if (ret == 0) {
> +                       if (check_kernel_text_object((unsigned long)ptr,
> +                                               (unsigned long)ptr + n))
> +                               type = "<kernel text>";
> +                       else
> +                               return;
> +               } else
> +                       type = "<process stack>";
> +       }
> +
> +       report_hardusercopy(ptr, n, to_user, type);
> +
> +}
> +EXPORT_SYMBOL(__check_object_size);
> +#endif /* CONFIG_HARDUSERCOPY */
> diff --git a/fs/jfs/super.c b/fs/jfs/super.c
> index 4cd9798..493668e 100644
> --- a/fs/jfs/super.c
> +++ b/fs/jfs/super.c
> @@ -899,10 +899,17 @@ static int __init init_jfs_fs(void)
>         int i;
>         int rc;
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       jfs_inode_cachep =
> +           kmem_cache_create("jfs_ip", sizeof(struct jfs_inode_info), 0,
> +                           SLAB_USERCOPY|SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD,
> +                           init_once);
> +#else
>         jfs_inode_cachep =
>             kmem_cache_create("jfs_ip", sizeof(struct jfs_inode_info), 0,
>                             SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD,
>                             init_once);
> +#endif
>         if (jfs_inode_cachep == NULL)
>                 return -ENOMEM;
>
> diff --git a/fs/seq_file.c b/fs/seq_file.c
> index 225586e..a468a16 100644
> --- a/fs/seq_file.c
> +++ b/fs/seq_file.c
> @@ -30,7 +30,12 @@ static void *seq_buf_alloc(unsigned long size)
>          * __GFP_NORETRY to avoid oom-killings with high-order allocations -
>          * it's better to fall back to vmalloc() than to kill things.
>          */
> +#ifdef CONFIG_HARDUSERCOPY
> +       buf = kmalloc(size, GFP_KERNEL | GFP_USERCOPY | __GFP_NORETRY |
> +                               __GFP_NOWARN);
> +#else
>         buf = kmalloc(size, GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN);
> +#endif
>         if (!buf && size > PAGE_SIZE)
>                 buf = vmalloc(size);
>         return buf;
> diff --git a/include/linux/gfp.h b/include/linux/gfp.h
> index f92cbd2..5807645 100644
> --- a/include/linux/gfp.h
> +++ b/include/linux/gfp.h
> @@ -35,6 +35,10 @@ struct vm_area_struct;
>  #define ___GFP_NO_KSWAPD       0x400000u
>  #define ___GFP_OTHER_NODE      0x800000u
>  #define ___GFP_WRITE           0x1000000u
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +#define ___GFP_USERCOPY         0x2000000u
> +#endif
> +
>  /* If the above are modified, __GFP_BITS_SHIFT may need updating */
>
>  /*
> @@ -97,6 +101,9 @@ struct vm_area_struct;
>  #define __GFP_NO_KSWAPD        ((__force gfp_t)___GFP_NO_KSWAPD)
>  #define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE) /* On behalf of other node */
>  #define __GFP_WRITE    ((__force gfp_t)___GFP_WRITE)   /* Allocator intends to dirty page */
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +#define __GFP_USERCOPY ((__force gfp_t)___GFP_USERCOPY)/* Allocator intends to copy page to/from userland */
> +#endif
>
>  /*
>   * This may seem redundant, but it's a way of annotating false positives vs.
> @@ -104,7 +111,11 @@ struct vm_area_struct;
>   */
>  #define __GFP_NOTRACK_FALSE_POSITIVE (__GFP_NOTRACK)
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +#define __GFP_BITS_SHIFT 26    /* Room for N __GFP_FOO bits */
> +#else
>  #define __GFP_BITS_SHIFT 25    /* Room for N __GFP_FOO bits */
> +#endif
>  #define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
>
>  /* This equals 0, but use constants in case they ever change */
> @@ -149,6 +160,10 @@ struct vm_area_struct;
>  /* 4GB DMA on some platforms */
>  #define GFP_DMA32      __GFP_DMA32
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +#define GFP_USERCOPY         __GFP_USERCOPY
> +#endif
> +
>  /* Convert GFP flags to their corresponding migrate type */
>  static inline int gfpflags_to_migratetype(const gfp_t gfp_flags)
>  {
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index b7b9501..048eea8 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -757,6 +757,9 @@ struct signal_struct {
>  #ifdef CONFIG_TASKSTATS
>         struct taskstats *stats;
>  #endif
> +#ifdef CONFIG_HARDUSERCOPY
> +       u32 curr_ip;
> +#endif
>  #ifdef CONFIG_AUDIT
>         unsigned audit_tty;
>         unsigned audit_tty_log_passwd;
> diff --git a/include/linux/slab.h b/include/linux/slab.h
> index 7e37d44..b6d311c 100644
> --- a/include/linux/slab.h
> +++ b/include/linux/slab.h
> @@ -21,6 +21,9 @@
>   * The ones marked DEBUG are only valid if CONFIG_DEBUG_SLAB is set.
>   */
>  #define SLAB_DEBUG_FREE                0x00000100UL    /* DEBUG: Perform (expensive) checks on free */
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +#define SLAB_USERCOPY           0x00000200UL    /* HARD: Allow copying objs to/from userland */
> +#endif
>  #define SLAB_RED_ZONE          0x00000400UL    /* DEBUG: Red zone objs in a cache */
>  #define SLAB_POISON            0x00000800UL    /* DEBUG: Poison objects */
>  #define SLAB_HWCACHE_ALIGN     0x00002000UL    /* Align objs on cache lines */
> @@ -144,6 +147,10 @@ void kfree(const void *);
>  void kzfree(const void *);
>  size_t ksize(const void *);
>
> +#ifdef CONFIG_HARDUSERCOPY
> +const char *check_heap_object(const void *ptr, unsigned long n);
> +#endif
> +
>  /*
>   * Some archs want to perform DMA into kmalloc caches and need a guaranteed
>   * alignment larger than the alignment of a 64-bit integer.
> @@ -235,6 +242,10 @@ extern struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];
>  extern struct kmem_cache *kmalloc_dma_caches[KMALLOC_SHIFT_HIGH + 1];
>  #endif
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +extern struct kmem_cache *kmalloc_usercopy_caches[KMALLOC_SHIFT_HIGH + 1];
> +#endif
> +
>  /*
>   * Figure out which kmalloc slab an allocation of a certain size
>   * belongs to.
> diff --git a/include/linux/thread_info.h b/include/linux/thread_info.h
> index ff307b5..3449364 100644
> --- a/include/linux/thread_info.h
> +++ b/include/linux/thread_info.h
> @@ -145,6 +145,17 @@ static inline bool test_and_clear_restore_sigmask(void)
>  #error "no set_restore_sigmask() provided and default one won't work"
>  #endif
>
> +#ifdef CONFIG_HARDUSERCOPY
> +extern void __check_object_size(const void *ptr, unsigned long n,
> +                                       bool to_user, bool const_size);
> +
> +static inline void check_object_size(const void *ptr, unsigned long n,
> +                                       bool to_user)
> +{
> +       __check_object_size(ptr, n, to_user, __builtin_constant_p(n));
> +}
> +#endif /* CONFIG_HARDUSERCOPY */
> +
>  #endif /* __KERNEL__ */
>
>  #endif /* _LINUX_THREAD_INFO_H */
> diff --git a/ipc/msgutil.c b/ipc/msgutil.c
> index 71f448e..818bdd3 100644
> --- a/ipc/msgutil.c
> +++ b/ipc/msgutil.c
> @@ -55,7 +55,11 @@ static struct msg_msg *alloc_msg(size_t len)
>         size_t alen;
>
>         alen = min(len, DATALEN_MSG);
> +#ifdef CONFIG_HARDUSERCOPY
> +       msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL|GFP_USERCOPY);
> +#else
>         msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL);
> +#endif
>         if (msg == NULL)
>                 return NULL;
>
> @@ -67,7 +71,11 @@ static struct msg_msg *alloc_msg(size_t len)
>         while (len > 0) {
>                 struct msg_msgseg *seg;
>                 alen = min(len, DATALEN_SEG);
> +#ifdef CONFIG_HARDUSERCOPY
> +               seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL|GFP_USERCOPY);
> +#else
>                 seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL);
> +#endif
>                 if (seg == NULL)
>                         goto out_err;
>                 *pseg = seg;
> diff --git a/kernel/fork.c b/kernel/fork.c
> index 2845623..519d6b6 100644
> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -187,8 +187,13 @@ static void free_thread_info(struct thread_info *ti)
>
>  void thread_info_cache_init(void)
>  {
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       thread_info_cache = kmem_cache_create("thread_info", THREAD_SIZE,
> +                                             THREAD_SIZE, SLAB_USERCOPY, NULL);
> +#else /* CONFIG_HARDUSERCOPY_SLABS */
>         thread_info_cache = kmem_cache_create("thread_info", THREAD_SIZE,
>                                               THREAD_SIZE, 0, NULL);
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
>         BUG_ON(thread_info_cache == NULL);
>  }
>  # endif
> diff --git a/mm/slab.c b/mm/slab.c
> index 4fcc5dd..4207a19 100644
> --- a/mm/slab.c
> +++ b/mm/slab.c
> @@ -1451,8 +1451,14 @@ void __init kmem_cache_init(void)
>          * Initialize the caches that provide memory for the  kmem_cache_node
>          * structures first.  Without this, further allocations will bug.
>          */
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",
> +                               kmalloc_size(INDEX_NODE),
> +                               SLAB_USERCOPY | ARCH_KMALLOC_FLAGS);
> +#else /* CONFIG_HARDUSERCOPY_SLABS */
>         kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",
>                                 kmalloc_size(INDEX_NODE), ARCH_KMALLOC_FLAGS);
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
>         slab_state = PARTIAL_NODE;
>         setup_kmalloc_cache_index_table();
>
> @@ -4238,6 +4244,39 @@ static int __init slab_proc_init(void)
>  module_init(slab_proc_init);
>  #endif
>
> +#ifdef CONFIG_HARDUSERCOPY
> +const char *check_heap_object(const void *ptr, unsigned long n)
> +{
> +       struct page *page;
> +       struct kmem_cache *cachep;
> +       unsigned int objnr;
> +       unsigned long offset;
> +
> +       if (ZERO_OR_NULL_PTR(ptr))
> +               return "<null>";
> +
> +       if (!virt_addr_valid(ptr))
> +               return NULL;
> +
> +       page = virt_to_head_page(ptr);
> +
> +       if (!PageSlab(page))
> +               return NULL;
> +
> +       cachep = page->slab_cache;
> +       if (!(cachep->flags & SLAB_USERCOPY))
> +               return cachep->name;
> +
> +       objnr = obj_to_index(cachep, page, ptr);
> +       BUG_ON(objnr >= cachep->num);
> +       offset = ptr - index_to_obj(cachep, page, objnr) - obj_offset(cachep);
> +       if (offset <= cachep->object_size && n <= cachep->object_size - offset)
> +               return NULL;
> +
> +       return cachep->name;
> +}
> +#endif /* CONFIG_HARDUSERCOPY */
> +
>  /**
>   * ksize - get the actual amount of memory allocated for a given object
>   * @objp: Pointer to the object
> diff --git a/mm/slab.h b/mm/slab.h
> index a3a967d..d5307a8 100644
> --- a/mm/slab.h
> +++ b/mm/slab.h
> @@ -114,8 +114,14 @@ static inline unsigned long kmem_cache_flags(unsigned long object_size,
>
>
>  /* Legal flag mask for kmem_cache_create(), for various configurations */
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +#define SLAB_CORE_FLAGS (SLAB_HWCACHE_ALIGN | SLAB_CACHE_DMA | SLAB_PANIC | \
> +                        SLAB_DESTROY_BY_RCU | SLAB_DEBUG_OBJECTS | \
> +                        SLAB_USERCOPY)
> +#else
>  #define SLAB_CORE_FLAGS (SLAB_HWCACHE_ALIGN | SLAB_CACHE_DMA | SLAB_PANIC | \
>                          SLAB_DESTROY_BY_RCU | SLAB_DEBUG_OBJECTS )
> +#endif
>
>  #if defined(CONFIG_DEBUG_SLAB)
>  #define SLAB_DEBUG_FLAGS (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER)
> diff --git a/mm/slab_common.c b/mm/slab_common.c
> index 5ce4fae..655cc17d 100644
> --- a/mm/slab_common.c
> +++ b/mm/slab_common.c
> @@ -43,7 +43,11 @@ struct kmem_cache *kmem_cache;
>   * Merge control. If this is set then no merging of slab caches will occur.
>   * (Could be removed. This was introduced to pacify the merge skeptics.)
>   */
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +static int slab_nomerge = 1;
> +#else
>  static int slab_nomerge;
> +#endif
>
>  static int __init setup_slab_nomerge(char *str)
>  {
> @@ -741,6 +745,11 @@ struct kmem_cache *kmalloc_dma_caches[KMALLOC_SHIFT_HIGH + 1];
>  EXPORT_SYMBOL(kmalloc_dma_caches);
>  #endif
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +struct kmem_cache *kmalloc_usercopy_caches[KMALLOC_SHIFT_HIGH + 1];
> +EXPORT_SYMBOL(kmalloc_usercopy_caches);
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
> +
>  /*
>   * Conversion table for small slabs sizes / 8 to the index in the
>   * kmalloc array. This is necessary for slabs < 192 since we have non power
> @@ -805,6 +814,11 @@ struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
>                 return kmalloc_dma_caches[index];
>
>  #endif
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       if (unlikely((flags & GFP_USERCOPY)))
> +               return kmalloc_usercopy_caches[index];
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
> +
>         return kmalloc_caches[index];
>  }
>
> @@ -897,7 +911,11 @@ void __init create_kmalloc_caches(unsigned long flags)
>
>         for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
>                 if (!kmalloc_caches[i])
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +                       new_kmalloc_cache(i, SLAB_USERCOPY | flags);
> +#else
>                         new_kmalloc_cache(i, flags);
> +#endif
>
>                 /*
>                  * Caches that are not of the two-to-the-power-of size.
> @@ -905,9 +923,17 @@ void __init create_kmalloc_caches(unsigned long flags)
>                  * earlier power of two caches
>                  */
>                 if (KMALLOC_MIN_SIZE <= 32 && !kmalloc_caches[1] && i == 6)
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +                       new_kmalloc_cache(1, SLAB_USERCOPY | flags);
> +#else
>                         new_kmalloc_cache(1, flags);
> +#endif
>                 if (KMALLOC_MIN_SIZE <= 64 && !kmalloc_caches[2] && i == 7)
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +                       new_kmalloc_cache(2, SLAB_USERCOPY | flags);
> +#else
>                         new_kmalloc_cache(2, flags);
> +#endif
>         }
>
>         /* Kmalloc array is now usable */
> @@ -928,6 +954,22 @@ void __init create_kmalloc_caches(unsigned long flags)
>                 }
>         }
>  #endif
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
> +               struct kmem_cache *s = kmalloc_caches[i];
> +
> +               if (s) {
> +                       int size = kmalloc_size(i);
> +                       char *n = kasprintf(GFP_NOWAIT,
> +                                "usercopy-kmalloc-%d", size);
> +
> +                       BUG_ON(!n);
> +                       kmalloc_usercopy_caches[i] = create_kmalloc_cache(n,
> +                               size, SLAB_USERCOPY | flags);
> +               }
> +        }
> +#endif /* CONFIG_HARDUSERCOPY_SLABS*/
> +
>  }
>  #endif /* !CONFIG_SLOB */
>
> diff --git a/mm/slob.c b/mm/slob.c
> index 0d7e5df..554cc69 100644
> --- a/mm/slob.c
> +++ b/mm/slob.c
> @@ -501,6 +501,67 @@ void kfree(const void *block)
>  }
>  EXPORT_SYMBOL(kfree);
>
> +#ifdef CONFIG_HARDUSERCOPY
> +const char *check_heap_object(const void *ptr, unsigned long n)
> +{
> +       struct page *page;
> +       const slob_t *free;
> +       const void *base;
> +       unsigned long flags;
> +
> +       if (ZERO_OR_NULL_PTR(ptr))
> +               return "<null>";
> +
> +       if (!virt_addr_valid(ptr))
> +               return NULL;
> +
> +       page = virt_to_head_page(ptr);
> +       if (!PageSlab(page))
> +               return NULL;
> +
> +       if (page->private) {
> +               base = page;
> +               if (base <= ptr && n <= page->private - (ptr - base))
> +                       return NULL;
> +               return "<slob>";
> +       }
> +
> +       /* some tricky double walking to find the chunk */
> +       spin_lock_irqsave(&slob_lock, flags);
> +       base = (void *)((unsigned long)ptr & PAGE_MASK);
> +       free = page->freelist;
> +
> +       while (!slob_last(free) && (void *)free <= ptr) {
> +               base = free + slob_units(free);
> +               free = slob_next(free);
> +       }
> +
> +       while (base < (void *)free) {
> +               slobidx_t m = ((slob_t *)base)[0].units, align = ((slob_t *)base)[1].units;
> +               int size = SLOB_UNIT * SLOB_UNITS(m + align);
> +               int offset;
> +
> +               if (ptr < base + align)
> +                       break;
> +
> +               offset = ptr - base - align;
> +               if (offset >= m) {
> +                       base += size;
> +                       continue;
> +               }
> +
> +               if (n > m - offset)
> +                       break;
> +
> +               spin_unlock_irqrestore(&slob_lock, flags);
> +               return NULL;
> +       }
> +
> +       spin_unlock_irqrestore(&slob_lock, flags);
> +       return "<slob>";
> +}
> +#endif /* CONFIG_HARDUSERCOPY */
> +
>  /* can't use ksize for kmem_cache_alloc memory, only kmalloc */
>  size_t ksize(const void *block)
>  {
> @@ -532,6 +593,53 @@ int __kmem_cache_create(struct kmem_cache *c, unsigned long flags)
>         return 0;
>  }
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +static __always_inline void *
> +__do_kmalloc_node_align(size_t size, gfp_t gfp, int node, unsigned long caller, int align)
> +{
> +       slob_t *m;
> +       void *ret = NULL;
> +
> +       gfp &= gfp_allowed_mask;
> +
> +       lockdep_trace_alloc(gfp);
> +
> +       if (size < PAGE_SIZE - align) {
> +               if (!size)
> +                       return ZERO_SIZE_PTR;
> +
> +               m = slob_alloc(size + align, gfp, align, node);
> +
> +               if (!m)
> +                       return NULL;
> +               BUILD_BUG_ON(ARCH_KMALLOC_MINALIGN < 2 * SLOB_UNIT);
> +               BUILD_BUG_ON(ARCH_SLAB_MINALIGN < 2 * SLOB_UNIT);
> +               m[0].units = size;
> +               m[1].units = align;
> +               ret = (void *)m + align;
> +
> +               trace_kmalloc_node(caller, ret,
> +                                  size, size + align, gfp, node);
> +       } else {
> +               unsigned int order = get_order(size);
> +               struct page *page;
> +
> +               if (likely(order))
> +                       gfp |= __GFP_COMP;
> +               page = slob_new_pages(gfp, order, node);
> +               if (page) {
> +                       ret = page_address(page);
> +                       page->private = size;
> +               }
> +
> +               trace_kmalloc_node(caller, ret,
> +                                  size, PAGE_SIZE << order, gfp, node);
> +       }
> +
> +       return ret;
> +}
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
> +
>  static void *slob_alloc_node(struct kmem_cache *c, gfp_t flags, int node)
>  {
>         void *b;
> @@ -540,6 +648,10 @@ static void *slob_alloc_node(struct kmem_cache *c, gfp_t flags, int node)
>
>         lockdep_trace_alloc(flags);
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       b = __do_kmalloc_node_align(c->size, flags, node, _RET_IP_, c->align);
> +#else /* CONFIG_HARDUSERCOPY_SLABS */
> +
>         if (c->size < PAGE_SIZE) {
>                 b = slob_alloc(c->size, flags, c->align, node);
>                 trace_kmem_cache_alloc_node(_RET_IP_, b, c->object_size,
> @@ -551,6 +663,7 @@ static void *slob_alloc_node(struct kmem_cache *c, gfp_t flags, int node)
>                                             PAGE_SIZE << get_order(c->size),
>                                             flags, node);
>         }
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
>
>         if (b && c->ctor)
>                 c->ctor(b);
> @@ -597,6 +710,15 @@ static void kmem_rcu_free(struct rcu_head *head)
>
>  void kmem_cache_free(struct kmem_cache *c, void *b)
>  {
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       int size = c->size;
> +
> +       if (size + c->align < PAGE_SIZE) {
> +               size += c->align;
> +               b -= c->align;
> +       }
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
> +
>         kmemleak_free_recursive(b, c->flags);
>         if (unlikely(c->flags & SLAB_DESTROY_BY_RCU)) {
>                 struct slob_rcu *slob_rcu;
> @@ -607,7 +729,11 @@ void kmem_cache_free(struct kmem_cache *c, void *b)
>                 __kmem_cache_free(b, c->size);
>         }
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       trace_kfree(_RET_IP_, b);
> +#else /* CONFIG_HARDUSERCOPY_SLABS */
>         trace_kmem_cache_free(_RET_IP_, b);
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
>  }
>  EXPORT_SYMBOL(kmem_cache_free);
>
> diff --git a/mm/slub.c b/mm/slub.c
> index f614b5d..4ed0635 100644
> --- a/mm/slub.c
> +++ b/mm/slub.c
> @@ -3475,6 +3475,36 @@ void *__kmalloc_node(size_t size, gfp_t flags, int node)
>  EXPORT_SYMBOL(__kmalloc_node);
>  #endif
>
> +#ifdef CONFIG_HARDUSERCOPY
> +const char *check_heap_object(const void *ptr, unsigned long n)
> +{
> +       struct page *page;
> +       struct kmem_cache *s;
> +       unsigned long offset;
> +
> +       if (ZERO_OR_NULL_PTR(ptr))
> +               return "<null>";
> +
> +       if (!virt_addr_valid(ptr))
> +               return NULL;
> +
> +       page = virt_to_head_page(ptr);
> +
> +       if (!PageSlab(page))
> +               return NULL;
> +
> +       s = page->slab_cache;
> +       if (!(s->flags & SLAB_USERCOPY))
> +               return s->name;
> +
> +       offset = (ptr - page_address(page)) % s->size;
> +       if (offset <= s->object_size && n <= s->object_size - offset)
> +               return NULL;
> +
> +       return s->name;
> +}
> +#endif /* CONFIG_HARDUSERCOPY */
> +
>  static size_t __ksize(const void *object)
>  {
>         struct page *page;
> @@ -4677,6 +4707,14 @@ static ssize_t cache_dma_show(struct kmem_cache *s, char *buf)
>  SLAB_ATTR_RO(cache_dma);
>  #endif
>
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +static ssize_t usercopy_show(struct kmem_cache *s, char *buf)
> +{
> +       return sprintf(buf, "%d\n", !!(s->flags & SLAB_USERCOPY));
> +}
> +SLAB_ATTR_RO(usercopy);
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
> +
>  static ssize_t destroy_by_rcu_show(struct kmem_cache *s, char *buf)
>  {
>         return sprintf(buf, "%d\n", !!(s->flags & SLAB_DESTROY_BY_RCU));
> @@ -5019,6 +5057,9 @@ static struct attribute *slab_attrs[] = {
>  #ifdef CONFIG_ZONE_DMA
>         &cache_dma_attr.attr,
>  #endif
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       &usercopy_attr.attr,
> +#endif /* CONFIG_HARDUSERCOPY_SLABS */
>  #ifdef CONFIG_NUMA
>         &remote_node_defrag_ratio_attr.attr,
>  #endif
> @@ -5284,6 +5325,7 @@ static int sysfs_slab_add(struct kmem_cache *s)
>
>         s->kobj.kset = cache_kset(s);
>         err = kobject_init_and_add(&s->kobj, &slab_ktype, NULL, "%s", name);
> +
>         if (err)
>                 goto out;
>
> diff --git a/net/decnet/af_decnet.c b/net/decnet/af_decnet.c
> index 675cf94..f8d2803 100644
> --- a/net/decnet/af_decnet.c
> +++ b/net/decnet/af_decnet.c
> @@ -466,6 +466,9 @@ static struct proto dn_proto = {
>         .sysctl_rmem            = sysctl_decnet_rmem,
>         .max_header             = DN_MAX_NSP_DATA_HEADER + 64,
>         .obj_size               = sizeof(struct dn_sock),
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       .slab_flags             = SLAB_USERCOPY,
> +#endif
>  };
>
>  static struct sock *dn_alloc_sock(struct net *net, struct socket *sock, gfp_t gfp, int kern)
> diff --git a/security/Kconfig b/security/Kconfig
> index e452378..476f203 100644
> --- a/security/Kconfig
> +++ b/security/Kconfig
> @@ -118,6 +118,21 @@ config LSM_MMAP_MIN_ADDR
>           this low address space will need the permission specific to the
>           systems running LSM.
>
> +config HARDUSERCOPY_SLABS
> +       bool "Memory set correctly for harden user copy"
> +       default n
> +       help
> +         Just for debug now.
> +         If you are unsure as to whether this is required, answer N.
> +
> +config HARDUSERCOPY
> +       bool "Harden user copy"
> +       select HARDUSERCOPY_SLABS
> +       default n
> +       help
> +         Just for debug now.
> +         If you are unsure as to whether this is required, answer N.

While still RFC, this help should be filled in. The PaX config help
explains it pretty well. (Though I would expand on the stack checking
details: under what situations does it not work?)

I wonder if this should be called "STRICT_USERCOPY" or
"CHECKED_USERCOPY"? Hilariously we already have a
DEBUG_STRICT_USER_COPY_CHECKS, which doesn't work due to compiler
bugs.

Notes about the slab merging would be good too.

> +
>  source security/selinux/Kconfig
>  source security/smack/Kconfig
>  source security/tomoyo/Kconfig
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> index 8db1d93..6d479c1 100644
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
> @@ -3560,8 +3560,13 @@ int kvm_init(void *opaque, unsigned vcpu_size, unsigned vcpu_align,
>         /* A kmem cache lets us meet the alignment requirements of fx_save. */
>         if (!vcpu_align)
>                 vcpu_align = __alignof__(struct kvm_vcpu);
> +#ifdef CONFIG_HARDUSERCOPY_SLABS
> +       kvm_vcpu_cache = kmem_cache_create("kvm_vcpu", vcpu_size, vcpu_align,
> +                                          SLAB_USERCOPY, NULL);
> +#else
>         kvm_vcpu_cache = kmem_cache_create("kvm_vcpu", vcpu_size, vcpu_align,
>                                            0, NULL);
> +#endif
>         if (!kvm_vcpu_cache) {
>                 r = -ENOMEM;
>                 goto out_free_3;
> --
> 2.1.4
>

-Kees

-- 
Kees Cook
Chrome OS & Brillo Security

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2016-05-20 18:22 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-05-19 16:14 [kernel-hardening] [PATCH] Subject: [RFC PATCH] mm: Hardened usercopy casey.schaufler
2016-05-19 19:15 ` Rik van Riel
2016-05-20 18:22 ` Kees Cook

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).