Add audit support for mkdir, mknod, symlink, unlink, rmdir, truncate, and open requests.
Signed-off-by: Mickaël Salaün <[email protected]> --- security/landlock/audit.c | 114 ++++++++++++++++++++++++++++++++++++++ security/landlock/audit.h | 32 +++++++++++ security/landlock/fs.c | 62 ++++++++++++++++++--- 3 files changed, 199 insertions(+), 9 deletions(-) diff --git a/security/landlock/audit.c b/security/landlock/audit.c index d9589d07e126..148fc0fafef4 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -14,6 +14,25 @@ atomic64_t ruleset_and_domain_counter = ATOMIC64_INIT(0); +static const char *op_to_string(enum landlock_operation operation) +{ + const char *const desc[] = { + [0] = "", + [LANDLOCK_OP_MKDIR] = "mkdir", + [LANDLOCK_OP_MKNOD] = "mknod", + [LANDLOCK_OP_SYMLINK] = "symlink", + [LANDLOCK_OP_UNLINK] = "unlink", + [LANDLOCK_OP_RMDIR] = "rmdir", + [LANDLOCK_OP_TRUNCATE] = "truncate", + [LANDLOCK_OP_OPEN] = "open", + }; + + if (WARN_ON_ONCE(operation < 0 || operation > ARRAY_SIZE(desc))) + return "unknown"; + + return desc[operation]; +} + #define BIT_INDEX(bit) HWEIGHT(bit - 1) static void log_accesses(struct audit_buffer *const ab, @@ -141,3 +160,98 @@ void landlock_log_release_ruleset(const struct landlock_ruleset *const ruleset) audit_log_format(ab, "op=release-%s %s=%llu", name, name, id); audit_log_end(ab); } + +/* Update request.youngest_domain and request.missing_access */ +static void +update_request(struct landlock_request *const request, + const struct landlock_ruleset *const domain, + const access_mask_t access_request, + const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + const unsigned long access_req = access_request; + unsigned long access_bit; + long youngest_denied_layer = -1; + const struct landlock_hierarchy *node = domain->hierarchy; + size_t i; + + WARN_ON_ONCE(request->youngest_domain); + WARN_ON_ONCE(request->missing_access); + + if (WARN_ON_ONCE(!access_request)) + return; + + if (WARN_ON_ONCE(!layer_masks)) + return; + + for_each_set_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks)) { + long domain_layer; + + if (!(*layer_masks)[access_bit]) + continue; + + domain_layer = __fls((*layer_masks)[access_bit]); + + /* + * Gets the access rights that are missing from + * the youngest (i.e. closest) domain. + */ + if (domain_layer == youngest_denied_layer) { + request->missing_access |= BIT_ULL(access_bit); + } else if (domain_layer > youngest_denied_layer) { + youngest_denied_layer = domain_layer; + request->missing_access = BIT_ULL(access_bit); + } + } + + WARN_ON_ONCE(!request->missing_access); + WARN_ON_ONCE(youngest_denied_layer < 0); + + /* Gets the nearest domain ID that denies request.missing_access */ + for (i = domain->num_layers - youngest_denied_layer - 1; i > 0; i--) + node = node->parent; + request->youngest_domain = node->id; +} + +static void +log_request(const int error, struct landlock_request *const request, + const struct landlock_ruleset *const domain, + const access_mask_t access_request, + const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + struct audit_buffer *ab; + + if (WARN_ON_ONCE(!error)) + return; + if (WARN_ON_ONCE(!request)) + return; + if (WARN_ON_ONCE(!domain || !domain->hierarchy)) + return; + + /* Uses GFP_ATOMIC to not sleep. */ + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, + AUDIT_LANDLOCK); + if (!ab) + return; + + update_request(request, domain, access_request, layer_masks); + + log_task(ab); + audit_log_format(ab, " domain=%llu op=%s errno=%d missing-fs-accesses=", + request->youngest_domain, + op_to_string(request->operation), -error); + log_accesses(ab, request->missing_access); + audit_log_lsm_data(ab, &request->audit); + audit_log_end(ab); +} + +// TODO: Make it generic, not FS-centric. +int landlock_log_request( + const int error, struct landlock_request *const request, + const struct landlock_ruleset *const domain, + const access_mask_t access_request, + const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + /* No need to log the access request, only the missing accesses. */ + log_request(error, request, domain, access_request, layer_masks); + return error; +} diff --git a/security/landlock/audit.h b/security/landlock/audit.h index bc17dc8ca6f1..8edc68b98fca 100644 --- a/security/landlock/audit.h +++ b/security/landlock/audit.h @@ -13,6 +13,23 @@ #include "ruleset.h" +enum landlock_operation { + LANDLOCK_OP_MKDIR = 1, + LANDLOCK_OP_MKNOD, + LANDLOCK_OP_SYMLINK, + LANDLOCK_OP_UNLINK, + LANDLOCK_OP_RMDIR, + LANDLOCK_OP_TRUNCATE, + LANDLOCK_OP_OPEN, +}; + +struct landlock_request { + const enum landlock_operation operation; + access_mask_t missing_access; + u64 youngest_domain; + struct common_audit_data audit; +}; + #ifdef CONFIG_AUDIT void landlock_log_create_ruleset(struct landlock_ruleset *const ruleset); @@ -20,6 +37,12 @@ void landlock_log_restrict_self(struct landlock_ruleset *const domain, struct landlock_ruleset *const ruleset); void landlock_log_release_ruleset(const struct landlock_ruleset *const ruleset); +int landlock_log_request( + const int error, struct landlock_request *const request, + const struct landlock_ruleset *const domain, + const access_mask_t access_request, + const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]); + #else /* CONFIG_AUDIT */ static inline void @@ -38,6 +61,15 @@ landlock_log_release_ruleset(const struct landlock_ruleset *const ruleset) { } +static inline int landlock_log_request( + const int error, struct landlock_request *const request, + const struct landlock_ruleset *const domain, + const access_mask_t access_request, + const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + return error; +} + #endif /* CONFIG_AUDIT */ #endif /* _SECURITY_LANDLOCK_AUDIT_H */ diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 978e325d8708..104dfb2abc32 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -18,6 +18,7 @@ #include <linux/kernel.h> #include <linux/limits.h> #include <linux/list.h> +#include <linux/lsm_audit.h> #include <linux/lsm_hooks.h> #include <linux/mount.h> #include <linux/namei.h> @@ -30,6 +31,7 @@ #include <linux/workqueue.h> #include <uapi/linux/landlock.h> +#include "audit.h" #include "common.h" #include "cred.h" #include "fs.h" @@ -636,7 +638,8 @@ static bool is_access_to_paths_allowed( } static int current_check_access_path(const struct path *const path, - access_mask_t access_request) + access_mask_t access_request, + struct landlock_request *const request) { const struct landlock_ruleset *const dom = landlock_get_current_domain(); @@ -650,7 +653,10 @@ static int current_check_access_path(const struct path *const path, NULL, 0, NULL, NULL)) return 0; - return -EACCES; + request->audit.type = LSM_AUDIT_DATA_PATH; + request->audit.u.path = *path; + return landlock_log_request(-EACCES, request, dom, access_request, + &layer_masks); } static inline access_mask_t get_mode_access(const umode_t mode) @@ -1097,6 +1103,7 @@ static int hook_path_link(struct dentry *const old_dentry, const struct path *const new_dir, struct dentry *const new_dentry) { + // TODO: Implement fine-grained audit return current_check_refer_path(old_dentry, new_dir, new_dentry, false, false); } @@ -1115,38 +1122,67 @@ static int hook_path_rename(const struct path *const old_dir, static int hook_path_mkdir(const struct path *const dir, struct dentry *const dentry, const umode_t mode) { - return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR); + struct landlock_request request = { + .operation = LANDLOCK_OP_MKDIR, + }; + + return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR, + &request); } static int hook_path_mknod(const struct path *const dir, struct dentry *const dentry, const umode_t mode, const unsigned int dev) { - return current_check_access_path(dir, get_mode_access(mode)); + struct landlock_request request = { + .operation = LANDLOCK_OP_MKNOD, + }; + + return current_check_access_path(dir, get_mode_access(mode), &request); } static int hook_path_symlink(const struct path *const dir, struct dentry *const dentry, const char *const old_name) { - return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM); + struct landlock_request request = { + .operation = LANDLOCK_OP_SYMLINK, + }; + + return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM, + &request); } static int hook_path_unlink(const struct path *const dir, struct dentry *const dentry) { - return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE); + struct landlock_request request = { + .operation = LANDLOCK_OP_UNLINK, + }; + + return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE, + &request); } static int hook_path_rmdir(const struct path *const dir, struct dentry *const dentry) { - return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); + struct landlock_request request = { + .operation = LANDLOCK_OP_RMDIR, + }; + + return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR, + &request); } static int hook_path_truncate(const struct path *const path) { - return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE); + struct landlock_request request = { + .operation = LANDLOCK_OP_TRUNCATE, + }; + + return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE, + &request); } /* File hooks */ @@ -1199,6 +1235,13 @@ static int hook_file_open(struct file *const file) const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE; const struct landlock_ruleset *const dom = landlock_get_current_domain(); + struct landlock_request request = { + .operation = LANDLOCK_OP_OPEN, + .audit = { + .type = LSM_AUDIT_DATA_PATH, + .u.path = file->f_path, + }, + }; if (!dom) return 0; @@ -1249,7 +1292,8 @@ static int hook_file_open(struct file *const file) if ((open_access_request & allowed_access) == open_access_request) return 0; - return -EACCES; + return landlock_log_request(-EACCES, &request, dom, open_access_request, + &layer_masks); } static int hook_file_truncate(struct file *const file) -- 2.42.0
