@@ -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 */
@@ -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 = {};
@@ -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.
@@ -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;
@@ -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
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(-)