Skip to contentSkip to conversation

Compile-time stack frame pointer validation

PATCH Superseded Josh Poimboeuf 3 participants 10 messages Apr 27, 2015 – Apr 28, 2015

Subsystems (3)

v1 of 19 Superseded Latest is v19 →

lore
Share & tools
lore
#0 lore raw

cover Compile-time stack frame pointer validation

[ This is a repost of a previous RFC, rebased onto linux-next. ]

In discussions around the live kernel patching consistency model RFC [1], Peter and Ingo correctly pointed out that stack traces aren't reliable. And as Ingo said, there's no "strong force" which ensures we can rely on them.

So I've been thinking about how to fix that. My goal is to eventually make stack traces reliable. Or at the very least, to be able to detect at runtime when a given stack trace *might* be unreliable. But improved stack traces would broadly benefit the entire kernel, regardless of the outcome of the live kernel patching consistency model discussions.

This patch set is just the first in a series of proposed stack trace reliability improvements. Future proposals will include runtime stack reliability checking, as well as compile-time and runtime DWARF validations.

As far as I can tell, there are two main obstacles which prevent frame pointer based stack traces from being reliable:

1) Missing frame pointer logic: currently, most assembly functions don't set up the frame pointer.

2) Interrupts: if a function is interrupted before it can save and set up the frame pointer, its caller won't show up in the stack trace.

This patch set aims to remove the first obstacle by enforcing that all asm functions honor CONFIG_FRAME_POINTER. This is done with a new stackvalidate host tool which is automatically run for every compiled .S file and which validates that every asm function does the proper frame pointer setup.

Also, to make sure somebody didn't forget to annotate their callable asm code as a function, flag an error for any return instructions which are hiding outside of a function. In almost all cases, return instructions are part of callable functions and should be annotated as such so that we can validate their frame pointer usage. A whitelist mechanism exists for those few return instructions which are not actually in callable code.

It currently only supports x86_64. It *almost* supports x86_32, but the stackvalidate code doesn't yet know how to deal with 32-bit REL relocations for the return whitelists. I tried to make the code generic so that support for other architectures can be plugged in pretty easily.

As a first step, all reported non-compliances result in warnings. Right now I'm seeing 200+ warnings. Once we get them all cleaned up, we can change the warnings to build errors so the asm code can stay clean.

The patches are based on linux-next. Patch 1 adds the stackvalidate host tool. Patch 2 adds some helper macros for asm functions so that they can comply with stackvalidate.

[1] https://lkml.org/lkml/2015/2/9/475

Josh Poimboeuf (2):
x86, stackvalidate: Compile-time stack frame pointer validation x86, stackvalidate: Add asm frame pointer setup macros

 MAINTAINERS                           |   6 +
 arch/Kconfig                          |   4 +
 arch/x86/Kconfig                      |   1 +
 arch/x86/Makefile                     |   6 +-
 arch/x86/include/asm/func.h           |  82 ++++++++
 lib/Kconfig.debug                     |  11 ++
 scripts/Makefile                      |   1 +
 scripts/Makefile.build                |  22 ++-
 scripts/stackvalidate/Makefile        |  17 ++
 scripts/stackvalidate/arch-x86.c      | 134 +++++++++++++
 scripts/stackvalidate/arch.h          |  10 +
 scripts/stackvalidate/elf.c           | 341 ++++++++++++++++++++++++++++++++++
 scripts/stackvalidate/elf.h           |  56 ++++++
 scripts/stackvalidate/list.h          | 217 ++++++++++++++++++++++
 scripts/stackvalidate/stackvalidate.c | 226 ++++++++++++++++++++++
 15 files changed, 1131 insertions(+), 3 deletions(-)
 create mode 100644 arch/x86/include/asm/func.h
 create mode 100644 scripts/stackvalidate/Makefile
 create mode 100644 scripts/stackvalidate/arch-x86.c
 create mode 100644 scripts/stackvalidate/arch.h
 create mode 100644 scripts/stackvalidate/elf.c
 create mode 100644 scripts/stackvalidate/elf.h
 create mode 100644 scripts/stackvalidate/list.h
 create mode 100644 scripts/stackvalidate/stackvalidate.c
Signature · 1 line
2.1.0
#1 lore raw

PATCH 1/2 x86, stackvalidate: Compile-time stack frame pointer validation

Frame pointer based stack traces aren't always reliable. One big reason is that most asm functions don't set up the frame pointer.

Fix that by enforcing that all asm functions honor CONFIG_FRAME_POINTER. This is done with a new stackvalidate host tool which is automatically run for every compiled .S file and which validates that every asm function does the proper frame pointer setup.

Also, to make sure somebody didn't forget to annotate their callable asm code as a function, flag an error for any return instructions which are hiding outside of a function. In almost all cases, return instructions are part of callable functions and should be annotated as such so that we can validate their frame pointer usage. A whitelist mechanism exists for those few return instructions which are not actually in callable code.

It currently only supports x86_64. It *almost* supports x86_32, but the stackvalidate code doesn't yet know how to deal with 32-bit REL relocations for the return whitelists. I tried to make the code generic so that support for other architectures can be plugged in pretty easily.

As a first step, all reported non-compliances result in warnings. Right now I'm seeing 200+ warnings. Once we get them all cleaned up, we can change the warnings to build errors so the asm code can stay clean.

---
 MAINTAINERS                           |   6 +
 arch/Kconfig                          |   4 +
 arch/x86/Kconfig                      |   1 +
 arch/x86/Makefile                     |   6 +-
 lib/Kconfig.debug                     |  11 ++
 scripts/Makefile                      |   1 +
 scripts/Makefile.build                |  22 ++-
 scripts/stackvalidate/Makefile        |  17 ++
 scripts/stackvalidate/arch-x86.c      | 134 +++++++++++++
 scripts/stackvalidate/arch.h          |  10 +
 scripts/stackvalidate/elf.c           | 341 ++++++++++++++++++++++++++++++++++
 scripts/stackvalidate/elf.h           |  56 ++++++
 scripts/stackvalidate/list.h          | 217 ++++++++++++++++++++++
 scripts/stackvalidate/stackvalidate.c | 226 ++++++++++++++++++++++
 14 files changed, 1049 insertions(+), 3 deletions(-)
 create mode 100644 scripts/stackvalidate/Makefile
 create mode 100644 scripts/stackvalidate/arch-x86.c
 create mode 100644 scripts/stackvalidate/arch.h
 create mode 100644 scripts/stackvalidate/elf.c
 create mode 100644 scripts/stackvalidate/elf.h
 create mode 100644 scripts/stackvalidate/list.h
 create mode 100644 scripts/stackvalidate/stackvalidate.c
MAINTAINERS +6 -0
index 68e96ee..1af2d64 100644
@@ -9359,6 +9359,12 @@ L:	[email protected]
 S:	Supported
 F:	Documentation/stable_kernel_rules.txt
 
+STACK VALIDATION
+M:	Josh Poimboeuf <[email protected]>
+S:	Supported
+F:	scripts/stackvalidate/
+F:	arch/x86/include/asm/func.h
+
 STAGING SUBSYSTEM
 M:	Greg Kroah-Hartman <[email protected]>
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
arch/Kconfig +4 -0
index a65eafb..9e7e388 100644
@@ -499,6 +499,10 @@ config ARCH_HAS_ELF_RANDOMIZE
 	  - arch_mmap_rnd()
 	  - arch_randomize_brk()
 
+config HAVE_STACK_VALIDATION
+	bool
+
+
 #
 # ABI hall of shame
 #
arch/x86/Kconfig +1 -0
index 40ada6e..a876c17 100644
@@ -143,6 +143,7 @@ config X86
 	select ACPI_LEGACY_TABLES_LOOKUP if ACPI
 	select X86_FEATURE_NAMES if PROC_FS
 	select SRCU
+	select HAVE_STACK_VALIDATION if FRAME_POINTER && X86_64
 
 config INSTRUCTION_DECODER
 	def_bool y
arch/x86/Makefile +5 -1
index 2fda005..72f2f04 100644
@@ -171,9 +171,13 @@ KBUILD_CFLAGS += $(call cc-option,-mno-avx,)
 KBUILD_CFLAGS += $(mflags-y)
 KBUILD_AFLAGS += $(mflags-y)
 
-archscripts: scripts_basic
+archscripts: scripts_basic $(objtree)/arch/x86/lib/inat-tables.c
 	$(Q)$(MAKE) $(build)=arch/x86/tools relocs
 
+# this file is needed early by scripts/stackvalidate
+$(objtree)/arch/x86/lib/inat-tables.c:
+	$(Q)$(MAKE) $(build)=arch/x86/lib $@
+
 ###
 # Syscall table generation
 
lib/Kconfig.debug +11 -0
index f3d0e3e..dbc256a 100644
@@ -332,6 +332,17 @@ config FRAME_POINTER
 	  larger and slower, but it gives very useful debugging information
 	  in case of kernel bugs. (precise oopses/stacktraces/warnings)
 
+
+config STACK_VALIDATION
+	bool "Enable kernel stack validation"
+	depends on HAVE_STACK_VALIDATION
+	default y
+	help
+	  Add compile-time validations which help make kernel stack traces more
+	  reliable.  This includes checks to ensure that assembly functions
+	  save, update and restore the frame pointer or the back chain pointer.
+
+
 config DEBUG_FORCE_WEAK_PER_CPU
 	bool "Force weak per-cpu definitions"
 	depends on DEBUG_KERNEL
scripts/Makefile +1 -0
index 2016a64..c882a91 100644
@@ -37,6 +37,7 @@ subdir-y                     += mod
 subdir-$(CONFIG_SECURITY_SELINUX) += selinux
 subdir-$(CONFIG_DTC)         += dtc
 subdir-$(CONFIG_GDB_SCRIPTS) += gdb
+subdir-$(CONFIG_STACK_VALIDATION) += stackvalidate
 
 # Let clean descend into subdirs
 subdir-	+= basic kconfig package
scripts/Makefile.build +20 -2
index 01df30a..3b05833 100644
@@ -253,6 +253,24 @@ define rule_cc_o_c
 	mv -f $(dot-target).tmp $(dot-target).cmd
 endef
 
+ifdef CONFIG_STACK_VALIDATION
+stackvalidate = $(objtree)/scripts/stackvalidate/stackvalidate
+cmd_stackvalidate =							  \
+	case $(@) in							  \
+		arch/x86/purgatory/*) ;;				  \
+		*) $(stackvalidate) "$(@)"; \
+	esac;
+endif
+
+define rule_as_o_S
+	$(call echo-cmd,as_o_S) $(cmd_as_o_S);				  \
+	$(cmd_stackvalidate)						  \
+	scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,as_o_S)' >    \
+	                                              $(dot-target).tmp;  \
+	rm -f $(depfile);						  \
+	mv -f $(dot-target).tmp $(dot-target).cmd
+endef
+
 # Built-in and composite module parts
 $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
 	$(call cmd,force_checksrc)
@@ -290,8 +308,8 @@ $(obj)/%.s: $(src)/%.S FORCE
 quiet_cmd_as_o_S = AS $(quiet_modtag)  $@
 cmd_as_o_S       = $(CC) $(a_flags) -c -o $@ $<
 
-$(obj)/%.o: $(src)/%.S FORCE
-	$(call if_changed_dep,as_o_S)
+$(obj)/%.o: $(src)/%.S $(stackvalidate) FORCE
+	$(call if_changed_rule,as_o_S)
 
 targets += $(real-objs-y) $(real-objs-m) $(lib-y)
 targets += $(extra-y) $(MAKECMDGOALS) $(always)
scripts/stackvalidate/Makefile new file +17 -0
new file mode 100644
index 0000000..6027ec4
@@ -0,0 +1,17 @@
+hostprogs-y := stackvalidate
+always := $(hostprogs-y)
+
+stackvalidate-objs := stackvalidate.o elf.o
+
+HOSTCFLAGS += -Werror
+HOSTLOADLIBES_stackvalidate := -lelf
+
+ifdef CONFIG_X86
+
+stackvalidate-objs += arch-x86.o
+
+HOSTCFLAGS_arch-x86.o := -I$(objtree)/arch/x86/lib/ -I$(srctree)/arch/x86/include/ -I$(srctree)/arch/x86/lib/
+
+$(obj)/arch-x86.o: $(srctree)/arch/x86/lib/insn.c $(srctree)/arch/x86/lib/inat.c $(srctree)/arch/x86/include/asm/inat_types.h $(srctree)/arch/x86/include/asm/inat.h $(srctree)/arch/x86/include/asm/insn.h $(objtree)/arch/x86/lib/inat-tables.c
+
+endif
scripts/stackvalidate/arch-x86.c new file +134 -0

134 lines not on this page · raw message

scripts/stackvalidate/arch.h new file +10 -0
new file mode 100644
index 0000000..3b91b1c
@@ -0,0 +1,10 @@
+#ifndef _ARCH_H_
+#define _ARCH_H_
+
+#include "elf.h"
+
+int arch_validate_function(struct elf *elf, struct symbol *func);
+int arch_is_return_insn(struct elf *elf, unsigned long addr,
+			unsigned int maxlen, unsigned int *len);
+
+#endif /* _ARCH_H_ */
scripts/stackvalidate/elf.c new file +341 -0

341 lines not on this page · raw message

scripts/stackvalidate/elf.h new file +56 -0

56 lines not on this page · raw message

scripts/stackvalidate/list.h new file +217 -0

217 lines not on this page · raw message

scripts/stackvalidate/stackvalidate.c new file +226 -0

226 lines not on this page · raw message

Signature · 1 line
2.1.0
#2 lore raw

Re: [PATCH 1/2] x86, stackvalidate: Compile-time stack frame pointer validation

Quoted 7 lines · Josh Poimboeuf

Frame pointer based stack traces aren't always reliable. One big reason is that most asm functions don't set up the frame pointer.

Fix that by enforcing that all asm functions honor CONFIG_FRAME_POINTER. This is done with a new stackvalidate host tool which is automatically run for every compiled .S file and which validates that every asm function does the proper frame pointer setup.

Would it make sense (maybe as an additional CONFIG_*_DEBUG thing) to also process the output of GCC with this tool? To both double check GCC and to give the tool more input?

#3 lore raw

Re: [PATCH 1/2] x86, stackvalidate: Compile-time stack frame pointer validation

Quoted 4 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf

Frame pointer based stack traces aren't always reliable. One big reason is that most asm functions don't set up the frame pointer.

Quoted 8 lines
Quoted 4 lines

Fix that by enforcing that all asm functions honor CONFIG_FRAME_POINTER. This is done with a new stackvalidate host tool which is automatically run for every compiled .S file and which validates that every asm function does the proper frame pointer setup.

Would it make sense (maybe as an additional CONFIG_*_DEBUG thing) to also process the output of GCC with this tool? To both double check GCC and to give the tool more input?

I tried that, but I discovered that gcc's usage of frame pointers would be a lot harder to validate. It only sets up the frame pointer in code paths which have call instructions. There are a lot of functions which have conditional jumps at the beginning which can jump straight to a return instruction without first doing the frame pointer setup.

So it would really need to have a much more sophisticated static code analysis. But I think the possibility of gcc messing up frame pointers is very slim. I doubt it would be worth the complexity (and added compile time) needed to try to find any gcc bugs there.

Signature · 1 line
Josh
#4 lore raw

Re: [PATCH 1/2] x86, stackvalidate: Compile-time stack frame pointer validation

Quoted 14 lines · Josh Poimboeuf
Quoted 4 lines · Peter Zijlstra
Quoted 3 lines · Josh Poimboeuf

Frame pointer based stack traces aren't always reliable. One big reason is that most asm functions don't set up the frame pointer.

Quoted 8 lines
Quoted 4 lines

Fix that by enforcing that all asm functions honor CONFIG_FRAME_POINTER. This is done with a new stackvalidate host tool which is automatically run for every compiled .S file and which validates that every asm function does the proper frame pointer setup.

Would it make sense (maybe as an additional CONFIG_*_DEBUG thing) to also process the output of GCC with this tool? To both double check GCC and to give the tool more input?

Quoted 5 lines

I tried that, but I discovered that gcc's usage of frame pointers would be a lot harder to validate. It only sets up the frame pointer in code paths which have call instructions. There are a lot of functions which have conditional jumps at the beginning which can jump straight to a return instruction without first doing the frame pointer setup.

Hmm, would not such code break your patching?

#5 lore raw

Re: [PATCH 1/2] x86, stackvalidate: Compile-time stack frame pointer validation

Quoted 15 lines · Peter Zijlstra
Quoted 14 lines · Josh Poimboeuf
Quoted 12 lines · Peter Zijlstra
Quoted 7 lines · Josh Poimboeuf

Frame pointer based stack traces aren't always reliable. One big reason is that most asm functions don't set up the frame pointer.

Fix that by enforcing that all asm functions honor CONFIG_FRAME_POINTER. This is done with a new stackvalidate host tool which is automatically run for every compiled .S file and which validates that every asm function does the proper frame pointer setup.

Would it make sense (maybe as an additional CONFIG_*_DEBUG thing) to also process the output of GCC with this tool? To both double check GCC and to give the tool more input?

Quoted 7 lines
Quoted 5 lines

I tried that, but I discovered that gcc's usage of frame pointers would be a lot harder to validate. It only sets up the frame pointer in code paths which have call instructions. There are a lot of functions which have conditional jumps at the beginning which can jump straight to a return instruction without first doing the frame pointer setup.

Hmm, would not such code break your patching?

No, because we'll also do some runtime stack validation (which will be a future patch set). If we detect preemption or an irq frame on the stack, we'll assume the stack is unreliable and delay the patching of the task (*). Otherwise the stack will only consist of calls down to schedule() which will be guaranteed to have frame pointers.

(*) This can be even further improved by making _all_ stacks reliable if
we can ensure that dwarf call frame information is reliable (more future patch sets).

Signature · 1 line
Josh
#6 lore raw

Re: [PATCH 1/2] x86, stackvalidate: Compile-time stack frame pointer validation

Quoted 6 lines · Josh Poimboeuf
Quoted 6 lines
Quoted 5 lines

I tried that, but I discovered that gcc's usage of frame pointers would be a lot harder to validate. It only sets up the frame pointer in code paths which have call instructions. There are a lot of functions which have conditional jumps at the beginning which can jump straight to a return instruction without first doing the frame pointer setup.

Quoted 6 lines
Quoted 1 line

Hmm, would not such code break your patching?

No, because we'll also do some runtime stack validation (which will be a future patch set). If we detect preemption or an irq frame on the stack, we'll assume the stack is unreliable and delay the patching of the task (*).

Ah, which fixes your second issue too (the interrupt before frame setup). OK.

#7 lore raw

Re: [PATCH 1/2] x86, stackvalidate: Compile-time stack frame pointer validation

In reply to Josh Poimboeuf
Quoted 20 lines · Josh Poimboeuf

Frame pointer based stack traces aren't always reliable. One big reason is that most asm functions don't set up the frame pointer.

Fix that by enforcing that all asm functions honor CONFIG_FRAME_POINTER. This is done with a new stackvalidate host tool which is automatically run for every compiled .S file and which validates that every asm function does the proper frame pointer setup.

Also, to make sure somebody didn't forget to annotate their callable asm code as a function, flag an error for any return instructions which are hiding outside of a function. In almost all cases, return instructions are part of callable functions and should be annotated as such so that we can validate their frame pointer usage. A whitelist mechanism exists for those few return instructions which are not actually in callable code.

It currently only supports x86_64. It *almost* supports x86_32, but the stackvalidate code doesn't yet know how to deal with 32-bit REL relocations for the return whitelists. I tried to make the code generic so that support for other architectures can be plugged in pretty easily.

Quoted 5 lines

As a first step, all reported non-compliances result in warnings. Right now I'm seeing 200+ warnings. Once we get them all cleaned up, we can change the warnings to build errors so the asm code can stay clean.

I made a quick look and have few questions.

[...]

Quoted 55 lines
scripts/stackvalidate/arch-x86.c new file +50 -0
new file mode 100644
index 0000000..fbc0756
+/*
+ * arch_validate_function() - Ensures the given asm function saves, sets up,
+ * and restores the frame pointer.
+ *
+ * The frame pointer prologue/epilogue should look something like:
+ *
+ *   push %rbp
+ *   mov %rsp, %rbp
+ *   [ function body ]
+ *   pop %rbp
+ *   ret
+ *
+ * Return value:
+ *   -1: bad instruction
+ *    1: missing frame pointer logic
+ *    0: validation succeeded
+ */
+int arch_validate_function(struct elf *elf, struct symbol *func)
+{
+	struct insn insn;
+	unsigned long addr, length;
+	int push, mov, pop, ret, x86_64;
+
+	push = mov = pop = ret = 0;
+
+	x86_64 = is_x86_64(elf);
+	if (x86_64 == -1)
+		return -1;
+
+	for (addr = func->start; addr < func->end; addr += length) {
+		insn_init(&insn, (void *)addr, func->end - addr, x86_64);
+		insn_get_length(&insn);
+		length = insn.length;
+		insn_get_opcode(&insn);
+		if (!length || !insn.opcode.got) {
+			WARN("%s+0x%lx: bad instruction", func->name,
+			     addr - func->start);
+			return -1;
+		}
+
+		switch (insn.opcode.bytes[0]) {
+		case 0x55:
+			if (!insn.rex_prefix.nbytes)
+				/* push bp */
+				push++;
+			break;
+		case 0x5d:
+			if (!insn.rex_prefix.nbytes)
+				/* pop bp */
+				pop++;

scripts/stackvalidate/arch-x86.c line 83

Quoted 10 lines
scripts/stackvalidate/arch-x86.c new file +10 -0
+			break;
+		case 0xc9: /* leave */
+			pop++;
+			break;
+		case 0x89:
+			insn_get_modrm(&insn);
+			if (insn.modrm.bytes[0] == 0xe5)
+				/* mov sp, bp */
+				mov++;
+			break;

I am a bit courious. You check 0x89 and 0x5e but I see the following in the disasembly:

48 89 e5 mov %rsp,%rbp

I wonder if 48 is just a prefix that is filtered by insn_get_opcode or what.

Sigh, I still need to learn a lot about assembly.

scripts/stackvalidate/arch-x86.c line 92

Quoted 9 lines
scripts/stackvalidate/arch-x86.c new file +9 -0
+		case 0xc3: /* ret */
+			ret++;
+			break;
+		}
+	}
+
+	if (push != 1 || mov != 1 || !pop || !ret || pop != ret) {
+		WARN("%s() is missing frame pointer logic.  Please use FUNC_ENTER.",
+		     func->name);

This looks quite weak condition to me. It accepts the instructions in any order and at any place.

Also livepatching will rely on having fentry before the prologue. Do you plan to add support for this ordering?

Quoted 11 lines
scripts/stackvalidate/arch-x86.c new file +11 -0
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * arch_is_return_insn() - Determines whether the instruction at the given
+ * address is a return instruction.  Also returns the instruction length in
+ * *len.
+ *

[...]

Quoted 21 lines
scripts/stackvalidate/arch.h new file +10 -0
new file mode 100644
index 0000000..3b91b1c
@@ -0,0 +1,10 @@
+#ifndef _ARCH_H_
+#define _ARCH_H_
+
+#include "elf.h"
+
+int arch_validate_function(struct elf *elf, struct symbol *func);
+int arch_is_return_insn(struct elf *elf, unsigned long addr,
+			unsigned int maxlen, unsigned int *len);
+
+#endif /* _ARCH_H_ */
scripts/stackvalidate/elf.c new file +0 -0
new file mode 100644
index 0000000..7879a0f

[...]

Quoted 22 lines
scripts/stackvalidate/elf.c new file +22 -0
+static int elf_read_sections(struct elf *elf)
+{
+	Elf_Scn *s = NULL;
+	struct section *sec;
+	size_t shstrndx, sections_nr;
+	int i;
+
+	if (elf_getshdrnum(elf->elf, &sections_nr)) {
+		perror("elf_getshdrnum");
+		return -1;
+	}
+
+	if (elf_getshdrstrndx(elf->elf, &shstrndx)) {
+		perror("elf_getshdrstrndx");
+		return -1;
+	}
+
+	for (i = 0; i < sections_nr; i++) {
+		sec = malloc(sizeof(*sec));
+		if (!sec) {
+			perror("malloc");
+			return -1;

scripts/stackvalidate/elf.c line 100

Quoted 10 lines
scripts/stackvalidate/elf.c new file +10 -0
+		}
+		memset(sec, 0, sizeof(*sec));
+
+		INIT_LIST_HEAD(&sec->symbols);
+		INIT_LIST_HEAD(&sec->relas);
+
+		s = elf_getscn(elf->elf, i);
+		if (!s) {
+			perror("elf_getscn");
+			return -1;

This is leaking "sec". It is not added to the list, so you could not free it later. The same problem is on many other locations.

Quoted 31 lines
scripts/stackvalidate/elf.c new file +31 -0
+		}
+
+		sec->index = elf_ndxscn(s);
+
+		if (!gelf_getshdr(s, &sec->sh)) {
+			perror("gelf_getshdr");
+			return -1;
+		}
+
+		sec->name = elf_strptr(elf->elf, shstrndx, sec->sh.sh_name);
+		if (!sec->name) {
+			perror("elf_strptr");
+			return -1;
+		}
+
+		sec->data = elf_getdata(s, NULL);
+		if (!sec->data) {
+			perror("elf_getdata");
+			return -1;
+		}
+
+		if (sec->data->d_off != 0 ||
+		    sec->data->d_size != sec->sh.sh_size) {
+			WARN("unexpected data attributes for %s", sec->name);
+			return -1;
+		}
+
+		sec->start = (unsigned long)sec->data->d_buf;
+		sec->end = sec->start + sec->data->d_size;
+
+		list_add_tail(&sec->list, &elf->sections);

scripts/stackvalidate/elf.c line 141

Quoted 10 lines
scripts/stackvalidate/elf.c new file +10 -0
+	}
+
+	/* sanity check, one more call to elf_nextscn() should return NULL */
+	if (elf_nextscn(elf->elf, s)) {
+		WARN("section entry mismatch");
+		return -1;
+	}
+
+	return 0;
+}

I basically ended here. I was rather curious how such a thing could get implemented than doing a proper review.

Best Regards,
Petr

#8 lore raw

Re: [PATCH 1/2] x86, stackvalidate: Compile-time stack frame pointer validation

Quoted 11 lines · Petr Mladek

scripts/stackvalidate/arch-x86.c line 83

Quoted 6 lines · Josh Poimboeuf
scripts/stackvalidate/arch-x86.c new file +6 -0
+		case 0x89:
+			insn_get_modrm(&insn);
+			if (insn.modrm.bytes[0] == 0xe5)
+				/* mov sp, bp */
+				mov++;
+			break;

I am a bit courious. You check 0x89 and 0x5e but I see the following in the disasembly:

Quoted 6 lines

48 89 e5 mov %rsp,%rbp

I wonder if 48 is just a prefix that is filtered by insn_get_opcode or what.

Sigh, I still need to learn a lot about assembly.

Yeah, 0x48 is a prefix which means the operands are 64-bit. Here is a useful, albeit very dense, x86 assembly reference:

http://ref.x86asm.net/coder64.html

Quoted 4 lines
Quoted 4 lines
scripts/stackvalidate/arch-x86.c new file +4 -0
+		case 0xc3: /* ret */
+			ret++;
+			break;
+		}
Quoted 8 lines

scripts/stackvalidate/arch-x86.c line 92

Quoted 5 lines
scripts/stackvalidate/arch-x86.c new file +5 -0
+	}
+
+	if (push != 1 || mov != 1 || !pop || !ret || pop != ret) {
+		WARN("%s() is missing frame pointer logic.  Please use FUNC_ENTER.",
+		     func->name);

This looks quite weak condition to me. It accepts the instructions in any order and at any place.

It's really designed to determine whether the frame pointer is being updated, with some basic sanity checks. It doesn't guarantee that it's being done _correctly_. Which is ok I think, since all assembly code is hand-coded by people who (hopefully) are being careful and know exactly what they're doing.

And as it turns out, today, >99% of callable asm functions don't have any frame pointer logic and will fail this check.

Quoted 2 lines

Also livepatching will rely on having fentry before the prologue. Do you plan to add support for this ordering?

No, because this is only intended to analyze hand-crafted asm code, which generally doesn't have the fentry call (and so live patching can't patch it).

However, if we wanted to enable the patching of asm functions in the future, we could consider adding the fentry call to the FUNC_ENTER macro later on.

Quoted 10 lines
Quoted 10 lines
scripts/stackvalidate/elf.c new file +10 -0
+	for (i = 0; i < sections_nr; i++) {
+		sec = malloc(sizeof(*sec));
+		if (!sec) {
+			perror("malloc");
+			return -1;
+		}
+		memset(sec, 0, sizeof(*sec));
+
+		INIT_LIST_HEAD(&sec->symbols);
+		INIT_LIST_HEAD(&sec->relas);
Quoted 8 lines

scripts/stackvalidate/elf.c line 100

Quoted 5 lines
scripts/stackvalidate/elf.c new file +5 -0
+
+		s = elf_getscn(elf->elf, i);
+		if (!s) {
+			perror("elf_getscn");
+			return -1;

This is leaking "sec". It is not added to the list, so you could not free it later. The same problem is on many other locations.

Ah, right. It really doesn't matter much since this is a short running userspace program, but I'll fix it up.

Quoted 11 lines

scripts/stackvalidate/elf.c line 141

Quoted 8 lines
scripts/stackvalidate/elf.c new file +8 -0
+	/* sanity check, one more call to elf_nextscn() should return NULL */
+	if (elf_nextscn(elf->elf, s)) {
+		WARN("section entry mismatch");
+		return -1;
+	}
+
+	return 0;
+}

I basically ended here. I was rather curious how such a thing could get implemented than doing a proper review.

Thanks a lot for looking!

Signature · 1 line
Josh
#9 lore raw

PATCH 2/2 x86, stackvalidate: Add asm frame pointer setup macros

In reply to Josh Poimboeuf

Add some helper macros for asm functions so that they can comply with stackvalidate.

The FUNC_ENTER and FUNC_RETURN macros help asm functions save, set up, and restore frame pointers.

The RET_NOVALIDATE and FILE_NOVALIDATE macros can be used to whitelist the few locations which need a return instruction outside of a callable function.

---
 arch/x86/include/asm/func.h | 82 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)
 create mode 100644 arch/x86/include/asm/func.h
arch/x86/include/asm/func.h new file +82 -0
new file mode 100644
index 0000000..ae84196
@@ -0,0 +1,82 @@
+#ifndef _ASM_X86_FUNC_H
+#define _ASM_X86_FUNC_H
+
+#include <linux/linkage.h>
+#include <asm/dwarf2.h>
+#include <asm/asm.h>
+
+.macro FUNC_ENTER_NO_FP name
+	ENTRY(\name)
+	CFI_STARTPROC
+	CFI_DEF_CFA _ASM_SP, __ASM_SEL(4, 8)
+.endm
+
+.macro FUNC_RETURN_NO_FP name
+	CFI_DEF_CFA _ASM_SP, __ASM_SEL(4, 8)
+	__ASM_SIZE(ret)
+	CFI_ENDPROC
+	ENDPROC(\name)
+.endm
+
+#ifdef CONFIG_FRAME_POINTER
+
+.macro FUNC_ENTER_FP name
+	FUNC_ENTER_NO_FP \name
+	__ASM_SIZE(push, _cfi) %_ASM_BP
+	CFI_REL_OFFSET _ASM_BP, 0
+	_ASM_MOV %_ASM_SP, %_ASM_BP
+	CFI_DEF_CFA_REGISTER _ASM_BP
+.endm
+
+.macro FUNC_RETURN_FP name
+	__ASM_SIZE(pop, _cfi) %_ASM_BP
+	CFI_RESTORE _ASM_BP
+	FUNC_RETURN_NO_FP \name
+.endm
+
+/*
+ * Every callable asm function should be bookended with FUNC_ENTER and
+ * FUNC_RETURN.  They do proper frame pointer and DWARF CFI setups in order to
+ * achieve more reliable stack traces.
+ *
+ * For the sake of simplicity and correct DWARF annotations, use of the macros
+ * requires that the return instruction comes at the end of the function.
+ */
+#define FUNC_ENTER(name) FUNC_ENTER_FP name
+#define FUNC_RETURN(name) FUNC_RETURN_FP name
+
+/*
+ * RET_NOVALIDATE tells the stack validation script to whitelist the return
+ * instruction immediately after the macro.  Only use it if you're completely
+ * sure you need a return instruction outside of a callable function.
+ * Otherwise, if the code can be called and you haven't annotated it with
+ * FUNC_ENTER/FUNC_RETURN, it will break stack trace reliability.
+ */
+.macro RET_NOVALIDATE
+	163:
+	.pushsection __stackvalidate_whitelist_ret, "ae"
+	_ASM_ALIGN
+	.long 163b - .
+	.popsection
+.endm
+
+/*
+ * FILE_NOVALIDATE is like RET_NOVALIDATE except it whitelists the entire file.
+ * Use with extreme caution or you will silently break stack traces.
+ */
+.macro FILE_NOVALIDATE
+	.pushsection __stackvalidate_whitelist_file, "ae"
+	.long 0
+	.popsection
+.endm
+
+#else /* !FRAME_POINTER */
+
+#define FUNC_ENTER(name) FUNC_ENTER_NO_FP name
+#define FUNC_RETURN(name) FUNC_RETURN_NO_FP name
+#define RET_NOVALIDATE
+#define FILE_NOVALIDATE
+
+#endif /* FRAME_POINTER */
+
+#endif /* _ASM_X86_FUNC_H */
Signature · 1 line
2.1.0