On Thu, 2023-03-09 at 13:57 +0100, Borislav Petkov wrote: > So this all sounds weird. Especially from a user point of view. > > Now let's imagine there's a Linux user called Boris and he goes and > buys > a CPU which supports shadow stack, gets a distro which has shadow > stack > enabled. All good. > > Now, at some point he loads a program which pulls in an old library > which hasn't been enabled for shadow stack yet. > > In the name of not breaking stuff, his glibc is configured in > permissive > mode by default so that program loads and shadow stack for it is > disabled. > > And Boris doesn't even know and continues on his merry way thinking > that > he has all that cool ROP protection. There is a proc that shows if shadow stack is enabled in a thread. It does indeed come later in the series. > > So where is the knob that says, "disable permissive mode"? glibc has an environment variable that can change the loader's behavior. There is also a compile time config for the default mode. But this "permissive mode" is a glibc thing. The kernel doesn't implement it per-se, just provides building blocks. > > Or at least where does the user get a warning saying, "hey, this app > doesn't do shadow stack and we disabled it for ya so that it can > still > work"? > > Or am I way off? I don't think so. The whole "when to enable shadow stack" question is thornier than it might seem though, and what we have here is based on some trade offs in the details. > > I hope you're catching my drift. Because if there's no enforcement of > shstk and we do this permissive mode by default, this whole overhead > is > just a unnecessary nuisance... In the existing glibc patches, and this is highly related to glibc behavior because the decisions around enabling and locking have been pushed there, there are two reasons why shadow stack would get disabled on an supporting executable after it gets enabled. 1. An executable is loaded and one of the shared objects (the ones that come out of ldd) does not support shadow stack 2. An executable is loaded in permissive mode, and much later during execution dlopen()s a DSO that does not support shadow stack. One of the challenges with enabling shadow stack is you only start recording the shadow stack history when you enable it. If you enable it at some point, and then return from that function you underflow the shadow stack and get a violation. So if the shadow stack will be locked, it has to be enabled at the earliest point it might return to at some point (for example after returning from main()). So in 1, the existing logic of glibc is to enable shadow stack at the very beginning of the loader. Then go through the whole loading/linking process. If problems are found, disable shadow stack. If no problems are found, then lock it. I've wondered if this super early glibc enabling behavior is really needed and if they could enable it after processing the linked libraries in the elf. Then save the work of enabling and disabling shadow stack for situations that don't support it. To me this is the big wart in the whole thing, but I don't think the kernel can help resolve it. If glibc can enable later, then we can combine the locking and enabling into a single operation. But it only saves a syscall and it might prevent some other libc that needs to do things like glibc does currently, from being able to make it work at all. In 2, the enabling happens like normal and the locking is skipped, so that shadow stack can be enabled during a dlopen(). But glibc permissive mode promises more than it delivers. Since it can only disable shadow stack per-thread, it leaves the other threads enabled. Making a working permissive mode is sort of an unsolved problem. There are some proposals to make it work in just userspace, and some that would need additional kernel support. If you are interested I can go into why per-process disabling is not straightforward. So the locking is needed for the basic support in 1 and the weak permissive mode in 2 uses it. I am considering this series to support 1, but people may end up using 2 to get some permissive-ness. In general the idea of this API is to push the enabling decisions into userspace because that is where the information for making the decision is known. We previously tried to add some batch operations to improve the performance, but tglx had suggested to start with something simple. So we end up with this simple composable API.