mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Peter Zijlstra <peterz@infradead.org>
To: x86@kernel.org
Cc: linux-kernel@vger.kernel.org, peterz@infradead.org,
	jpoimboe@kernel.org, leitao@debian.org, keescook@chromium.org,
	hpa@zytor.com, kernel test robot <lkp@intel.com>
Subject: [PATCH 5/6] objtool: Allow EXPORT_SYMBOL() of __noendbr functions
Date: Mon, 02 Nov 2026 12:25:54 +0100	[thread overview]
Message-ID: <20261102114000.887706274@infradead.org> (raw)
In-Reply-To: <20261102112549.714541309@infradead.org>

Objtool IBT validation does two things:

 - it checks that all text references have an ENDBR instruction (WARN);
 - it finds all ENDBR instructions that have no references (SEAL).

The first ensures that all function pointers are of functions that have ENDBR,
since doing indirect calls requires this. The flip side is that you can have
text references that are not function pointers in the sense of later calling
them. Eg. ip_within_syscall_gap() will take the address of symbols, but will
never call them. Any such location that is referenced but does not have ENDBR
requires an ANNOTATE_NOENDBR.

The second is used to create a list of ENDBR instructions to SEAL. That is, at
boot time the kernel will replace all these ENDBR instructions with a NOP
instruction, limiting indirect targets.

Now, EXPORT_SYMBOL*(sym) is counted as a reference. This is important because
even if the kernel itself does not reference the symbol, a module still might.
The flip side is that exporting a __noendbr function needs an ANNOTATE_NOENDBR.

Relax this by supressing the objtool-WARN for .export_symbol references. This
allows exporting of __noendbr without further ANNOTATE_NOENDBR. Remove any such
now redudant annotation.

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
---
 arch/x86/entry/calling.h             |    1 -
 arch/x86/entry/entry_64.S            |    2 +-
 arch/x86/entry/entry_64_compat.S     |    6 +++---
 arch/x86/lib/clear_page_64.S         |    1 -
 arch/x86/lib/copy_user_64.S          |    1 -
 arch/x86/lib/copy_user_uncached_64.S |    1 -
 arch/x86/lib/getuser.S               |    4 ----
 arch/x86/lib/hweight.S               |    3 ---
 arch/x86/lib/putuser.S               |    4 ----
 tools/objtool/check.c                |   15 ++++++++++++---
 10 files changed, 16 insertions(+), 22 deletions(-)

--- a/arch/x86/entry/calling.h
+++ b/arch/x86/entry/calling.h
@@ -432,7 +432,6 @@ For 32-bit we have the following convent
 /* rdi:	arg1 ... normal C conventions. rax is saved/restored. */
 .macro THUNK name, func
 SYM_FUNC_START(\name)
-	ANNOTATE_NOENDBR
 	pushq %rbp
 	movq %rsp, %rbp
 
--- a/arch/x86/entry/entry_64.S
+++ b/arch/x86/entry/entry_64.S
@@ -95,7 +95,7 @@ SYM_CODE_START(entry_SYSCALL_64)
 	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp
 
 SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)
-	ANNOTATE_NOENDBR
+	ANNOTATE_NOENDBR // ip_within_syscall_gap
 
 	/* Construct struct pt_regs on stack */
 	pushq	$__USER_DS				/* pt_regs->ss */
--- a/arch/x86/entry/entry_64_compat.S
+++ b/arch/x86/entry/entry_64_compat.S
@@ -196,7 +196,7 @@ SYM_CODE_START(entry_SYSCALL_compat)
 	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp
 
 SYM_INNER_LABEL(entry_SYSCALL_compat_safe_stack, SYM_L_GLOBAL)
-	ANNOTATE_NOENDBR
+	ANNOTATE_NOENDBR // ip_within_syscall_gap
 
 	/* Construct struct pt_regs on stack */
 	pushq	$__USER_DS		/* pt_regs->ss */
@@ -260,7 +260,7 @@ SYM_INNER_LABEL(entry_SYSCALL_compat_aft
          */
 	movq	RSP-ORIG_RAX(%rsp), %rsp
 SYM_INNER_LABEL(entry_SYSRETL_compat_unsafe_stack, SYM_L_GLOBAL)
-	ANNOTATE_NOENDBR
+	ANNOTATE_NOENDBR // ip_within_syscall_gap
 
 	/*
 	 * The original userspace %rsp (RSP-ORIG_RAX(%rsp)) is stored
@@ -280,7 +280,7 @@ SYM_INNER_LABEL(entry_SYSRETL_compat_uns
 	CLEAR_CPU_BUFFERS
 	sysretl
 SYM_INNER_LABEL(entry_SYSRETL_compat_end, SYM_L_GLOBAL)
-	ANNOTATE_NOENDBR
+	ANNOTATE_NOENDBR // ip_within_syscall_gap
 	int3
 SYM_CODE_END(entry_SYSCALL_compat)
 
--- a/arch/x86/lib/clear_page_64.S
+++ b/arch/x86/lib/clear_page_64.S
@@ -42,7 +42,6 @@ EXPORT_SYMBOL_GPL(__clear_pages_unrolled
  * rcx: uncleared bytes or 0 if successful.
  */
 SYM_FUNC_START(rep_stos_alternative)
-	ANNOTATE_NOENDBR
 	cmpq $64,%rcx
 	jae .Lunrolled
 
--- a/arch/x86/lib/copy_user_64.S
+++ b/arch/x86/lib/copy_user_64.S
@@ -32,7 +32,6 @@
  * it simpler for us, we can clobber rsi/rdi and rax freely.
  */
 SYM_FUNC_START(rep_movs_alternative)
-	ANNOTATE_NOENDBR
 	cmpq $64,%rcx
 	jae .Llarge
 
--- a/arch/x86/lib/copy_user_uncached_64.S
+++ b/arch/x86/lib/copy_user_uncached_64.S
@@ -28,7 +28,6 @@
  * rax uncopied bytes or 0 if successful.
  */
 SYM_FUNC_START(copy_to_nontemporal)
-	ANNOTATE_NOENDBR
 	/* If destination is not 7-byte aligned, we'll have to align it */
 	testb $7,%dil
 	jne .Lalign
--- a/arch/x86/lib/getuser.S
+++ b/arch/x86/lib/getuser.S
@@ -58,7 +58,6 @@
 
 	.text
 SYM_FUNC_START(__get_user_1)
-	ANNOTATE_NOENDBR
 	check_range size=1
 	ASM_STAC
 	UACCESS movzbl (%_ASM_AX),%edx
@@ -69,7 +68,6 @@ SYM_FUNC_END(__get_user_1)
 EXPORT_SYMBOL(__get_user_1)
 
 SYM_FUNC_START(__get_user_2)
-	ANNOTATE_NOENDBR
 	check_range size=2
 	ASM_STAC
 	UACCESS movzwl (%_ASM_AX),%edx
@@ -80,7 +78,6 @@ SYM_FUNC_END(__get_user_2)
 EXPORT_SYMBOL(__get_user_2)
 
 SYM_FUNC_START(__get_user_4)
-	ANNOTATE_NOENDBR
 	check_range size=4
 	ASM_STAC
 	UACCESS movl (%_ASM_AX),%edx
@@ -91,7 +88,6 @@ SYM_FUNC_END(__get_user_4)
 EXPORT_SYMBOL(__get_user_4)
 
 SYM_FUNC_START(__get_user_8)
-	ANNOTATE_NOENDBR
 #ifndef CONFIG_X86_64
 	xor %ecx,%ecx
 #endif
--- a/arch/x86/lib/hweight.S
+++ b/arch/x86/lib/hweight.S
@@ -10,8 +10,6 @@
  * %rdi: w
  */
 SYM_FUNC_START(__sw_hweight32)
-	ANNOTATE_NOENDBR
-
 #ifdef CONFIG_X86_64
 	movl %edi, %eax				# w
 #endif
@@ -44,7 +42,6 @@ EXPORT_SYMBOL(__sw_hweight32)
  */
 #ifdef CONFIG_X86_64
 SYM_FUNC_START(__sw_hweight64)
-	ANNOTATE_NOENDBR
 	pushq   %rdi
 	pushq   %rdx
 
--- a/arch/x86/lib/putuser.S
+++ b/arch/x86/lib/putuser.S
@@ -46,7 +46,6 @@
 
 .text
 SYM_FUNC_START(__put_user_1)
-	ANNOTATE_NOENDBR
 	check_range size=1
 	ASM_STAC
 1:	movb %al,(%_ASM_CX)
@@ -57,7 +56,6 @@ SYM_FUNC_END(__put_user_1)
 EXPORT_SYMBOL(__put_user_1)
 
 SYM_FUNC_START(__put_user_2)
-	ANNOTATE_NOENDBR
 	check_range size=2
 	ASM_STAC
 2:	movw %ax,(%_ASM_CX)
@@ -68,7 +66,6 @@ SYM_FUNC_END(__put_user_2)
 EXPORT_SYMBOL(__put_user_2)
 
 SYM_FUNC_START(__put_user_4)
-	ANNOTATE_NOENDBR
 	check_range size=4
 	ASM_STAC
 3:	movl %eax,(%_ASM_CX)
@@ -79,7 +76,6 @@ SYM_FUNC_END(__put_user_4)
 EXPORT_SYMBOL(__put_user_4)
 
 SYM_FUNC_START(__put_user_8)
-	ANNOTATE_NOENDBR
 	check_range size=8
 	ASM_STAC
 4:	mov %_ASM_AX,(%_ASM_CX)
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4597,7 +4597,7 @@ static int validate_ibt_insn(struct objt
 }
 
 static int validate_ibt_data_reloc(struct objtool_file *file,
-				   struct reloc *reloc)
+				   struct reloc *reloc, bool warn)
 {
 	struct instruction *dest;
 
@@ -4611,7 +4611,7 @@ static int validate_ibt_data_reloc(struc
 		return 0;
 	}
 
-	if (dest->noendbr)
+	if (dest->noendbr || !warn)
 		return 0;
 
 	WARN_FUNC(reloc->sec->base, reloc_offset(reloc),
@@ -4636,6 +4636,7 @@ static int validate_ibt(struct objtool_f
 		warnings += validate_ibt_insn(file, insn);
 
 	for_each_sec(file->elf, sec) {
+		bool warn = true;
 
 		/* Already done by validate_ibt_insn() */
 		if (is_text_sec(sec))
@@ -4672,6 +4673,14 @@ static int validate_ibt(struct objtool_f
 		    !strcmp(sec->name, "__patchable_function_entries"))
 			continue;
 
+		/*
+		 * EXPORT_SYMBOL() must be counted towards an ENDBR usage,
+		 * such symbols must not be implicitly sealed. But it is fine
+		 * to EXPORT an explicitly (__noendbr) sealed function.
+		 */
+		if (!strcmp(sec->name, ".export_symbol"))
+			warn = false;
+
 		for_each_reloc(sec->rsec, reloc) {
 			struct symbol *sym = find_symbol_containing(reloc->sec->base,
 								    reloc_offset(reloc));
@@ -4679,7 +4688,7 @@ static int validate_ibt(struct objtool_f
 			if (sym && !strcmp(sym->name, "sys_call_table"))
 				continue;
 
-			warnings += validate_ibt_data_reloc(file, reloc);
+			warnings += validate_ibt_data_reloc(file, reloc, warn);
 		}
 	}
 



  parent reply	other threads:[~2026-11-02 11:40 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02 11:25 [PATCH 0/6] x86: Harden syscall dispatch Peter Zijlstra
2026-11-02 11:25 ` [PATCH 1/6] x86_64/entry: Unconditionally use -fno-jump-table Peter Zijlstra
2026-11-02 14:28   ` Breno Leitao
2026-11-02 11:25 ` [PATCH 2/6] objtool: Add instrumentation_weak() Peter Zijlstra
2026-11-02 11:25 ` [PATCH 3/6] x86_64/entry: Make syscall dispatch noinstr Peter Zijlstra
2026-11-02 11:25 ` [PATCH 4/6] x86_64/entry: Mark all syscalls __noendbr Peter Zijlstra
2026-11-02 11:25 ` Peter Zijlstra [this message]
2026-11-02 11:25 ` [PATCH 6/6] x86_64/entry: Mark C noinstr __noendbr Peter Zijlstra

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20261102114000.887706274@infradead.org \
    --to=peterz@infradead.org \
    --cc=hpa@zytor.com \
    --cc=jpoimboe@kernel.org \
    --cc=keescook@chromium.org \
    --cc=leitao@debian.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lkp@intel.com \
    --cc=x86@kernel.org \
    /path/to/YOUR_REPLY

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

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

all inboxes | Powered by JetHome®