Skip to content
Browse files

stackleak: reintroduce checking of alloca() calls

This reintroduces the portion of the STACKLEAK GCC plugin that
instruments the kernel code by inserting the stackleak_check_alloca()
call before alloca(). The goal is to block kernel stack depth overflows
caused by alloca() (e.g. Stack Clash).

This feature was dropped on STACKLEAK's way to mainline but we are
interested in it despite the VLA removal because:
    - it protects out-of-tree code;
    - it acts as a failsafe, for instance if VLAs are inadvertently

The STACKLEAK feature was ported by Alexander Popov from
grsecurity/PaX's code. More information at:

This code is modified from Brad Spengler/PaX Team's code in the last
public patch of grsecurity/PaX based on our understanding of the code.
Changes or omissions from the original code are ours and don't reflect
the original grsecurity/PaX code.

Change-Id: Iae2fa98ee442bc75731534fe21edc7de26bc00e0
Signed-off-by: Thibaut Sautereau <>
  • Loading branch information...
tsautereau-anssi committed Oct 30, 2018
1 parent 6fcde90 commit 7a94313c154dfe78223729b015b16d5f257afc35
@@ -165,10 +165,15 @@ Stack depth overflow
A less well understood attack is using a bug that triggers the
kernel to consume stack memory with deep function calls or large stack
allocations. With this attack it is possible to write beyond the end of
the kernel's preallocated stack space and into sensitive structures. Two
important changes need to be made for better protections: moving the
sensitive thread_info structure elsewhere, and adding a faulting memory
hole at the bottom of the stack to catch these overflows.
the kernel's preallocated stack space and into sensitive structures.
The combination of the following measures gives better protection:

* moving the sensitive thread_info structure off the stack
* adding a faulting memory hole at the bottom of the stack to catch
these overflows (``CONFIG_VMAP_STACK``);
* runtime checking that alloca() calls don't overstep the stack boundary

Heap memory integrity
@@ -422,3 +422,34 @@ void show_regs(struct pt_regs *regs)
if (!user_mode(regs))
show_trace_log_lvl(current, regs, NULL, KERN_DEFAULT);

void __used stackleak_check_alloca(unsigned long size)
unsigned long sp = (unsigned long)&sp;
struct stack_info stack_info = {0};
unsigned long visit_mask = 0;
unsigned long stack_left;

BUG_ON(get_stack_info(&sp, current, &stack_info, &visit_mask));

stack_left = sp - (unsigned long)stack_info.begin;

if (size >= stack_left) {
* Kernel stack depth overflow is detected, let's report that.
* If CONFIG_VMAP_STACK is enabled, we can safely use BUG().
* If CONFIG_VMAP_STACK is disabled, BUG() handling can corrupt
* the neighbour memory. CONFIG_SCHED_STACK_END_CHECK calls
* panic() in a similar situation, so let's do the same if that
* option is on. Otherwise just use BUG() and hope for the best.
panic("alloca() over the kernel stack boundary\n");
@@ -143,6 +143,8 @@ config GCC_PLUGIN_STACKLEAK
bool "Erase the kernel stack before returning from syscalls"
depends on GCC_PLUGINS
This option makes the kernel erase the kernel stack before
returning from system calls. That reduces the information which
@@ -8,11 +8,13 @@
* but for the kernel it doesn't matter since it doesn't link against
* any of the gcc libraries
* This gcc plugin is needed for tracking the lowest border of the kernel stack.
* It instruments the kernel code inserting stackleak_track_stack() calls:
* - after alloca();
* - for the functions with a stack frame size greater than or equal
* to the "track-min-size" plugin parameter.
* This gcc plugin is needed for tracking the lowest border of the kernel stack
* and checking that alloca() calls don't cause stack overflow. It instruments
* the kernel code inserting:
* - the stackleak_check_alloca() call before alloca() and the
* stackleak_track_stack() call after it;
* - the stackleak_track_stack() call for the functions with a stack frame
* size greater than or equal to the "track-min-size" plugin parameter.
* This plugin is ported from grsecurity/PaX. For more information see:
@@ -33,19 +35,45 @@ __visible int plugin_is_GPL_compatible;

static int track_frame_size = -1;
static const char track_function[] = "stackleak_track_stack";
static const char check_function[] = "stackleak_check_alloca";

* Mark these global variables (roots) for gcc garbage collector since
* they point to the garbage-collected memory.
static GTY(()) tree track_function_decl;
static GTY(()) tree check_function_decl;

static struct plugin_info stackleak_plugin_info = {
.version = "201707101337",
.help = "track-min-size=nn\ttrack stack for functions with a stack frame size >= nn bytes\n"
"disable\t\tdo not activate the plugin\n"

static void stackleak_add_check_alloca(gimple_stmt_iterator *gsi)
gimple stmt;
gcall *stackleak_check_alloca;
tree alloca_size;
cgraph_node_ptr node;
int frequency;
basic_block bb;

/* Insert call to void stackleak_check_alloca(unsigned long size) */
alloca_size = gimple_call_arg(gsi_stmt(*gsi), 0);
stmt = gimple_build_call(check_function_decl, 1, alloca_size);
stackleak_check_alloca = as_a_gcall(stmt);
gsi_insert_before(gsi, stackleak_check_alloca, GSI_SAME_STMT);

/* Update the cgraph */
bb = gimple_bb(stackleak_check_alloca);
node = cgraph_get_create_node(check_function_decl);
frequency = compute_call_stmt_bb_frequency(current_function_decl, bb);
cgraph_create_edge(cgraph_get_node(current_function_decl), node,
stackleak_check_alloca, bb->count, frequency);

static void stackleak_add_track_stack(gimple_stmt_iterator *gsi, bool after)
gimple stmt;
@@ -88,7 +116,8 @@ static bool is_alloca(gimple stmt)

* Work with the GIMPLE representation of the code. Insert the
* stackleak_track_stack() call after alloca() and into the beginning
* stackleak_check_alloca() call before alloca() and stackleak_track_stack()
* call after it. Also insert stackleak_track_stack() call into the beginning
* of the function if it is not instrumented.
static unsigned int stackleak_instrument_execute(void)
@@ -123,6 +152,9 @@ static unsigned int stackleak_instrument_execute(void)
if (!is_alloca(stmt))

/* Insert stack overflow check before alloca() */

/* Insert stackleak_track_stack() call after alloca() */
stackleak_add_track_stack(&gsi, true);
if (bb == entry_bb)
@@ -284,7 +316,10 @@ static bool stackleak_gate(void)
return track_frame_size >= 0;

/* Build the function declaration for stackleak_track_stack() */
* Build function declarations for stackleak_track_stack() and
* stackleak_check_alloca().
static void stackleak_start_unit(void *gcc_data __unused,
void *user_data __unused)
@@ -299,6 +334,17 @@ static void stackleak_start_unit(void *gcc_data __unused,
DECL_EXTERNAL(track_function_decl) = 1;
DECL_ARTIFICIAL(track_function_decl) = 1;
DECL_PRESERVE_P(track_function_decl) = 1;

/* void stackleak_check_alloca(unsigned long) */
fntype = build_function_type_list(void_type_node,
long_unsigned_type_node, NULL_TREE);
check_function_decl = build_fn_decl(check_function, fntype);
DECL_ASSEMBLER_NAME(check_function_decl); /* for LTO */
TREE_PUBLIC(check_function_decl) = 1;
TREE_USED(check_function_decl) = 1;
DECL_EXTERNAL(check_function_decl) = 1;
DECL_ARTIFICIAL(check_function_decl) = 1;
DECL_PRESERVE_P(check_function_decl) = 1;

@@ -349,6 +395,13 @@ __visible int plugin_init(struct plugin_name_args *plugin_info,
.cb = &gt_ggc_mx_tree_node,
.pchw = &gt_pch_nx_tree_node
.base = &check_function_decl,
.nelt = 1,
.stride = sizeof(check_function_decl),
.cb = &gt_ggc_mx_tree_node,
.pchw = &gt_pch_nx_tree_node

0 comments on commit 7a94313

Please sign in to comment.
You can’t perform that action at this time.