Implement the LANDLOCK_ACCESS_FS_READ_METADATA and
LANDLOCK_ACCESS_FS_WRITE_METADATA access rights by hooking the
inode_getattr, inode_setattr, inode_setxattr, inode_getxattr,
inode_listxattr, inode_removexattr, inode_set_acl, inode_get_acl and
inode_remove_acl LSM hooks, which now receive a struct path thanks to
the preceding VFS and LSM refactoring.

The following system calls are now controlled:

- stat(2), fstat(2), lstat(2), newfstatat(2), getxattr(2) and
  friends, listxattr(2) and friends, and POSIX ACL reads via
  inode_getattr, inode_getxattr, inode_listxattr and inode_get_acl
  (READ_METADATA)
- chmod(2), fchmod(2), fchmodat(2), fchmodat2(2), chown(2), fchown(2),
  lchown(2), fchownat(2), chgrp(2), utimensat(2), futimens(2),
  utime(2), setxattr(2) and friends, removexattr(2) and friends, and
  POSIX ACL set and remove via inode_setattr, inode_setxattr,
  inode_removexattr, inode_set_acl and inode_remove_acl
  (WRITE_METADATA)

Both new rights are added to ACCESS_FILE as they apply to both files
and directories.

hook_inode_setattr only restricts explicit metadata changes, i.e. it
checks WRITE_METADATA only when the ia_valid mask contains
ATTR_MODE, ATTR_UID, ATTR_GID, ATTR_TIMES_SET or ATTR_TOUCH.
Metadata changes that the kernel performs implicitly, such as
timestamp updates on write(2) or size changes on truncate(2), are
therefore not restricted, and neither are chmod(2)/chown(2) calls
that do not change any attribute (e.g. chown(2) with -1/-1, which is
a no-op that never reaches the hook), matching the behavior of the
SELinux inode_setattr hook.

Kernel-internal accesses performed with override_creds() (e.g.
overlayfs and cachefiles) are not affected because Landlock domains
are attached to credentials, and kernel threads without a Landlock
domain (e.g. nfsd and ksmbd) are not restricted either.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <[email protected]>
---
 security/landlock/fs.c | 86 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index cab43892ec2f..e58b2aa0da65 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -318,6 +318,8 @@ static struct landlock_object *get_inode_object(struct 
inode *const inode)
        LANDLOCK_ACCESS_FS_EXECUTE | \
        LANDLOCK_ACCESS_FS_WRITE_FILE | \
        LANDLOCK_ACCESS_FS_READ_FILE | \
+       LANDLOCK_ACCESS_FS_READ_METADATA | \
+       LANDLOCK_ACCESS_FS_WRITE_METADATA | \
        LANDLOCK_ACCESS_FS_TRUNCATE | \
        LANDLOCK_ACCESS_FS_IOCTL_DEV | \
        LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
@@ -1676,6 +1678,81 @@ static int hook_path_truncate(const struct path *const 
path)
        return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
 }
 
+static int hook_inode_getattr(const struct path *const path)
+{
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_inode_setattr(const struct path *const path,
+                             struct iattr *const attr)
+{
+       /*
+        * Explicit metadata changes (i.e. mode, ownership, and timestamps
+        * set with utimes() and friends) require
+        * LANDLOCK_ACCESS_FS_WRITE_METADATA.  Implicit timestamp updates
+        * (e.g. ATTR_CTIME set for a write) and size changes (handled by
+        * the truncate hooks) are not restricted.
+        */
+       if (!(attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID |
+                               ATTR_TIMES_SET | ATTR_TOUCH)))
+               return 0;
+
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_inode_setxattr(const struct path *const path,
+                              const char *const name,
+                              const void *const value, const size_t size,
+                              const int flags)
+{
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_inode_getxattr(const struct path *const path,
+                              const char *const name)
+{
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_inode_listxattr(const struct path *const path)
+{
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_inode_removexattr(const struct path *const path,
+                                 const char *const name)
+{
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_inode_set_acl(const struct path *const path,
+                             const char *const acl_name,
+                             struct posix_acl *const kacl)
+{
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_inode_get_acl(const struct path *const path,
+                             const char *const acl_name)
+{
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_inode_remove_acl(const struct path *const path,
+                                const char *const acl_name)
+{
+       return current_check_access_path(path,
+                                       LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
 /**
  * unmask_scoped_access - Remove access right bits in @masks in all layers
  *                        where @client and @server have the same domain
@@ -2100,6 +2177,15 @@ static struct security_hook_list landlock_hooks[] 
__ro_after_init = {
        LSM_HOOK_INIT(path_unlink, hook_path_unlink),
        LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
        LSM_HOOK_INIT(path_truncate, hook_path_truncate),
+       LSM_HOOK_INIT(inode_getattr, hook_inode_getattr),
+       LSM_HOOK_INIT(inode_setattr, hook_inode_setattr),
+       LSM_HOOK_INIT(inode_setxattr, hook_inode_setxattr),
+       LSM_HOOK_INIT(inode_getxattr, hook_inode_getxattr),
+       LSM_HOOK_INIT(inode_listxattr, hook_inode_listxattr),
+       LSM_HOOK_INIT(inode_removexattr, hook_inode_removexattr),
+       LSM_HOOK_INIT(inode_set_acl, hook_inode_set_acl),
+       LSM_HOOK_INIT(inode_get_acl, hook_inode_get_acl),
+       LSM_HOOK_INIT(inode_remove_acl, hook_inode_remove_acl),
        LSM_HOOK_INIT(unix_find, hook_unix_find),
 
        LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
-- 
2.18.0.huawei.25


Reply via email to