diff mbox series

[v3,60/70] x86: Build check for embedded endbr64 instructions

Message ID 20220222152645.8844-15-andrew.cooper3@citrix.com (mailing list archive)
State New, archived
Headers show
Series x86: Support for CET Indirect Branch Tracking | expand

Commit Message

Andrew Cooper Feb. 22, 2022, 3:26 p.m. UTC
From: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>

An interesting corner case occurs when the byte sequence making up endb64 ends
up on a non-instruction boundary.  Such embedded instructions mark legal
indirect branch targets as far as the CPU is concerned, which aren't legal as
far as the logic is concerned.

When CET-IBT is active, check for embedded byte sequences.  Example failures
look like:

  check-endbr.sh xen-syms Fail: Found 2 embedded endbr64 instructions
  0xffff82d040325677: test_endbr64 at /local/xen.git/xen/arch/x86/x86_64/entry.S:28
  0xffff82d040352da6: init_done at /local/xen.git/xen/arch/x86/setup.c:675

Signed-off-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
---
CC: Jan Beulich <JBeulich@suse.com>
CC: Roger Pau Monné <roger.pau@citrix.com>
CC: Wei Liu <wl@xen.org>

v2:
 * New
v3:
 * Reposition to the end of the cf_check-ing, to retain bisectability
 * Reword commit message to explain 'embedded'
 * Use ${ADDR2LINE} if present in the environment
 * Use objdump -w
 * Explain the use of octal
 * Check the EFI build too.  Reposition to be last action, so all build
   artefacts remain in a failure case
 * Check for grep support and warn if missing
 * Replace strtonum() with int() to avoid gaining a gawk dependency
 * Replace `join` with `sort | uniq` to avoid adding a coreutils dependency
---
 README                   |  1 +
 xen/arch/x86/Makefile    |  6 ++++
 xen/tools/check-endbr.sh | 85 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+)
 create mode 100755 xen/tools/check-endbr.sh

Comments

Jan Beulich Feb. 23, 2022, 11:31 a.m. UTC | #1
On 22.02.2022 16:26, Andrew Cooper wrote:
> From: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
> 
> An interesting corner case occurs when the byte sequence making up endb64 ends

Nit: For grep-ability it would be nice to spell this "endbr64".

> up on a non-instruction boundary.  Such embedded instructions mark legal
> indirect branch targets as far as the CPU is concerned, which aren't legal as
> far as the logic is concerned.

Thinking about it: Wouldn't it be yet slightly more reassuring to also
look for ENDBR32?

> When CET-IBT is active, check for embedded byte sequences.  Example failures
> look like:
> 
>   check-endbr.sh xen-syms Fail: Found 2 embedded endbr64 instructions
>   0xffff82d040325677: test_endbr64 at /local/xen.git/xen/arch/x86/x86_64/entry.S:28
>   0xffff82d040352da6: init_done at /local/xen.git/xen/arch/x86/setup.c:675
> 
> Signed-off-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>

Reviewed-by: Jan Beulich <jbeulich@suse.com>

> --- a/README
> +++ b/README
> @@ -68,6 +68,7 @@ provided by your OS distributor:
>  In addition to the above there are a number of optional build
>  prerequisites. Omitting these will cause the related features to be
>  disabled at compile time:
> +    * Binary-search capable grep (if building Xen with CET support)

Nit: With this (maybe this was the case already earlier though)
s/will/may/ in the previous sentence?

> --- /dev/null
> +++ b/xen/tools/check-endbr.sh
> @@ -0,0 +1,85 @@
> +#!/bin/sh
> +#
> +# Usage ./$0 xen-syms
> +#
> +set -e
> +
> +# Prettyprint parameters a little for message
> +MSG_PFX="${0##*/} ${1##*/}"
> +
> +OBJCOPY="${OBJCOPY:-objcopy} -j .text $1"
> +OBJDUMP="${OBJDUMP:-objdump} -j .text $1"

While embedding the arguments here shortens the lines where these are
used, the appearance especially of $OBJCOPY with a single file name
argument ...

> +ADDR2LINE="${ADDR2LINE:-addr2line}"
> +
> +D=$(mktemp -d)
> +trap "rm -rf $D" EXIT
> +
> +TEXT_BIN=$D/xen-syms.text
> +VALID=$D/valid-addrs
> +ALL=$D/all-addrs
> +BAD=$D/bad-addrs
> +
> +# Check that grep can do binary searches.  Some, e.g. busybox, can't.  Leave a
> +# warning but don't fail the build.
> +echo "X" | grep -aob "X" -q 2>/dev/null ||
> +    { echo "$MSG_PFX Warning: grep can't do binary searches" >&2; exit 0; }
> +
> +#
> +# First, look for all the valid endbr64 instructions.
> +# A worst-case disassembly, viewed through cat -A, may look like:
> +#
> +# ffff82d040337bd4 <endbr64>:$
> +# ffff82d040337bd4:^If3 0f 1e fa          ^Iendbr64 $
> +# ffff82d040337bd8:^Ieb fe                ^Ijmp    ffff82d040337bd8 <endbr64+0x4>$
> +# ffff82d040337bda:^Ib8 f3 0f 1e fa       ^Imov    $0xfa1e0ff3,%eax$
> +#
> +# Want to grab the address of endbr64 instructions only, ignoring function
> +# names/jump labels/etc, so look for 'endbr64' preceeded by a tab and with any
> +# number of trailing spaces before the end of the line.
> +#
> +${OBJDUMP} -d -w | grep '	endbr64 *$' | cut -f 1 -d ':' > $VALID &
> +
> +#
> +# Second, look for any endbr64 byte sequence
> +# This has a couple of complications:
> +#
> +# 1) Grep binary search isn't VMA aware.  Copy .text out as binary, causing
> +#    the grep offset to be from the start of .text.
> +#
> +# 2) dash's printf doesn't understand hex escapes, hence the use of octal.
> +#
> +# 3) AWK can't add 64bit integers, because internally all numbers are doubles.
> +#    When the upper bits are set, the exponents worth of precision is lost in
> +#    the lower bits, rounding integers to the nearest 4k.
> +#
> +#    Instead, use the fact that Xen's .text is within a 1G aligned region, and
> +#    split the VMA in half so AWK's numeric addition is only working on 32 bit
> +#    numbers, which don't lose precision.
> +#
> +eval $(${OBJDUMP} -h | awk '$2 == ".text" {printf "vma_hi=%s\nvma_lo=%s\n", substr($4, 1, 8), substr($4, 9, 16)}')
> +
> +${OBJCOPY} -O binary $TEXT_BIN

..., like here, is then somewhat misleading considering that the tool
can take one or two filenames as arguments.

Jan
Andrew Cooper Feb. 23, 2022, 12:05 p.m. UTC | #2
On 23/02/2022 11:31, Jan Beulich wrote:
> On 22.02.2022 16:26, Andrew Cooper wrote:
>> up on a non-instruction boundary.  Such embedded instructions mark legal
>> indirect branch targets as far as the CPU is concerned, which aren't legal as
>> far as the logic is concerned.
> Thinking about it: Wouldn't it be yet slightly more reassuring to also
> look for ENDBR32?

I considered that, but it's awkward to do and doubles the length of this
already ~0.7s (x2 for efi because this step isn't performed in parallel)
delay to the build.

We do not have __HYPERVISOR_CS32, so ENDBR32 will yield #CP[endbr] if
encountered.

If an attacker has managed to edit the GDT to insert a compatibility
code segment, and hijacked a far transfer to use it, then the absence of
ENDBR32's in the binary isn't going to be an impediment.

>
>> When CET-IBT is active, check for embedded byte sequences.  Example failures
>> look like:
>>
>>   check-endbr.sh xen-syms Fail: Found 2 embedded endbr64 instructions
>>   0xffff82d040325677: test_endbr64 at /local/xen.git/xen/arch/x86/x86_64/entry.S:28
>>   0xffff82d040352da6: init_done at /local/xen.git/xen/arch/x86/setup.c:675
>>
>> Signed-off-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
>> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
> Reviewed-by: Jan Beulich <jbeulich@suse.com>

Thanks.

>
>> --- a/README
>> +++ b/README
>> @@ -68,6 +68,7 @@ provided by your OS distributor:
>>  In addition to the above there are a number of optional build
>>  prerequisites. Omitting these will cause the related features to be
>>  disabled at compile time:
>> +    * Binary-search capable grep (if building Xen with CET support)
> Nit: With this (maybe this was the case already earlier though)
> s/will/may/ in the previous sentence?

I'm planning a separate overhaul to README because bits of it are quite
wrong, including lots of this section.  This was the lead bad addition I
could come up with that didn't involve a major rewrite.

>> --- /dev/null
>> +++ b/xen/tools/check-endbr.sh
>> @@ -0,0 +1,85 @@
>> +#!/bin/sh
>> +#
>> +# Usage ./$0 xen-syms
>> +#
>> +set -e
>> +
>> +# Prettyprint parameters a little for message
>> +MSG_PFX="${0##*/} ${1##*/}"
>> +
>> +OBJCOPY="${OBJCOPY:-objcopy} -j .text $1"
>> +OBJDUMP="${OBJDUMP:-objdump} -j .text $1"
> While embedding the arguments here shortens the lines where these are
> used, the appearance especially of $OBJCOPY with a single file name
> argument ...
>
>> +ADDR2LINE="${ADDR2LINE:-addr2line}"
>> +
>> +D=$(mktemp -d)
>> +trap "rm -rf $D" EXIT
>> +
>> +TEXT_BIN=$D/xen-syms.text
>> +VALID=$D/valid-addrs
>> +ALL=$D/all-addrs
>> +BAD=$D/bad-addrs
>> +
>> +# Check that grep can do binary searches.  Some, e.g. busybox, can't.  Leave a
>> +# warning but don't fail the build.
>> +echo "X" | grep -aob "X" -q 2>/dev/null ||
>> +    { echo "$MSG_PFX Warning: grep can't do binary searches" >&2; exit 0; }
>> +
>> +#
>> +# First, look for all the valid endbr64 instructions.
>> +# A worst-case disassembly, viewed through cat -A, may look like:
>> +#
>> +# ffff82d040337bd4 <endbr64>:$
>> +# ffff82d040337bd4:^If3 0f 1e fa          ^Iendbr64 $
>> +# ffff82d040337bd8:^Ieb fe                ^Ijmp    ffff82d040337bd8 <endbr64+0x4>$
>> +# ffff82d040337bda:^Ib8 f3 0f 1e fa       ^Imov    $0xfa1e0ff3,%eax$
>> +#
>> +# Want to grab the address of endbr64 instructions only, ignoring function
>> +# names/jump labels/etc, so look for 'endbr64' preceeded by a tab and with any
>> +# number of trailing spaces before the end of the line.
>> +#
>> +${OBJDUMP} -d -w | grep '	endbr64 *$' | cut -f 1 -d ':' > $VALID &
>> +
>> +#
>> +# Second, look for any endbr64 byte sequence
>> +# This has a couple of complications:
>> +#
>> +# 1) Grep binary search isn't VMA aware.  Copy .text out as binary, causing
>> +#    the grep offset to be from the start of .text.
>> +#
>> +# 2) dash's printf doesn't understand hex escapes, hence the use of octal.
>> +#
>> +# 3) AWK can't add 64bit integers, because internally all numbers are doubles.
>> +#    When the upper bits are set, the exponents worth of precision is lost in
>> +#    the lower bits, rounding integers to the nearest 4k.
>> +#
>> +#    Instead, use the fact that Xen's .text is within a 1G aligned region, and
>> +#    split the VMA in half so AWK's numeric addition is only working on 32 bit
>> +#    numbers, which don't lose precision.
>> +#
>> +eval $(${OBJDUMP} -h | awk '$2 == ".text" {printf "vma_hi=%s\nvma_lo=%s\n", substr($4, 1, 8), substr($4, 9, 16)}')
>> +
>> +${OBJCOPY} -O binary $TEXT_BIN
> ..., like here, is then somewhat misleading considering that the tool
> can take one or two filenames as arguments.

I can re-expand them if you'd prefer.  This would be the delta:

diff --git a/xen/tools/check-endbr.sh b/xen/tools/check-endbr.sh
index 85878353112a..3019ca1c7db0 100755
--- a/xen/tools/check-endbr.sh
+++ b/xen/tools/check-endbr.sh
@@ -7,8 +7,8 @@ set -e
 # Prettyprint parameters a little for message
 MSG_PFX="${0##*/} ${1##*/}"
 
-OBJCOPY="${OBJCOPY:-objcopy} -j .text $1"
-OBJDUMP="${OBJDUMP:-objdump} -j .text $1"
+OBJCOPY="${OBJCOPY:-objcopy}"
+OBJDUMP="${OBJDUMP:-objdump}"
 ADDR2LINE="${ADDR2LINE:-addr2line}"
 
 D=$(mktemp -d)
@@ -37,7 +37,7 @@ echo "X" | grep -aob "X" -q 2>/dev/null ||
 # names/jump labels/etc, so look for 'endbr64' preceeded by a tab and
with any
 # number of trailing spaces before the end of the line.
 #
-${OBJDUMP} -d -w | grep '      endbr64 *$' | cut -f 1 -d ':' > $VALID &
+${OBJDUMP} -j .text $1 -d -w | grep '  endbr64 *$' | cut -f 1 -d ':' >
$VALID &
 
 #
 # Second, look for any endbr64 byte sequence
@@ -56,9 +56,10 @@ ${OBJDUMP} -d -w | grep '    endbr64 *$' | cut -f 1
-d ':' > $VALID &
 #    split the VMA in half so AWK's numeric addition is only working on
32 bit
 #    numbers, which don't lose precision.
 #
-eval $(${OBJDUMP} -h | awk '$2 == ".text" {printf
"vma_hi=%s\nvma_lo=%s\n", substr($4, 1, 8), substr($4, 9, 16)}')
+eval $(${OBJDUMP} -j .text $1 -h |
+    awk '$2 == ".text" {printf "vma_hi=%s\nvma_lo=%s\n", substr($4, 1,
8), substr($4, 9, 16)}')
 
-${OBJCOPY} -O binary $TEXT_BIN
+${OBJCOPY} -j .text $1 -O binary $TEXT_BIN
 grep -aob "$(printf '\363\17\36\372')" $TEXT_BIN |
     awk -F':' '{printf "%s%x\n", "'$vma_hi'", int(0x'$vma_lo') + $1}' >
$ALL
 

~Andrew
Jan Beulich Feb. 23, 2022, 2:29 p.m. UTC | #3
On 23.02.2022 13:05, Andrew Cooper wrote:
> On 23/02/2022 11:31, Jan Beulich wrote:
>> On 22.02.2022 16:26, Andrew Cooper wrote:
>>> up on a non-instruction boundary.  Such embedded instructions mark legal
>>> indirect branch targets as far as the CPU is concerned, which aren't legal as
>>> far as the logic is concerned.
>> Thinking about it: Wouldn't it be yet slightly more reassuring to also
>> look for ENDBR32?
> 
> I considered that, but it's awkward to do and doubles the length of this
> already ~0.7s (x2 for efi because this step isn't performed in parallel)
> delay to the build.

(Side note: In general the two linking steps can occur in parallel. An
exception is when the note.o need to be extracted from xen-syms for use
by xen.efi. But that should happen only with old binutils.)

> We do not have __HYPERVISOR_CS32, so ENDBR32 will yield #CP[endbr] if
> encountered.
> 
> If an attacker has managed to edit the GDT to insert a compatibility
> code segment, and hijacked a far transfer to use it, then the absence of
> ENDBR32's in the binary isn't going to be an impediment.

True.

>>> --- /dev/null
>>> +++ b/xen/tools/check-endbr.sh
>>> @@ -0,0 +1,85 @@
>>> +#!/bin/sh
>>> +#
>>> +# Usage ./$0 xen-syms
>>> +#
>>> +set -e
>>> +
>>> +# Prettyprint parameters a little for message
>>> +MSG_PFX="${0##*/} ${1##*/}"
>>> +
>>> +OBJCOPY="${OBJCOPY:-objcopy} -j .text $1"
>>> +OBJDUMP="${OBJDUMP:-objdump} -j .text $1"
>> While embedding the arguments here shortens the lines where these are
>> used, the appearance especially of $OBJCOPY with a single file name
>> argument ...
>>
>>> +ADDR2LINE="${ADDR2LINE:-addr2line}"
>>> +
>>> +D=$(mktemp -d)
>>> +trap "rm -rf $D" EXIT
>>> +
>>> +TEXT_BIN=$D/xen-syms.text
>>> +VALID=$D/valid-addrs
>>> +ALL=$D/all-addrs
>>> +BAD=$D/bad-addrs
>>> +
>>> +# Check that grep can do binary searches.  Some, e.g. busybox, can't.  Leave a
>>> +# warning but don't fail the build.
>>> +echo "X" | grep -aob "X" -q 2>/dev/null ||
>>> +    { echo "$MSG_PFX Warning: grep can't do binary searches" >&2; exit 0; }
>>> +
>>> +#
>>> +# First, look for all the valid endbr64 instructions.
>>> +# A worst-case disassembly, viewed through cat -A, may look like:
>>> +#
>>> +# ffff82d040337bd4 <endbr64>:$
>>> +# ffff82d040337bd4:^If3 0f 1e fa          ^Iendbr64 $
>>> +# ffff82d040337bd8:^Ieb fe                ^Ijmp    ffff82d040337bd8 <endbr64+0x4>$
>>> +# ffff82d040337bda:^Ib8 f3 0f 1e fa       ^Imov    $0xfa1e0ff3,%eax$
>>> +#
>>> +# Want to grab the address of endbr64 instructions only, ignoring function
>>> +# names/jump labels/etc, so look for 'endbr64' preceeded by a tab and with any
>>> +# number of trailing spaces before the end of the line.
>>> +#
>>> +${OBJDUMP} -d -w | grep '	endbr64 *$' | cut -f 1 -d ':' > $VALID &
>>> +
>>> +#
>>> +# Second, look for any endbr64 byte sequence
>>> +# This has a couple of complications:
>>> +#
>>> +# 1) Grep binary search isn't VMA aware.  Copy .text out as binary, causing
>>> +#    the grep offset to be from the start of .text.
>>> +#
>>> +# 2) dash's printf doesn't understand hex escapes, hence the use of octal.
>>> +#
>>> +# 3) AWK can't add 64bit integers, because internally all numbers are doubles.
>>> +#    When the upper bits are set, the exponents worth of precision is lost in
>>> +#    the lower bits, rounding integers to the nearest 4k.
>>> +#
>>> +#    Instead, use the fact that Xen's .text is within a 1G aligned region, and
>>> +#    split the VMA in half so AWK's numeric addition is only working on 32 bit
>>> +#    numbers, which don't lose precision.
>>> +#
>>> +eval $(${OBJDUMP} -h | awk '$2 == ".text" {printf "vma_hi=%s\nvma_lo=%s\n", substr($4, 1, 8), substr($4, 9, 16)}')
>>> +
>>> +${OBJCOPY} -O binary $TEXT_BIN
>> ..., like here, is then somewhat misleading considering that the tool
>> can take one or two filenames as arguments.
> 
> I can re-expand them if you'd prefer.  This would be the delta:

I'd actually be happy to keep "-j .text" where you had it, and merely
move the file arguments to the actual invocation lines. But the way
you have it with the incremental diff is of course even less
"unexpected".

Jan

> diff --git a/xen/tools/check-endbr.sh b/xen/tools/check-endbr.sh
> index 85878353112a..3019ca1c7db0 100755
> --- a/xen/tools/check-endbr.sh
> +++ b/xen/tools/check-endbr.sh
> @@ -7,8 +7,8 @@ set -e
>  # Prettyprint parameters a little for message
>  MSG_PFX="${0##*/} ${1##*/}"
>  
> -OBJCOPY="${OBJCOPY:-objcopy} -j .text $1"
> -OBJDUMP="${OBJDUMP:-objdump} -j .text $1"
> +OBJCOPY="${OBJCOPY:-objcopy}"
> +OBJDUMP="${OBJDUMP:-objdump}"
>  ADDR2LINE="${ADDR2LINE:-addr2line}"
>  
>  D=$(mktemp -d)
> @@ -37,7 +37,7 @@ echo "X" | grep -aob "X" -q 2>/dev/null ||
>  # names/jump labels/etc, so look for 'endbr64' preceeded by a tab and
> with any
>  # number of trailing spaces before the end of the line.
>  #
> -${OBJDUMP} -d -w | grep '      endbr64 *$' | cut -f 1 -d ':' > $VALID &
> +${OBJDUMP} -j .text $1 -d -w | grep '  endbr64 *$' | cut -f 1 -d ':' >
> $VALID &
>  
>  #
>  # Second, look for any endbr64 byte sequence
> @@ -56,9 +56,10 @@ ${OBJDUMP} -d -w | grep '    endbr64 *$' | cut -f 1
> -d ':' > $VALID &
>  #    split the VMA in half so AWK's numeric addition is only working on
> 32 bit
>  #    numbers, which don't lose precision.
>  #
> -eval $(${OBJDUMP} -h | awk '$2 == ".text" {printf
> "vma_hi=%s\nvma_lo=%s\n", substr($4, 1, 8), substr($4, 9, 16)}')
> +eval $(${OBJDUMP} -j .text $1 -h |
> +    awk '$2 == ".text" {printf "vma_hi=%s\nvma_lo=%s\n", substr($4, 1,
> 8), substr($4, 9, 16)}')
>  
> -${OBJCOPY} -O binary $TEXT_BIN
> +${OBJCOPY} -j .text $1 -O binary $TEXT_BIN
>  grep -aob "$(printf '\363\17\36\372')" $TEXT_BIN |
>      awk -F':' '{printf "%s%x\n", "'$vma_hi'", int(0x'$vma_lo') + $1}' >
> $ALL
>  
> 
> ~Andrew
diff mbox series

Patch

diff --git a/README b/README
index 562b80808033..5e55047ffd9e 100644
--- a/README
+++ b/README
@@ -68,6 +68,7 @@  provided by your OS distributor:
 In addition to the above there are a number of optional build
 prerequisites. Omitting these will cause the related features to be
 disabled at compile time:
+    * Binary-search capable grep (if building Xen with CET support)
     * Development install of Ocaml (e.g. ocaml-nox and
       ocaml-findlib). Required to build ocaml components which
       includes the alternative ocaml xenstored.
diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
index db97ae8c07f0..b90146b75636 100644
--- a/xen/arch/x86/Makefile
+++ b/xen/arch/x86/Makefile
@@ -142,6 +142,9 @@  $(TARGET)-syms: $(BASEDIR)/prelink.o $(obj)/xen.lds
 		| $(BASEDIR)/tools/symbols --all-symbols --xensyms --sysv --sort \
 		>$(@D)/$(@F).map
 	rm -f $(@D)/.$(@F).[0-9]* $(@D)/..$(@F).[0-9]*
+ifeq ($(CONFIG_XEN_IBT),y)
+	$(SHELL) $(BASEDIR)/tools/check-endbr.sh $@
+endif
 
 $(obj)/note.o: $(TARGET)-syms
 	$(OBJCOPY) -O binary --only-section=.note.gnu.build-id $< $@.bin
@@ -212,6 +215,9 @@  endif
 	$(NM) -pa --format=sysv $(@D)/$(@F) \
 		| $(BASEDIR)/tools/symbols --all-symbols --xensyms --sysv --sort >$(@D)/$(@F).map
 	rm -f $(@D)/.$(@F).[0-9]* $(@D)/..$(@F).[0-9]*
+ifeq ($(CONFIG_XEN_IBT),y)
+	$(SHELL) $(BASEDIR)/tools/check-endbr.sh $@
+endif
 else
 $(TARGET).efi: FORCE
 	rm -f $@
diff --git a/xen/tools/check-endbr.sh b/xen/tools/check-endbr.sh
new file mode 100755
index 000000000000..85878353112a
--- /dev/null
+++ b/xen/tools/check-endbr.sh
@@ -0,0 +1,85 @@ 
+#!/bin/sh
+#
+# Usage ./$0 xen-syms
+#
+set -e
+
+# Prettyprint parameters a little for message
+MSG_PFX="${0##*/} ${1##*/}"
+
+OBJCOPY="${OBJCOPY:-objcopy} -j .text $1"
+OBJDUMP="${OBJDUMP:-objdump} -j .text $1"
+ADDR2LINE="${ADDR2LINE:-addr2line}"
+
+D=$(mktemp -d)
+trap "rm -rf $D" EXIT
+
+TEXT_BIN=$D/xen-syms.text
+VALID=$D/valid-addrs
+ALL=$D/all-addrs
+BAD=$D/bad-addrs
+
+# Check that grep can do binary searches.  Some, e.g. busybox, can't.  Leave a
+# warning but don't fail the build.
+echo "X" | grep -aob "X" -q 2>/dev/null ||
+    { echo "$MSG_PFX Warning: grep can't do binary searches" >&2; exit 0; }
+
+#
+# First, look for all the valid endbr64 instructions.
+# A worst-case disassembly, viewed through cat -A, may look like:
+#
+# ffff82d040337bd4 <endbr64>:$
+# ffff82d040337bd4:^If3 0f 1e fa          ^Iendbr64 $
+# ffff82d040337bd8:^Ieb fe                ^Ijmp    ffff82d040337bd8 <endbr64+0x4>$
+# ffff82d040337bda:^Ib8 f3 0f 1e fa       ^Imov    $0xfa1e0ff3,%eax$
+#
+# Want to grab the address of endbr64 instructions only, ignoring function
+# names/jump labels/etc, so look for 'endbr64' preceeded by a tab and with any
+# number of trailing spaces before the end of the line.
+#
+${OBJDUMP} -d -w | grep '	endbr64 *$' | cut -f 1 -d ':' > $VALID &
+
+#
+# Second, look for any endbr64 byte sequence
+# This has a couple of complications:
+#
+# 1) Grep binary search isn't VMA aware.  Copy .text out as binary, causing
+#    the grep offset to be from the start of .text.
+#
+# 2) dash's printf doesn't understand hex escapes, hence the use of octal.
+#
+# 3) AWK can't add 64bit integers, because internally all numbers are doubles.
+#    When the upper bits are set, the exponents worth of precision is lost in
+#    the lower bits, rounding integers to the nearest 4k.
+#
+#    Instead, use the fact that Xen's .text is within a 1G aligned region, and
+#    split the VMA in half so AWK's numeric addition is only working on 32 bit
+#    numbers, which don't lose precision.
+#
+eval $(${OBJDUMP} -h | awk '$2 == ".text" {printf "vma_hi=%s\nvma_lo=%s\n", substr($4, 1, 8), substr($4, 9, 16)}')
+
+${OBJCOPY} -O binary $TEXT_BIN
+grep -aob "$(printf '\363\17\36\372')" $TEXT_BIN |
+    awk -F':' '{printf "%s%x\n", "'$vma_hi'", int(0x'$vma_lo') + $1}' > $ALL
+
+# Wait for $VALID to become complete
+wait
+
+# Sanity check $VALID and $ALL, in case the string parsing bitrots
+val_sz=$(stat -c '%s' $VALID)
+all_sz=$(stat -c '%s' $ALL)
+[ "$val_sz" -eq 0 ]         && { echo "$MSG_PFX Error: Empty valid-addrs" >&2; exit 1; }
+[ "$all_sz" -eq 0 ]         && { echo "$MSG_PFX Error: Empty all-addrs" >&2; exit 1; }
+[ "$all_sz" -lt "$val_sz" ] && { echo "$MSG_PFX Error: More valid-addrs than all-addrs" >&2; exit 1; }
+
+# $BAD = $ALL - $VALID
+sort $VALID $ALL | uniq -u > $BAD
+nr_bad=$(wc -l < $BAD)
+
+# Success
+[ "$nr_bad" -eq 0 ] && exit 0
+
+# Failure
+echo "$MSG_PFX Fail: Found ${nr_bad} embedded endbr64 instructions" >&2
+${ADDR2LINE} -afip -e $1 < $BAD >&2
+exit 1