-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
libunwind: Use new unwinding APIs under c18n #740
Conversation
This works fine on my end, but wait for @jrtc27 to review as well. |
By using the same name for these unversioned functions, you have a compatibility problem when upgrading from 23.05 to a newer version, as there will be a window within which rtld has been upgraded but not libunwind. This isn't limited to when c18n is enabled (which would make it a non-issue), because part of the restoration process is done by the callee currently even for non-c18n, and so with a new rtld you will no longer have those registers restored which, importantly, includes CSP. C2 and C3 are probably unused enough in practice that you wouldn't notice those during that window. |
Now, having said all that, I had commented before that these functions probably shouldn't be called _rtld_foo, because that's a very FreeBSD-oriented naming scheme. So I think the right approach is to use new, less FreeBSD-y names for them, and the problem will then be avoided. If you enable c18n then things will break unless rtld provides the old names for compatibility, but I think it's fine to just say "don't turn it on when upgrading". |
How about |
I would rather see it be a generic API that both {set,long}jmp and unw{get, set}context use, because at the end of the day they want exactly the same thing. |
RTLD's unwinding code is indeed shared by both *jmp and *context. Separate APIs are provided just because different sealers are used. (See rtld_c18n.c and excerpt below)
|
If we do the register restoring part of libunwind inside rtld, we don't need the sealer to be passed to libunwind and thus could probably use the same sealer. This would allow us to use the same APIs sans the |
Teaching RTLD about the internal layout of jmpbufs and libunwind structures seems too much of a cost to pay to unify the API.
That's true. |
Restoring of registers from the trusted stack (well, really, extraction into some generic “c18n state” struct for the caller to use, libunwind doesn’t want them actually restored whilst unwinding, just recorded in its context). |
Why would we have to teach it about libunwind internals? The idea would be to pass in a buffer into rtld from libunwind (guarded by a policy that only exposes it to libunwind), have rtld fill out the data from the trusted stack that we passed in (which would be sealed using the jmpbuf) and then populate the buffer. rtld would remain unaware about libunwind internals, and libunwind could then use that buffer to |
Oh, I see what you mean now. Yes, delegating the task of I'll implement that and push a patch to CTSRD-CHERI/cheribsd#2122. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran the tests and it works for me. A few nits in the code, but overall I'm fine with this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to see the cumulative diff significantly reducing and becoming much more additive instead of invasive. Some comments to polish it up.
@jrtc27 I removed the |
@@ -13,21 +13,68 @@ | |||
#ifndef __COMPARTMENT_INFO_HPP__ | |||
#define __COMPARTMENT_INFO_HPP__ | |||
|
|||
extern "C" { | |||
|
|||
#include <link.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the declarations for dl_c18n_is_tramp
and dl_c18n_pop_trusted_stk
are now in link_elf.h
, I include link.h
to avoid having to re-declare both functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but then you need to detect them in CMake, because otherwise you'll break the build on Linux or current/old versions of CheriBSD.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, why isn't the struct declared by system headers then?..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm maybe it's easier to just hard-code everything here in libunwind then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, test it? See if you can build against old and new CheriBSD.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And make sure to test against CHERI-RISC-V too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I've successfully built the latest commit with CTSRD-CHERI/cheribsd#2123 for both Morello and CHERI-RISC-V, and it also builds on an older CheriBSD installation through CMake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And when built for Morello with 2123 it detected c18n support?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, CMake detects c18n support on 2123.
@@ -731,8 +712,12 @@ WEAK_ALIAS(__rtld_unw_setcontext, _rtld_unw_setcontext) | |||
.p2align 2 | |||
DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) | |||
#ifdef __CHERI_PURE_CAPABILITY__ | |||
ldr c2, [c0, #0x1f0] // Pass the target untrusted stack pointer | |||
ldr c3, [c0, #0x210] // Pass the target trusted stack pointer | |||
bl dl_c18n_unwind_trusted_stk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would much rather not have all this magic "pass through an arbitrary set of arguments as the return values so those registers are preserved, according to whatever the caller this is designed for happens to need" and instead do one of:
-
Follow the ABI of a normal function call
-
Use a highly-customised calling convention like TLSDESC does
I don't think any of the callers of these kinds of functions is performance-critical enough to warrant 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest commit now does 1.
@@ -13,21 +13,68 @@ | |||
#ifndef __COMPARTMENT_INFO_HPP__ | |||
#define __COMPARTMENT_INFO_HPP__ | |||
|
|||
extern "C" { | |||
|
|||
#include <link.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do it properly. Detect them in CMake and make unwindIfAtBoundary a stub if not present (with CheriBSD's config indicating their presence). You also won't need stubs for _dl_c18n_get/unwind_trusted_stk then too.
fb33dfe
to
e7e5fff
Compare
@@ -731,8 +712,18 @@ WEAK_ALIAS(__rtld_unw_setcontext, _rtld_unw_setcontext) | |||
.p2align 2 | |||
DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) | |||
#ifdef __CHERI_PURE_CAPABILITY__ | |||
#ifdef _LIBUNWIND_HAS_CHERI_LIB_C18N | |||
// Preserve the argument in a callee-saved register instead of pushing it onto | |||
// the stack because stack unwinding will switch the stack. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whilst using a saved register is better anyway, this justification seems undesirable, shouldn't it leave us on the same stack? Then when this function / longjmp returns it can restore the real stack that corresponds to what the compartment should have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can leave the stack unchanged, but then there would be a small window between after dl_c18n_unwind_trusted_stk
returns and the new stack is installed where the trusted stack says we're in the target compartment but the stack pointer still contains that of the source compartment. This doesn't seem desirable.
Under the current implementation, after dl_c18n_unwind_trusted_stk
returns, we are for all purposes already in the target compartment (i.e. not even in libunwind's compartment anymore). And the final ret
resumes execution of the target compartment without going through a trampoline. (Because unw_getcontext
is a trusted function, it has access to the raw return capability to the target compartment.)
libunwind/src/UnwindRegistersSave.S
Outdated
#ifdef _LIBUNWIND_HAS_CHERI_LIB_C18N | ||
// Store the trusted stack pointer | ||
stp c0, c30, [csp, #-0x20]! | ||
bl dl_c18n_get_trusted_stk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when we return back to the calling compartment? Do we not end up referencing a trusted frame that no longer exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of dl_c18n_get_trusted_stk
already accounts for this and returns the trusted frame below the top one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well that doesn't work if you have, say, something within [TCB] call setjmp?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems surprising and fragile, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, setjmp
is marked as a trusted symbol so calling it anywhere won't result in a new frame being pushed. setjmp
calling dl_c18n_get_trusted_stk
will push a new frame which will be skipped over in the implementation of dl_c18n_get_trusted_stk
. So this always works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unw_getcontext
is also marked as trusted for that matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, it's not ideal having that as a requirement for the caller. In these contexts it's fine, but ideally it'd be general enough that you could ask for your own trusted frame or your caller's. Anything more than that is going to need the full complexity of unwinding involved to be useful so having to go through libunwind to do it is fine, but I could imagine both those options being useful.
To throw some ideas around, you could:
- Have dl_c18n_get_trusted_stack take a bool for whether to pop a frame (you'd have to have the caller also call dl_c18_is_trampoline on CLR unless it knows it's a trusted symbol)
- Have dl_c18n_get_trusted_stack take a pointer (i.e. CLR) which it then calls dl_c18n_is_trampoline on and proceeds like the above (with NULL being treated as not a trampoline, i.e. get the current frame, if dl_c18n_is_trampoline itself doesn't do that)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I've implemented the second option and modified dl_c18n_is_trampoline
to check for the tag on the CLR passed in.
libunwind/src/UnwindRegistersSave.S
Outdated
#ifdef _LIBUNWIND_HAS_CHERI_LIB_C18N | ||
// Store the trusted stack pointer | ||
stp c0, c30, [csp, #-0x20]! | ||
mov x0, xzr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason not to pass c30 here and let it automatically do the right thing, beyond the overhead of doing the trampoline check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but I can certainly change it to pass c30.
Assembly stubs for _rtld_unw_{get,set}context are no longer needed. Due to the significantly simplified implementation, the LIBUNWIND_CHERI_C18N_SUPPORT option has been removed and c18n support is now included by default for supported architectures (currently Morello only).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks ok to my eye now, especially when looking at https://github.com/CTSRD-CHERI/llvm-project/compare/559dbe0..unwind-patch (and ignoring purecap-benchmark changes)
Assembly stubs for
_rtld_unw_{get,set}context
are removed. Instead, turn calls to these functions into no-ops when they are not defined by RTLD.