|
| 1 | +========================================= |
| 2 | +Lightweight Fault Isolation (LFI) in LLVM |
| 3 | +========================================= |
| 4 | + |
| 5 | +.. contents:: |
| 6 | + :local: |
| 7 | + |
| 8 | +Introduction |
| 9 | +++++++++++++ |
| 10 | + |
| 11 | +Lightweight Fault Isolation (LFI) is a compiler-based sandboxing technology for |
| 12 | +native code. Like WebAssembly and Native Client, LFI isolates sandboxed code in-process |
| 13 | +(i.e., in the same address space as a host application). |
| 14 | + |
| 15 | +LFI is designed from the ground up to sandbox existing code, such as C/C++ |
| 16 | +libraries (including assembly code) and device drivers. |
| 17 | + |
| 18 | +LFI aims for the following goals: |
| 19 | + |
| 20 | +* Compatibility: LFI can be used to sandbox nearly all existing C/C++/assembly |
| 21 | + libraries unmodified (they just need to be recompiled). Sandboxed libraries |
| 22 | + work with existing system call interfaces, and are compatible with existing |
| 23 | + development tools such as profilers, debuggers, and sanitizers. |
| 24 | +* Performance: LFI aims for minimal overhead vs. unsandboxed code. |
| 25 | +* Security: The LFI runtime and compiler elements aim to be simple and |
| 26 | + verifiable when possible. |
| 27 | +* Usability: LFI aims to make it easy as possible to used retrofit sandboxing, |
| 28 | + i.e., to migrate from unsandboxed to sandboxed libraries with minimal effort. |
| 29 | + |
| 30 | +When building a program for the LFI target the compiler is designed to ensure |
| 31 | +that the program will only be able to access memory within a limited region of |
| 32 | +the virtual address space, starting from where the program is loaded (the |
| 33 | +current design sets this region to a size of 4GiB of virtual memory). Programs |
| 34 | +built for the LFI target are restricted to using a subset of the instruction |
| 35 | +set, designed so that the programs can be soundly confined to their sandbox |
| 36 | +region. LFI programs must run inside of an "emulator" (usually called the LFI |
| 37 | +runtime), responsible for initializing the sandbox region, loading the program, |
| 38 | +and servicing system call requests, or other forms of runtime calls. |
| 39 | + |
| 40 | +LFI uses an architecture-specific sandboxing scheme based on the general |
| 41 | +technique of Software-Based Fault Isolation (SFI). Initial support for LFI in |
| 42 | +LLVM is focused on the AArch64 platform, with x86-64 support planned for the |
| 43 | +future. The initial version of LFI for AArch64 is designed to support the |
| 44 | +Armv8.1 AArch64 architecture. |
| 45 | + |
| 46 | +See `https://github.com/lfi-project <https://github.com/lfi-project/>`__ for |
| 47 | +details about the LFI project and additional software needed to run LFI |
| 48 | +programs. |
| 49 | + |
| 50 | +Compiler Requirements |
| 51 | ++++++++++++++++++++++ |
| 52 | + |
| 53 | +When building for the ``aarch64_lfi`` target, the compiler must restrict use of |
| 54 | +the instruction set to a subset of instructions, which are known to be safe |
| 55 | +from a sandboxing perspective. To do this, we apply a set of simple rewrites at |
| 56 | +the assembly language level to transform standard native AArch64 assembly into |
| 57 | +LFI-compatible AArch64 assembly. |
| 58 | + |
| 59 | +These rewrites (also called "expansions") are applied at the very end of the |
| 60 | +LLVM compilation pipeline (during the assembler step). This allows the rewrites |
| 61 | +to be applied to hand-written assembly, including inline assembly. |
| 62 | + |
| 63 | +Compiler Options |
| 64 | +================ |
| 65 | + |
| 66 | +The LFI target has several configuration options. |
| 67 | + |
| 68 | +* ``+lfi-stores``: create a "stores-only" sandbox, where rewrites are not applied to loads. |
| 69 | +* ``+lfi-jumps``: create a "jumps-only" sandbox, where rewrites are not applied to loads/stores. |
| 70 | + |
| 71 | +Reserved Registers |
| 72 | +================== |
| 73 | + |
| 74 | +The LFI target uses a custom ABI that reserves additional registers for the |
| 75 | +platform. The registers are listed below, along with the security invariant |
| 76 | +that must be maintained. |
| 77 | + |
| 78 | +* ``x27``: always holds the sandbox base address. |
| 79 | +* ``x28``: always holds an address within the sandbox. |
| 80 | +* ``sp``: always holds an address within the sandbox. |
| 81 | +* ``x30``: always holds an address within the sandbox. |
| 82 | +* ``x26``: scratch register. |
| 83 | +* ``x25``: points to a thread-local virtual register file for storing runtime context information. |
| 84 | + |
| 85 | +Linker Support |
| 86 | +============== |
| 87 | + |
| 88 | +In the initial version, LFI only supports static linking, and only supports |
| 89 | +creating ``static-pie`` binaries. There is nothing that fundamentally precludes |
| 90 | +support for dynamic linking on the LFI target, but such support would require |
| 91 | +that the code generated by the linker for PLT entries be slightly modified in |
| 92 | +order to conform to the LFI architecture subset. |
| 93 | + |
| 94 | +Assembly Rewrites |
| 95 | +================= |
| 96 | + |
| 97 | +Terminology |
| 98 | +~~~~~~~~~~~ |
| 99 | + |
| 100 | +In the following assembly rewrites, some shorthand is used. |
| 101 | + |
| 102 | +* ``xN`` or ``wN``: refers to any general-purpose non-reserved register. |
| 103 | +* ``{a,b,c}``: matches any of ``a``, ``b``, or ``c``. |
| 104 | +* ``LDSTr``: a load/store instruction that supports register-register addressing modes, with one source/destination register. |
| 105 | +* ``LDSTx``: a load/store instruction not matched by ``LDSTr``. |
| 106 | + |
| 107 | +Control flow |
| 108 | +~~~~~~~~~~~~ |
| 109 | + |
| 110 | +Indirect branches get rewritten to branch through register ``x28``, which must |
| 111 | +always contain an address within the sandbox. An ``add`` is used to safely load |
| 112 | +``x28`` with the destination address. Since ``ret`` uses ``x30`` by default, |
| 113 | +which already must contain an address within the sandbox, it does not require |
| 114 | +any rewrite. |
| 115 | + |
| 116 | ++--------------------+---------------------------+ |
| 117 | +| Original | Rewritten | |
| 118 | ++--------------------+---------------------------+ |
| 119 | +| .. code-block:: | .. code-block:: | |
| 120 | +| | | |
| 121 | +| {br,blr,ret} xN | add x28, x27, wN, uxtw | |
| 122 | +| | {br,blr,ret} x28 | |
| 123 | +| | | |
| 124 | ++--------------------+---------------------------+ |
| 125 | +| .. code-block:: | .. code-block:: | |
| 126 | +| | | |
| 127 | +| ret | ret | |
| 128 | +| | | |
| 129 | ++--------------------+---------------------------+ |
| 130 | + |
| 131 | +Memory accesses |
| 132 | +~~~~~~~~~~~~~~~ |
| 133 | + |
| 134 | +Memory accesses are rewritten to use the ``[x27, wM, uxtw]`` addressing mode if |
| 135 | +it is available, which is automatically safe. Otherwise, rewrites fall back to |
| 136 | +using ``x28`` along with an instruction to safely load it with the target |
| 137 | +address. |
| 138 | + |
| 139 | ++---------------------------------+-------------------------------+ |
| 140 | +| Original | Rewritten | |
| 141 | ++---------------------------------+-------------------------------+ |
| 142 | +| .. code-block:: | .. code-block:: | |
| 143 | +| | | |
| 144 | +| LDSTr xN, [xM] | LDSTr xN, [x27, wM, uxtw] | |
| 145 | +| | | |
| 146 | ++---------------------------------+-------------------------------+ |
| 147 | +| .. code-block:: | .. code-block:: | |
| 148 | +| | | |
| 149 | +| LDSTr xN, [xM, #I] | add x28, x27, wM, uxtw | |
| 150 | +| | LDSTr xN, [x28, #I] | |
| 151 | +| | | |
| 152 | ++---------------------------------+-------------------------------+ |
| 153 | +| .. code-block:: | .. code-block:: | |
| 154 | +| | | |
| 155 | +| LDSTr xN, [xM, #I]! | add xM, xM, #I | |
| 156 | +| | LDSTr xN, [x27, wM, uxtw] | |
| 157 | +| | | |
| 158 | ++---------------------------------+-------------------------------+ |
| 159 | +| .. code-block:: | .. code-block:: | |
| 160 | +| | | |
| 161 | +| LDSTr xN, [xM], #I | LDSTr xN, [x27, wM, uxtw] | |
| 162 | +| | add xM, xM, #I | |
| 163 | +| | | |
| 164 | ++---------------------------------+-------------------------------+ |
| 165 | +| .. code-block:: | .. code-block:: | |
| 166 | +| | | |
| 167 | +| LDSTr xN, [xM1, xM2] | add x26, xM1, xM2 | |
| 168 | +| | LDSTr xN, [x27, w26, uxtw] | |
| 169 | +| | | |
| 170 | ++---------------------------------+-------------------------------+ |
| 171 | +| .. code-block:: | .. code-block:: | |
| 172 | +| | | |
| 173 | +| LDSTr xN, [xM1, xM2, MOD #I] | add x26, xM1, xM2, MOD #I | |
| 174 | +| | LDSTr xN, [x27, w26, uxtw] | |
| 175 | +| | | |
| 176 | ++---------------------------------+-------------------------------+ |
| 177 | +| .. code-block:: | .. code-block:: | |
| 178 | +| | | |
| 179 | +| LDSTx ..., [xM] | add x28, x27, wM, uxtw | |
| 180 | +| | LDSTx ..., [x28] | |
| 181 | +| | | |
| 182 | ++---------------------------------+-------------------------------+ |
| 183 | +| .. code-block:: | .. code-block:: | |
| 184 | +| | | |
| 185 | +| LDSTx ..., [xM, #I] | add x28, x27, wM, uxtw | |
| 186 | +| | LDSTx ..., [x28, #I] | |
| 187 | +| | | |
| 188 | ++---------------------------------+-------------------------------+ |
| 189 | +| .. code-block:: | .. code-block:: | |
| 190 | +| | | |
| 191 | +| LDSTx ..., [xM, #I]! | add x28, x27, wM, uxtw | |
| 192 | +| | LDSTx ..., [x28, #I] | |
| 193 | +| | add xM, xM, #I | |
| 194 | +| | | |
| 195 | ++---------------------------------+-------------------------------+ |
| 196 | +| .. code-block:: | .. code-block:: | |
| 197 | +| | | |
| 198 | +| LDSTx ..., [xM], #I | add x28, x27, wM, uxtw | |
| 199 | +| | LDSTx ..., [x28] | |
| 200 | +| | add xM, xM, #I | |
| 201 | +| | | |
| 202 | ++---------------------------------+-------------------------------+ |
| 203 | +| .. code-block:: | .. code-block:: | |
| 204 | +| | | |
| 205 | +| LDSTx ..., [xM1], xM2 | add x28, x27, wM1, uxtw | |
| 206 | +| | LDSTx ..., [x28] | |
| 207 | +| | add xM1, xM1, xM2 | |
| 208 | +| | | |
| 209 | ++---------------------------------+-------------------------------+ |
| 210 | + |
| 211 | +Stack pointer modification |
| 212 | +~~~~~~~~~~~~~~~~~~ |
| 213 | + |
| 214 | +When the stack pointer is modified, we write the modified value to a temporary, |
| 215 | +before loading it back into ``sp`` with a safe ``add``. |
| 216 | + |
| 217 | ++------------------------------+-------------------------------+ |
| 218 | +| Original | Rewritten | |
| 219 | ++------------------------------+-------------------------------+ |
| 220 | +| .. code-block:: | .. code-block:: | |
| 221 | +| | | |
| 222 | +| mov sp, xN | add sp, x27, wN, uxtw | |
| 223 | +| | | |
| 224 | ++------------------------------+-------------------------------+ |
| 225 | +| .. code-block:: | .. code-block:: | |
| 226 | +| | | |
| 227 | +| {add,sub} sp, sp, {#I,xN} | {add,sub} x26, sp, {#I,xN} | |
| 228 | +| | add sp, x27, w26, uxtw | |
| 229 | +| | | |
| 230 | ++------------------------------+-------------------------------+ |
| 231 | + |
| 232 | +Link register modification |
| 233 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 234 | + |
| 235 | +When the link register is modified, we write the modified value to a |
| 236 | +temporary, before loading it back into ``x30`` with a safe ``add``. |
| 237 | + |
| 238 | ++-----------------------+----------------------------+ |
| 239 | +| Original | Rewritten | |
| 240 | ++-----------------------+----------------------------+ |
| 241 | +| .. code-block:: | .. code-block:: | |
| 242 | +| | | |
| 243 | +| ldr x30, [...] | ldr x26, [...] | |
| 244 | +| | add x30, x27, w26, uxtw | |
| 245 | +| | | |
| 246 | ++-----------------------+----------------------------+ |
| 247 | +| .. code-block:: | .. code-block:: | |
| 248 | +| | | |
| 249 | +| ldp xN, x30, [...] | ldp xN, x26, [...] | |
| 250 | +| | add x30, x27, w26, uxtw | |
| 251 | +| | | |
| 252 | ++-----------------------+----------------------------+ |
| 253 | +| .. code-block:: | .. code-block:: | |
| 254 | +| | | |
| 255 | +| ldp x30, xN, [...] | ldp x26, xN, [...] | |
| 256 | +| | add x30, x27, w26, uxtw | |
| 257 | +| | | |
| 258 | ++-----------------------+----------------------------+ |
| 259 | + |
| 260 | +System instructions |
| 261 | +~~~~~~~~~~~~~~~~~~~ |
| 262 | + |
| 263 | +System calls are rewritten into a sequence that loads the address of the first |
| 264 | +runtime call entrypoint and jumps to it. The runtime call entrypoint table is |
| 265 | +stored at the start of the sandbox, so it can be referenced by ``x27``. The |
| 266 | +rewrite also saves and restores the link register, since it is used for |
| 267 | +branching into the runtime. |
| 268 | + |
| 269 | ++-----------------+----------------------------+ |
| 270 | +| Original | Rewritten | |
| 271 | ++-----------------+----------------------------+ |
| 272 | +| .. code-block:: | .. code-block:: | |
| 273 | +| | | |
| 274 | +| svc #0 | mov w26, w30 | |
| 275 | +| | ldr x30, [x27] | |
| 276 | +| | blr x30 | |
| 277 | +| | add x30, x27, w26, uxtw | |
| 278 | +| | | |
| 279 | ++-----------------+----------------------------+ |
| 280 | + |
| 281 | +Thread-local storage |
| 282 | +~~~~~~~~~~~~~~~~~~~~ |
| 283 | + |
| 284 | +TLS accesses are rewritten into accesses offset from ``x25``, which is a |
| 285 | +reserved register that points to a virtual register file, with a location for |
| 286 | +storing the sandbox's thread pointer. ``TP`` is the offset into that virtual |
| 287 | +register file where the thread pointer is stored. |
| 288 | + |
| 289 | ++----------------------+-----------------------+ |
| 290 | +| Original | Rewritten | |
| 291 | ++----------------------+-----------------------+ |
| 292 | +| .. code-block:: | .. code-block:: | |
| 293 | +| | | |
| 294 | +| mrs xN, tpidr_el0 | ldr xN, [x25, #TP] | |
| 295 | +| | | |
| 296 | ++----------------------+-----------------------+ |
| 297 | +| .. code-block:: | .. code-block:: | |
| 298 | +| | | |
| 299 | +| mrs tpidr_el0, xN | str xN, [x25, #TP] | |
| 300 | +| | | |
| 301 | ++----------------------+-----------------------+ |
| 302 | + |
| 303 | +Optimizations |
| 304 | +============= |
| 305 | + |
| 306 | +Basic guard elimination |
| 307 | +~~~~~~~~~~~~~~~~~~~~~~~ |
| 308 | + |
| 309 | +If a register is guarded multiple times in the same basic block without any |
| 310 | +modifications to it during the intervening instructions, then subsequent guards |
| 311 | +can be removed. |
| 312 | + |
| 313 | ++---------------------------+---------------------------+ |
| 314 | +| Original | Rewritten | |
| 315 | ++---------------------------+---------------------------+ |
| 316 | +| .. code-block:: | .. code-block:: | |
| 317 | +| | | |
| 318 | +| add x28, x27, wN, uxtw | add x28, x27, wN, uxtw | |
| 319 | +| ldur xN, [x28] | ldur xN, [x28] | |
| 320 | +| add x28, x27, wN, uxtw | ldur xN, [x28, #8] | |
| 321 | +| ldur xN, [x28, #8] | ldur xN, [x28, #16] | |
| 322 | +| add x28, x27, wN, uxtw | | |
| 323 | +| ldur xN, [x28, #16] | | |
| 324 | +| | | |
| 325 | ++---------------------------+---------------------------+ |
| 326 | + |
| 327 | +Stack guard elimination |
| 328 | +~~~~~~~~~~~~~~~~~~~~~~~ |
| 329 | + |
| 330 | +**Note**: this optimization has not been implemented. |
| 331 | + |
| 332 | +If the stack pointer is modified by adding/subtracting a small immediate, and |
| 333 | +then later used to perform a memory access without any intervening jumps, then |
| 334 | +the guard on the stack pointer modification can be removed. This is because the |
| 335 | +load/store is guaranteed to trap if the stack pointer has been moved outside of |
| 336 | +the sandbox region. |
| 337 | + |
| 338 | ++---------------------------+---------------------------+ |
| 339 | +| Original | Rewritten | |
| 340 | ++---------------------------+---------------------------+ |
| 341 | +| .. code-block:: | .. code-block:: | |
| 342 | +| | | |
| 343 | +| add x26, sp, #8 | add sp, sp, #8 | |
| 344 | +| add sp, x27, w26, uxtw | ... (same basic block) | |
| 345 | +| ... (same basic block) | ldr xN, [sp] | |
| 346 | +| ldr xN, [sp] | | |
| 347 | +| | | |
| 348 | ++---------------------------+---------------------------+ |
| 349 | + |
| 350 | +Guard hoisting |
| 351 | +~~~~~~~~~~~~~~ |
| 352 | + |
| 353 | +**Note**: this optimization has not been implemented. |
| 354 | + |
| 355 | +In certain cases, guards may be hoisted outside of loops. |
| 356 | + |
| 357 | ++-----------------------+-------------------------------+ |
| 358 | +| Original | Rewritten | |
| 359 | ++-----------------------+-------------------------------+ |
| 360 | +| .. code-block:: | .. code-block:: | |
| 361 | +| | | |
| 362 | +| mov w8, #10 | mov w8, #10 | |
| 363 | +| mov w9, #0 | mov w9, #0 | |
| 364 | +| .loop: | add x28, x27, wM, uxtw | |
| 365 | +| add w9, w9, #1 | .loop: | |
| 366 | +| ldr xN, [xM] | add w9, w9, #1 | |
| 367 | +| cmp w9, w8 | ldr xN, [x28] | |
| 368 | +| b.lt .loop | cmp w9, w8 | |
| 369 | +| .end: | b.lt .loop | |
| 370 | +| | .end: | |
| 371 | +| | | |
| 372 | ++-----------------------+-------------------------------+ |
| 373 | + |
| 374 | +References |
| 375 | +++++++++++ |
| 376 | + |
| 377 | +For more information, please see the following resources: |
| 378 | + |
| 379 | +* `LFI project page <https://github.com/lfi-project/>`__ |
| 380 | +* `LFI RFC <https://discourse.llvm.org/t/rfc-lightweight-fault-isolation-lfi-efficient-native-code-sandboxing-upstream-lfi-target-and-compiler-changes/88380>`__ |
| 381 | +* `LFI paper <https://zyedidia.github.io/papers/lfi_asplos24.pdf>`__ |
| 382 | + |
| 383 | +Contact info: |
| 384 | + |
| 385 | +* Zachary Yedidia - [email protected] |
| 386 | +* Tal Garfinkel - [email protected] |
| 387 | +* Sharjeel Khan - [email protected] |
0 commit comments