Introduce the LANDLOCK_ACCESS_FS_TRUNCATE flag for file truncation. This flag hooks into the path_truncate, file_truncate and file_alloc_security LSM hooks and covers file truncation using truncate(2), ftruncate(2), open(2) with O_TRUNC, as well as creat(). This change also increments the Landlock ABI version, updates corresponding selftests, and updates code documentation to document the flag. In security/security.c, allocate security blobs at pointer-aligned offsets. This fixes the problem where one LSM's security blob can shift another LSM's security blob to an unaligned address (reported by Nathan Chancellor). The following operations are restricted: open(2): requires the LANDLOCK_ACCESS_FS_TRUNCATE right if a file gets implicitly truncated as part of the open() (e.g. using O_TRUNC). Notable special cases: * open(..., O_RDONLY|O_TRUNC) can truncate files as well in Linux * open() with O_TRUNC does *not* need the TRUNCATE right when it creates a new file. truncate(2) (on a path): requires the LANDLOCK_ACCESS_FS_TRUNCATE right. ftruncate(2) (on a file): requires that the file had the TRUNCATE right when it was previously opened. File descriptors acquired by other means than open(2) (e.g. memfd_create(2)) continue to support truncation with ftruncate(2).
Unified · Split
| @@ -95,8 +95,19 @@ struct landlock_path_beneath_attr { · 94 unmodified lines | ||
| 95 | 95 | * A file can only receive these access rights: |
| 96 | 96 | * |
| 97 | 97 | * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file. |
| 98 | − * - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. | |
| 98 | + * - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. Note that | |
| 99 | + * you might additionally need the %LANDLOCK_ACCESS_FS_TRUNCATE right in order | |
| 100 | + * to overwrite files with :manpage:`open(2)` using ``O_TRUNC`` or | |
| 101 | + * :manpage:`creat(2)`. | |
| 99 | 102 | * - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access. |
| 103 | + * - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`, | |
| 104 | + * :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with | |
| 105 | + * ``O_TRUNC``. Whether an opened file can be truncated with | |
| 106 | + * :manpage:`ftruncate(2)` is determined during :manpage:`open(2)`, in the | |
| 107 | + * same way as read and write permissions are checked during | |
| 108 | + * :manpage:`open(2)` using %LANDLOCK_ACCESS_FS_READ_FILE and | |
| 109 | + * %LANDLOCK_ACCESS_FS_WRITE_FILE. This access right is available since the | |
| 110 | + * third version of the Landlock ABI. | |
| 100 | 111 | * |
| 101 | 112 | * A directory can receive access rights related to files or directories. The |
| 102 | 113 | * following access right is applied to the directory itself, and the |
| @@ -139,10 +150,9 @@ struct landlock_path_beneath_attr { · 36 unmodified lines | ||
| 139 | 150 | * |
| 140 | 151 | * It is currently not possible to restrict some file-related actions |
| 141 | 152 | * accessible through these syscall families: :manpage:`chdir(2)`, |
| 142 | − * :manpage:`truncate(2)`, :manpage:`stat(2)`, :manpage:`flock(2)`, | |
| 143 | − * :manpage:`chmod(2)`, :manpage:`chown(2)`, :manpage:`setxattr(2)`, | |
| 144 | − * :manpage:`utime(2)`, :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, | |
| 145 | − * :manpage:`access(2)`. | |
| 153 | + * :manpage:`stat(2)`, :manpage:`flock(2)`, :manpage:`chmod(2)`, | |
| 154 | + * :manpage:`chown(2)`, :manpage:`setxattr(2)`, :manpage:`utime(2)`, | |
| 155 | + * :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, :manpage:`access(2)`. | |
| 146 | 156 | * Future Landlock evolutions will enable to restrict them. |
| 147 | 157 | */ |
| 148 | 158 | /* clang-format off */ |
| @@ -160,6 +170,7 @@ struct landlock_path_beneath_attr { · 11 unmodified lines | ||
| 160 | 170 | #define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11) |
| 161 | 171 | #define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) |
| 162 | 172 | #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13) |
| 173 | +#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14) | |
| 163 | 174 | /* clang-format on */ |
| 164 | 175 | |
| 165 | 176 | #endif /* _UAPI_LINUX_LANDLOCK_H */ |
| @@ -146,7 +146,8 @@ retry: · 145 unmodified lines | ||
| 146 | 146 | #define ACCESS_FILE ( \ |
| 147 | 147 | LANDLOCK_ACCESS_FS_EXECUTE | \ |
| 148 | 148 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ |
| 149 | − LANDLOCK_ACCESS_FS_READ_FILE) | |
| 149 | + LANDLOCK_ACCESS_FS_READ_FILE | \ | |
| 150 | + LANDLOCK_ACCESS_FS_TRUNCATE) | |
| 150 | 151 | /* clang-format on */ |
| 151 | 152 | |
| 152 | 153 | /* |
| @@ -1154,9 +1155,23 @@ static int hook_path_rmdir(const struct path *const dir, · 1001 unmodified lines | ||
| 1154 | 1155 | return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); |
| 1155 | 1156 | } |
| 1156 | 1157 | |
| 1158 | +static int hook_path_truncate(const struct path *const path) | |
| 1159 | +{ | |
| 1160 | + return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE); | |
| 1161 | +} | |
| 1162 | + | |
| 1157 | 1163 | /* File hooks */ |
| 1158 | 1164 | |
| 1159 | −static inline access_mask_t get_file_access(const struct file *const file) | |
| 1165 | +/** | |
| 1166 | + * get_required_file_open_access - Get access needed to open a file | |
| 1167 | + * | |
| 1168 | + * @file: File being opened. | |
| 1169 | + * | |
| 1170 | + * Returns the access rights that are required for opening the given file, | |
| 1171 | + * depending on the file type and open mode. | |
| 1172 | + */ | |
| 1173 | +static inline access_mask_t | |
| 1174 | +get_required_file_open_access(const struct file *const file) | |
| 1160 | 1175 | { |
| 1161 | 1176 | access_mask_t access = 0; |
| 1162 | 1177 | |
| @@ -1174,19 +1189,95 @@ static inline access_mask_t get_file_access(const struct file *const file) · 11 unmodified lines | ||
| 1174 | 1189 | return access; |
| 1175 | 1190 | } |
| 1176 | 1191 | |
| 1192 | +static int hook_file_alloc_security(struct file *const file) | |
| 1193 | +{ | |
| 1194 | + /* | |
| 1195 | + * Grants all access rights, even if most of them are not checked later | |
| 1196 | + * on. It is more consistent. | |
| 1197 | + * | |
| 1198 | + * Notably, file descriptors for regular files can also be acquired | |
| 1199 | + * without going through the file_open hook, for example when using | |
| 1200 | + * memfd_create(2). | |
| 1201 | + */ | |
| 1202 | + landlock_file(file)->allowed_access = LANDLOCK_MASK_ACCESS_FS; | |
| 1203 | + return 0; | |
| 1204 | +} | |
| 1205 | + | |
| 1177 | 1206 | static int hook_file_open(struct file *const file) |
| 1178 | 1207 | { |
| 1208 | + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; | |
| 1209 | + access_mask_t open_access_request, full_access_request, allowed_access; | |
| 1210 | + const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE; | |
| 1179 | 1211 | const struct landlock_ruleset *const dom = |
| 1180 | 1212 | landlock_get_current_domain(); |
| 1181 | 1213 | |
| 1182 | 1214 | if (!dom) |
| 1183 | 1215 | return 0; |
| 1216 | + | |
| 1184 | 1217 | /* |
| 1185 | − * Because a file may be opened with O_PATH, get_file_access() may | |
| 1186 | − * return 0. This case will be handled with a future Landlock | |
| 1218 | + * Because a file may be opened with O_PATH, get_required_file_open_access() | |
| 1219 | + * may return 0. This case will be handled with a future Landlock | |
| 1187 | 1220 | * evolution. |
| 1188 | 1221 | */ |
| 1189 | − return check_access_path(dom, &file->f_path, get_file_access(file)); | |
| 1222 | + open_access_request = get_required_file_open_access(file); | |
| 1223 | + | |
| 1224 | + /* | |
| 1225 | + * We look up more access than what we immediately need for open(), so | |
| 1226 | + * that we can later authorize operations on opened files. | |
| 1227 | + */ | |
| 1228 | + full_access_request = open_access_request | optional_access; | |
| 1229 | + | |
| 1230 | + if (is_access_to_paths_allowed( | |
| 1231 | + dom, &file->f_path, | |
| 1232 | + init_layer_masks(dom, full_access_request, &layer_masks), | |
| 1233 | + &layer_masks, NULL, 0, NULL, NULL)) { | |
| 1234 | + allowed_access = full_access_request; | |
| 1235 | + } else { | |
| 1236 | + unsigned long access_bit; | |
| 1237 | + const unsigned long access_req = full_access_request; | |
| 1238 | + | |
| 1239 | + /* | |
| 1240 | + * Calculate the actual allowed access rights from layer_masks. | |
| 1241 | + * Add each access right to allowed_access which has not been | |
| 1242 | + * vetoed by any layer. | |
| 1243 | + */ | |
| 1244 | + allowed_access = 0; | |
| 1245 | + for_each_set_bit(access_bit, &access_req, | |
| 1246 | + ARRAY_SIZE(layer_masks)) { | |
| 1247 | + if (!layer_masks[access_bit]) | |
| 1248 | + allowed_access |= BIT_ULL(access_bit); | |
| 1249 | + } | |
| 1250 | + } | |
| 1251 | + | |
| 1252 | + /* | |
| 1253 | + * For operations on already opened files (i.e. ftruncate()), it is the | |
| 1254 | + * access rights at the time of open() which decide whether the | |
| 1255 | + * operation is permitted. Therefore, we record the relevant subset of | |
| 1256 | + * file access rights in the opened struct file. | |
| 1257 | + */ | |
| 1258 | + landlock_file(file)->allowed_access = allowed_access; | |
| 1259 | + | |
| 1260 | + if ((open_access_request & allowed_access) == open_access_request) | |
| 1261 | + return 0; | |
| 1262 | + | |
| 1263 | + return -EACCES; | |
| 1264 | +} | |
| 1265 | + | |
| 1266 | +static int hook_file_truncate(struct file *const file) | |
| 1267 | +{ | |
| 1268 | + /* | |
| 1269 | + * Allows truncation if the truncate right was available at the time of | |
| 1270 | + * opening the file, to get a consistent access check as for read, write | |
| 1271 | + * and execute operations. | |
| 1272 | + * | |
| 1273 | + * Note: For checks done based on the file's Landlock allowed access, we | |
| 1274 | + * enforce them independently of whether the current thread is in a | |
| 1275 | + * Landlock domain, so that open files passed between independent | |
| 1276 | + * processes retain their behaviour. | |
| 1277 | + */ | |
| 1278 | + if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE) | |
| 1279 | + return 0; | |
| 1280 | + return -EACCES; | |
| 1190 | 1281 | } |
| 1191 | 1282 | |
| 1192 | 1283 | static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { |
| @@ -1206,8 +1297,11 @@ static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { · 13 unmodified lines | ||
| 1206 | 1297 | LSM_HOOK_INIT(path_symlink, hook_path_symlink), |
| 1207 | 1298 | LSM_HOOK_INIT(path_unlink, hook_path_unlink), |
| 1208 | 1299 | LSM_HOOK_INIT(path_rmdir, hook_path_rmdir), |
| 1300 | + LSM_HOOK_INIT(path_truncate, hook_path_truncate), | |
| 1209 | 1301 | |
| 1302 | + LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security), | |
| 1210 | 1303 | LSM_HOOK_INIT(file_open, hook_file_open), |
| 1304 | + LSM_HOOK_INIT(file_truncate, hook_file_truncate), | |
| 1211 | 1305 | }; |
| 1212 | 1306 | |
| 1213 | 1307 | __init void landlock_add_fs_hooks(void) |
| @@ -36,6 +36,24 @@ struct landlock_inode_security { · 35 unmodified lines | ||
| 36 | 36 | struct landlock_object __rcu *object; |
| 37 | 37 | }; |
| 38 | 38 | |
| 39 | +/** | |
| 40 | + * struct landlock_file_security - File security blob | |
| 41 | + * | |
| 42 | + * This information is populated when opening a file in hook_file_open, and | |
| 43 | + * tracks the relevant Landlock access rights that were available at the time | |
| 44 | + * of opening the file. Other LSM hooks use these rights in order to authorize | |
| 45 | + * operations on already opened files. | |
| 46 | + */ | |
| 47 | +struct landlock_file_security { | |
| 48 | + /** | |
| 49 | + * @allowed_access: Access rights that were available at the time of | |
| 50 | + * opening the file. This is not necessarily the full set of access | |
| 51 | + * rights available at that time, but it's the necessary subset as | |
| 52 | + * needed to authorize later operations on the open file. | |
| 53 | + */ | |
| 54 | + access_mask_t allowed_access; | |
| 55 | +}; | |
| 56 | + | |
| 39 | 57 | /** |
| 40 | 58 | * struct landlock_superblock_security - Superblock security blob |
| 41 | 59 | * |
| @@ -50,6 +68,12 @@ struct landlock_superblock_security { · 8 unmodified lines | ||
| 50 | 68 | atomic_long_t inode_refs; |
| 51 | 69 | }; |
| 52 | 70 | |
| 71 | +static inline struct landlock_file_security * | |
| 72 | +landlock_file(const struct file *const file) | |
| 73 | +{ | |
| 74 | + return file->f_security + landlock_blob_sizes.lbs_file; | |
| 75 | +} | |
| 76 | + | |
| 53 | 77 | static inline struct landlock_inode_security * |
| 54 | 78 | landlock_inode(const struct inode *const inode) |
| 55 | 79 | { |
| @@ -18,7 +18,7 @@ · 17 unmodified lines | ||
| 18 | 18 | #define LANDLOCK_MAX_NUM_LAYERS 16 |
| 19 | 19 | #define LANDLOCK_MAX_NUM_RULES U32_MAX |
| 20 | 20 | |
| 21 | −#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_REFER | |
| 21 | +#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_TRUNCATE | |
| 22 | 22 | #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) |
| 23 | 23 | #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) |
| 24 | 24 | |
| @@ -19,6 +19,7 @@ bool landlock_initialized __lsm_ro_after_init = false; · 18 unmodified lines | ||
| 19 | 19 | |
| 20 | 20 | struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = { |
| 21 | 21 | .lbs_cred = sizeof(struct landlock_cred_security), |
| 22 | + .lbs_file = sizeof(struct landlock_file_security), | |
| 22 | 23 | .lbs_inode = sizeof(struct landlock_inode_security), |
| 23 | 24 | .lbs_superblock = sizeof(struct landlock_superblock_security), |
| 24 | 25 | }; |
| @@ -129,7 +129,7 @@ static const struct file_operations ruleset_fops = { · 128 unmodified lines | ||
| 129 | 129 | .write = fop_dummy_write, |
| 130 | 130 | }; |
| 131 | 131 | |
| 132 | −#define LANDLOCK_ABI_VERSION 2 | |
| 132 | +#define LANDLOCK_ABI_VERSION 3 | |
| 133 | 133 | |
| 134 | 134 | /** |
| 135 | 135 | * sys_landlock_create_ruleset - Create a new ruleset |
| @@ -185,11 +185,12 @@ static void __init lsm_set_blob_size(int *need, int *lbs) · 184 unmodified lines | ||
| 185 | 185 | { |
| 186 | 186 | int offset; |
| 187 | 187 | |
| 188 | − if (*need > 0) { | |
| 189 | − offset = *lbs; | |
| 190 | − *lbs += *need; | |
| 191 | − *need = offset; | |
| 192 | − } | |
| 188 | + if (*need <= 0) | |
| 189 | + return; | |
| 190 | + | |
| 191 | + offset = ALIGN(*lbs, sizeof(void *)); | |
| 192 | + *lbs = offset + *need; | |
| 193 | + *need = offset; | |
| 193 | 194 | } |
| 194 | 195 | |
| 195 | 196 | static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) |
| @@ -75,7 +75,7 @@ TEST(abi_version) · 74 unmodified lines | ||
| 75 | 75 | const struct landlock_ruleset_attr ruleset_attr = { |
| 76 | 76 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, |
| 77 | 77 | }; |
| 78 | − ASSERT_EQ(2, landlock_create_ruleset(NULL, 0, | |
| 78 | + ASSERT_EQ(3, landlock_create_ruleset(NULL, 0, | |
| 79 | 79 | LANDLOCK_CREATE_RULESET_VERSION)); |
| 80 | 80 | |
| 81 | 81 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, |
| @@ -406,9 +406,10 @@ TEST_F_FORK(layout1, inval) · 405 unmodified lines | ||
| 406 | 406 | #define ACCESS_FILE ( \ |
| 407 | 407 | LANDLOCK_ACCESS_FS_EXECUTE | \ |
| 408 | 408 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ |
| 409 | − LANDLOCK_ACCESS_FS_READ_FILE) | |
| 409 | + LANDLOCK_ACCESS_FS_READ_FILE | \ | |
| 410 | + LANDLOCK_ACCESS_FS_TRUNCATE) | |
| 410 | 411 | |
| 411 | −#define ACCESS_LAST LANDLOCK_ACCESS_FS_REFER | |
| 412 | +#define ACCESS_LAST LANDLOCK_ACCESS_FS_TRUNCATE | |
| 412 | 413 | |
| 413 | 414 | #define ACCESS_ALL ( \ |
| 414 | 415 | ACCESS_FILE | \ |
| @@ -422,7 +423,7 @@ TEST_F_FORK(layout1, inval) · 7 unmodified lines | ||
| 422 | 423 | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ |
| 423 | 424 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ |
| 424 | 425 | LANDLOCK_ACCESS_FS_MAKE_SYM | \ |
| 425 | − ACCESS_LAST) | |
| 426 | + LANDLOCK_ACCESS_FS_REFER) | |
| 426 | 427 | |
| 427 | 428 | /* clang-format on */ |
| 428 | 429 | |