diff mbox series

[v4,1/2] hardening : prevent write to read-only pages of user process

Message ID 20200416155917.28536-2-levonshe@gmail.com (mailing list archive)
State New, archived
Headers show
Series hardening : prevent write to proces's read-only pages | expand

Commit Message

Lev R. Oshvang . April 16, 2020, 3:59 p.m. UTC
The purpose of this patch is produce hardened kernel for Embedded
or Production systems.
This patch shouild close issue 37 opened by Kees Cook in KSPP project

Typically debuggers, such as gdb, write to read-only code [text]
sections of target process.(ptrace)
This kind of page protectiion violation raises minor page fault, but
kernel's fault handler allows it by default.
This is clearly attack surface for adversary.

The proposed kernel hardening configuration option checks the type of
protection of the foreign vma and blocks writes to read only vma.

When enabled, it will stop attacks modifying code or jump tables, etc.

Code of arch_vma_access_permitted() function was extended to
check foreign vma flags.

Tested on x86_64 and ARM(QEMU) with dd command which writes to
/proc/PID/mem in r--p or r--xp of vma area address range

dd reports IO failure when tries to write to address taken from
from /proc/PID/maps (PLT or code section)

Signed-off-by: Lev Olshvang <levonshe@gmail.com>
---
 include/asm-generic/mm_hooks.h |  7 ++++++-
 include/linux/mm.h             | 21 ++++++++++++++++++++-
 kernel/sysctl.c                | 29 +++++++++++++++++++----------
 mm/util.c                      |  2 ++
 security/Kconfig               | 18 ++++++++++++++++++
 5 files changed, 65 insertions(+), 12 deletions(-)
diff mbox series

Patch

diff --git a/include/asm-generic/mm_hooks.h b/include/asm-generic/mm_hooks.h
index 4dbb177d1150..7f7f9fa6a17e 100644
--- a/include/asm-generic/mm_hooks.h
+++ b/include/asm-generic/mm_hooks.h
@@ -26,6 +26,11 @@  static inline bool arch_vma_access_permitted(struct vm_area_struct *vma,
 		bool write, bool execute, bool foreign)
 {
 	/* by default, allow everything */
-	return true;
+	if (likely(vma_write_allowed(vma, write, foreign)))
+		return true;
+
+	pr_err_once("Error : PID[%d] %s writes to read only memory\n",
+		    current->tgid, current->comm);
+	return false;
 }
 #endif	/* _ASM_GENERIC_MM_HOOKS_H */
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 5a323422d783..9f259ef35011 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5,7 +5,6 @@ 
 #include <linux/errno.h>
 
 #ifdef __KERNEL__
-
 #include <linux/mmdebug.h>
 #include <linux/gfp.h>
 #include <linux/bug.h>
@@ -603,6 +602,26 @@  struct vm_operations_struct {
 					  unsigned long addr);
 };
 
+extern int sysctl_forbid_write_ro_mem __read_mostly;
+
+static inline bool vma_write_allowed(struct vm_area_struct *vma,
+		bool write, bool foreign)
+{
+	if (likely(sysctl_forbid_write_ro_mem == 0))
+		return true;
+
+	/* This function was called when write is true */
+	if (likely(write && (!(vma->vm_flags & VM_WRITE)))) {
+		/* Forbid write to PROT_READ pages from any process*/
+		if (sysctl_forbid_write_ro_mem == 2)
+			return false;
+		/* Forbid write to PROT_READ pages from foreign process*/
+		if (sysctl_forbid_write_ro_mem == 1 && foreign)
+			return false;
+	}
+
+	return true;
+}
 static inline void vma_init(struct vm_area_struct *vma, struct mm_struct *mm)
 {
 	static const struct vm_operations_struct dummy_vm_ops = {};
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 8a176d8727a3..3df5c3223c19 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1320,7 +1320,7 @@  static struct ctl_table vm_table[] = {
 		.proc_handler	= overcommit_kbytes_handler,
 	},
 	{
-		.procname	= "page-cluster", 
+		.procname	= "page-cluster",
 		.data		= &page_cluster,
 		.maxlen		= sizeof(int),
 		.mode		= 0644,
@@ -1553,6 +1553,15 @@  static struct ctl_table vm_table[] = {
 		.proc_handler	= proc_dointvec,
 		.extra1		= SYSCTL_ZERO,
 	},
+	{
+		.procname	= "forbid_write_ro_mem",
+		.data		= &sysctl_forbid_write_ro_mem,
+		.maxlen		= sizeof(sysctl_forbid_write_ro_mem),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= &zero_ul,
+		.extra2		= &two,
+	},
 #if defined(HAVE_ARCH_PICK_MMAP_LAYOUT) || \
     defined(CONFIG_ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT)
 	{
@@ -1838,7 +1847,7 @@  static struct ctl_table fs_table[] = {
 		.mode		= 0555,
 		.child		= inotify_table,
 	},
-#endif	
+#endif
 #ifdef CONFIG_EPOLL
 	{
 		.procname	= "epoll",
@@ -2319,12 +2328,12 @@  static int __do_proc_dointvec(void *tbl_data, struct ctl_table *table,
 	int *i, vleft, first = 1, err = 0;
 	size_t left;
 	char *kbuf = NULL, *p;
-	
+
 	if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) {
 		*lenp = 0;
 		return 0;
 	}
-	
+
 	i = (int *) tbl_data;
 	vleft = table->maxlen / sizeof(*i);
 	left = *lenp;
@@ -2550,7 +2559,7 @@  static int do_proc_douintvec(struct ctl_table *table, int write,
  * @ppos: file position
  *
  * Reads/writes up to table->maxlen/sizeof(unsigned int) integer
- * values from/to the user buffer, treated as an ASCII string. 
+ * values from/to the user buffer, treated as an ASCII string.
  *
  * Returns 0 on success.
  */
@@ -3087,7 +3096,7 @@  static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp,
  * @ppos: file position
  *
  * Reads/writes up to table->maxlen/sizeof(unsigned int) integer
- * values from/to the user buffer, treated as an ASCII string. 
+ * values from/to the user buffer, treated as an ASCII string.
  * The values read are assumed to be in seconds, and are converted into
  * jiffies.
  *
@@ -3109,8 +3118,8 @@  int proc_dointvec_jiffies(struct ctl_table *table, int write,
  * @ppos: pointer to the file position
  *
  * Reads/writes up to table->maxlen/sizeof(unsigned int) integer
- * values from/to the user buffer, treated as an ASCII string. 
- * The values read are assumed to be in 1/USER_HZ seconds, and 
+ * values from/to the user buffer, treated as an ASCII string.
+ * The values read are assumed to be in 1/USER_HZ seconds, and
  * are converted into jiffies.
  *
  * Returns 0 on success.
@@ -3132,8 +3141,8 @@  int proc_dointvec_userhz_jiffies(struct ctl_table *table, int write,
  * @ppos: the current position in the file
  *
  * Reads/writes up to table->maxlen/sizeof(unsigned int) integer
- * values from/to the user buffer, treated as an ASCII string. 
- * The values read are assumed to be in 1/1000 seconds, and 
+ * values from/to the user buffer, treated as an ASCII string.
+ * The values read are assumed to be in 1/1000 seconds, and
  * are converted into jiffies.
  *
  * Returns 0 on success.
diff --git a/mm/util.c b/mm/util.c
index 988d11e6c17c..99366829c759 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -911,3 +911,5 @@  int memcmp_pages(struct page *page1, struct page *page2)
 	kunmap_atomic(addr1);
 	return ret;
 }
+
+int sysctl_forbid_write_ro_mem __read_mostly = CONFIG_PROTECT_READONLY_USER_MEMORY;
diff --git a/security/Kconfig b/security/Kconfig
index cd3cc7da3a55..3d4ffcd8ec24 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -143,6 +143,24 @@  config LSM_MMAP_MIN_ADDR
 	  this low address space will need the permission specific to the
 	  systems running LSM.
 
+
+config PROTECT_READONLY_USER_MEMORY
+	int "Protect read only process memory 0 - 2"
+	default 0
+	range 0 2
+	help
+	  Protects read only memory of process code and PLT/GOT table
+	  from possible attack. Attack vector might be /proc/PID/mem, /dev/mem
+	  or process_vm_write() or ptrace() syscalls.
+	  Mostly advised for embedded and production system.
+	  Controlled by sysctl_forbid_write_ro_mem.
+	  Select 0 to allow write to read only memory
+	  Select 1 to forbid write to read only memory from other processes.
+	  As en effect, GDB will refuse to insert a breakpoints.
+	  Select 2 to forbid write to read only memory from any process including itself
+	  As en effect, will stop self modifing code
+
+
 config HAVE_HARDENED_USERCOPY_ALLOCATOR
 	bool
 	help