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 |
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
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
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 --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