<div dir="ltr"><div>Hi, </div>some thoughts on the feature. <div><br></div><div>* Huge complexity. This is not just the compiler, but also the rest of the toolchain and run-times (linkers, debuggers, unwinders, symbolizers). </div><div>* Relatively little benefit. Yes, PageRando (+XOM) will somewhat complicate the attacks (not sure how much, not my exact area), but we are only talking about code reuse attacks, and nothing else. </div><div>At the cost of PageRando (complexity+overhead) we could deploy other code reuse mitigations 5 times over. </div><div>And unlike CFI, PageRando doesn't even block anything, just makes it harder to find the gadgets. </div><div>I'd like to hear from some offensive security experts here their comparison of PageRando vs CFI-like schemes (that are much cheaper, and are already available)</div><div>* Spilling the POT register may reveal the secret and make all this pointless. If we want to mix instrumented and non-instrumented code (and still have the protection)</div><div>we'll need to at least recompile all the non-instrumented code with x18 reserved so that we don't need to spill it. That's the same problem we have with the ShadowCallStack though. </div><div>* 20-30% code size overhead is a no-go for the majority of large apps (speaking from my Chrome experience) and thus this will remain a niche tool.</div><div>* 3%-6% CPU overhead is also a concern for this kind of benefit, and I'm afraid that the overhead grows super-linearly with the binary size (more cache lines are touched by POT) </div><div>* adding so many new indirect calls in the post-spectre world sounds scary, and so far I haven't seen any evaluation from spectre exerts on this thread. </div><div><br></div><div>thanks! </div><div><br></div><div>--kcc </div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr">On Mon, Aug 21, 2017 at 2:59 PM Stephen Crane <<a href="mailto:sjc@immunant.com" target="_blank">sjc@immunant.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">+CC Vlad, Kostya<br>
<br>
On Tue, Jun 6, 2017 at 10:55 AM, Stephen Crane <<a href="mailto:sjc@immunant.com" target="_blank">sjc@immunant.com</a>> wrote:<br>
> This RFC describes pagerando, an improvement upon ASLR for shared<br>
> libraries. We're planning to submit this work for upstreaming and<br>
> would appreciate feedback before we get to the patch submission stage.<br>
><br>
> Pagerando randomizes the location of individual memory pages (ASLR<br>
> only randomizes the library base address). This increases security<br>
> against code-reuse attacks (such as ROP) by tolerating pointer leaks.<br>
> Pagerando splits libraries into page-aligned bins at compile time. At<br>
> load time, each bin is mapped to a random address. The code in each<br>
> bin is immutable and thus shared between processes.<br>
><br>
> To implement pagerando, the compiler and linker need to build shared<br>
> libraries with text segments split into page-aligned (and ideally<br>
> page-sized) bins. All inter-bin references are indirected through a<br>
> table initialized by the dynamic loader that holds the absolute<br>
> address of each bin. At load time the loader randomly chooses an<br>
> address for each bin and maps the bin pages from disk into memory.<br>
><br>
> We're focusing on ARM and AArch64 initially, although there is nothing<br>
> particularly target specific that precludes support for other LLVM<br>
> backends.<br>
><br>
> ## Design Goals<br>
><br>
> 1. Improve security over ASLR. The randomization granularity<br>
> determines how much information a single code pointer leaks. A pointer<br>
> to a page reveals less about the location of other code than a pointer<br>
> into a contiguous library would.<br>
> 2. Avoid randomizing files on disk. Modern operating systems provide<br>
> verified boot techniques to detect tampering with files. Randomizing<br>
> the on-disk layout of system libraries would interfere with the<br>
> trusted boot process. Randomizing libraries at compile or link time<br>
> would also needlessly complicate deployment and provisioning.<br>
> 3. Preserve code page sharing. The OS reduces memory usage by mapping<br>
> shared file pages to the same physical memory in each process and<br>
> locates these pages at different virtual addresses with ASLR. To<br>
> preserve sharing of code pages, we cannot modify the contents of<br>
> file-mapped pages at load time and are restricted to changing their<br>
> ordering and placement in the virtual address space.<br>
> 4. Backwards compatibility. Randomized code must interoperate<br>
> transparently with existing, unmodified executables and shared<br>
> libraries. Calls into randomized code must work as-is according to the<br>
> normal ABI.<br>
> 5. Compatibility with other mitigations. Enabling randomization must<br>
> not preclude deploying other mitigations such as control-flow<br>
> integrity as well.<br>
><br>
> ## Pagerando Design<br>
><br>
> Pagerando requires a platform-specific extension to the dynamic<br>
> loading ABI for compatible libraries to opt-in to. In order to<br>
> decouple the address of each code bin (segment) from that of other<br>
> bins and global data, we must disallow relative addressing between<br>
> different bin segments as well as between legacy segments and bin<br>
> segments.<br>
><br>
> To prepare a library for pagerando, the compiler must first allocate<br>
> functions into page-aligned bins corresponding to segments in the<br>
> final ELF file. Since these bins will be independently positioned, the<br>
> compiler must redirect all inter-bin references through an indirection<br>
> table – the Page Offset Table (POT) – which stores the virtual address<br>
> of each bin in the library. Indices of POT entries and bin offsets are<br>
> statically determined at link time so code will not require any<br>
> dynamic relocations to reference functions in another bin or globals<br>
> outside of bins. We reserve a register in pagerando-compatible code to<br>
> hold the address of the POT. This register is initialized on entry to<br>
> the shared library. At load time the dynamic loader maps code bins at<br>
> independent, random addresses and updates the dynamic relocations in<br>
> the POT.<br>
><br>
> Reserving a register to hold the POT address changes the internal ABI<br>
> calling convention and requires that the POT register be correctly<br>
> initialized when entering a library from external code. To initialize<br>
> the register, the compiler emits entry wrappers which save the old<br>
> contents of the POT register if necessary, initialize the POT<br>
> register, and call the target function. Each externally visible<br>
> function (conservatively including all address taken functions) needs<br>
> an entry wrapper which replaces the function for all external uses.<br>
><br>
> To optimally pack functions into bins and avoid new static<br>
> relocations, we propose using (traditional) LTO. With new static<br>
> relocations (i.e. linker cooperation), LTO would not be necessary, but<br>
> it is still desirable for more efficient bin packing.<br>
><br>
> The design of pagerando is based on the mitigations proposed by Backes<br>
> and Nürnberger [1], with improvements for compatibility and<br>
> deployability. The present design is a refinement of our first<br>
> pagerando prototype [2].<br>
><br>
> ## LLVM Changes<br>
><br>
> To implement pagerando, we propose the following LLVM changes:<br>
><br>
> New module pass to create entry wrapper functions. This pass will<br>
> create entry wrappers as described above and replace exported function<br>
> names and all address taken uses with the wrapper. This pass will only<br>
> be run when pagerando is enabled.<br>
><br>
> Instruction Lowering. Pagerando-compatible code must access all global<br>
> values (including functions) through the POT since PC-relative memory<br>
> addressing is not allowed between a bin and another segment. We<br>
> propose that when pagerando is enabled, all global variable accesses<br>
> from functions marked as pagerando-compatible must be lowered into<br>
> GOT-relative accesses and added to the GOT address loaded from the POT<br>
> (currently stored in the first POT entry). Lowering of direct function<br>
> calls targeting pagerando-compatible code is slightly more complicated<br>
> because we need to determine the POT index of the bin containing the<br>
> target function if the target is not in the same bin. However, we<br>
> can't properly allocate functions to bins before they are lowered and<br>
> an approximate size is available. Therefore, during lowering we should<br>
> assume that all function calls must be made indirectly through the POT<br>
> with the computation of the POT index and bin offset of the target<br>
> function postponed until assembly printing.<br>
><br>
> New machine module LTO pass to allocate functions into bins. This pass<br>
> relies on targets implementing TargetInstrInfo::getInstSizeInBytes<br>
> (MachineInstr) so that it knows (approximately) how large the final<br>
> function code will be. Functions can also be packed in such a way that<br>
> the number of inter-bin calls are minimized by taking the function<br>
> call graph and/or execution profiles into account while packing. This<br>
> pass only needs to run when pagerando is enabled.<br>
><br>
> Code Emission. After functions are assigned to bins, we create an<br>
> individual MCSection for each bin. These MCSections will map to<br>
> independent segments during linking. The AsmPrinter is responsible for<br>
> emitting the POT entries during code emission. We cannot easily<br>
> represent the POT as a standard IR object because it needs to contain<br>
> bin (MCSection) addresses. The AsmPrinter instead can query the<br>
> MCContext for the list of bin symbols and emit these symbols directly<br>
> into a global POT array.<br>
><br>
> Gold Plugin Interface. If using LTO to build the module, LLVM can<br>
> generate the complete POT for the module and instrument all references<br>
> that need to use the POT. However, we must still ensure that bin<br>
> sections are each placed into an independent segment so that the<br>
> dynamic loader can map each bin separately. The gold plugin interface<br>
> currently provides support to assign sections to unique output<br>
> segments. However, it does not yet provide plugins an opportunity to<br>
> call this interface for new, plugin-created input files. Gold requires<br>
> that the plugin provide the file handle of the input section to assign<br>
> a section to a unique segment. We will need to upstream a small patch<br>
> for gold that provides a new callback to the LTO plugin when gold<br>
> receives a new, plugin-generated input file. This would allow the<br>
> plugin to obtain the new file’s handle and map its sections to unique<br>
> segments. The linker must mark pagerando bin segments in such a way<br>
> that the dynamic loader knows that it can randomize each bin segment<br>
> independently. We propose a new ELF segment flag PF_RAND_ADDR that can<br>
> communicate this for each compatible segment. The compiler and/or<br>
> linker must add this flag to compatible segments for the loader to<br>
> recognize and randomize the relevant segments.<br>
><br>
> ## Target-Specific Details<br>
><br>
> We will initially support pagerando for ARM and AArch64, so several<br>
> details are worth considering on those targets. For ARM/AArch64, the<br>
> r9 register is a platform-specific register that can be used as the<br>
> static base register, which is similar in many ways to pagerando. When<br>
> not specified by the platform, r9 is a callee-saved general-purpose<br>
> register. Thus, using r9 as the POT register will be backwards<br>
> compatible when calling out of pagerando code into either legacy code<br>
> or a different module; the callee will preserve r9 for use after<br>
> returning to pagerando code. In AArch64, r18 is designated as a<br>
> platform-specific register, however, it is not specified as<br>
> callee-saved when not reserved by the target platform. Thus, to<br>
> interoperate with unmodified legacy AArch64 software, we would need to<br>
> save r18 in pagerando code before calling into any external code. When<br>
> using LTO, the compiler will see the entire module and therefore be<br>
> able to identify calls into external vs internal code. Without LTO, it<br>
> will likely be more efficient to use a callee-saved register to avoid<br>
> the need to save the POT register before each call. We will experiment<br>
> with both caller- and callee-saved registers to determine which is<br>
> most efficient.<br>
><br>
><br>
> [1] M. Backes and S. Nürnberger. Oxymoron - making fine-grained memory<br>
> randomization practical by allowing code sharing. In USENIX Security<br>
> Symposium, 2014. <a href="https://www.usenix.org/node/184466" rel="noreferrer" target="_blank">https://www.usenix.org/node/184466</a><br>
><br>
> [2] S. Crane, A. Homescu, and P. Larsen. Code randomization: Haven’t<br>
> we solved this problem yet? In IEEE Cybersecurity Development<br>
> Conference (SecDev), 2016.<br>
> <a href="http://www.ics.uci.edu/~perl/sd16_pagerando.pdf" rel="noreferrer" target="_blank">http://www.ics.uci.edu/~perl/sd16_pagerando.pdf</a><br>
</blockquote></div>