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

Reply via email to