[PATCH] objtool: Fix stack overflow in validate_branch()

Josh Poimboeuf posted 1 patch 2 weeks, 3 days ago
tools/objtool/check.c | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)
[PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 3 days ago
On an allmodconfig kernel compiled with Clang, objtool is segfaulting in
drivers/scsi/qla2xxx/qla2xxx.o due to a stack overflow in
validate_branch().

Due in part to KASAN being enabled, the qla2xxx code has a large number
of conditional jumps, causing objtool to go quite deep in its recursion.

By far the biggest offender of stack usage is the recently added
'prev_state' stack variable in validate_insn(), coming in at 328 bytes.

Move that variable (and its tracing usage) to handle_insn_ops() and make
handle_insn_ops() noinline to keep its stack frame outside the recursive
call chain.

Fixes: fcb268b47a2f ("objtool: Trace instruction state changes during function validation")
Reported-by: Nathan Chancellor <nathan@kernel.org>
Closes: https://lore.kernel.org/20251201202329.GA3225984@ax162
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 tools/objtool/check.c | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 9ec0e07cce90..4e7b44f13b8c 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3282,18 +3282,19 @@ static int propagate_alt_cfi(struct objtool_file *file, struct instruction *insn
 	return 0;
 }
 
-static int handle_insn_ops(struct instruction *insn,
-			   struct instruction *next_insn,
-			   struct insn_state *state)
+static int noinline handle_insn_ops(struct instruction *insn,
+				    struct instruction *next_insn,
+				    struct insn_state *state)
 {
+	struct insn_state prev_state __maybe_unused = *state;
 	struct stack_op *op;
-	int ret;
+	int ret = 0;
 
 	for (op = insn->stack_ops; op; op = op->next) {
 
 		ret = update_cfi_state(insn, next_insn, &state->cfi, op);
 		if (ret)
-			return ret;
+			goto done;
 
 		if (!opts.uaccess || !insn->alt_group)
 			continue;
@@ -3303,7 +3304,8 @@ static int handle_insn_ops(struct instruction *insn,
 				state->uaccess_stack = 1;
 			} else if (state->uaccess_stack >> 31) {
 				WARN_INSN(insn, "PUSHF stack exhausted");
-				return 1;
+				ret = 1;
+				goto done;
 			}
 			state->uaccess_stack <<= 1;
 			state->uaccess_stack  |= state->uaccess;
@@ -3319,6 +3321,8 @@ static int handle_insn_ops(struct instruction *insn,
 		}
 	}
 
+done:
+	TRACE_INSN_STATE(insn, &prev_state, state);
 	return 0;
 }
 
@@ -3694,8 +3698,6 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 			 struct instruction *prev_insn, struct instruction *next_insn,
 			 bool *dead_end)
 {
-	/* prev_state and alt_name are not used if there is no disassembly support */
-	struct insn_state prev_state __maybe_unused;
 	char *alt_name __maybe_unused = NULL;
 	struct alternative *alt;
 	u8 visited;
@@ -3798,11 +3800,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 	if (skip_alt_group(insn))
 		return 0;
 
-	prev_state = *statep;
-	ret = handle_insn_ops(insn, next_insn, statep);
-	TRACE_INSN_STATE(insn, &prev_state, statep);
-
-	if (ret)
+	if (handle_insn_ops(insn, next_insn, statep))
 		return 1;
 
 	switch (insn->type) {
-- 
2.51.1
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 3 days ago
On Tue, Dec 02, 2025 at 08:16:28AM -0800, Josh Poimboeuf wrote:
> -static int handle_insn_ops(struct instruction *insn,
> -			   struct instruction *next_insn,
> -			   struct insn_state *state)
> +static int noinline handle_insn_ops(struct instruction *insn,
> +				    struct instruction *next_insn,
> +				    struct insn_state *state)
>  {
> +	struct insn_state prev_state __maybe_unused = *state;
>  	struct stack_op *op;
> -	int ret;
> +	int ret = 0;
>  
>  	for (op = insn->stack_ops; op; op = op->next) {
>  
>  		ret = update_cfi_state(insn, next_insn, &state->cfi, op);
>  		if (ret)
> -			return ret;
> +			goto done;
>  
>  		if (!opts.uaccess || !insn->alt_group)
>  			continue;
> @@ -3303,7 +3304,8 @@ static int handle_insn_ops(struct instruction *insn,
>  				state->uaccess_stack = 1;
>  			} else if (state->uaccess_stack >> 31) {
>  				WARN_INSN(insn, "PUSHF stack exhausted");
> -				return 1;
> +				ret = 1;
> +				goto done;
>  			}
>  			state->uaccess_stack <<= 1;
>  			state->uaccess_stack  |= state->uaccess;
> @@ -3319,6 +3321,8 @@ static int handle_insn_ops(struct instruction *insn,
>  		}
>  	}
>  
> +done:
> +	TRACE_INSN_STATE(insn, &prev_state, state);
>  	return 0;
>  }

Argh, that should return 'ret'.  Ingo, can you fix that or should I post
a v2?

-- 
Josh
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Ingo Molnar 2 weeks, 3 days ago
* Josh Poimboeuf <jpoimboe@kernel.org> wrote:

> On Tue, Dec 02, 2025 at 08:16:28AM -0800, Josh Poimboeuf wrote:
> > -static int handle_insn_ops(struct instruction *insn,
> > -			   struct instruction *next_insn,
> > -			   struct insn_state *state)
> > +static int noinline handle_insn_ops(struct instruction *insn,
> > +				    struct instruction *next_insn,
> > +				    struct insn_state *state)
> >  {
> > +	struct insn_state prev_state __maybe_unused = *state;
> >  	struct stack_op *op;
> > -	int ret;
> > +	int ret = 0;
> >  
> >  	for (op = insn->stack_ops; op; op = op->next) {
> >  
> >  		ret = update_cfi_state(insn, next_insn, &state->cfi, op);
> >  		if (ret)
> > -			return ret;
> > +			goto done;
> >  
> >  		if (!opts.uaccess || !insn->alt_group)
> >  			continue;
> > @@ -3303,7 +3304,8 @@ static int handle_insn_ops(struct instruction *insn,
> >  				state->uaccess_stack = 1;
> >  			} else if (state->uaccess_stack >> 31) {
> >  				WARN_INSN(insn, "PUSHF stack exhausted");
> > -				return 1;
> > +				ret = 1;
> > +				goto done;
> >  			}
> >  			state->uaccess_stack <<= 1;
> >  			state->uaccess_stack  |= state->uaccess;
> > @@ -3319,6 +3321,8 @@ static int handle_insn_ops(struct instruction *insn,
> >  		}
> >  	}
> >  
> > +done:
> > +	TRACE_INSN_STATE(insn, &prev_state, state);
> >  	return 0;
> >  }
> 
> Argh, that should return 'ret'.  Ingo, can you fix that or should I post
> a v2?

No need, I've fixed it up.

Thanks,

	Ingo
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Ingo Molnar 2 weeks, 3 days ago
* Josh Poimboeuf <jpoimboe@kernel.org> wrote:

> On an allmodconfig kernel compiled with Clang, objtool is segfaulting in
> drivers/scsi/qla2xxx/qla2xxx.o due to a stack overflow in
> validate_branch().
> 
> Due in part to KASAN being enabled, the qla2xxx code has a large number
> of conditional jumps, causing objtool to go quite deep in its recursion.
> 
> By far the biggest offender of stack usage is the recently added
> 'prev_state' stack variable in validate_insn(), coming in at 328 bytes.

That's weird - how can a user-space tool run into stack 
limits, are they set particularly conservatively?

In any case, applied to tip:objtool/urgent, thanks!

	Ingo
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 3 days ago
On Tue, Dec 02, 2025 at 05:20:22PM +0100, Ingo Molnar wrote:
> 
> * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> 
> > On an allmodconfig kernel compiled with Clang, objtool is segfaulting in
> > drivers/scsi/qla2xxx/qla2xxx.o due to a stack overflow in
> > validate_branch().
> > 
> > Due in part to KASAN being enabled, the qla2xxx code has a large number
> > of conditional jumps, causing objtool to go quite deep in its recursion.
> > 
> > By far the biggest offender of stack usage is the recently added
> > 'prev_state' stack variable in validate_insn(), coming in at 328 bytes.
> 
> That's weird - how can a user-space tool run into stack 
> limits, are they set particularly conservatively?

On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
enough :-)

In this case, objtool had over 20,000 stack frames caused by recursively
following over 7,000(!) conditional jumps in a single function.

> In any case, applied to tip:objtool/urgent, thanks!

Looks good now, thanks for fixing that up!

-- 
Josh
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Ingo Molnar 2 weeks, 2 days ago
* Josh Poimboeuf <jpoimboe@kernel.org> wrote:

> On Tue, Dec 02, 2025 at 05:20:22PM +0100, Ingo Molnar wrote:
> >
> > * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> >
> > > On an allmodconfig kernel compiled with Clang, objtool is
> > > segfaulting in drivers/scsi/qla2xxx/qla2xxx.o due to a stack
> > > overflow in validate_branch().
> > >
> > > Due in part to KASAN being enabled, the qla2xxx code has a large
> > > number of conditional jumps, causing objtool to go quite deep in
> > > its recursion.
> > >
> > > By far the biggest offender of stack usage is the recently added
> > > 'prev_state' stack variable in validate_insn(), coming in at 328
> > > bytes.
> >
> > That's weird - how can a user-space tool run into stack limits, are
> > they set particularly conservatively?
>
> On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> enough :-)
>
> In this case, objtool had over 20,000 stack frames caused by
> recursively following over 7,000(!) conditional jumps in a single
> function.

BTW., I just instrumented it, and it's even worse: on current upstream,
the allmodconfig qla2xxx.o code built with clang-20.1.8 has a worst-case
recursion depth of 50,944 (!), for the qla83xx_fw_dump() function.

That function has 69,817 instructions and 14,645 branches.

BTW., beyond the better handling of the crash itself, we should IMO step
back and question whether a recursion depth of 50,000 is really necessary
and justified.

AFAICS the deep recursions mostly come from parsing long,
repetitive sequences of KASAN instrumentation intermixed with
conditional branches, such as:

	...
	1a0a4a:  qla83xx_fw_dump+0x1a18a                                jne    0x1c569a <qla83xx_fw_dump+0x3edda>
	...
	1a0a80:  qla83xx_fw_dump+0x1a1c0                                jne    0x1c56b7 <qla83xx_fw_dump+0x3edf7>
	...
	1a0ab6:  qla83xx_fw_dump+0x1a1f6                                jne    0x1c56d5 <qla83xx_fw_dump+0x3ee15>
	...
	1a0aec:  qla83xx_fw_dump+0x1a22c                                jne    0x1c56f2 <qla83xx_fw_dump+0x3ee32> ====> [1]
[2] ==>	1a0af2:  qla83xx_fw_dump+0x1a232                                mov    %r12d,0x1ed4(%rbp)
	...
	1a0b22:  qla83xx_fw_dump+0x1a262                                jne    0x1c5710 <qla83xx_fw_dump+0x3ee50>
	...
	1a0b58:  qla83xx_fw_dump+0x1a298                                jne    0x1c572d <qla83xx_fw_dump+0x3ee6d>
	...
	1a0b91:  qla83xx_fw_dump+0x1a2d1                                jne    0x1c574b <qla83xx_fw_dump+0x3ee8b>
	...
	1a0bc7:  qla83xx_fw_dump+0x1a307                                jne    0x1c5768 <qla83xx_fw_dump+0x3eea8>
	...
	1a0bfd:  qla83xx_fw_dump+0x1a33d                                jne    0x1c5786 <qla83xx_fw_dump+0x3eec6>
	...
	1a0c33:  qla83xx_fw_dump+0x1a373                                jne    0x1c57a3 <qla83xx_fw_dump+0x3eee3>
	...
	1a0c69:  qla83xx_fw_dump+0x1a3a9                                jne    0x1c57c1 <qla83xx_fw_dump+0x3ef01>
	...
	1a0c9f:  qla83xx_fw_dump+0x1a3df                                jne    0x1c57de <qla83xx_fw_dump+0x3ef1e>
	...
	1a0cd5:  qla83xx_fw_dump+0x1a415                                jne    0x1c57fc <qla83xx_fw_dump+0x3ef3c>
	...

Where each conditional jump target has a short, site specific trampoline:

[1] ==>	1c56f2:  qla83xx_fw_dump+0x3ee32                                mov    %r15d,%ecx
	1c56f5:  qla83xx_fw_dump+0x3ee35                                and    $0x7,%cl                                        [2]
	1c56f8:  qla83xx_fw_dump+0x3ee38                                add    $0x3,%cl                                         A
	1c56fb:  qla83xx_fw_dump+0x3ee3b                                cmp    %al,%cl                                          |
	1c56fd:  qla83xx_fw_dump+0x3ee3d                                jl     0x1a0af2 <qla83xx_fw_dump+0x1a232>  =============+
	1c5703:  qla83xx_fw_dump+0x3ee43                                mov    %r15,%rdi                                        |
	1c5706:  qla83xx_fw_dump+0x3ee46                                call   0x1c570b <__asan_report_store4_noabort>          |
	1c570b:  qla83xx_fw_dump+0x3ee4b                                jmp    0x1a0af2 <qla83xx_fw_dump+0x1a232>  ============='

Where objtool recurses into validate_insn() on every new branch
instruction found, due to:

  tools/objtool/check.c:validate_insn():

	...
        case INSN_JUMP_CONDITIONAL:
        case INSN_JUMP_UNCONDITIONAL:
        ...

                        ret = validate_branch(file, func, insn->jump_dest, *statep);
	...

While this flow and default parsing logic is correct and is guaranteed
to cover every instruction of a function eventually, it has several
disadvantages:

 - Note how it recurses deeper and deeper as it first parses the [1]
   conditional branch, then goes back with another new recursion via the
   [2] conditional branch in the trampoline.

 - It splits validation into two long passes, one where it sparsely
   skips forward thousands of times in the 1c56f2 trampoline range,
   then when finally the last branch is parsed, it returns and
   starts parsing the 'holes' in the trampoline area *in reverse*.

 - The stack footprint is ~5.5MB on my system, and the stack usage is
   disadvantageous as execution yo-yos up and down this large stack and
   moves parts of it in/out of the L1 data cache. qla2xxx.o is large at
   13MB, and the +5.5MB recursion stack footprint baloons its working
   set by at least +40% ...

I'm quite certain that this kind of 'sparse' instruction decoding where
objtool is merrily chasing and recursing after branches increases dcache
footprint unnecessarily and slows down overall execution, especially
with larger object files.

Ie. this looks like a self-inflicted wound by objtool.

One relatively simple method to 'straighten out' the parsing flow would
be to add an internal 'branch queue' with a limited size of say 16 or 32
entries, and defer the parsing of these branch targets and continue with
the next instruction, until one of these conditions is true:

  - 'branch queue' is full

  - JMP, CALL, RET or any other branching/trapping instruction is found

  - already validated instruction is found

  - end of symbol/section/file/etc.

At which point the current 'branch queue' is flushed. (It might even be
implemented as a branch-target stack, which may have a bit better
locality.)

I bet such an approach that does straight-line instruction parsing would
reduce the worst-case recursion depth to well below 100, from today's
50,000+, and might measurably speed up objtool as well as a side effect ...

Thoughts?

	Ingo
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 2 days ago
On Wed, Dec 03, 2025 at 10:25:34AM +0100, Ingo Molnar wrote:
> * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > On Tue, Dec 02, 2025 at 05:20:22PM +0100, Ingo Molnar wrote:
> > > * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > > > On an allmodconfig kernel compiled with Clang, objtool is
> > > > segfaulting in drivers/scsi/qla2xxx/qla2xxx.o due to a stack
> > > > overflow in validate_branch().
> > > >
> > > > Due in part to KASAN being enabled, the qla2xxx code has a large
> > > > number of conditional jumps, causing objtool to go quite deep in
> > > > its recursion.
> > > >
> > > > By far the biggest offender of stack usage is the recently added
> > > > 'prev_state' stack variable in validate_insn(), coming in at 328
> > > > bytes.
> > >
> > > That's weird - how can a user-space tool run into stack limits, are
> > > they set particularly conservatively?
> >
> > On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> > enough :-)
> >
> > In this case, objtool had over 20,000 stack frames caused by
> > recursively following over 7,000(!) conditional jumps in a single
> > function.
> 
> BTW., I just instrumented it, and it's even worse: on current upstream,
> the allmodconfig qla2xxx.o code built with clang-20.1.8 has a worst-case
> recursion depth of 50,944 (!), for the qla83xx_fw_dump() function.

Is that number of loops or total stack frames?  With an allmodconfig
kernel and clang 20.1.8 I'm getting a max recursion depth of 7,165 loops
(not frames).  See the below patch for how I measured that.

> While this flow and default parsing logic is correct and is guaranteed
> to cover every instruction of a function eventually, it has several
> disadvantages:
> 
>  - Note how it recurses deeper and deeper as it first parses the [1]
>    conditional branch, then goes back with another new recursion via the
>    [2] conditional branch in the trampoline.
> 
>  - It splits validation into two long passes, one where it sparsely
>    skips forward thousands of times in the 1c56f2 trampoline range,
>    then when finally the last branch is parsed, it returns and
>    starts parsing the 'holes' in the trampoline area *in reverse*.
> 
>  - The stack footprint is ~5.5MB on my system, and the stack usage is
>    disadvantageous as execution yo-yos up and down this large stack and
>    moves parts of it in/out of the L1 data cache. qla2xxx.o is large at
>    13MB, and the +5.5MB recursion stack footprint baloons its working
>    set by at least +40% ...

You may be underestimating the amount of memory usage objtool needs.
Running objtool on that binary with "/usr/bin/time -v" shows the maximum
resident set size is 140M.  So the stack usage of 5.5MB is only about
4.4% of the total memory usage.

Keep in mind this is a worst case function for objtool: KASAN with tons
of memory accesses.  The vast majority of functions won't come anywhere
near that level of recursion.

> I'm quite certain that this kind of 'sparse' instruction decoding where
> objtool is merrily chasing and recursing after branches increases dcache
> footprint unnecessarily and slows down overall execution, especially
> with larger object files.
> 
> Ie. this looks like a self-inflicted wound by objtool.
> 
> One relatively simple method to 'straighten out' the parsing flow would
> be to add an internal 'branch queue' with a limited size of say 16 or 32
> entries, and defer the parsing of these branch targets and continue with
> the next instruction, until one of these conditions is true:
> 
>   - 'branch queue' is full
> 
>   - JMP, CALL, RET or any other branching/trapping instruction is found
> 
>   - already validated instruction is found
> 
>   - end of symbol/section/file/etc.
> 
> At which point the current 'branch queue' is flushed. (It might even be
> implemented as a branch-target stack, which may have a bit better
> locality.)

Objtool tracks a considerable amount of state across branches.  The
recursion works well for keeping that state at hand.  So there is a
certain level of dependency there which I have a feeling might be
difficult to extricate.  I haven't really looked at it though.

Here's how I measured the 7,000+ loops:

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 3f7999317f4d..da9c43d84ddc 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3694,6 +3694,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
 static int do_validate_branch(struct objtool_file *file, struct symbol *func,
 			      struct instruction *insn, struct insn_state state);
 
+int recurse_depth, max_recurse_depth;
+
 static int validate_insn(struct objtool_file *file, struct symbol *func,
 			 struct instruction *insn, struct insn_state *statep,
 			 struct instruction *prev_insn, struct instruction *next_insn,
@@ -3843,11 +3845,18 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 			else
 				TRACE_INSN(insn, "jump taken");
 
+			recurse_depth++;
+
+			if (recurse_depth > max_recurse_depth)
+				max_recurse_depth = recurse_depth;
+
 			ret = validate_branch(file, func, insn->jump_dest, *statep);
 			if (ret) {
 				BT_INSN(insn, "(branch)");
 				return ret;
 			}
+
+			recurse_depth--;
 		}
 
 		if (insn->type == INSN_JUMP_UNCONDITIONAL)
@@ -5067,6 +5076,8 @@ int check(struct objtool_file *file)
 		printf("nr_cfi_cache: %ld\n", nr_cfi_cache);
 	}
 
+	WARN("max_recurse_depth = %d", max_recurse_depth);
+
 out:
 	if (ret || warnings) {
 		if (opts.werror && warnings)
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Ingo Molnar 2 weeks, 2 days ago
* Josh Poimboeuf <jpoimboe@kernel.org> wrote:

> On Wed, Dec 03, 2025 at 10:25:34AM +0100, Ingo Molnar wrote:
> > * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > > On Tue, Dec 02, 2025 at 05:20:22PM +0100, Ingo Molnar wrote:
> > > > * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > > > > On an allmodconfig kernel compiled with Clang, objtool is
> > > > > segfaulting in drivers/scsi/qla2xxx/qla2xxx.o due to a stack
> > > > > overflow in validate_branch().
> > > > >
> > > > > Due in part to KASAN being enabled, the qla2xxx code has a large
> > > > > number of conditional jumps, causing objtool to go quite deep in
> > > > > its recursion.
> > > > >
> > > > > By far the biggest offender of stack usage is the recently added
> > > > > 'prev_state' stack variable in validate_insn(), coming in at 328
> > > > > bytes.
> > > >
> > > > That's weird - how can a user-space tool run into stack limits, are
> > > > they set particularly conservatively?
> > >
> > > On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> > > enough :-)
> > >
> > > In this case, objtool had over 20,000 stack frames caused by
> > > recursively following over 7,000(!) conditional jumps in a single
> > > function.
> >
> > BTW., I just instrumented it, and it's even worse: on current upstream,
> > the allmodconfig qla2xxx.o code built with clang-20.1.8 has a worst-case
> > recursion depth of 50,944 (!), for the qla83xx_fw_dump() function.
>
> Is that number of loops or total stack frames?

So I tracked the depth of validate_insn() recursion directly:

                ret = validate_insn(file, func, insn, &state, prev_insn, next_insn,
-                                   &dead_end);
+                                   &dead_end, depth++);

                if (!insn->trace) {
                        if (ret)

And in validate_insn():

	if (depth > max_depth) {
		max_depth = depth;
		printf("# objtool new max depth: %ld for %s()\n", max_depth, func->name);
	}

Actual function recursion depth may be deeper, if any of the helper
functions get uninlined.

> kernel and clang 20.1.8 I'm getting a max recursion depth of 7,165 loops
> (not frames).  See the below patch for how I measured that.

Your patch seems to be similar, except that I passed in 'depth'
directly, because as a kernel developer I don't trust globals :-)

But it should measure the same thing AFAICS, right?

> You may be underestimating the amount of memory usage objtool needs.
> Running objtool on that binary with "/usr/bin/time -v" shows the maximum
> resident set size is 140M.  So the stack usage of 5.5MB is only about
> 4.4% of the total memory usage.

Still, the stack is some of the cache-hottest pieces of memory
in that workload - and the biggest negative impact from the
current recursion pattern comes from the sparse parsing, which
suffers an even worse negative effect with a 140MB working set.

> > One relatively simple method to 'straighten out' the parsing flow would
> > be to add an internal 'branch queue' with a limited size of say 16 or 32
> > entries, and defer the parsing of these branch targets and continue with
> > the next instruction, until one of these conditions is true:
> >
> >   - 'branch queue' is full
> >
> >   - JMP, CALL, RET or any other branching/trapping instruction is found
> >
> >   - already validated instruction is found
> >
> >   - end of symbol/section/file/etc.
> >
> > At which point the current 'branch queue' is flushed. (It might even be
> > implemented as a branch-target stack, which may have a bit better
> > locality.)
>
> Objtool tracks a considerable amount of state across branches.  The
> recursion works well for keeping that state at hand.  So there is a
> certain level of dependency there which I have a feeling might be
> difficult to extricate.  I haven't really looked at it though.

I'm not against recursion for branches at all, I just suggest
to change the order of how the recursion is fed: instead of parsing
the two instruction streams of a branch point in this order:

	verify target recursively
	verify next instruction

(Which is arguably the simplest.)

I suggest the following recursion pattern:

	verify a batch of serial sequence of instruction(s) and save conditional branch targets (if any)
	verify saved branch targets, recursively

This change to the recursion pattern should make a very large
impact on max recursion depth, in addition to substantially better
cache locality.

Thanks,

	Ingo
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 1 day ago
On Wed, Dec 03, 2025 at 08:11:54PM +0100, Ingo Molnar wrote:
> * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > Objtool tracks a considerable amount of state across branches.  The
> > recursion works well for keeping that state at hand.  So there is a
> > certain level of dependency there which I have a feeling might be
> > difficult to extricate.  I haven't really looked at it though.
> 
> I'm not against recursion for branches at all, I just suggest
> to change the order of how the recursion is fed: instead of parsing
> the two instruction streams of a branch point in this order:
> 
> 	verify target recursively
> 	verify next instruction
> 
> (Which is arguably the simplest.)
> 
> I suggest the following recursion pattern:
> 
> 	verify a batch of serial sequence of instruction(s) and save conditional branch targets (if any)
> 	verify saved branch targets, recursively
> 
> This change to the recursion pattern should make a very large
> impact on max recursion depth, in addition to substantially better
> cache locality.

I implemented this (see below) and it seems to work ok.  But it still
has some issues:

1) For each deferred branch, the 320-byte insn_state needs to be saved.
   I suspect most of the savings in stack memory usage will just get
   relocated to the heap.

2) It effectively breaks the objtool --backtrace option, which can be
   useful to see which sequence of branches led to a certain state.
 
3) It's just kind of ugly compared to the elegance of the current
   depth-first recursion (though I'm sure there are more elegant ways to
   do a breadth-first traversal).

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 3f7999317f4d..d4f1d89c6ba6 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -27,6 +27,13 @@
 #include <linux/static_call_types.h>
 #include <linux/string.h>
 
+struct deferred_jump {
+	struct instruction *src;
+	struct instruction *dest;
+	struct insn_state state;
+	struct deferred_jump *next;
+};
+
 static unsigned long nr_cfi, nr_cfi_reused, nr_cfi_cache;
 
 static struct cfi_init_state initial_func_cfi;
@@ -3697,7 +3704,7 @@ static int do_validate_branch(struct objtool_file *file, struct symbol *func,
 static int validate_insn(struct objtool_file *file, struct symbol *func,
 			 struct instruction *insn, struct insn_state *statep,
 			 struct instruction *prev_insn, struct instruction *next_insn,
-			 bool *dead_end)
+			 bool *dead_end, struct instruction **defer_jump)
 {
 	char *alt_name __maybe_unused = NULL;
 	struct alternative *alt;
@@ -3709,6 +3716,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 	 * ends, i.e. validate_branch() has reached the end of the branch.
 	 */
 	*dead_end = true;
+	*defer_jump = NULL;
 
 	visited = VISITED_BRANCH << statep->uaccess;
 	if (insn->visited & VISITED_BRANCH_MASK) {
@@ -3838,15 +3846,21 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 				return ret;
 
 		} else if (insn->jump_dest) {
-			if (insn->type == INSN_JUMP_UNCONDITIONAL)
+			if (insn->type == INSN_JUMP_UNCONDITIONAL) {
 				TRACE_INSN(insn, "unconditional jump");
-			else
-				TRACE_INSN(insn, "jump taken");
 
-			ret = validate_branch(file, func, insn->jump_dest, *statep);
-			if (ret) {
-				BT_INSN(insn, "(branch)");
-				return ret;
+				ret = validate_branch(file, func, insn->jump_dest, *statep);
+				if (ret) {
+					BT_INSN(insn, "(branch)");
+					return ret;
+				}
+			} else {
+				/*
+				 * Do all fallthrough paths first, deferring
+				 * conditional jumps to the end to minimize
+				 * objtool stack recursion.
+				 */
+				*defer_jump = insn->jump_dest;
 			}
 		}
 
@@ -3959,14 +3973,17 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 static int do_validate_branch(struct objtool_file *file, struct symbol *func,
 			      struct instruction *insn, struct insn_state state)
 {
+	struct deferred_jump *deferred_list = NULL, *deferred, *tmp;
 	struct instruction *next_insn, *prev_insn = NULL;
 	bool dead_end;
-	int ret;
+	int ret = 0;
 
 	if (func && func->ignore)
 		return 0;
 
 	do {
+		struct instruction *defer_jump;
+
 		insn->trace = 0;
 		next_insn = next_insn_to_validate(file, insn);
 
@@ -3976,20 +3993,20 @@ static int do_validate_branch(struct objtool_file *file, struct symbol *func,
 		if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
 			/* Ignore KCFI type preambles, which always fall through */
 			if (is_prefix_func(func))
-				return 0;
+				break;
 
 			if (file->ignore_unreachables)
-				return 0;
+				break;
 
 			WARN("%s() falls through to next function %s()",
 			     func->name, insn_func(insn)->name);
 			func->warned = 1;
-
-			return 1;
+			ret = 1;
+			goto cleanup;
 		}
 
 		ret = validate_insn(file, func, insn, &state, prev_insn, next_insn,
-				    &dead_end);
+				    &dead_end, &defer_jump);
 
 		if (!insn->trace) {
 			if (ret)
@@ -3998,16 +4015,34 @@ static int do_validate_branch(struct objtool_file *file, struct symbol *func,
 				TRACE_INSN(insn, NULL);
 		}
 
+		if (ret)
+			goto cleanup;
+
+		if (defer_jump) {
+			deferred = malloc(sizeof(*deferred));
+			if (!deferred) {
+				ERROR_GLIBC("malloc");
+				ret = 1;
+				goto cleanup;
+			}
+			deferred->src = insn;
+			deferred->dest = defer_jump;
+			deferred->state = state;
+			deferred->next = deferred_list;
+			deferred_list = deferred;
+		}
+
 		if (!dead_end && !next_insn) {
 			if (state.cfi.cfa.base == CFI_UNDEFINED)
-				return 0;
+				break;
 			if (file->ignore_unreachables)
-				return 0;
+				break;
 
 			WARN("%s%sunexpected end of section %s",
 			     func ? func->name : "", func ? "(): " : "",
 			     insn->sec->name);
-			return 1;
+			ret = 1;
+			goto cleanup;
 		}
 
 		prev_insn = insn;
@@ -4015,6 +4050,20 @@ static int do_validate_branch(struct objtool_file *file, struct symbol *func,
 
 	} while (!dead_end);
 
+	for (deferred = deferred_list; deferred; deferred = deferred->next) {
+		ret = validate_branch(file, func, deferred->dest, deferred->state);
+		if (ret) {
+			BT_INSN(deferred->src, "(branch)");
+			break;
+		}
+	}
+
+cleanup:
+	for (deferred = deferred_list; deferred; deferred = tmp) {
+		tmp = deferred->next;
+		free(deferred);
+	}
+
 	return ret;
 }
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 2 days ago
On Wed, Dec 03, 2025 at 10:54:19AM -0800, Josh Poimboeuf wrote:
> Keep in mind this is a worst case function for objtool: KASAN with tons
> of memory accesses.  The vast majority of functions won't come anywhere
> near that level of recursion.

And BTW, supporting that further, the max recursion depth on vmlinux.o
on a defconfig kernel is showing 162.

-- 
Josh
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Ingo Molnar 2 weeks, 2 days ago
* Josh Poimboeuf <jpoimboe@kernel.org> wrote:

> On Wed, Dec 03, 2025 at 10:54:19AM -0800, Josh Poimboeuf wrote:
> > Keep in mind this is a worst case function for objtool: KASAN with tons
> > of memory accesses.  The vast majority of functions won't come anywhere
> > near that level of recursion.
> 
> And BTW, supporting that further, the max recursion depth on vmlinux.o
> on a defconfig kernel is showing 162.

That's mostly immaterial - 'make allmodconfig' is not some esoteric
test, it's one of the most common testing methods by developers and CI
projects ... For example I use allmodconfig builds for every commit I
write or apply.

Thanks,

	Ingo
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 1 day ago
On Wed, Dec 03, 2025 at 08:15:19PM +0100, Ingo Molnar wrote:
> * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> 
> > On Wed, Dec 03, 2025 at 10:54:19AM -0800, Josh Poimboeuf wrote:
> > > Keep in mind this is a worst case function for objtool: KASAN with tons
> > > of memory accesses.  The vast majority of functions won't come anywhere
> > > near that level of recursion.
> > 
> > And BTW, supporting that further, the max recursion depth on vmlinux.o
> > on a defconfig kernel is showing 162.
> 
> That's mostly immaterial - 'make allmodconfig' is not some esoteric
> test, it's one of the most common testing methods by developers and CI
> projects ... For example I use allmodconfig builds for every commit I
> write or apply.

FWIW, here are some numbers from a clang allmodconfig build:

vmlinux.o max recursion: 1,017

modules max recursion:
- mean: 69
- median: 41
- max: 7,165

-- 
Josh
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by David Laight 2 weeks, 2 days ago
On Wed, 3 Dec 2025 20:15:19 +0100
Ingo Molnar <mingo@kernel.org> wrote:

> * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> 
> > On Wed, Dec 03, 2025 at 10:54:19AM -0800, Josh Poimboeuf wrote:  
> > > Keep in mind this is a worst case function for objtool: KASAN with tons
> > > of memory accesses.  The vast majority of functions won't come anywhere
> > > near that level of recursion.  
> > 
> > And BTW, supporting that further, the max recursion depth on vmlinux.o
> > on a defconfig kernel is showing 162.  
> 
> That's mostly immaterial - 'make allmodconfig' is not some esoteric
> test, it's one of the most common testing methods by developers and CI
> projects ... For example I use allmodconfig builds for every commit I
> write or apply.

Both allmodconfig and allyesconfig should probably manage to disable KASAN.
Apart from building faster, the object files will be more realistic.

I'm not sure how to make that work though.
Could you have an ALLYESCONFIG config option that only 'all yes' will set and
then make KASAN depend on !ALLYESCONFIG

	David

> 
> Thanks,
> 
> 	Ingo
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Linus Torvalds 2 weeks, 2 days ago
On Wed, 3 Dec 2025 at 11:37, David Laight <david.laight.linux@gmail.com> wrote:
>
> Both allmodconfig and allyesconfig should probably manage to disable KASAN.
> Apart from building faster, the object files will be more realistic.

The problem is that it would cut down on build coverage a lot. We've
had lots of build things where KASAN makes things much worse, and
_finding_ them is worthwhile.

In fact, I'd argue that this objtool issue is very much one such
thing, where KASAN makes for horrid code generation and in the process
then finds scalability issues with objtool.

Is it a *realistic* build config? No. But that's not the point. The
point is literally to find weak points in our infrastructure by having
big configurations that do odd things.

Nobody sane should run an allmodconfig kernel in any kind of real life
situation - it's about getting reasonable build coverage.

That said, I'd love to have a faster allmodconfig build, and so I
would like to eventually get rid of KASAN as part of the build just
for that reason if we'd just feel that it's not adding enough value.

But I do think it has found enough coverage issues that we're not there yet.

           Linus
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Ingo Molnar 2 weeks, 3 days ago
* Josh Poimboeuf <jpoimboe@kernel.org> wrote:

> On Tue, Dec 02, 2025 at 05:20:22PM +0100, Ingo Molnar wrote:
> > 
> > * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > 
> > > On an allmodconfig kernel compiled with Clang, objtool is segfaulting in
> > > drivers/scsi/qla2xxx/qla2xxx.o due to a stack overflow in
> > > validate_branch().
> > > 
> > > Due in part to KASAN being enabled, the qla2xxx code has a large number
> > > of conditional jumps, causing objtool to go quite deep in its recursion.
> > > 
> > > By far the biggest offender of stack usage is the recently added
> > > 'prev_state' stack variable in validate_insn(), coming in at 328 bytes.
> > 
> > That's weird - how can a user-space tool run into stack 
> > limits, are they set particularly conservatively?
> 
> On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> enough :-)
> 
> In this case, objtool had over 20,000 stack frames caused by recursively
> following over 7,000(!) conditional jumps in a single function.

Ouch ...

... which means that very likely we'll run into this problem again. :-/

Time to add stack overflow self-detection?

I've attached a simple proof-of-concept that uses 
sigaltstacks based SIGSEGV handler to catch a stack 
overflow:

  starship:/s/stack-overflow> ./overflow 
  # Starting stack recursion:

  # WARNING: SIGSEGV received: Possible stack overflow detected!

  starship:/s/stack-overflow> 

Could we add something like this to objtool, with 
perhaps a look at the interrupted stack pointer from 
SIGSEGV_handler(), to make sure the SIGSEGV was due to 
a stack overflow?

Thanks,

	Ingo


#
# Build with: gcc -Wall -o overflow overflow.c
#
======={ overflow.c }============>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/resource.h>

void SIGSEGV_handler(int sig)
{
	/*
	 * From this point on we are running on the sigaltstack:
	 */
	fprintf(stderr, "\n# WARNING: SIGSEGV received: Possible stack overflow detected!\n");

	_exit(EXIT_FAILURE);
}

void setup_SIGSEGV_handler(void)
{
	struct sigaction sa;
	stack_t ss;

	ss.ss_sp = malloc(SIGSTKSZ);
	if (ss.ss_sp == NULL) {
		perror("FAIL: malloc");
		exit(EXIT_FAILURE);
	}
	ss.ss_size = SIGSTKSZ;
	ss.ss_flags = 0;

	if (sigaltstack(&ss, NULL) == -1) {
		perror("FAIL: sigaltstack");
		exit(EXIT_FAILURE);
	}

	sa.sa_handler = SIGSEGV_handler;
	sigemptyset(&sa.sa_mask);

	/*
	 * SA_ONSTACK tells the kernel to use the sigaltstack
	 * for this handler:
	 */
	sa.sa_flags = SA_RESTART | SA_ONSTACK;

	if (sigaction(SIGSEGV, &sa, NULL) == -1) {
		perror("sigaction");
		exit(EXIT_FAILURE);
	}
}

// Example function to force a recursive stack overflow
void recurse_into_stack(int depth)
{
	char buffer[1000];

	(void)buffer;

	if (depth < 0)
		return;

	recurse_into_stack(depth - 1);
}

int main(void)
{
	setup_SIGSEGV_handler();

	printf("# Starting stack recursion:\n");

	recurse_into_stack(1000000);

	return 0;
}
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 3 days ago
On Tue, Dec 02, 2025 at 06:03:49PM +0100, Ingo Molnar wrote:
> 
> * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> 
> > On Tue, Dec 02, 2025 at 05:20:22PM +0100, Ingo Molnar wrote:
> > > 
> > > * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > > 
> > > > On an allmodconfig kernel compiled with Clang, objtool is segfaulting in
> > > > drivers/scsi/qla2xxx/qla2xxx.o due to a stack overflow in
> > > > validate_branch().
> > > > 
> > > > Due in part to KASAN being enabled, the qla2xxx code has a large number
> > > > of conditional jumps, causing objtool to go quite deep in its recursion.
> > > > 
> > > > By far the biggest offender of stack usage is the recently added
> > > > 'prev_state' stack variable in validate_insn(), coming in at 328 bytes.
> > > 
> > > That's weird - how can a user-space tool run into stack 
> > > limits, are they set particularly conservatively?
> > 
> > On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> > enough :-)
> > 
> > In this case, objtool had over 20,000 stack frames caused by recursively
> > following over 7,000(!) conditional jumps in a single function.
> 
> Ouch ...
> 
> ... which means that very likely we'll run into this problem again. :-/
> 
> Time to add stack overflow self-detection?
> 
> I've attached a simple proof-of-concept that uses 
> sigaltstacks based SIGSEGV handler to catch a stack 
> overflow:
> 
>   starship:/s/stack-overflow> ./overflow 
>   # Starting stack recursion:
> 
>   # WARNING: SIGSEGV received: Possible stack overflow detected!
> 
>   starship:/s/stack-overflow> 
> 
> Could we add something like this to objtool, with 
> perhaps a look at the interrupted stack pointer from 
> SIGSEGV_handler(), to make sure the SIGSEGV was due to 
> a stack overflow?

Yes, I think that would be wise.  I've been thinking objtool could use a
SIGSEGV handler anyway, as it crashes more often than one would hope,
with a cryptic non-helpful error message for the user.

I'll work on it.

-- 
Josh
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 3 days ago
On Tue, Dec 02, 2025 at 09:11:50AM -0800, Josh Poimboeuf wrote:
> > > > That's weird - how can a user-space tool run into stack 
> > > > limits, are they set particularly conservatively?
> > > 
> > > On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> > > enough :-)
> > > 
> > > In this case, objtool had over 20,000 stack frames caused by recursively
> > > following over 7,000(!) conditional jumps in a single function.
> > 
> > Ouch ...
> > 
> > ... which means that very likely we'll run into this problem again. :-/
> > 
> > Time to add stack overflow self-detection?
> > 
> > I've attached a simple proof-of-concept that uses 
> > sigaltstacks based SIGSEGV handler to catch a stack 
> > overflow:
> > 
> >   starship:/s/stack-overflow> ./overflow 
> >   # Starting stack recursion:
> > 
> >   # WARNING: SIGSEGV received: Possible stack overflow detected!
> > 
> >   starship:/s/stack-overflow> 
> > 
> > Could we add something like this to objtool, with 
> > perhaps a look at the interrupted stack pointer from 
> > SIGSEGV_handler(), to make sure the SIGSEGV was due to 
> > a stack overflow?
> 
> Yes, I think that would be wise.  I've been thinking objtool could use a
> SIGSEGV handler anyway, as it crashes more often than one would hope,
> with a cryptic non-helpful error message for the user.
> 
> I'll work on it.

Is something like the below sufficient?  Or do you think we should add
logic to distinguish the stack overflow from other crashes?

---8<---

From: Josh Poimboeuf <jpoimboe@kernel.org>
Subject: [PATCH] objtool: Improve error message for SIGSEGV

When the kernel build fails due to an objtool seg fault, the error
message is confusing:

  make[5]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
  make[5]: *** Deleting file 'drivers/scsi/qla2xxx/qla2xxx.o'
  make[4]: *** [scripts/Makefile.build:556: drivers/scsi/qla2xxx] Error 2
  make[3]: *** [scripts/Makefile.build:556: drivers/scsi] Error 2
  make[2]: *** [scripts/Makefile.build:556: drivers] Error 2
  make[1]: *** [/home/jpoimboe/git/linux/Makefile:2013: .] Error 2
  make: *** [Makefile:248: __sub-make] Error 2

Add a signal handler which prints an error message like:

  drivers/scsi/qla2xxx/qla2xxx.o: error: objtool: SIGSEGV (Segmentation Fault) received at address 0x7ffc5f33bf30

... and re-raises the signal so the core dump still gets triggered.

Reported-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 tools/objtool/Build                     |  1 +
 tools/objtool/include/objtool/objtool.h |  2 +
 tools/objtool/objtool.c                 |  4 +-
 tools/objtool/signal.c                  | 77 +++++++++++++++++++++++++
 4 files changed, 83 insertions(+), 1 deletion(-)
 create mode 100644 tools/objtool/signal.c

diff --git a/tools/objtool/Build b/tools/objtool/Build
index 9982e665d58d..600da051af12 100644
--- a/tools/objtool/Build
+++ b/tools/objtool/Build
@@ -18,6 +18,7 @@ objtool-y += libstring.o
 objtool-y += libctype.o
 objtool-y += str_error_r.o
 objtool-y += librbtree.o
+objtool-y += signal.o
 
 $(OUTPUT)libstring.o: ../lib/string.c FORCE
 	$(call rule_mkdir)
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index f7051bbe0bcb..6dc12a59ad00 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -41,6 +41,8 @@ struct objtool_file {
 
 char *top_level_dir(const char *file);
 
+int init_signal_handler(void);
+
 struct objtool_file *objtool_open_read(const char *_objname);
 
 int objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func);
diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c
index 3c26ed561c7e..1c3622117c33 100644
--- a/tools/objtool/objtool.c
+++ b/tools/objtool/objtool.c
@@ -104,11 +104,13 @@ char *top_level_dir(const char *file)
 	return str;
 }
 
-
 int main(int argc, const char **argv)
 {
 	static const char *UNUSED = "OBJTOOL_NOT_IMPLEMENTED";
 
+	if (init_signal_handler())
+		return -1;
+
 	/* libsubcmd init */
 	exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED);
 	pager_init(UNUSED);
diff --git a/tools/objtool/signal.c b/tools/objtool/signal.c
new file mode 100644
index 000000000000..9f99fb2fde24
--- /dev/null
+++ b/tools/objtool/signal.c
@@ -0,0 +1,77 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+
+static void signal_handler(int sig_num, siginfo_t *info, void *context)
+{
+	struct sigaction sa_dfl = {0};
+	const char *sig_name;
+	char msg[256];
+	int msg_len;
+
+	switch (sig_num) {
+	case SIGSEGV:
+		sig_name = "SIGSEGV (Segmentation Fault)";
+		break;
+	case SIGBUS:
+		sig_name = "SIGBUS (Bus Error)";
+		break;
+	case SIGILL:
+		sig_name = "SIGILL (Illegal Instruction)";
+		break;
+	case SIGABRT:
+		sig_name = "SIGABRT (Program Abort)";
+		break;
+	default:
+		sig_name = "Unknown signal";
+		break;
+	}
+
+	msg_len = snprintf(msg, sizeof(msg),
+			   "%s: error: objtool: %s received at address %p\n",
+			   objname, sig_name, info->si_addr);
+	write(STDERR_FILENO, msg, msg_len);
+
+	/* Re-raise the signal to trigger the core dump */
+	sa_dfl.sa_handler = SIG_DFL;
+	sigaction(sig_num, &sa_dfl, NULL);
+	raise(sig_num);
+}
+
+int init_signal_handler(void)
+{
+	int signals[] = {SIGSEGV, SIGBUS, SIGILL, SIGABRT};
+	struct sigaction sa;
+	stack_t ss;
+
+	ss.ss_sp = malloc(SIGSTKSZ);
+	if (!ss.ss_sp) {
+		ERROR_GLIBC("malloc");
+		return -1;
+	}
+	ss.ss_size = SIGSTKSZ;
+	ss.ss_flags = 0;
+
+	if (sigaltstack(&ss, NULL) == -1) {
+		ERROR_GLIBC("sigaltstack");
+		return -1;
+	}
+
+	sa.sa_sigaction = signal_handler;
+	sigemptyset(&sa.sa_mask);
+
+	sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
+
+	for (int i = 0; i < ARRAY_SIZE(signals); i++) {
+		if (sigaction(signals[i], &sa, NULL) == -1) {
+			ERROR_GLIBC("sigaction");
+			return -1;
+		}
+	}
+
+	return 0;
+}
-- 
2.51.1
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Ingo Molnar 2 weeks, 3 days ago
* Josh Poimboeuf <jpoimboe@kernel.org> wrote:

> On Tue, Dec 02, 2025 at 09:11:50AM -0800, Josh Poimboeuf wrote:
> > > > > That's weird - how can a user-space tool run into stack 
> > > > > limits, are they set particularly conservatively?
> > > > 
> > > > On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> > > > enough :-)
> > > > 
> > > > In this case, objtool had over 20,000 stack frames caused by recursively
> > > > following over 7,000(!) conditional jumps in a single function.
> > > 
> > > Ouch ...
> > > 
> > > ... which means that very likely we'll run into this problem again. :-/
> > > 
> > > Time to add stack overflow self-detection?
> > > 
> > > I've attached a simple proof-of-concept that uses 
> > > sigaltstacks based SIGSEGV handler to catch a stack 
> > > overflow:
> > > 
> > >   starship:/s/stack-overflow> ./overflow 
> > >   # Starting stack recursion:
> > > 
> > >   # WARNING: SIGSEGV received: Possible stack overflow detected!
> > > 
> > >   starship:/s/stack-overflow> 
> > > 
> > > Could we add something like this to objtool, with 
> > > perhaps a look at the interrupted stack pointer from 
> > > SIGSEGV_handler(), to make sure the SIGSEGV was due to 
> > > a stack overflow?
> > 
> > Yes, I think that would be wise.  I've been thinking objtool could use a
> > SIGSEGV handler anyway, as it crashes more often than one would hope,
> > with a cryptic non-helpful error message for the user.
> > 
> > I'll work on it.
> 
> Is something like the below sufficient?  Or do you think we should add
> logic to distinguish the stack overflow from other crashes?
> 
> ---8<---
> 
> From: Josh Poimboeuf <jpoimboe@kernel.org>
> Subject: [PATCH] objtool: Improve error message for SIGSEGV
> 
> When the kernel build fails due to an objtool seg fault, the error
> message is confusing:
> 
>   make[5]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
>   make[5]: *** Deleting file 'drivers/scsi/qla2xxx/qla2xxx.o'
>   make[4]: *** [scripts/Makefile.build:556: drivers/scsi/qla2xxx] Error 2
>   make[3]: *** [scripts/Makefile.build:556: drivers/scsi] Error 2
>   make[2]: *** [scripts/Makefile.build:556: drivers] Error 2
>   make[1]: *** [/home/jpoimboe/git/linux/Makefile:2013: .] Error 2
>   make: *** [Makefile:248: __sub-make] Error 2
> 
> Add a signal handler which prints an error message like:
> 
>   drivers/scsi/qla2xxx/qla2xxx.o: error: objtool: SIGSEGV (Segmentation Fault) received at address 0x7ffc5f33bf30
> 
> ... and re-raises the signal so the core dump still gets triggered.

Could we somehow determine that 0x7ffc5f33bf30 is off 
the end of the stack or so and that this is a stack 
overflow?

Maybe objtool could have a look into /proc/self/maps:

   7fc21a543000-7fc21a544000 rw-p 0003f000 103:02 96610309                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
   7fc21a544000-7fc21a545000 rw-p 00000000 00:00 0 
   7ffd6a5a0000-7ffd6a5c1000 rw-p 00000000 00:00 0                          [stack]

?

Thanks,

	Ingo
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by David Laight 2 weeks, 2 days ago
On Tue, 2 Dec 2025 21:20:55 +0100
Ingo Molnar <mingo@kernel.org> wrote:

> * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> 
> > On Tue, Dec 02, 2025 at 09:11:50AM -0800, Josh Poimboeuf wrote:  
> > > > > > That's weird - how can a user-space tool run into stack 
> > > > > > limits, are they set particularly conservatively?  
> > > > > 
> > > > > On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> > > > > enough :-)
> > > > > 
> > > > > In this case, objtool had over 20,000 stack frames caused by recursively
> > > > > following over 7,000(!) conditional jumps in a single function.  
> > > > 
> > > > Ouch ...
> > > > 
> > > > ... which means that very likely we'll run into this problem again. :-/
> > > > 
> > > > Time to add stack overflow self-detection?
> > > > 
> > > > I've attached a simple proof-of-concept that uses 
> > > > sigaltstacks based SIGSEGV handler to catch a stack 
> > > > overflow:
> > > > 
> > > >   starship:/s/stack-overflow> ./overflow 
> > > >   # Starting stack recursion:
> > > > 
> > > >   # WARNING: SIGSEGV received: Possible stack overflow detected!
> > > > 
> > > >   starship:/s/stack-overflow> 
> > > > 
> > > > Could we add something like this to objtool, with 
> > > > perhaps a look at the interrupted stack pointer from 
> > > > SIGSEGV_handler(), to make sure the SIGSEGV was due to 
> > > > a stack overflow?  
> > > 
> > > Yes, I think that would be wise.  I've been thinking objtool could use a
> > > SIGSEGV handler anyway, as it crashes more often than one would hope,
> > > with a cryptic non-helpful error message for the user.
> > > 
> > > I'll work on it.  
> > 
> > Is something like the below sufficient?  Or do you think we should add
> > logic to distinguish the stack overflow from other crashes?
> > 
> > ---8<---
> > 
> > From: Josh Poimboeuf <jpoimboe@kernel.org>
> > Subject: [PATCH] objtool: Improve error message for SIGSEGV
> > 
> > When the kernel build fails due to an objtool seg fault, the error
> > message is confusing:
> > 
> >   make[5]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
> >   make[5]: *** Deleting file 'drivers/scsi/qla2xxx/qla2xxx.o'
> >   make[4]: *** [scripts/Makefile.build:556: drivers/scsi/qla2xxx] Error 2
> >   make[3]: *** [scripts/Makefile.build:556: drivers/scsi] Error 2
> >   make[2]: *** [scripts/Makefile.build:556: drivers] Error 2
> >   make[1]: *** [/home/jpoimboe/git/linux/Makefile:2013: .] Error 2
> >   make: *** [Makefile:248: __sub-make] Error 2
> > 
> > Add a signal handler which prints an error message like:
> > 
> >   drivers/scsi/qla2xxx/qla2xxx.o: error: objtool: SIGSEGV (Segmentation Fault) received at address 0x7ffc5f33bf30
> > 
> > ... and re-raises the signal so the core dump still gets triggered.  
> 
> Could we somehow determine that 0x7ffc5f33bf30 is off 
> the end of the stack or so and that this is a stack 
> overflow?

You could compare it to the address of something on-stack during program startup.
Probably even argv[] - isn't that always at the bottom of the stack?
If you read the rlimit value, maybe the recursive loop could abort
before the fault.

	David

> 
> Maybe objtool could have a look into /proc/self/maps:
> 
>    7fc21a543000-7fc21a544000 rw-p 0003f000 103:02 96610309                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
>    7fc21a544000-7fc21a545000 rw-p 00000000 00:00 0 
>    7ffd6a5a0000-7ffd6a5c1000 rw-p 00000000 00:00 0                          [stack]
> 
> ?
> 
> Thanks,
> 
> 	Ingo
>
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Josh Poimboeuf 2 weeks, 2 days ago
On Tue, Dec 02, 2025 at 10:05:40PM +0000, David Laight wrote:
> On Tue, 2 Dec 2025 21:20:55 +0100
> Ingo Molnar <mingo@kernel.org> wrote:
> 
> > * Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > 
> > > On Tue, Dec 02, 2025 at 09:11:50AM -0800, Josh Poimboeuf wrote:  
> > > > > > > That's weird - how can a user-space tool run into stack 
> > > > > > > limits, are they set particularly conservatively?  
> > > > > > 
> > > > > > On my Fedora system, "ulimit -s" is 8MB.  You'd think that would be
> > > > > > enough :-)
> > > > > > 
> > > > > > In this case, objtool had over 20,000 stack frames caused by recursively
> > > > > > following over 7,000(!) conditional jumps in a single function.  
> > > > > 
> > > > > Ouch ...
> > > > > 
> > > > > ... which means that very likely we'll run into this problem again. :-/
> > > > > 
> > > > > Time to add stack overflow self-detection?
> > > > > 
> > > > > I've attached a simple proof-of-concept that uses 
> > > > > sigaltstacks based SIGSEGV handler to catch a stack 
> > > > > overflow:
> > > > > 
> > > > >   starship:/s/stack-overflow> ./overflow 
> > > > >   # Starting stack recursion:
> > > > > 
> > > > >   # WARNING: SIGSEGV received: Possible stack overflow detected!
> > > > > 
> > > > >   starship:/s/stack-overflow> 
> > > > > 
> > > > > Could we add something like this to objtool, with 
> > > > > perhaps a look at the interrupted stack pointer from 
> > > > > SIGSEGV_handler(), to make sure the SIGSEGV was due to 
> > > > > a stack overflow?  
> > > > 
> > > > Yes, I think that would be wise.  I've been thinking objtool could use a
> > > > SIGSEGV handler anyway, as it crashes more often than one would hope,
> > > > with a cryptic non-helpful error message for the user.
> > > > 
> > > > I'll work on it.  
> > > 
> > > Is something like the below sufficient?  Or do you think we should add
> > > logic to distinguish the stack overflow from other crashes?
> > > 
> > > ---8<---
> > > 
> > > From: Josh Poimboeuf <jpoimboe@kernel.org>
> > > Subject: [PATCH] objtool: Improve error message for SIGSEGV
> > > 
> > > When the kernel build fails due to an objtool seg fault, the error
> > > message is confusing:
> > > 
> > >   make[5]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
> > >   make[5]: *** Deleting file 'drivers/scsi/qla2xxx/qla2xxx.o'
> > >   make[4]: *** [scripts/Makefile.build:556: drivers/scsi/qla2xxx] Error 2
> > >   make[3]: *** [scripts/Makefile.build:556: drivers/scsi] Error 2
> > >   make[2]: *** [scripts/Makefile.build:556: drivers] Error 2
> > >   make[1]: *** [/home/jpoimboe/git/linux/Makefile:2013: .] Error 2
> > >   make: *** [Makefile:248: __sub-make] Error 2
> > > 
> > > Add a signal handler which prints an error message like:
> > > 
> > >   drivers/scsi/qla2xxx/qla2xxx.o: error: objtool: SIGSEGV (Segmentation Fault) received at address 0x7ffc5f33bf30
> > > 
> > > ... and re-raises the signal so the core dump still gets triggered.  
> > 
> > Could we somehow determine that 0x7ffc5f33bf30 is off 
> > the end of the stack or so and that this is a stack 
> > overflow?
> 
> You could compare it to the address of something on-stack during program startup.
> Probably even argv[] - isn't that always at the bottom of the stack?
> If you read the rlimit value, maybe the recursive loop could abort
> before the fault.

Here's with reading /proc/self/maps and rlimit, it's not too bad:

From: Josh Poimboeuf <jpoimboe@kernel.org>
Subject: [PATCH] objtool: Add signal error handling

When the kernel build fails due to an objtool seg fault, the error
message is confusing:

  make[5]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
  make[5]: *** Deleting file 'drivers/scsi/qla2xxx/qla2xxx.o'
  make[4]: *** [scripts/Makefile.build:556: drivers/scsi/qla2xxx] Error 2
  make[3]: *** [scripts/Makefile.build:556: drivers/scsi] Error 2
  make[2]: *** [scripts/Makefile.build:556: drivers] Error 2
  make[1]: *** [/home/jpoimboe/git/linux/Makefile:2013: .] Error 2
  make: *** [Makefile:248: __sub-make] Error 2

Add a signal handler which prints an error message like:

  drivers/scsi/qla2xxx/qla2xxx.o: error: SIGSEGV: objtool stack overflow!

or

  drivers/scsi/qla2xxx/qla2xxx.o: error: SIGSEGV: objtool crash!

Also, re-raise the signal so the core dump still gets triggered.

Suggested-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 tools/objtool/Build                     |   1 +
 tools/objtool/include/objtool/objtool.h |   2 +
 tools/objtool/objtool.c                 |   4 +-
 tools/objtool/signal.c                  | 138 ++++++++++++++++++++++++
 4 files changed, 144 insertions(+), 1 deletion(-)
 create mode 100644 tools/objtool/signal.c

diff --git a/tools/objtool/Build b/tools/objtool/Build
index 9982e665d58d..600da051af12 100644
--- a/tools/objtool/Build
+++ b/tools/objtool/Build
@@ -18,6 +18,7 @@ objtool-y += libstring.o
 objtool-y += libctype.o
 objtool-y += str_error_r.o
 objtool-y += librbtree.o
+objtool-y += signal.o
 
 $(OUTPUT)libstring.o: ../lib/string.c FORCE
 	$(call rule_mkdir)
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index f7051bbe0bcb..6dc12a59ad00 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -41,6 +41,8 @@ struct objtool_file {
 
 char *top_level_dir(const char *file);
 
+int init_signal_handler(void);
+
 struct objtool_file *objtool_open_read(const char *_objname);
 
 int objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func);
diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c
index 3c26ed561c7e..1c3622117c33 100644
--- a/tools/objtool/objtool.c
+++ b/tools/objtool/objtool.c
@@ -104,11 +104,13 @@ char *top_level_dir(const char *file)
 	return str;
 }
 
-
 int main(int argc, const char **argv)
 {
 	static const char *UNUSED = "OBJTOOL_NOT_IMPLEMENTED";
 
+	if (init_signal_handler())
+		return -1;
+
 	/* libsubcmd init */
 	exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED);
 	pager_init(UNUSED);
diff --git a/tools/objtool/signal.c b/tools/objtool/signal.c
new file mode 100644
index 000000000000..ce6605d53044
--- /dev/null
+++ b/tools/objtool/signal.c
@@ -0,0 +1,138 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/resource.h>
+#include <string.h>
+
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+
+static unsigned long stack_limit;
+
+static bool is_stack_overflow(void *fault_addr)
+{
+	unsigned long fault = (unsigned long)fault_addr;
+
+	/* Check if fault is in the guard page just below the limit. */
+	return fault < stack_limit && fault >= stack_limit - 4096;
+}
+
+static void signal_handler(int sig_num, siginfo_t *info, void *context)
+{
+	struct sigaction sa_dfl = {0};
+	const char *sig_name;
+	char msg[256];
+	int msg_len;
+
+	switch (sig_num) {
+	case SIGSEGV:
+		sig_name = "SIGSEGV";
+		break;
+	case SIGBUS:
+		sig_name = "SIGBUS";
+		break;
+	case SIGILL:
+		sig_name = "SIGILL";
+		break;
+	case SIGABRT:
+		sig_name = "SIGABRT";
+		break;
+	default:
+		sig_name = "Unknown signal";
+		break;
+	}
+
+	if (is_stack_overflow(info->si_addr)) {
+		msg_len = snprintf(msg, sizeof(msg),
+				   "%s: error: %s: objtool stack overflow!\n",
+				   objname, sig_name);
+	} else {
+		msg_len = snprintf(msg, sizeof(msg),
+				   "%s: error: %s: objtool crash!\n",
+				   objname, sig_name);
+	}
+
+	write(STDERR_FILENO, msg, msg_len);
+
+	/* Re-raise the signal to trigger the core dump */
+	sa_dfl.sa_handler = SIG_DFL;
+	sigaction(sig_num, &sa_dfl, NULL);
+	raise(sig_num);
+}
+
+static int read_stack_limit(void)
+{
+	unsigned long stack_start, stack_end;
+	struct rlimit rlim;
+	char line[256];
+	int ret = 0;
+	FILE *fp;
+
+	if (getrlimit(RLIMIT_STACK, &rlim)) {
+		ERROR_GLIBC("getrlimit");
+		return -1;
+	}
+
+	fp = fopen("/proc/self/maps", "r");
+	if (!fp) {
+		ERROR_GLIBC("fopen");
+		return -1;
+	}
+
+	while (fgets(line, sizeof(line), fp)) {
+		if (strstr(line, "[stack]")) {
+			if (sscanf(line, "%lx-%lx", &stack_start, &stack_end) != 2) {
+				ERROR_GLIBC("sscanf");
+				ret = -1;
+				goto done;
+			}
+			stack_limit = stack_end - rlim.rlim_cur;
+			goto done;
+		}
+	}
+
+	ret = -1;
+	ERROR("/proc/self/maps: can't find [stack]");
+
+done:
+	fclose(fp);
+	return ret;
+}
+
+int init_signal_handler(void)
+{
+	int signals[] = {SIGSEGV, SIGBUS, SIGILL, SIGABRT};
+	struct sigaction sa;
+	stack_t ss;
+
+	if (read_stack_limit())
+		return -1;
+
+	ss.ss_sp = malloc(SIGSTKSZ);
+	if (!ss.ss_sp) {
+		ERROR_GLIBC("malloc");
+		return -1;
+	}
+	ss.ss_size = SIGSTKSZ;
+	ss.ss_flags = 0;
+
+	if (sigaltstack(&ss, NULL) == -1) {
+		ERROR_GLIBC("sigaltstack");
+		return -1;
+	}
+
+	sa.sa_sigaction = signal_handler;
+	sigemptyset(&sa.sa_mask);
+
+	sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
+
+	for (int i = 0; i < ARRAY_SIZE(signals); i++) {
+		if (sigaction(signals[i], &sa, NULL) == -1) {
+			ERROR_GLIBC("sigaction");
+			return -1;
+		}
+	}
+
+	return 0;
+}
-- 
2.51.1
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by Ingo Molnar 2 weeks, 2 days ago
* Josh Poimboeuf <jpoimboe@kernel.org> wrote:

> Here's with reading /proc/self/maps and rlimit, it's not too bad:
> 
> From: Josh Poimboeuf <jpoimboe@kernel.org>
> Subject: [PATCH] objtool: Add signal error handling
> 
> When the kernel build fails due to an objtool seg fault, the error
> message is confusing:
> 
>   make[5]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
>   make[5]: *** Deleting file 'drivers/scsi/qla2xxx/qla2xxx.o'
>   make[4]: *** [scripts/Makefile.build:556: drivers/scsi/qla2xxx] Error 2
>   make[3]: *** [scripts/Makefile.build:556: drivers/scsi] Error 2
>   make[2]: *** [scripts/Makefile.build:556: drivers] Error 2
>   make[1]: *** [/home/jpoimboe/git/linux/Makefile:2013: .] Error 2
>   make: *** [Makefile:248: __sub-make] Error 2
> 
> Add a signal handler which prints an error message like:
> 
>   drivers/scsi/qla2xxx/qla2xxx.o: error: SIGSEGV: objtool stack overflow!
> 
> or
> 
>   drivers/scsi/qla2xxx/qla2xxx.o: error: SIGSEGV: objtool crash!
> 
> Also, re-raise the signal so the core dump still gets triggered.

Great - this version looks good to me, and I've applied it to
tip:objtool/urgent.

Thanks,

	Ingo
Re: [PATCH] objtool: Fix stack overflow in validate_branch()
Posted by David Laight 2 weeks, 2 days ago
On Tue, 2 Dec 2025 15:01:17 -0800
Josh Poimboeuf <jpoimboe@kernel.org> wrote:

...
> +	while (fgets(line, sizeof(line), fp)) {
> +		if (strstr(line, "[stack]")) {
> +			if (sscanf(line, "%lx-%lx", &stack_start, &stack_end) != 2) {
> +				ERROR_GLIBC("sscanf");
> +				ret = -1;
> +				goto done;
> +			}
> +			stack_limit = stack_end - rlim.rlim_cur;
> +			goto done;

That assumes 'stack grows down'.
Someone will copy the code onto a 'backwards stack' system one day.
(I can't remember which ones they are.)

	David
[tip: objtool/urgent] objtool: Add more robust signal error handling, detect and warn about stack overflows
Posted by tip-bot2 for Josh Poimboeuf 2 weeks, 2 days ago
The following commit has been merged into the objtool/urgent branch of tip:

Commit-ID:     799647ddb4c0ce1d7084fcf5b524e9a0c7728325
Gitweb:        https://git.kernel.org/tip/799647ddb4c0ce1d7084fcf5b524e9a0c7728325
Author:        Josh Poimboeuf <jpoimboe@kernel.org>
AuthorDate:    Tue, 02 Dec 2025 15:01:17 -08:00
Committer:     Ingo Molnar <mingo@kernel.org>
CommitterDate: Wed, 03 Dec 2025 19:42:37 +01:00

objtool: Add more robust signal error handling, detect and warn about stack overflows

When the kernel build fails due to an objtool segfault, the error
message is a bit obtuse and confusing:

  make[5]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
                                                                            ^^^^^^^^^
  make[5]: *** Deleting file 'drivers/scsi/qla2xxx/qla2xxx.o'
  make[4]: *** [scripts/Makefile.build:556: drivers/scsi/qla2xxx] Error 2
  make[3]: *** [scripts/Makefile.build:556: drivers/scsi] Error 2
  make[2]: *** [scripts/Makefile.build:556: drivers] Error 2
  make[1]: *** [/home/jpoimboe/git/linux/Makefile:2013: .] Error 2
  make: *** [Makefile:248: __sub-make] Error 2

Add a signal handler to objtool which prints an error message like if
the local stack has overflown (for which there's a chance as objtool
makes heavy use of recursion):

  drivers/scsi/qla2xxx/qla2xxx.o: error: SIGSEGV: objtool stack overflow!

or:

  drivers/scsi/qla2xxx/qla2xxx.o: error: SIGSEGV: objtool crash!

Also, re-raise the signal so the core dump still gets triggered.

[ mingo: Applied a build fix, added more comments and prettified the code. ]

Suggested-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Cc: Alexandre Chartre <alexandre.chartre@oracle.com>
Cc: David Laight <david.laight.linux@gmail.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Nathan Chancellor <nathan@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/mi4tihk4dbncn7belrhp6ooudhpw4vdggerktu5333w3gqf3uf@vqlhc3y667mg
---
 tools/objtool/Build                     |   1 +-
 tools/objtool/include/objtool/objtool.h |   2 +-
 tools/objtool/objtool.c                 |   4 +-
 tools/objtool/signal.c                  | 135 +++++++++++++++++++++++-
 4 files changed, 141 insertions(+), 1 deletion(-)
 create mode 100644 tools/objtool/signal.c

diff --git a/tools/objtool/Build b/tools/objtool/Build
index 9982e66..600da05 100644
--- a/tools/objtool/Build
+++ b/tools/objtool/Build
@@ -18,6 +18,7 @@ objtool-y += libstring.o
 objtool-y += libctype.o
 objtool-y += str_error_r.o
 objtool-y += librbtree.o
+objtool-y += signal.o
 
 $(OUTPUT)libstring.o: ../lib/string.c FORCE
 	$(call rule_mkdir)
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index f7051bb..6dc12a5 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -41,6 +41,8 @@ struct objtool_file {
 
 char *top_level_dir(const char *file);
 
+int init_signal_handler(void);
+
 struct objtool_file *objtool_open_read(const char *_objname);
 
 int objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func);
diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c
index 3c26ed5..1c36221 100644
--- a/tools/objtool/objtool.c
+++ b/tools/objtool/objtool.c
@@ -104,11 +104,13 @@ char *top_level_dir(const char *file)
 	return str;
 }
 
-
 int main(int argc, const char **argv)
 {
 	static const char *UNUSED = "OBJTOOL_NOT_IMPLEMENTED";
 
+	if (init_signal_handler())
+		return -1;
+
 	/* libsubcmd init */
 	exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED);
 	pager_init(UNUSED);
diff --git a/tools/objtool/signal.c b/tools/objtool/signal.c
new file mode 100644
index 0000000..af5c65c
--- /dev/null
+++ b/tools/objtool/signal.c
@@ -0,0 +1,135 @@
+/*
+ * signal.c: Register a sigaltstack for objtool, to be able to
+ *	     run a signal handler on a separate stack even if
+ *	     the main process stack has overflown. Print out
+ *	     stack overflow errors when this happens.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/resource.h>
+#include <string.h>
+
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+
+static unsigned long stack_limit;
+
+static bool is_stack_overflow(void *fault_addr)
+{
+	unsigned long fault = (unsigned long)fault_addr;
+
+	/* Check if fault is in the guard page just below the limit. */
+	return fault < stack_limit && fault >= stack_limit - 4096;
+}
+
+static void signal_handler(int sig_num, siginfo_t *info, void *context)
+{
+	struct sigaction sa_dfl = {0};
+	const char *sig_name;
+	char msg[256];
+	int msg_len;
+
+	switch (sig_num) {
+	case SIGSEGV:	sig_name = "SIGSEGV";		break;
+	case SIGBUS:	sig_name = "SIGBUS";		break;
+	case SIGILL:	sig_name = "SIGILL";		break;
+	case SIGABRT:	sig_name = "SIGABRT";		break;
+	default:	sig_name = "Unknown signal";	break;
+	}
+
+	if (is_stack_overflow(info->si_addr)) {
+		msg_len = snprintf(msg, sizeof(msg),
+				   "%s: error: %s: objtool stack overflow!\n",
+				   objname, sig_name);
+	} else {
+		msg_len = snprintf(msg, sizeof(msg),
+				   "%s: error: %s: objtool crash!\n",
+				   objname, sig_name);
+	}
+
+	msg_len = write(STDERR_FILENO, msg, msg_len);
+
+	/* Re-raise the signal to trigger the core dump */
+	sa_dfl.sa_handler = SIG_DFL;
+	sigaction(sig_num, &sa_dfl, NULL);
+	raise(sig_num);
+}
+
+static int read_stack_limit(void)
+{
+	unsigned long stack_start, stack_end;
+	struct rlimit rlim;
+	char line[256];
+	int ret = 0;
+	FILE *fp;
+
+	if (getrlimit(RLIMIT_STACK, &rlim)) {
+		ERROR_GLIBC("getrlimit");
+		return -1;
+	}
+
+	fp = fopen("/proc/self/maps", "r");
+	if (!fp) {
+		ERROR_GLIBC("fopen");
+		return -1;
+	}
+
+	while (fgets(line, sizeof(line), fp)) {
+		if (strstr(line, "[stack]")) {
+			if (sscanf(line, "%lx-%lx", &stack_start, &stack_end) != 2) {
+				ERROR_GLIBC("sscanf");
+				ret = -1;
+				goto done;
+			}
+			stack_limit = stack_end - rlim.rlim_cur;
+			goto done;
+		}
+	}
+
+	ret = -1;
+	ERROR("/proc/self/maps: can't find [stack]");
+
+done:
+	fclose(fp);
+
+	return ret;
+}
+
+int init_signal_handler(void)
+{
+	int signals[] = {SIGSEGV, SIGBUS, SIGILL, SIGABRT};
+	struct sigaction sa;
+	stack_t ss;
+
+	if (read_stack_limit())
+		return -1;
+
+	ss.ss_sp = malloc(SIGSTKSZ);
+	if (!ss.ss_sp) {
+		ERROR_GLIBC("malloc");
+		return -1;
+	}
+	ss.ss_size = SIGSTKSZ;
+	ss.ss_flags = 0;
+
+	if (sigaltstack(&ss, NULL) == -1) {
+		ERROR_GLIBC("sigaltstack");
+		return -1;
+	}
+
+	sa.sa_sigaction = signal_handler;
+	sigemptyset(&sa.sa_mask);
+
+	sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
+
+	for (int i = 0; i < ARRAY_SIZE(signals); i++) {
+		if (sigaction(signals[i], &sa, NULL) == -1) {
+			ERROR_GLIBC("sigaction");
+			return -1;
+		}
+	}
+
+	return 0;
+}
[tip: objtool/urgent] objtool: Add more robust signal error handling, detect and warn about stack overflows
Posted by tip-bot2 for Josh Poimboeuf 2 weeks, 2 days ago
The following commit has been merged into the objtool/urgent branch of tip:

Commit-ID:     0b528ad72c66f829ec2d0d89407d9b2917c0f8ae
Gitweb:        https://git.kernel.org/tip/0b528ad72c66f829ec2d0d89407d9b2917c0f8ae
Author:        Josh Poimboeuf <jpoimboe@kernel.org>
AuthorDate:    Tue, 02 Dec 2025 15:01:17 -08:00
Committer:     Ingo Molnar <mingo@kernel.org>
CommitterDate: Wed, 03 Dec 2025 17:31:53 +01:00

objtool: Add more robust signal error handling, detect and warn about stack overflows

When the kernel build fails due to an objtool segfault, the error
message is a bit obtuse and confusing:

  make[5]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
                                                                            ^^^^^^^^^
  make[5]: *** Deleting file 'drivers/scsi/qla2xxx/qla2xxx.o'
  make[4]: *** [scripts/Makefile.build:556: drivers/scsi/qla2xxx] Error 2
  make[3]: *** [scripts/Makefile.build:556: drivers/scsi] Error 2
  make[2]: *** [scripts/Makefile.build:556: drivers] Error 2
  make[1]: *** [/home/jpoimboe/git/linux/Makefile:2013: .] Error 2
  make: *** [Makefile:248: __sub-make] Error 2

Add a signal handler to objtool which prints an error message like if
the local stack has overflown (for which there's a chance as objtool
makes heavy use of recursion):

  drivers/scsi/qla2xxx/qla2xxx.o: error: SIGSEGV: objtool stack overflow!

or:

  drivers/scsi/qla2xxx/qla2xxx.o: error: SIGSEGV: objtool crash!

Also, re-raise the signal so the core dump still gets triggered.

[ mingo: Applied a build fix, added more comments and prettified the code. ]

Suggested-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Cc: Alexandre Chartre <alexandre.chartre@oracle.com>
Cc: David Laight <david.laight.linux@gmail.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Nathan Chancellor <nathan@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/mi4tihk4dbncn7belrhp6ooudhpw4vdggerktu5333w3gqf3uf@vqlhc3y667mg
---
 tools/objtool/Build                     |   1 +-
 tools/objtool/include/objtool/objtool.h |   2 +-
 tools/objtool/objtool.c                 |   4 +-
 tools/objtool/signal.c                  | 135 +++++++++++++++++++++++-
 4 files changed, 141 insertions(+), 1 deletion(-)
 create mode 100644 tools/objtool/signal.c

diff --git a/tools/objtool/Build b/tools/objtool/Build
index 9982e66..600da05 100644
--- a/tools/objtool/Build
+++ b/tools/objtool/Build
@@ -18,6 +18,7 @@ objtool-y += libstring.o
 objtool-y += libctype.o
 objtool-y += str_error_r.o
 objtool-y += librbtree.o
+objtool-y += signal.o
 
 $(OUTPUT)libstring.o: ../lib/string.c FORCE
 	$(call rule_mkdir)
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index f7051bb..6dc12a5 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -41,6 +41,8 @@ struct objtool_file {
 
 char *top_level_dir(const char *file);
 
+int init_signal_handler(void);
+
 struct objtool_file *objtool_open_read(const char *_objname);
 
 int objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func);
diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c
index 3c26ed5..1c36221 100644
--- a/tools/objtool/objtool.c
+++ b/tools/objtool/objtool.c
@@ -104,11 +104,13 @@ char *top_level_dir(const char *file)
 	return str;
 }
 
-
 int main(int argc, const char **argv)
 {
 	static const char *UNUSED = "OBJTOOL_NOT_IMPLEMENTED";
 
+	if (init_signal_handler())
+		return -1;
+
 	/* libsubcmd init */
 	exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED);
 	pager_init(UNUSED);
diff --git a/tools/objtool/signal.c b/tools/objtool/signal.c
new file mode 100644
index 0000000..af5c65c
--- /dev/null
+++ b/tools/objtool/signal.c
@@ -0,0 +1,135 @@
+/*
+ * signal.c: Register a sigaltstack for objtool, to be able to
+ *	     run a signal handler on a separate stack even if
+ *	     the main process stack has overflown. Print out
+ *	     stack overflow errors when this happens.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/resource.h>
+#include <string.h>
+
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+
+static unsigned long stack_limit;
+
+static bool is_stack_overflow(void *fault_addr)
+{
+	unsigned long fault = (unsigned long)fault_addr;
+
+	/* Check if fault is in the guard page just below the limit. */
+	return fault < stack_limit && fault >= stack_limit - 4096;
+}
+
+static void signal_handler(int sig_num, siginfo_t *info, void *context)
+{
+	struct sigaction sa_dfl = {0};
+	const char *sig_name;
+	char msg[256];
+	int msg_len;
+
+	switch (sig_num) {
+	case SIGSEGV:	sig_name = "SIGSEGV";		break;
+	case SIGBUS:	sig_name = "SIGBUS";		break;
+	case SIGILL:	sig_name = "SIGILL";		break;
+	case SIGABRT:	sig_name = "SIGABRT";		break;
+	default:	sig_name = "Unknown signal";	break;
+	}
+
+	if (is_stack_overflow(info->si_addr)) {
+		msg_len = snprintf(msg, sizeof(msg),
+				   "%s: error: %s: objtool stack overflow!\n",
+				   objname, sig_name);
+	} else {
+		msg_len = snprintf(msg, sizeof(msg),
+				   "%s: error: %s: objtool crash!\n",
+				   objname, sig_name);
+	}
+
+	msg_len = write(STDERR_FILENO, msg, msg_len);
+
+	/* Re-raise the signal to trigger the core dump */
+	sa_dfl.sa_handler = SIG_DFL;
+	sigaction(sig_num, &sa_dfl, NULL);
+	raise(sig_num);
+}
+
+static int read_stack_limit(void)
+{
+	unsigned long stack_start, stack_end;
+	struct rlimit rlim;
+	char line[256];
+	int ret = 0;
+	FILE *fp;
+
+	if (getrlimit(RLIMIT_STACK, &rlim)) {
+		ERROR_GLIBC("getrlimit");
+		return -1;
+	}
+
+	fp = fopen("/proc/self/maps", "r");
+	if (!fp) {
+		ERROR_GLIBC("fopen");
+		return -1;
+	}
+
+	while (fgets(line, sizeof(line), fp)) {
+		if (strstr(line, "[stack]")) {
+			if (sscanf(line, "%lx-%lx", &stack_start, &stack_end) != 2) {
+				ERROR_GLIBC("sscanf");
+				ret = -1;
+				goto done;
+			}
+			stack_limit = stack_end - rlim.rlim_cur;
+			goto done;
+		}
+	}
+
+	ret = -1;
+	ERROR("/proc/self/maps: can't find [stack]");
+
+done:
+	fclose(fp);
+
+	return ret;
+}
+
+int init_signal_handler(void)
+{
+	int signals[] = {SIGSEGV, SIGBUS, SIGILL, SIGABRT};
+	struct sigaction sa;
+	stack_t ss;
+
+	if (read_stack_limit())
+		return -1;
+
+	ss.ss_sp = malloc(SIGSTKSZ);
+	if (!ss.ss_sp) {
+		ERROR_GLIBC("malloc");
+		return -1;
+	}
+	ss.ss_size = SIGSTKSZ;
+	ss.ss_flags = 0;
+
+	if (sigaltstack(&ss, NULL) == -1) {
+		ERROR_GLIBC("sigaltstack");
+		return -1;
+	}
+
+	sa.sa_sigaction = signal_handler;
+	sigemptyset(&sa.sa_mask);
+
+	sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
+
+	for (int i = 0; i < ARRAY_SIZE(signals); i++) {
+		if (sigaction(signals[i], &sa, NULL) == -1) {
+			ERROR_GLIBC("sigaction");
+			return -1;
+		}
+	}
+
+	return 0;
+}