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 | * A file can only receive these access rights: | 95 | * A file can only receive these access rights: |
| 96 | * | 96 | * |
| 97 | * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file. | 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 | * - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access. | 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 | * A directory can receive access rights related to files or directories. The | 112 | * A directory can receive access rights related to files or directories. The |
| 102 | * following access right is applied to the directory itself, and the | 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 | * It is currently not possible to restrict some file-related actions | 151 | * It is currently not possible to restrict some file-related actions |
| 141 | * accessible through these syscall families: :manpage:`chdir(2)`, | 152 | * accessible through these syscall families: :manpage:`chdir(2)`, |
| 142 | * :manpage:`truncate(2)`, :manpage:`stat(2)`, :manpage:`flock(2)`, | 153 | * :manpage:`stat(2)`, :manpage:`flock(2)`, :manpage:`chmod(2)`, |
| 143 | * :manpage:`chmod(2)`, :manpage:`chown(2)`, :manpage:`setxattr(2)`, | 154 | * :manpage:`chown(2)`, :manpage:`setxattr(2)`, :manpage:`utime(2)`, |
| 144 | * :manpage:`utime(2)`, :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, | 155 | * :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, :manpage:`access(2)`. |
| 145 | * :manpage:`access(2)`. | ||
| 146 | * Future Landlock evolutions will enable to restrict them. | 156 | * Future Landlock evolutions will enable to restrict them. |
| 147 | */ | 157 | */ |
| 148 | /* clang-format off */ | 158 | /* clang-format off */ |
| @@ -160,6 +170,7 @@ struct landlock_path_beneath_attr { · 11 unmodified lines | |||
| 160 | #define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11) | 170 | #define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11) |
| 161 | #define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) | 171 | #define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) |
| 162 | #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13) | 172 | #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13) |
| 173 | #define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14) | ||
| 163 | /* clang-format on */ | 174 | /* clang-format on */ |
| 164 | 175 | ||
| 165 | #endif /* _UAPI_LINUX_LANDLOCK_H */ | 176 | #endif /* _UAPI_LINUX_LANDLOCK_H */ |
| @@ -146,7 +146,8 @@ retry: · 145 unmodified lines | |||
| 146 | #define ACCESS_FILE ( \ | 146 | #define ACCESS_FILE ( \ |
| 147 | LANDLOCK_ACCESS_FS_EXECUTE | \ | 147 | LANDLOCK_ACCESS_FS_EXECUTE | \ |
| 148 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | 148 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ |
| 149 | LANDLOCK_ACCESS_FS_READ_FILE) | 149 | LANDLOCK_ACCESS_FS_READ_FILE | \ |
| 150 | LANDLOCK_ACCESS_FS_TRUNCATE) | ||
| 150 | /* clang-format on */ | 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 | return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); | 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 | /* File hooks */ | 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 | access_mask_t access = 0; | 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 | return access; | 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 | static int hook_file_open(struct file *const file) | 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 | const struct landlock_ruleset *const dom = | 1211 | const struct landlock_ruleset *const dom = |
| 1180 | landlock_get_current_domain(); | 1212 | landlock_get_current_domain(); |
| 1181 | 1213 | ||
| 1182 | if (!dom) | 1214 | if (!dom) |
| 1183 | return 0; | 1215 | return 0; |
| 1216 | |||
| 1184 | /* | 1217 | /* |
| 1185 | * Because a file may be opened with O_PATH, get_file_access() may | 1218 | * Because a file may be opened with O_PATH, get_required_file_open_access() |
| 1186 | * return 0. This case will be handled with a future Landlock | 1219 | * may return 0. This case will be handled with a future Landlock |
| 1187 | * evolution. | 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 | static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { | 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 | LSM_HOOK_INIT(path_symlink, hook_path_symlink), | 1297 | LSM_HOOK_INIT(path_symlink, hook_path_symlink), |
| 1207 | LSM_HOOK_INIT(path_unlink, hook_path_unlink), | 1298 | LSM_HOOK_INIT(path_unlink, hook_path_unlink), |
| 1208 | LSM_HOOK_INIT(path_rmdir, hook_path_rmdir), | 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 | LSM_HOOK_INIT(file_open, hook_file_open), | 1303 | LSM_HOOK_INIT(file_open, hook_file_open), |
| 1304 | LSM_HOOK_INIT(file_truncate, hook_file_truncate), | ||
| 1211 | }; | 1305 | }; |
| 1212 | 1306 | ||
| 1213 | __init void landlock_add_fs_hooks(void) | 1307 | __init void landlock_add_fs_hooks(void) |
| @@ -36,6 +36,24 @@ struct landlock_inode_security { · 35 unmodified lines | |||
| 36 | struct landlock_object __rcu *object; | 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 | * struct landlock_superblock_security - Superblock security blob | 58 | * struct landlock_superblock_security - Superblock security blob |
| 41 | * | 59 | * |
| @@ -50,6 +68,12 @@ struct landlock_superblock_security { · 8 unmodified lines | |||
| 50 | atomic_long_t inode_refs; | 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 | static inline struct landlock_inode_security * | 77 | static inline struct landlock_inode_security * |
| 54 | landlock_inode(const struct inode *const inode) | 78 | landlock_inode(const struct inode *const inode) |
| 55 | { | 79 | { |
| @@ -18,7 +18,7 @@ · 17 unmodified lines | |||
| 18 | #define LANDLOCK_MAX_NUM_LAYERS 16 | 18 | #define LANDLOCK_MAX_NUM_LAYERS 16 |
| 19 | #define LANDLOCK_MAX_NUM_RULES U32_MAX | 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 | #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) | 22 | #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) |
| 23 | #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) | 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 | struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = { | 20 | struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = { |
| 21 | .lbs_cred = sizeof(struct landlock_cred_security), | 21 | .lbs_cred = sizeof(struct landlock_cred_security), |
| 22 | .lbs_file = sizeof(struct landlock_file_security), | ||
| 22 | .lbs_inode = sizeof(struct landlock_inode_security), | 23 | .lbs_inode = sizeof(struct landlock_inode_security), |
| 23 | .lbs_superblock = sizeof(struct landlock_superblock_security), | 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 | .write = fop_dummy_write, | 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 | * sys_landlock_create_ruleset - Create a new ruleset | 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 | int offset; | 186 | int offset; |
| 187 | 187 | ||
| 188 | if (*need > 0) { | 188 | if (*need <= 0) |
| 189 | offset = *lbs; | 189 | return; |
| 190 | *lbs += *need; | 190 | |
| 191 | *need = offset; | 191 | offset = ALIGN(*lbs, sizeof(void *)); |
| 192 | } | 192 | *lbs = offset + *need; |
| 193 | *need = offset; | ||
| 193 | } | 194 | } |
| 194 | 195 | ||
| 195 | static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) | 196 | static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) |
| @@ -75,7 +75,7 @@ TEST(abi_version) · 74 unmodified lines | |||
| 75 | const struct landlock_ruleset_attr ruleset_attr = { | 75 | const struct landlock_ruleset_attr ruleset_attr = { |
| 76 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, | 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 | LANDLOCK_CREATE_RULESET_VERSION)); | 79 | LANDLOCK_CREATE_RULESET_VERSION)); |
| 80 | 80 | ||
| 81 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, | 81 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, |
| @@ -406,9 +406,10 @@ TEST_F_FORK(layout1, inval) · 405 unmodified lines | |||
| 406 | #define ACCESS_FILE ( \ | 406 | #define ACCESS_FILE ( \ |
| 407 | LANDLOCK_ACCESS_FS_EXECUTE | \ | 407 | LANDLOCK_ACCESS_FS_EXECUTE | \ |
| 408 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | 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 | #define ACCESS_ALL ( \ | 414 | #define ACCESS_ALL ( \ |
| 414 | ACCESS_FILE | \ | 415 | ACCESS_FILE | \ |
| @@ -422,7 +423,7 @@ TEST_F_FORK(layout1, inval) · 7 unmodified lines | |||
| 422 | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ | 423 | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ |
| 423 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ | 424 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ |
| 424 | LANDLOCK_ACCESS_FS_MAKE_SYM | \ | 425 | LANDLOCK_ACCESS_FS_MAKE_SYM | \ |
| 425 | ACCESS_LAST) | 426 | LANDLOCK_ACCESS_FS_REFER) |
| 426 | 427 | ||
| 427 | /* clang-format on */ | 428 | /* clang-format on */ |
| 428 | 429 | ||