[PATCH] sns: check related executable memory of binaries [3/4]

Alexander Wuerstlein
2007-06-22T18:26:10Z

From: Johannes Schlumberger <spjsschl@cip.informatik.uni-erlangen.de>

Checks on mmap and mprotect (i.e. libraries) wether they are signed and adjusts
the processe's signed flag accordingly.

If a process looses its signed state it gets, in our current design, killed, for
it is no longer trustworthy. A process also looses its signed flag if it mprotects
any memory as executable.

Signed-off-by: Johannes Schlumberger <spjsschl@cip.informatik.uni-erlangen.de>
---
include/linux/mm.h | 3 ++
include/linux/sns.h | 17 +++++++++++
kernel/fork.c | 7 ++++
mm/mmap.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++----
mm/mprotect.c | 30 +++++++++++++++++++
security/Kconfig | 36 +++++++++++++++++++++++
security/sns.c | 10 ++++++
7 files changed, 176 insertions(+), 6 deletions(-)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index e4183c6..903bc45 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -112,6 +112,9 @@ struct vm_area_struct {
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
+#ifdef CONFIG_SNS_SIGNED
+ int sns_valid_sig;
+#endif
};

extern struct kmem_cache *vm_area_cachep;
diff --git a/include/linux/sns.h b/include/linux/sns.h
index ad15e4b..eefb6e7 100644
--- a/include/linux/sns.h
+++ b/include/linux/sns.h
@@ -1,3 +1,20 @@
#ifdef CONFIG_SNS_SIGNED
int sns_signature_valid(struct file *);
+int sns_signature_becomes_invalid(void);
+
+/*
+ * The following is unfortunately necessary, there does not seem to be a
+ * common define to find out wether some ominous DSO which somebody
+ * likes to mmap or mprotect is in fact trustworthy kernel code.
+ */
+#ifdef CONFIG_X86
+#define sns_is_gate_vdso(addr, len) (addr==0xffffe000 && len == PAGE_SIZE)
+#else
+#ifdef CONFIG_IA64
+#define sns_is_gate_vdso(addr, len) (addr==0xffffe000UL && len == PAGE_SIZE)
+#else
+#define sns_is_gate_vdso(addr, len) 0
+#endif
+#endif
+
#endif
diff --git a/kernel/fork.c b/kernel/fork.c
index c12cf61..b1afa57 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -260,6 +260,9 @@ static inline int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
file = tmp->vm_file;
if (file) {
struct inode *inode = file->f_path.dentry->d_inode;
+#ifdef CONFIG_SNS_SIGNED
+ tmp->sns_valid_sig = mpnt->sns_valid_sig;
+#endif
get_file(file);
if (tmp->vm_flags & VM_DENYWRITE)
atomic_dec(&inode->i_writecount);
@@ -271,6 +274,10 @@ static inline int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
vma_prio_tree_add(tmp, mpnt);
flush_dcache_mmap_unlock(file->f_mapping);
spin_unlock(&file->f_mapping->i_mmap_lock);
+#ifdef CONFIG_SNS_SIGNED
+ } else {
+ tmp->sns_valid_sig = 0;
+#endif
}

/*
diff --git a/mm/mmap.c b/mm/mmap.c
index 68b9ad2..1f4bdf0 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -25,6 +25,9 @@
#include <linux/mount.h>
#include <linux/mempolicy.h>
#include <linux/rmap.h>
+#ifdef CONFIG_SNS_SIGNED
+#include <linux/sns.h>
+#endif

#include <asm/uaccess.h>
#include <asm/cacheflush.h>
@@ -1101,10 +1104,33 @@ munmap_back:
error = file->f_op->mmap(file, vma);
if (error)
goto unmap_and_free_vma;
- } else if (vm_flags & VM_SHARED) {
- error = shmem_zero_setup(vma);
- if (error)
- goto free_vma;
+#ifdef CONFIG_SNS_SIGNED
+ if (current->sns_valid_sig && (vm_flags & VM_EXEC)) {
+ if (vm_flags & VM_WRITE){
+ vma->sns_valid_sig = 0;
+ current->sns_valid_sig = 0;
+ sns_signature_becomes_invalid();
+ } else {
+ vma->sns_valid_sig = sns_signature_valid(file);
+ current->sns_valid_sig = vma->sns_valid_sig;
+ if(!current->sns_valid_sig)
+ sns_signature_becomes_invalid();
+ }
+ }
+#endif
+ } else {
+#ifdef CONFIG_SNS_SIGNED
+ /* JIT could have written some evil code here, which we are unable to verify */
+ if (prot & PROT_EXEC && current->sns_valid_sig) {
+ if ((vma->sns_valid_sig = (current->sns_valid_sig = (sns_is_gate_vdso(addr, len)))))
+ sns_signature_becomes_invalid();
+ }
+#endif
+ if (vm_flags & VM_SHARED) {
+ error = shmem_zero_setup(vma);
+ if (error)
+ goto free_vma;
+ }
}

/* We set VM_ACCOUNT in a shared mapping's vm_flags, to inform
@@ -1946,8 +1972,49 @@ unsigned long do_brk(unsigned long addr, unsigned long len)
vma->vm_end = addr + len;
vma->vm_pgoff = pgoff;
vma->vm_flags = flags;
- vma->vm_page_prot = protection_map[flags &
- (VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)];
+
+#ifdef CONFIG_SNS_SIGNED
+ /*
+ * A signed process could put executable code into an area
+ * it got from brk(). We can either disable the exec-bit on
+ * that area, which might break some programs, or we can
+ * allow the exec bit but remove the signed status, thereby
+ * unsigning any program that does some malloc()...
+ *
+ * Choose your poison.
+ */
+
+ if (current->sns_valid_sig){
+#ifdef CONFIG_SNS_BRK_ALLOW_EXEC
+ vma->vm_page_prot = protection_map[flags &
+ (VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)];
+
+
+#ifdef CONFIG_SNS_BRK_UNSIGN
+ current->sns_valid_sig = 0;
+ sns_signature_becomes_invalid();
+ vma->sns_valid_sig = 0;
+#endif /* CONFIG_SNS_BRK_UNSIGN */
+
+
+
+#else /* not CONFIG_SNS_BRK_ALLOW_EXEC */
+ vma->vm_page_prot = protection_map[flags &
+ (VM_READ|VM_WRITE|VM_SHARED)];
+#endif /* CONFIG_SNS_BRK_ALLOW_EXEC */
+ } else {
+ vma->vm_page_prot = protection_map[flags & (VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)];
+ }
+
+
+#else /* not CONFIG_SNS_SIGNED */
+ vma->vm_page_prot = protection_map[flags & (VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)];
+
+
+
+
+#endif /* CONFIG_SNS_SIGNED */
+
vma_link(mm, vma, prev, rb_link, rb_parent);
out:
mm->total_vm += len >> PAGE_SHIFT;
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 3b8f3c0..db17887 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -26,6 +26,10 @@
#include <asm/cacheflush.h>
#include <asm/tlbflush.h>

+#ifdef CONFIG_SNS_SIGNED
+#include <linux/sns.h>
+#endif
+
static void change_pte_range(struct mm_struct *mm, pmd_t *pmd,
unsigned long addr, unsigned long end, pgprot_t newprot,
int dirty_accountable)
@@ -289,6 +293,32 @@ sys_mprotect(unsigned long start, size_t len, unsigned long prot)
if (error)
goto out;

+#ifdef CONFIG_SNS_SIGNED
+ if (! sns_is_gate_vdso(start, len)) {
+ if ((newflags & VM_EXEC) && current->sns_valid_sig){
+ if ((newflags & VM_WRITE) == 0) {
+ if (current->sns_valid_sig && vma->vm_file) {
+ vma->sns_valid_sig = sns_signature_valid(vma->vm_file);
+ current->sns_valid_sig = vma->sns_valid_sig;
+ if (!current->sns_valid_sig)
+ sns_signature_becomes_invalid();
+ } else {
+ vma->sns_valid_sig = 0;
+ current->sns_valid_sig = 0;
+ sns_signature_becomes_invalid();
+ }
+ } else {
+ vma->sns_valid_sig = 0;
+ current->sns_valid_sig = 0;
+ sns_signature_becomes_invalid();
+ }
+ }
+ } else {
+ /* always trust kernelspace */
+ vma->sns_valid_sig = 1;
+ }
+#endif
+
tmp = vma->vm_end;
if (tmp > end)
tmp = end;
diff --git a/security/Kconfig b/security/Kconfig
index bfaace7..9776e29 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -32,6 +32,42 @@ config SNS_SIGNED_SETGID

If you don't know exactly what you are doing, answer N.

+config SNS_BRK_ALLOW_EXEC
+ bool "Allows the executable bit to be set on brk()-ed memory"
+ depends on SNS_SIGNED
+ help
+ By default the memory a process gets from brk() is executable.
+ This is undesirable in our situation because it would allow
+ an evil binary to load unsigned code.
+
+ We can either disable the exec-bit on that area (set this option to
+ off), which might break some programs, or we can allow the exec bit
+ but remove the signed status (this option on, next option on),
+ thereby unsigning any program that does some malloc()...
+
+ Choose your poison.
+
+ On old x86 (without CPU-flag NX to be exact) this option is useless
+ because every readable page is also executable. This might apply to
+ some other broken architectures as well. YMMV.
+
+ The default behaviour everybody expects is probably broken
+ either way. If you do not want any problems or you are
+ unsure, enable this option (SNS_BREAK_ALLOW_EXEC) and
+ disable the next one (SNS_BRK_UNSIGN).
+
+ I repeat: Always say Y, unless you are very brave...
+
+config SNS_BRK_UNSIGN
+ bool "Unsign process after doing brk()"
+ depends on SNS_BRK_ALLOW_EXEC
+ help
+ Remove signed bit from any process that does brk().
+
+ See previous option (SNS_BRK_ALLOW_EXEC) for help.
+
+ If unsure say N.
+
config KEYS
bool "Enable access key retention support"
help
diff --git a/security/sns.c b/security/sns.c
index 4403e5a..3192a90 100644
--- a/security/sns.c
+++ b/security/sns.c
@@ -33,6 +33,16 @@ static int sns_sig_reader(read_descriptor_t *desc, struct page *page, unsigned l
return read;
}

+int sns_signature_becomes_invalid(void)
+{
+ current->sns_valid_sig = 0;
+ /*no checking necessary because of SEND_SIG_FORCED*/
+ force_sig_info(SIGKILL, SEND_SIG_FORCED, current);
+ return 0; /*Leave the actual killing to the scheduler*/
+ /*TODO check if the process has any way left to execute any code*/
+}
+
+
/*
* check file signature for setuid
*/
--
1.5.2.1