Kernel-hardening archive on lore.kernel.org
 help / color / Atom feed
* Alternative CET ABI
@ 2020-07-30 16:01 Florian Weimer
  2020-07-30 16:41 ` Jann Horn
  0 siblings, 1 reply; 5+ messages in thread
From: Florian Weimer @ 2020-07-30 16:01 UTC (permalink / raw)
  To: oss-security, x86-64-abi, kernel-hardening; +Cc: Szabolcs Nagy

CET (and Arm BTI) restrict targets for indirect jumps and calls to
landing pads which start with specially-formatted NOP instruction
dedicated to this purpose (endrb64 in the x86-64 case).

The traditional way of implementing ELF on top of this is to have every
global function start with that NOP, and also use these NOPs in PLT
stubs in the main program (which may provide the canonical address of
functions, i.e. there address may be taken).

The downside of this approach is that all functions in the process
become available for execution, whether they are used in the original
program or not.  (In principle, control flow integrity provides
reasonably efficient ways to counteract that, by keeping track of symbol
resolution and verifying flags at the start of critical functions, but
we do not have automated support for that today, and there are some open
issues about complex call graphs.)

CET has a NOTRACK prefix for indirect jumps/and calls.  It asserts that
the jump target address is trusted and disables the control flow
integrity check.  It is expected to be used with jump tables and the
like, in conjunction with RELRO (so that the address has been loaded
from read-only memory).

I think this also provides support for a completely different ABI, where
global functions are not automatically addressable.  It depends on
BIND_NOW and RELRO, for a read-only GOT.

First of all, it needs new relocation types that tell the static link
editor which symbol references are address-significant.  Generally,
function addresses which end up in RELRO data only are not
address-significant if they are used immediately in call instructions
(without indirection of any form through writable memory).  This means
that direct calls do not have address significance.  For vtables, it
depends on how they are used; their function addresses probably need to
be treated conservatively as address-significant (because the vtable
pointer is in writable memory; at least for C++ vtables, the address of
a virtual member function is not significant).

Functions no longer start with the ENDBR64 prefix.  Instead, the link
editor produces a PLT entry with an ENDBR64 prefix if it detects any
address-significant relocation for it.  The PLT entry performs a NOTRACK
jump to the target address.  This assumes that the target address is
subject to RELRO, of course, so that redirection is not possible.
Without address-significant relocations, the link editor produces a PLT
entry without the ENDBR64 prefix (but still with the NOTRACK jump), or
perhaps no PLT entry at all.

The net effect is that only functions which have their address taken in
the original program can be called through indirect function calls.  For
example, this means that the system function in libc is usually dormant,
and cannot be reached, even if an attacker can cause the process to call
arbitrary functions with an arbitrary string argument.  The reason is
that the system function lacks the ENDBR64 prefix, and all PLT entries
calling it also lack it.

dlopen'ing a shared object which has a address-significant relocation
against a function is not a problem under this model.  Either there
already was an address-significant relocation before, then the function
already has a canonical address, and that can be used.  Or there was
not, then the just-loaded PLT entry (which as an ENDBR64 prefix)
provides the canonical address function.

To support dlsym, each global function definition would have a separate
ENDBR64-enabled PLT/GOT slot for that, with the GOT slot only filled in
at the time of the dlsym call (with mprotect calls around that, with
some hand-waving required these can never fail).  This is probably the
most awkward part about all this.  Alternatively, these stubs could also
be generated at run time, from a pre-computed code page.

Obviously, it is too late for that now for x86-64, but maybe someone
else gets a chance to try this.

Thanks,
Florian


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Alternative CET ABI
  2020-07-30 16:01 Alternative CET ABI Florian Weimer
@ 2020-07-30 16:41 ` Jann Horn
  2020-07-30 16:47   ` Szabolcs Nagy
  2020-07-30 16:54   ` Florian Weimer
  0 siblings, 2 replies; 5+ messages in thread
From: Jann Horn @ 2020-07-30 16:41 UTC (permalink / raw)
  To: Florian Weimer; +Cc: oss-security, x86-64-abi, Kernel Hardening, Szabolcs Nagy

On Thu, Jul 30, 2020 at 6:02 PM Florian Weimer <fweimer@redhat.com> wrote:
> Functions no longer start with the ENDBR64 prefix.  Instead, the link
> editor produces a PLT entry with an ENDBR64 prefix if it detects any
> address-significant relocation for it.  The PLT entry performs a NOTRACK
> jump to the target address.  This assumes that the target address is
> subject to RELRO, of course, so that redirection is not possible.
> Without address-significant relocations, the link editor produces a PLT
> entry without the ENDBR64 prefix (but still with the NOTRACK jump), or
> perhaps no PLT entry at all.

How would this interact with function pointer comparisons? As in, if
library A exports a function func1 without referencing it, and
libraries B and C both take references to func1, would they end up
with different function pointers (pointing to their respective PLT
entries)? Would this mean that the behavior of a program that compares
function pointers obtained through different shared libraries might
change?

I guess you could maybe canonicalize function pointers somehow, but
that'd probably at least break dlclose(), right?

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Alternative CET ABI
  2020-07-30 16:41 ` Jann Horn
@ 2020-07-30 16:47   ` Szabolcs Nagy
  2020-07-30 16:54   ` Florian Weimer
  1 sibling, 0 replies; 5+ messages in thread
From: Szabolcs Nagy @ 2020-07-30 16:47 UTC (permalink / raw)
  To: Jann Horn; +Cc: Florian Weimer, oss-security, x86-64-abi, Kernel Hardening

The 07/30/2020 18:41, Jann Horn wrote:
> On Thu, Jul 30, 2020 at 6:02 PM Florian Weimer <fweimer@redhat.com> wrote:
> > Functions no longer start with the ENDBR64 prefix.  Instead, the link
> > editor produces a PLT entry with an ENDBR64 prefix if it detects any
> > address-significant relocation for it.  The PLT entry performs a NOTRACK
> > jump to the target address.  This assumes that the target address is
> > subject to RELRO, of course, so that redirection is not possible.
> > Without address-significant relocations, the link editor produces a PLT
> > entry without the ENDBR64 prefix (but still with the NOTRACK jump), or
> > perhaps no PLT entry at all.
>
> How would this interact with function pointer comparisons? As in, if
> library A exports a function func1 without referencing it, and
> libraries B and C both take references to func1, would they end up
> with different function pointers (pointing to their respective PLT
> entries)? Would this mean that the behavior of a program that compares

ld.so only needs to generate one plt entry
for a function in a process and that entry
can provided the canonical address that is
loaded from some got entry when the address
is used, so there is double indirection, but
it works.

> function pointers obtained through different shared libraries might
> change?
>
> I guess you could maybe canonicalize function pointers somehow, but
> that'd probably at least break dlclose(), right?
IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Alternative CET ABI
  2020-07-30 16:41 ` Jann Horn
  2020-07-30 16:47   ` Szabolcs Nagy
@ 2020-07-30 16:54   ` Florian Weimer
  2020-07-30 17:14     ` H.J. Lu
  1 sibling, 1 reply; 5+ messages in thread
From: Florian Weimer @ 2020-07-30 16:54 UTC (permalink / raw)
  To: Jann Horn; +Cc: oss-security, x86-64-abi, Kernel Hardening, Szabolcs Nagy

* Jann Horn:

> On Thu, Jul 30, 2020 at 6:02 PM Florian Weimer <fweimer@redhat.com> wrote:
>> Functions no longer start with the ENDBR64 prefix.  Instead, the link
>> editor produces a PLT entry with an ENDBR64 prefix if it detects any
>> address-significant relocation for it.  The PLT entry performs a NOTRACK
>> jump to the target address.  This assumes that the target address is
>> subject to RELRO, of course, so that redirection is not possible.
>> Without address-significant relocations, the link editor produces a PLT
>> entry without the ENDBR64 prefix (but still with the NOTRACK jump), or
>> perhaps no PLT entry at all.
>
> How would this interact with function pointer comparisons? As in, if
> library A exports a function func1 without referencing it, and
> libraries B and C both take references to func1, would they end up
> with different function pointers (pointing to their respective PLT
> entries)?

Same as today.  ELF already deals with this by picking one canonical
function address per process.

Some targets already need PLTs for inter-DSO calls, so the problem is
not new.  It happens even on x86 because the main program can refer to
its PLT stubs without run-time relocations, so those determine the
canonical address of those functions, and not the actual implementation
in a shared object.

> Would this mean that the behavior of a program that compares
> function pointers obtained through different shared libraries might
> change?

Hopefully not, because that would break things quite horribly (as it's
sometimes possible to observe if the RTLD_DEEPBIND flag is used).

Both the canonicalization and the fact in order to observe the function
pointer, you need to take its address should take care of this.

> I guess you could maybe canonicalize function pointers somehow, but
> that'd probably at least break dlclose(), right?

Ahh, dlclose.  I think in this case, my idea to generate a PLT stub
locally in the address-generating DSO will not work because the
canonical address must survive dlclose if it refers to another DSO.
There are two ways to deal with this: do not unload the PLT stub until
the target DSO is also unloaded (but make sure that the DSO can be
reloaded at a different address; probably not worth the complexity),
or use the dlsym hack I sketched for regular symbol binding as well.
Even more room for experiments, I guess.

Thanks,
Florian


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Alternative CET ABI
  2020-07-30 16:54   ` Florian Weimer
@ 2020-07-30 17:14     ` H.J. Lu
  0 siblings, 0 replies; 5+ messages in thread
From: H.J. Lu @ 2020-07-30 17:14 UTC (permalink / raw)
  To: Florian Weimer
  Cc: Jann Horn, oss-security, x86-64-abi, Kernel Hardening, Szabolcs Nagy

On Thu, Jul 30, 2020 at 9:54 AM Florian Weimer <fweimer@redhat.com> wrote:
>
> * Jann Horn:
>
> > On Thu, Jul 30, 2020 at 6:02 PM Florian Weimer <fweimer@redhat.com> wrote:
> >> Functions no longer start with the ENDBR64 prefix.  Instead, the link
> >> editor produces a PLT entry with an ENDBR64 prefix if it detects any
> >> address-significant relocation for it.  The PLT entry performs a NOTRACK
> >> jump to the target address.  This assumes that the target address is
> >> subject to RELRO, of course, so that redirection is not possible.
> >> Without address-significant relocations, the link editor produces a PLT
> >> entry without the ENDBR64 prefix (but still with the NOTRACK jump), or
> >> perhaps no PLT entry at all.
> >
> > How would this interact with function pointer comparisons? As in, if
> > library A exports a function func1 without referencing it, and
> > libraries B and C both take references to func1, would they end up
> > with different function pointers (pointing to their respective PLT
> > entries)?
>
> Same as today.  ELF already deals with this by picking one canonical
> function address per process.
>
> Some targets already need PLTs for inter-DSO calls, so the problem is
> not new.  It happens even on x86 because the main program can refer to
> its PLT stubs without run-time relocations, so those determine the
> canonical address of those functions, and not the actual implementation
> in a shared object.
>
> > Would this mean that the behavior of a program that compares
> > function pointers obtained through different shared libraries might
> > change?
>
> Hopefully not, because that would break things quite horribly (as it's
> sometimes possible to observe if the RTLD_DEEPBIND flag is used).
>
> Both the canonicalization and the fact in order to observe the function
> pointer, you need to take its address should take care of this.
>
> > I guess you could maybe canonicalize function pointers somehow, but
> > that'd probably at least break dlclose(), right?
>
> Ahh, dlclose.  I think in this case, my idea to generate a PLT stub
> locally in the address-generating DSO will not work because the
> canonical address must survive dlclose if it refers to another DSO.
> There are two ways to deal with this: do not unload the PLT stub until
> the target DSO is also unloaded (but make sure that the DSO can be
> reloaded at a different address; probably not worth the complexity),
> or use the dlsym hack I sketched for regular symbol binding as well.
> Even more room for experiments, I guess.
>
> Thanks,
> Florian

FWIW, we can introduce a different CET PLT as long as it is compatible
with the past, current and future binaries.

-- 
H.J.

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, back to index

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-30 16:01 Alternative CET ABI Florian Weimer
2020-07-30 16:41 ` Jann Horn
2020-07-30 16:47   ` Szabolcs Nagy
2020-07-30 16:54   ` Florian Weimer
2020-07-30 17:14     ` H.J. Lu

Kernel-hardening archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/kernel-hardening/0 kernel-hardening/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 kernel-hardening kernel-hardening/ https://lore.kernel.org/kernel-hardening \
		kernel-hardening@lists.openwall.com
	public-inbox-index kernel-hardening

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/com.openwall.lists.kernel-hardening


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git