-
Notifications
You must be signed in to change notification settings - Fork 60
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
c18n: [Draft] Save caller's stack pointer in trusted frame #2060
Conversation
Previously, the caller's stack pointer is only saved at the bottom of the caller's stack during domain transition. This means that the act of unwinding a trusted frame relies on external state, namely the value at the bottom of the caller's stack. We now also save the caller's stack pointer in the trusted frame so that unwinding can be stateless, i.e., inspecting the content of the trusted frame alone is sufficient for restoring the stack pointer of the caller.
Load a compartment policy file via the environment variable LD_C18N_COMPARTMENT_POLICY. Currently, the policy allows one to (a) define logical compartments comprising multiple libraries, (b) specify which symbols are trusted by a particular compartment, and (c) specify which libraries are allowed to link against any particular symbol.
Previously, after a process calls vfork, the child process then calls execve. This call causes a domain transition that changes the top of the stack as recorded by RTLD. This change is visible in the parent process, which then wakes up and sees that the top of the stack has been altered, which is something that should never happen. Trust execve so that calling it does not cause a domain transition.
Let trampoline hooks consume the trampoline header and trusted stack directly. Remove the dependency on mempcpy.
uint8_t ret_args : 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.
Hmm, is libunwind using this? GDB doesn't use the cookie, it matches on the instruction sequence just like for signal frames. That is, I don't understand how cookie
is different from pc
?
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.
libunwind uses this to detect if the current return address is a trampoline and we need to unwind from the trusted frame.
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.
pc
is what the trampoline should return to. cookie
is where the callee should return to. It is an address within the trampoline.
Checking the Executive bit of the return address to determine whether we are at a compartment boundary is unreliable because we might be returning into RTLD directly. Checking that the return address matches the cookie gives full assurance, and it also works under the benchmark ABI where everything is Executive.
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 guess what I don't understand is that since the cookie is already on the trusted stack, doesn't libunwind have to know before it can even see the cookie that it needs to read from the trusted stack (ECSP) instead of the normal stack (RCSP)? That is, when libunwind is unwinding from the uppermost frame of a compartment back out to the compartment switch, it gets a CLR (and thus PCC) value that points into a trampoline. How does it then decide that for this new frame that it is a compartment frame that is a compartment boundary thus requiring it to go looking in ECSP instead or RCSP for its saved frame? If this were DWARF based, you would look up the FDE/CFI based on the unwound PC value and it would tell you the register for CFA and the offsets of saved registers relative to the CFA. For custom unwinders in GDB including signal frames, GDB does a pattern match against known instructions (e.g. the first 3 instructions CLR points to) to locate a custom unwinder. Does CFP in this case store a scalar address back into the trusted stack (yes, that appears to be true) that libunwind notices is out of the bounds of RCSP and thus assume it might be on the trusted stack? If you aren't doing the bounds check, how do you avoid potential false positives if the stack garbage at the offset of cookie
in a "normal" frame happens to match PC? If you are doing the bounds check, isn't that alone sufficient to know you are on an alternate stack? (And you could even check if it is in bounds of the trusted stack which libunwind presumably has access to.)
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.
doesn't libunwind have to know before it can even see the cookie that it needs to read from the trusted stack (ECSP) instead of the normal stack (RCSP)?
libunwind always just blindly checks whether the CLR matches the cookie of the top trusted frame to decide if it's at a compartment boundary. (See https://github.com/CTSRD-CHERI/llvm-project/pull/731/files#diff-6dd9111398b6f61443d5eec566d751eb857a8ee262fe9c5f38c192881826300dR363 which is being used at https://github.com/CTSRD-CHERI/llvm-project/pull/731/files#diff-6dd9111398b6f61443d5eec566d751eb857a8ee262fe9c5f38c192881826300dR602)
The cookie feature was implemented before I started to properly set the CFP value, and your suggestion of checking if CFP is in bounds does seem like a viable idea. @dstolfa What do you think?
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 sounds like it could work, I'll prototype it to 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.
What I'm after is if we no longer need the cookie value, then we can remove it from the trusted_frame and with a bit of reorganizing have room for the full ecsp in place of the current ptraddr_t for csp.
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.
In particular, it is pretty unusual to restore "part" of a register on a stack frame in an unwinder. DWARF CFI annotations don't (afaik) have a way to describe this kind of case currently for example as stack frames in general save/restore entire registers. It's true that we can't use DWARF to describe this frame today for multiple reasons (dynamically allocated trampolines; no DWARF register number for RCSP and ECSP, just CSP; etc.), but I do think we should aim to create a frame that is generally compatible with how other stack frames generally work as we are less likely to run afoul of assumptions in other unwinders.
Superseded by #2061. |
The first commit (5094160) actually makes the change. The rest of the commits are rebased from #2054.