OLD | NEW |
(Empty) | |
| 1 # Safe filesystem access |
| 2 |
| 3 ## Background |
| 4 |
| 5 Native Client is a phenomenally useful library for restricted code execution |
| 6 that is useful beyond the browser. There’s all sorts of cases where one might |
| 7 want to run untrusted code in a system disconnected from Chromium or the PPAPI. |
| 8 |
| 9 In these cases, it is additionally useful to allow restricted filesystem |
| 10 access, but to still run untrusted code. Prior to this change, `sel_ldr` did |
| 11 not allow for safe and restricted filesystem access. |
| 12 |
| 13 ## Requirements |
| 14 |
| 15 * Very low overhead File I/O |
| 16 * Give users the ability to have selective read-only or read/write access |
| 17 * Give users control over the entire filesystem layout presented to the |
| 18 untrusted application. |
| 19 |
| 20 ## Feature user experience |
| 21 |
| 22 This feature adds one additional commandline argument `-m` to `sel_ldr`. `-m` |
| 23 takes a path to the directory to be used as the root by the untrusted |
| 24 application. |
| 25 |
| 26 **IMPORTANT WARNING**: The user should take great pains to make sure there are |
| 27 no relative symlinks at all or any absolute symlinks to outside of the |
| 28 `chroot` path inside the tree. It would be easy to get this wrong and may be |
| 29 safer to disallow starting with a path that has any symlinks contained inside |
| 30 at all. |
| 31 |
| 32 All of the given requirements can be satisfied by this `chroot`-style |
| 33 interface: |
| 34 |
| 35 * The only overhead for file I/O is in adding (and sanitizing) a path prefix |
| 36 to absolute paths passed through to the host. For relative paths, an |
| 37 additional `Getcwd` call is necessary. |
| 38 * Read-only or read/write access can be controlled using normal filesystem |
| 39 permissions for the user running the `sel_ldr` process. |
| 40 * Using host-side filesystem primitives such as Linux bind mounts, users can |
| 41 map disparate paths from the host into the untrusted process's root. |
| 42 |
| 43 ## Implementation |
| 44 |
| 45 For the most part, this feature is simple and relatively straightforward to |
| 46 add. When `-m` is given (even if `-a` is not), file operations will be enabled |
| 47 and the provided chroot path will be added as a path prefix to all absolute |
| 48 paths passed through to the host. Paths will be sanitized to make sure no |
| 49 escapes are made with `..` path elements. |
| 50 |
| 51 Relative paths are slightly more complex. Instead of prepending the path |
| 52 prefix, the CWD is checked to make sure it resides in the chroot path, then it |
| 53 is prepended, then everything after the chroot path in the resultant path is |
| 54 sanitized. |
| 55 |
| 56 Given that strategy, the following syscall changes were straightforward: |
| 57 |
| 58 * `NaClSysOpen` |
| 59 * `NaClSysStat` |
| 60 * `NaClSysMkdir` |
| 61 * `NaClSysRmdir` |
| 62 * `NaClSysUnlink` |
| 63 * `NaClSysTruncate` |
| 64 * `NaClSysLink` |
| 65 * `NaClSysRename` |
| 66 * `NaClSysChmod` |
| 67 * `NaClSysAccess` |
| 68 * `NaClSysUtimes` |
| 69 |
| 70 ### Path sanitization |
| 71 |
| 72 Path sanitization happens lexically, in the sense that no disk I/O happens |
| 73 while attempting to put the path in canonical form. Essentially, the path |
| 74 elements are parsed, and double path separators, '.' path elements, and '..' |
| 75 path elements are all handled and cleaned up prior to disk access. |
| 76 |
| 77 While this approach (assuming the algorithm is correct) is a secure way to |
| 78 eliminate parent folder references, it does result in a slight change of POSIX |
| 79 semantics. "/a/.." does not always refer to the same inode as "/", even if |
| 80 practically it almost always does. |
| 81 |
| 82 ### Symlinks |
| 83 |
| 84 Ideally, symlinks should behave as if we had just chrooted into the path given |
| 85 to `-m` (which means they would resolve relative to the untrusted root). |
| 86 |
| 87 However, it's not straightforward to have `sel_ldr` call `chroot` itself to |
| 88 make the kernel do appropriate symlink resolution in a cross-platform way. So |
| 89 our other option to support symlinks is to do an Lstat on every path load and |
| 90 try to resolve symlinks ourselves. This would be a pretty big performance hit |
| 91 and a significant amount of complication needing auditing. We wouldn't be able |
| 92 to just pass through sanitized paths to the host's open call without making |
| 93 sure the untrusted app hadn't created a symlink pointing elsewhere to jump out |
| 94 of jail. |
| 95 |
| 96 So instead, for v1 we're just not supporting symlinks. `Readlink` and `Lstat` |
| 97 don't allow the untrusted process access to any information the chroot |
| 98 filesystem creator didn't give it access to, so those are allowed when `-m` is |
| 99 given, but the symlink creation call will fail. |
| 100 |
| 101 Further, the path passed to `-m` might have symlinks in it, so it is up to the |
| 102 creator of the filesystem passed to `-m` to sanitize it for bad (or even |
| 103 relative!) symlinks prior to safely running `sel_ldr`. This is a significant |
| 104 amount of sharpness and potential insecurity this feature might bring, so it |
| 105 might be worth just failing to start if symlinks exist at any subpath from the |
| 106 path given to `-m`. Currently we're going to allow the sharpness of this |
| 107 interface. |
| 108 |
| 109 The following syscalls relate to symlinks. |
| 110 |
| 111 * `NaClSysLstat` - pass through (with appropriate path changes) |
| 112 * `NaClSysSymlink` - always fails |
| 113 * `NaClSysReadlink` - pass through (with appropriate path changes) |
| 114 |
| 115 ### Current working directory |
| 116 |
| 117 Being able to read the current working directory (aside from `readlink`, |
| 118 previously discussed) is the only syscall that has a path going from the host |
| 119 back to the untrusted application. All other syscalls are one way - paths go |
| 120 from the untrusted application out to the host. |
| 121 |
| 122 The current working directory, unless we call the host's `chdir` at startup to |
| 123 the path given to `-m`, might not be inside of the untrusted application's |
| 124 filesystem. |
| 125 |
| 126 This affects the following two syscalls: |
| 127 |
| 128 * `NaClSysChdir` - always adds the path prefix |
| 129 * `NaClSysGetcwd` - checks if the path is sanitized and starts with the path |
| 130 prefix. if it doesn't start with the path prefix this syscall fails. |
| 131 |
| 132 ### Filehandle syscalls |
| 133 |
| 134 These syscalls require no changes and are safe to just enable like they would |
| 135 be with `-a` when `-m` is passed: |
| 136 |
| 137 * `NaClSysGetdents` - `..` might reference an inode outside of the untrusted |
| 138 root, but that's okay because the untrusted application can't do anything |
| 139 with it. |
| 140 * `NaClSysFstat` - no different than `stat`, which is already allowed. |
| 141 |
| 142 For both of these calls, we might need to make sure there's no file descriptors |
| 143 in the untrusted application that reference files outside of the untrusted |
| 144 root, but even if we don't there's very little the untrusted application could |
| 145 do with the data these calls return. |
OLD | NEW |