All of lore.kernel.org
 help / color / mirror / Atom feed
From: Vasiliy Kulikov <segoon@openwall.com>
To: kernel-hardening@lists.openwall.com
Subject: [kernel-hardening] Re: GNU_STACK policy problem
Date: Thu, 21 Jul 2011 17:03:29 +0400	[thread overview]
Message-ID: <20110721130329.GA7309@albatros> (raw)
In-Reply-To: <20110718194803.GA5337@albatros>

On Mon, Jul 18, 2011 at 23:48 +0400, Vasiliy Kulikov wrote:
> I've written kernel part of PT_GNU_STACK NX enforcement for binaries and
> trampolines emulation (the latter is taken from PaX as-is).  It Just
> Works for both 32 and 64 bits.

If someone wants to test it, the patch is as follows (it doesn't handle
DSOs as I've written before):

(Some pr_err() should be removed.)

diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c
index 2dbf6bf..c6ddb02 100644
--- a/arch/x86/mm/fault.c
+++ b/arch/x86/mm/fault.c
@@ -559,6 +559,152 @@ static int is_f00f_bug(struct pt_regs *regs, unsigned long address)
 	return 0;
 }
 
+/*
+ * These hex patterns (both for 32 and 64 bits) for trampolines code are
+ * extracted directly from gcc sources, so the match shouldn't lead to
+ * false positive and false negative result.
+ */
+static bool handle_trampolines_32(struct pt_regs *regs)
+{
+	int err;
+
+#ifdef CONFIG_X86_64
+	/* If we overflow maximum x86-32 address space, something goes
+	 * really wrong */
+	if ((regs->ip + 11) >> 32)
+		return;
+#endif
+
+	do {
+		unsigned char mov1, mov2;
+		unsigned short jmp;
+		unsigned int addr1, addr2;
+
+		err = get_user(mov1, (unsigned char __user *)regs->ip);
+		err |= get_user(addr1, (unsigned int __user *)(regs->ip + 1));
+		err |= get_user(mov2, (unsigned char __user *)(regs->ip + 5));
+		err |= get_user(addr2, (unsigned int __user *)(regs->ip + 6));
+		err |= get_user(jmp, (unsigned short __user *)(regs->ip + 10));
+
+		if (err)
+			break;
+
+		if (mov1 == 0xB9 && mov2 == 0xB8 && jmp == 0xE0FF) {
+			regs->cx = addr1;
+			regs->ax = addr2;
+			regs->ip = addr2;
+			return true;
+		}
+	} while (0);
+
+	do {
+		unsigned char mov, jmp;
+		unsigned int addr1, addr2;
+
+		err = get_user(mov, (unsigned char __user *)regs->ip);
+		err |= get_user(addr1, (unsigned int __user *)(regs->ip + 1));
+		err |= get_user(jmp, (unsigned char __user *)(regs->ip + 5));
+		err |= get_user(addr2, (unsigned int __user *)(regs->ip + 6));
+
+		if (err)
+			break;
+
+		if (mov == 0xB9 && jmp == 0xE9) {
+			regs->cx = addr1;
+			regs->ip = (unsigned int)(regs->ip + addr2 + 10);
+			return true;
+		}
+	} while (0);
+
+	/* Not a trampoline, don't block SEGFAULT */
+	return false;
+}
+
+#ifdef CONFIG_X86_64
+static bool handle_trampolines_64(struct pt_regs *regs)
+{
+	int err;
+
+	do {
+		unsigned short mov1, mov2, jmp1;
+		unsigned char jmp2;
+		unsigned int addr1;
+		unsigned long addr2;
+
+		err = get_user(mov1, (unsigned short __user *)regs->ip);
+		err |= get_user(addr1, (unsigned int __user *)(regs->ip + 2));
+		err |= get_user(mov2, (unsigned short __user *)(regs->ip + 6));
+		err |= get_user(addr2, (unsigned long __user *)(regs->ip + 8));
+		err |= get_user(jmp1, (unsigned short __user *)(regs->ip + 16));
+		err |= get_user(jmp2, (unsigned char __user *)(regs->ip + 18));
+
+		if (err)
+			break;
+
+		if (mov1 == 0xBB41 && mov2 == 0xBA49 && jmp1 == 0xFF49 && jmp2 == 0xE3) {
+			regs->r11 = addr1;
+			regs->r10 = addr2;
+			regs->ip = addr1;
+			return true;
+		}
+	} while (0);
+
+	do {
+		unsigned short mov1, mov2, jmp1;
+		unsigned char jmp2;
+		unsigned long addr1, addr2;
+
+		err = get_user(mov1, (unsigned short __user *)regs->ip);
+		err |= get_user(addr1, (unsigned long __user *)(regs->ip + 2));
+		err |= get_user(mov2, (unsigned short __user *)(regs->ip + 10));
+		err |= get_user(addr2, (unsigned long __user *)(regs->ip + 12));
+		err |= get_user(jmp1, (unsigned short __user *)(regs->ip + 20));
+		err |= get_user(jmp2, (unsigned char __user *)(regs->ip + 22));
+
+		if (err)
+			break;
+
+		if (mov1 == 0xBB49 && mov2 == 0xBA49 && jmp1 == 0xFF49 && jmp2 == 0xE3) {
+			regs->r11 = addr1;
+			regs->r10 = addr2;
+			regs->ip = addr1;
+			return true;
+		}
+	} while (0);
+
+	/* Not a trampoline, don't block SEGFAULT */
+	return false;
+}
+#endif
+
+static bool handle_trampolines(struct pt_regs *regs, unsigned long error_code,
+				 unsigned long address)
+{
+	if ((current->flags & PF_EMULTRAMP) == 0) {
+		pr_err("tramp: no EMULTRAMP\n");
+		return false;
+	}
+
+	if (v8086_mode(regs)) {
+		pr_err("tramp: v8086\n");
+		return false;
+	}
+	if ((error_code & PF_INSTR) == 0) {
+		pr_err("tramp: not PF_INSTR\n");
+		return false;
+	}
+
+#ifdef CONFIG_X86_32
+	return handle_trampolines_32(regs);
+#else
+	if (regs->cs == __USER32_CS || (regs->cs & SEGMENT_LDT))
+		return handle_trampolines_32(regs);
+	else
+		return handle_trampolines_64(regs);
+#endif
+}
+EXPORT_SYMBOL_GPL(handle_trampolines);
+
 static const char nx_warning[] = KERN_CRIT
 "kernel tried to execute NX-protected page - exploit attempt? (uid: %d)\n";
 
@@ -720,6 +867,9 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,
 		if (is_errata100(regs, address))
 			return;
 
+		if (handle_trampolines(regs, error_code, address))
+			return;
+
 		if (unlikely(show_unhandled_signals))
 			show_signal_msg(regs, error_code, address, tsk);
 
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index 303983f..64330e5 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -32,6 +32,7 @@
 #include <linux/elf.h>
 #include <linux/utsname.h>
 #include <linux/coredump.h>
+#include <linux/pid_namespace.h>
 #include <asm/uaccess.h>
 #include <asm/param.h>
 #include <asm/page.h>
@@ -556,6 +557,53 @@ static unsigned long randomize_stack_top(unsigned long stack_top)
 #endif
 }
 
+static int get_execstack(bool gnu_stack_present, int p_flags)
+{
+	struct pid_namespace *ns;
+	int pf_x;
+
+	if (gnu_stack_present) {
+		pf_x = p_flags & PF_X;
+		/* GNU_STACK => NX */
+		if (!pf_x)
+			return EXSTACK_DISABLE_X;
+
+		/* GNU_STACK => X */
+		ns = current->nsproxy->pid_ns;
+		if (ns->execstack_mode & GNU_STACK_X_FORCE_NX)
+			return EXSTACK_DISABLE_X;
+
+		return EXSTACK_ENABLE_X;
+	} else {
+		/* GNU_STACK => ? */
+		if (ns->execstack_mode & NO_GNU_STACK_FORCE_NX)
+			return EXSTACK_DISABLE_X;
+
+		return EXSTACK_DEFAULT;
+	}
+}
+
+static bool need_emultramp(bool gnu_stack_present, int p_flags)
+{
+	struct pid_namespace *ns;
+	int pf_x;
+
+	if (gnu_stack_present) {
+		pf_x = p_flags & PF_X;
+		/* GNU_STACK => NX */
+		if (!pf_x)
+			return false;
+
+		/* GNU_STACK => X */
+		ns = current->nsproxy->pid_ns;
+		return ns->execstack_mode & GNU_STACK_X_EMULTRAMP;
+	} else {
+		/* GNU_STACK => ? */
+		return ns->execstack_mode & NO_GNU_STACK_EMULTRAMP;
+	}
+
+}
+
 static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 {
 	struct file *interpreter = NULL; /* to shut gcc up */
@@ -571,12 +619,14 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 	unsigned long interp_load_addr = 0;
 	unsigned long start_code, end_code, start_data, end_data;
 	unsigned long reloc_func_desc __maybe_unused = 0;
-	int executable_stack = EXSTACK_DEFAULT;
+	int executable_stack;
 	unsigned long def_flags = 0;
 	struct {
 		struct elfhdr elf_ex;
 		struct elfhdr interp_elf_ex;
 	} *loc;
+	bool gnu_stack_present = false;
+	int gnu_stack_flags = 0; /* unused value */
 
 	loc = kmalloc(sizeof(*loc), GFP_KERNEL);
 	if (!loc) {
@@ -689,13 +739,16 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 	elf_ppnt = elf_phdata;
 	for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)
 		if (elf_ppnt->p_type == PT_GNU_STACK) {
-			if (elf_ppnt->p_flags & PF_X)
-				executable_stack = EXSTACK_ENABLE_X;
-			else
-				executable_stack = EXSTACK_DISABLE_X;
-			break;
+			gnu_stack_present = true;
+			gnu_stack_flags = elf_ppnt->p_flags;
 		}
 
+	executable_stack = get_execstack(gnu_stack_present, gnu_stack_flags);
+	if (need_emultramp(gnu_stack_present, gnu_stack_flags)) {
+		pr_err("need emultramp\n");
+		current->flags |= PF_EMULTRAMP;
+	}
+
 	/* Some simple consistency checks for the interpreter */
 	if (elf_interpreter) {
 		retval = -ELIBBAD;
diff --git a/include/linux/pid_namespace.h b/include/linux/pid_namespace.h
index 38d1032..c54c43c 100644
--- a/include/linux/pid_namespace.h
+++ b/include/linux/pid_namespace.h
@@ -16,6 +16,13 @@ struct pidmap {
 
 struct bsd_acct_struct;
 
+enum execstack_mode {
+	GNU_STACK_X_FORCE_NX = 1,
+	GNU_STACK_X_EMULTRAMP = 2,
+	NO_GNU_STACK_FORCE_NX = 4,
+	NO_GNU_STACK_EMULTRAMP = 8
+};
+
 struct pid_namespace {
 	struct kref kref;
 	struct pidmap pidmap[PIDMAP_ENTRIES];
@@ -30,6 +37,7 @@ struct pid_namespace {
 #ifdef CONFIG_BSD_PROCESS_ACCT
 	struct bsd_acct_struct *bacct;
 #endif
+	enum execstack_mode execstack_mode;
 };
 
 extern struct pid_namespace init_pid_ns;
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 496770a..1b4a25e 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1765,6 +1765,7 @@ extern void thread_group_times(struct task_struct *p, cputime_t *ut, cputime_t *
 #define PF_FROZEN	0x00010000	/* frozen for system suspend */
 #define PF_FSTRANS	0x00020000	/* inside a filesystem transaction */
 #define PF_KSWAPD	0x00040000	/* I am kswapd */
+#define PF_EMULTRAMP	0x00080000	/* gcc tramplolines must be emulated */
 #define PF_LESS_THROTTLE 0x00100000	/* Throttle me less: I clean memory */
 #define PF_KTHREAD	0x00200000	/* I am a kernel thread */
 #define PF_RANDOMIZE	0x00400000	/* randomize virtual address space */
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index f175d98..de049f9 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -175,6 +175,8 @@ static int proc_taint(struct ctl_table *table, int write,
 static int proc_dmesg_restrict(struct ctl_table *table, int write,
 				void __user *buffer, size_t *lenp, loff_t *ppos);
 #endif
+static int proc_execstack_mode(struct ctl_table *table, int write,
+				void __user *buffer, size_t *lenp, loff_t *ppos);
 
 #ifdef CONFIG_MAGIC_SYSRQ
 /* Note: sysrq code uses it's own private copy */
@@ -984,6 +986,12 @@ static struct ctl_table kern_table[] = {
 		.proc_handler	= proc_dointvec,
 	},
 #endif
+	{
+		.procname	= "execstack_mode",
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_execstack_mode,
+	},
 	{ }
 };
 
@@ -2426,6 +2434,15 @@ static int proc_dmesg_restrict(struct ctl_table *table, int write,
 }
 #endif
 
+static int proc_execstack_mode(struct ctl_table *table, int write,
+				void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tbl = *table;
+	tbl.data = &current->nsproxy->pid_ns->execstack_mode;
+
+	return proc_dointvec_minmax(&tbl, write, buffer, lenp, ppos);
+}
+
 struct do_proc_dointvec_minmax_conv_param {
 	int *min;
 	int *max;

  reply	other threads:[~2011-07-21 13:03 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-07-18 19:48 [kernel-hardening] GNU_STACK policy problem Vasiliy Kulikov
2011-07-21 13:03 ` Vasiliy Kulikov [this message]
2011-07-23 15:06 ` Solar Designer
2011-07-24  8:39   ` Vasiliy Kulikov
2011-07-24 14:06     ` Solar Designer
2011-07-26 12:07       ` Vasiliy Kulikov
2011-07-23 15:37 ` Solar Designer
2011-08-22  9:34   ` Vasiliy Kulikov
2011-08-22  9:41     ` Solar Designer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20110721130329.GA7309@albatros \
    --to=segoon@openwall.com \
    --cc=kernel-hardening@lists.openwall.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.