[PATCH 05/10] CaitSith: Add LSM interface management file.

Tetsuo Handa penguin-kernel at I-love.SAKURA.ne.jp
Wed Nov 2 17:10:20 UTC 2022


This file is used for registering CaitSith module into the
security_hook_heads list. Further patches will not be interesting for
reviewers, for further patches are providing similar functions provided
by TOMOYO (but too different to share the code).

Signed-off-by: Tetsuo Handa <penguin-kernel at I-love.SAKURA.ne.jp>
---
 security/caitsith/lsm.c | 1358 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 1358 insertions(+)
 create mode 100644 security/caitsith/lsm.c

diff --git a/security/caitsith/lsm.c b/security/caitsith/lsm.c
new file mode 100644
index 000000000000..1a487b4021c6
--- /dev/null
+++ b/security/caitsith/lsm.c
@@ -0,0 +1,1358 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * lsm.c
+ *
+ * Copyright (C) 2010-2013  Tetsuo Handa <penguin-kernel at I-love.SAKURA.ne.jp>
+ *
+ * Version: 0.2.10   2021/06/06
+ */
+
+#include "caitsith.h"
+#include <linux/lsm_hooks.h>
+
+/* Prototype definition. */
+static int __cs_alloc_task_security(const struct task_struct *task);
+static void __cs_free_task_security(const struct task_struct *task);
+
+/* Dummy security context for avoiding NULL pointer dereference. */
+static struct cs_security cs_oom_security = {
+	.cs_domain_info = &cs_kernel_domain
+};
+
+/* Dummy security context for avoiding NULL pointer dereference. */
+static struct cs_security cs_default_security = {
+	.cs_domain_info = &cs_kernel_domain
+};
+
+/* List of "struct cs_security". */
+struct list_head cs_task_security_list[CS_MAX_TASK_SECURITY_HASH];
+/* Lock for protecting cs_task_security_list[]. */
+static DEFINE_SPINLOCK(cs_task_security_list_lock);
+
+/* Original hooks. */
+static union security_list_options original_cred_prepare;
+static union security_list_options original_task_alloc;
+static union security_list_options original_task_free;
+
+#if !defined(CONFIG_SECURITY_CAITSITH_DEBUG)
+#define cs_debug_trace(pos) do { } while (0)
+#else
+#define cs_debug_trace(pos)						\
+	do {								\
+		static bool done;					\
+		if (!done) {						\
+			pr_info("CAITSITH: Debug trace: " pos " of 2\n"); \
+			done = true;					\
+		}							\
+	} while (0)
+#endif
+
+/**
+ * cs_clear_execve - Release memory used by do_execve().
+ *
+ * @ret:      0 if do_execve() succeeded, negative value otherwise.
+ * @security: Pointer to "struct cs_security".
+ *
+ * Returns nothing.
+ */
+static void cs_clear_execve(int ret, struct cs_security *security)
+{
+	struct cs_request_info *r = security->r;
+
+	if (security == &cs_default_security || security == &cs_oom_security ||
+	    !r)
+		return;
+	security->r = NULL;
+	cs_finish_execve(ret, r);
+}
+
+/**
+ * cs_task_alloc_security - Allocate memory for new tasks.
+ *
+ * @p: Pointer to "struct task_struct".
+ * @clone_flags: Flags passed to clone().
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_task_alloc_security(struct task_struct *p,
+				   unsigned long clone_flags)
+{
+	int rc = __cs_alloc_task_security(p);
+
+	if (rc)
+		return rc;
+	if (original_task_alloc.task_alloc) {
+		rc = original_task_alloc.task_alloc(p, clone_flags);
+		if (rc)
+			__cs_free_task_security(p);
+	}
+	return rc;
+}
+
+/**
+ * cs_task_free_security - Release memory for "struct task_struct".
+ *
+ * @p: Pointer to "struct task_struct".
+ *
+ * Returns nothing.
+ */
+static void cs_task_free_security(struct task_struct *p)
+{
+	struct cs_security *ptr = cs_find_task_security(p);
+	struct cs_request_info *r = ptr->r;
+
+	if (original_task_free.task_free)
+		original_task_free.task_free(p);
+	/*
+	 * Since an LSM hook for reverting domain transition is missing,
+	 * cs_finish_execve() is not called if exited immediately after
+	 * execve() failed.
+	 */
+	if (r) {
+		cs_debug_trace("2");
+		kfree(r);
+		ptr->r = NULL;
+	}
+	__cs_free_task_security(p);
+}
+
+/**
+ * __cs_free_task_security - Release memory associated with "struct task_struct".
+ *
+ * @task: Pointer to "struct task_struct".
+ *
+ * Returns nothing.
+ */
+static void __cs_free_task_security(const struct task_struct *task)
+{
+	unsigned long flags;
+	struct cs_security *ptr = cs_find_task_security(task);
+
+	if (ptr == &cs_default_security || ptr == &cs_oom_security)
+		return;
+	spin_lock_irqsave(&cs_task_security_list_lock, flags);
+	list_del_rcu(&ptr->list);
+	spin_unlock_irqrestore(&cs_task_security_list_lock, flags);
+	kfree_rcu(ptr, rcu);
+}
+
+/**
+ * cs_cred_prepare - Allocate memory for new credentials.
+ *
+ * @new: Pointer to "struct cred".
+ * @old: Pointer to "struct cred".
+ * @gfp: Memory allocation flags.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_cred_prepare(struct cred *new, const struct cred *old,
+			   gfp_t gfp)
+{
+	/*
+	 * For checking whether reverting domain transition is needed or not.
+	 *
+	 * See cs_find_task_security() for reason.
+	 */
+	if (gfp == GFP_KERNEL)
+		cs_find_task_security(current);
+	if (original_cred_prepare.cred_prepare)
+		return original_cred_prepare.cred_prepare(new, old, gfp);
+	return 0;
+}
+
+/**
+ * cs_bprm_committing_creds - A hook which is called when do_execve() succeeded.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns nothing.
+ */
+static void cs_bprm_committing_creds(struct linux_binprm *bprm)
+{
+	cs_clear_execve(0, cs_current_security());
+}
+
+#ifndef CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+
+/**
+ * cs_policy_loader_exists - Check whether /sbin/caitsith-init exists.
+ *
+ * Returns true if /sbin/caitsith-init exists, false otherwise.
+ */
+static _Bool cs_policy_loader_exists(void)
+{
+	struct path path;
+
+	if (kern_path(CONFIG_SECURITY_CAITSITH_POLICY_LOADER, LOOKUP_FOLLOW, &path)
+	    == 0) {
+		path_put(&path);
+		return 1;
+	}
+	pr_info("Not activating CaitSith as %s does not exist.\n",
+		CONFIG_SECURITY_CAITSITH_POLICY_LOADER);
+	return 0;
+}
+
+/**
+ * cs_load_policy - Run external policy loader to load policy.
+ *
+ * @filename: The program about to start.
+ *
+ * Returns nothing.
+ *
+ * This function checks whether @filename is /sbin/init, and if so
+ * invoke /sbin/caitsith-init and wait for the termination of
+ * /sbin/caitsith-init and then continues invocation of /sbin/init.
+ * /sbin/caitsith-init reads policy files in /etc/caitsith/ directory and
+ * writes to /sys/kernel/security/caitsith/ interfaces.
+ */
+static void cs_load_policy(const char *filename)
+{
+	static _Bool done;
+
+	if (done)
+		return;
+	if (strcmp(filename, CONFIG_SECURITY_CAITSITH_ACTIVATION_TRIGGER))
+		return;
+	if (!cs_policy_loader_exists())
+		return;
+	done = 1;
+	{
+		char *argv[2];
+		char *envp[3];
+
+		pr_info("Calling %s to load policy. Please wait.\n",
+			CONFIG_SECURITY_CAITSITH_POLICY_LOADER);
+		argv[0] = (char *) CONFIG_SECURITY_CAITSITH_POLICY_LOADER;
+		argv[1] = NULL;
+		envp[0] = "HOME=/";
+		envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+		envp[2] = NULL;
+		call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
+	}
+	cs_check_profile();
+}
+
+#endif
+
+/**
+ * cs_bprm_check_security - Check permission for execve().
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_bprm_check_security(struct linux_binprm *bprm)
+{
+	struct cs_security *security = cs_current_security();
+
+	if (security == &cs_default_security || security == &cs_oom_security)
+		return -ENOMEM;
+	if (security->r)
+		return 0;
+#ifndef CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+	if (!cs_policy_loaded)
+		cs_load_policy(bprm->filename);
+#endif
+	return cs_start_execve(bprm, &security->r);
+}
+
+/**
+ * cs_file_open - Check permission for open().
+ *
+ * @f:    Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_file_open(struct file *f)
+{
+	return cs_open_permission(&f->f_path, f->f_flags);
+}
+
+#ifdef CONFIG_SECURITY_PATH
+
+/**
+ * cs_path_chown - Check permission for chown()/chgrp().
+ *
+ * @path:  Pointer to "struct path".
+ * @user:  User ID.
+ * @group: Group ID.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_chown(const struct path *path, kuid_t user, kgid_t group)
+{
+	return cs_chown_permission(path, user, group);
+}
+
+/**
+ * cs_path_chmod - Check permission for chmod().
+ *
+ * @path: Pointer to "struct path".
+ * @mode: Mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_chmod(const struct path *path, umode_t mode)
+{
+	return cs_chmod_permission(path, mode);
+}
+
+/**
+ * cs_path_chroot - Check permission for chroot().
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_chroot(const struct path *path)
+{
+	return cs_chroot_permission(path);
+}
+
+/**
+ * cs_path_truncate - Check permission for truncate().
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_truncate(const struct path *path)
+{
+	return cs_truncate_permission(path);
+}
+
+#else
+
+/**
+ * cs_inode_setattr - Check permission for chown()/chgrp()/chmod()/truncate().
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @attr:   Pointer to "struct iattr".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_setattr(struct dentry *dentry, struct iattr *attr)
+{
+	int rc = 0;
+	struct path path = { .mnt = NULL, .dentry = dentry };
+
+	if (attr->ia_valid & ATTR_UID)
+		rc = cs_chown_permission(&path, attr->ia_uid, INVALID_GID);
+	if (!rc && (attr->ia_valid & ATTR_GID))
+		rc = cs_chown_permission(&path, INVALID_UID, attr->ia_gid);
+	if (!rc && (attr->ia_valid & ATTR_MODE))
+		rc = cs_chmod_permission(&path, attr->ia_mode);
+	if (!rc && (attr->ia_valid & ATTR_SIZE))
+		rc = cs_truncate_permission(&path);
+	return rc;
+}
+
+#endif
+
+#ifdef CONFIG_SECURITY_CAITSITH_GETATTR
+
+/**
+ * cs_inode_getattr - Check permission for stat().
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_getattr(const struct path *path)
+{
+	return cs_getattr_permission(path);
+}
+
+#endif
+
+#ifdef CONFIG_SECURITY_PATH
+
+/**
+ * cs_path_mknod - Check permission for mknod().
+ *
+ * @dir:    Pointer to "struct path".
+ * @dentry: Pointer to "struct dentry".
+ * @mode:   Create mode.
+ * @dev:    Device major/minor number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_mknod(const struct path *dir, struct dentry *dentry,
+			 umode_t mode, unsigned int dev)
+{
+	struct path path = { .mnt = dir->mnt, .dentry = dentry };
+
+	return cs_mknod_permission(&path, mode, dev);
+}
+
+/**
+ * cs_path_mkdir - Check permission for mkdir().
+ *
+ * @dir:    Pointer to "struct path".
+ * @dentry: Pointer to "struct dentry".
+ * @mode:   Create mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_mkdir(const struct path *dir, struct dentry *dentry,
+			 umode_t mode)
+{
+	struct path path = { .mnt = dir->mnt, .dentry = dentry };
+
+	return cs_mkdir_permission(&path, mode);
+}
+
+/**
+ * cs_path_rmdir - Check permission for rmdir().
+ *
+ * @dir:    Pointer to "struct path".
+ * @dentry: Pointer to "struct dentry".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_rmdir(const struct path *dir, struct dentry *dentry)
+{
+	struct path path = { .mnt = dir->mnt, .dentry = dentry };
+
+	return cs_rmdir_permission(&path);
+}
+
+/**
+ * cs_path_unlink - Check permission for unlink().
+ *
+ * @dir:    Pointer to "struct path".
+ * @dentry: Pointer to "struct dentry".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_unlink(const struct path *dir, struct dentry *dentry)
+{
+	struct path path = { .mnt = dir->mnt, .dentry = dentry };
+
+	return cs_unlink_permission(&path);
+}
+
+/**
+ * cs_path_symlink - Check permission for symlink().
+ *
+ * @dir:      Pointer to "struct path".
+ * @dentry:   Pointer to "struct dentry".
+ * @old_name: Content of symbolic link.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_symlink(const struct path *dir, struct dentry *dentry,
+			   const char *old_name)
+{
+	struct path path = { .mnt = dir->mnt, .dentry = dentry };
+
+	return cs_symlink_permission(&path, old_name);
+}
+
+/**
+ * cs_path_rename - Check permission for rename().
+ *
+ * @old_dir:    Pointer to "struct path".
+ * @old_dentry: Pointer to "struct dentry".
+ * @new_dir:    Pointer to "struct path".
+ * @new_dentry: Pointer to "struct dentry".
+ * @flags:      Rename flags.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_rename(const struct path *old_dir,
+			  struct dentry *old_dentry,
+			  const struct path *new_dir,
+			  struct dentry *new_dentry,
+			  const unsigned int flags)
+{
+	struct path old = { .mnt = old_dir->mnt, .dentry = old_dentry };
+	struct path new = { .mnt = new_dir->mnt, .dentry = new_dentry };
+
+	if (flags & RENAME_EXCHANGE) {
+		const int err = cs_rename_permission(&new, &old);
+
+		if (err)
+			return err;
+	}
+	return cs_rename_permission(&old, &new);
+}
+
+/**
+ * cs_path_link - Check permission for link().
+ *
+ * @old_dentry: Pointer to "struct dentry".
+ * @new_dir:    Pointer to "struct path".
+ * @new_dentry: Pointer to "struct dentry".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_link(struct dentry *old_dentry, const struct path *new_dir,
+			struct dentry *new_dentry)
+{
+	struct path old = { .mnt = new_dir->mnt, .dentry = old_dentry };
+	struct path new = { .mnt = new_dir->mnt, .dentry = new_dentry };
+
+	return cs_link_permission(&old, &new);
+}
+
+#else
+
+/**
+ * cs_inode_mknod - Check permission for mknod().
+ *
+ * @dir:    Pointer to "struct inode".
+ * @dentry: Pointer to "struct dentry".
+ * @mode:   Create mode.
+ * @dev:    Device major/minor number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_mknod(struct inode *dir, struct dentry *dentry,
+			  umode_t mode, dev_t dev)
+{
+	struct path path = { .mnt = NULL, .dentry = dentry };
+
+	return cs_mknod_permission(&path, mode, dev);
+}
+
+/**
+ * cs_inode_mkdir - Check permission for mkdir().
+ *
+ * @dir:    Pointer to "struct inode".
+ * @dentry: Pointer to "struct dentry".
+ * @mode:   Create mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_mkdir(struct inode *dir, struct dentry *dentry,
+			  umode_t mode)
+{
+	struct path path = { .mnt = NULL, .dentry = dentry };
+
+	return cs_mkdir_permission(&path, mode);
+}
+
+/**
+ * cs_inode_rmdir - Check permission for rmdir().
+ *
+ * @dir:    Pointer to "struct inode".
+ * @dentry: Pointer to "struct dentry".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	struct path path = { .mnt = NULL, .dentry = dentry };
+
+	return cs_rmdir_permission(&path);
+}
+
+/**
+ * cs_inode_unlink - Check permission for unlink().
+ *
+ * @dir:    Pointer to "struct inode".
+ * @dentry: Pointer to "struct dentry".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct path path = { .mnt = NULL, .dentry = dentry };
+
+	return cs_unlink_permission(&path);
+}
+
+/**
+ * cs_inode_symlink - Check permission for symlink().
+ *
+ * @dir:      Pointer to "struct inode".
+ * @dentry:   Pointer to "struct dentry".
+ * @old_name: Content of symbolic link.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_symlink(struct inode *dir, struct dentry *dentry,
+			    const char *old_name)
+{
+	struct path path = { .mnt = NULL, .dentry = dentry };
+
+	return cs_symlink_permission(&path, old_name);
+}
+
+/**
+ * cs_inode_rename - Check permission for rename().
+ *
+ * @old_dir:    Pointer to "struct inode".
+ * @old_dentry: Pointer to "struct dentry".
+ * @new_dir:    Pointer to "struct inode".
+ * @new_dentry: Pointer to "struct dentry".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
+			   struct inode *new_dir, struct dentry *new_dentry)
+{
+	struct path old = { .mnt = NULL, .dentry = old_dentry };
+	struct path new = { .mnt = NULL, .dentry = new_dentry };
+
+	return cs_rename_permission(&old, &new);
+}
+
+/**
+ * cs_inode_link - Check permission for link().
+ *
+ * @old_dentry: Pointer to "struct dentry".
+ * @dir:        Pointer to "struct inode".
+ * @new_dentry: Pointer to "struct dentry".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_link(struct dentry *old_dentry, struct inode *dir,
+			 struct dentry *new_dentry)
+{
+	struct path old = { .mnt = NULL, .dentry = old_dentry };
+	struct path new = { .mnt = NULL, .dentry = new_dentry };
+
+	return cs_link_permission(&old, &new);
+}
+
+/**
+ * cs_inode_create - Check permission for creat().
+ *
+ * @dir:    Pointer to "struct inode".
+ * @dentry: Pointer to "struct dentry".
+ * @mode:   Create mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inode_create(struct inode *dir, struct dentry *dentry,
+			   umode_t mode)
+{
+	struct path path = { .mnt = NULL, .dentry = dentry };
+
+	return cs_mknod_permission(&path, mode, 0);
+}
+
+#endif
+
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+
+#include <net/sock.h>
+
+/* Structure for remembering an accept()ed socket's status. */
+struct cs_socket_tag {
+	struct list_head list;
+	struct inode *inode;
+	int status;
+	struct rcu_head rcu;
+};
+
+/*
+ * List for managing accept()ed sockets.
+ * Since we don't need to keep an accept()ed socket into this list after
+ * once the permission was granted, the number of entries in this list is
+ * likely small. Therefore, we don't use hash tables.
+ */
+static LIST_HEAD(cs_accepted_socket_list);
+/* Lock for protecting cs_accepted_socket_list . */
+static DEFINE_SPINLOCK(cs_accepted_socket_list_lock);
+
+/**
+ * cs_update_socket_tag - Update tag associated with accept()ed sockets.
+ *
+ * @inode:  Pointer to "struct inode".
+ * @status: New status.
+ *
+ * Returns nothing.
+ *
+ * If @status == 0, memory for that socket will be released after RCU grace
+ * period.
+ */
+static void cs_update_socket_tag(struct inode *inode, int status)
+{
+	struct cs_socket_tag *ptr;
+	/*
+	 * Protect whole section because multiple threads may call this
+	 * function with same "sock" via cs_validate_socket().
+	 */
+	spin_lock(&cs_accepted_socket_list_lock);
+	rcu_read_lock();
+	list_for_each_entry_rcu(ptr, &cs_accepted_socket_list, list) {
+		if (ptr->inode != inode)
+			continue;
+		ptr->status = status;
+		if (status)
+			break;
+		list_del_rcu(&ptr->list);
+		kfree_rcu(ptr, rcu);
+		break;
+	}
+	rcu_read_unlock();
+	spin_unlock(&cs_accepted_socket_list_lock);
+}
+
+/**
+ * cs_validate_socket - Check post accept() permission if needed.
+ *
+ * @sock: Pointer to "struct socket".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_validate_socket(struct socket *sock)
+{
+	struct inode *inode = SOCK_INODE(sock);
+	struct cs_socket_tag *ptr;
+	int ret = 0;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(ptr, &cs_accepted_socket_list, list) {
+		if (ptr->inode != inode)
+			continue;
+		ret = ptr->status;
+		break;
+	}
+	rcu_read_unlock();
+	if (ret <= 0)
+		/*
+		 * This socket is not an accept()ed socket or this socket is
+		 * an accept()ed socket and post accept() permission is done.
+		 */
+		return ret;
+	/*
+	 * Check post accept() permission now.
+	 *
+	 * Strictly speaking, we need to pass both listen()ing socket and
+	 * accept()ed socket to __cs_socket_post_accept_permission().
+	 * But since socket's family and type are same for both sockets,
+	 * passing the accept()ed socket in place for the listen()ing socket
+	 * will work.
+	 */
+	ret = cs_socket_post_accept_permission(sock, sock);
+	/*
+	 * If permission was granted, we forget that this is an accept()ed
+	 * socket. Otherwise, we remember that this socket needs to return
+	 * error for subsequent socketcalls.
+	 */
+	cs_update_socket_tag(inode, ret);
+	return ret;
+}
+
+/**
+ * cs_socket_accept - Check permission for accept().
+ *
+ * @sock:    Pointer to "struct socket".
+ * @newsock: Pointer to "struct socket".
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * This hook is used for setting up environment for doing post accept()
+ * permission check. If dereferencing sock->ops->something() were ordered by
+ * rcu_dereference(), we could replace sock->ops with "a copy of original
+ * sock->ops with modified sock->ops->accept()" using rcu_assign_pointer()
+ * in order to do post accept() permission check before returning to userspace.
+ * If we make the copy in security_socket_post_create(), it would be possible
+ * to safely replace sock->ops here, but we don't do so because we don't want
+ * to allocate memory for sockets which do not call sock->ops->accept().
+ * Therefore, we do post accept() permission check upon next socket syscalls
+ * rather than between sock->ops->accept() and returning to userspace.
+ * This means that if a socket was close()d before calling some socket
+ * syscalls, post accept() permission check will not be done.
+ */
+static int cs_socket_accept(struct socket *sock, struct socket *newsock)
+{
+	struct cs_socket_tag *ptr;
+	const int rc = cs_validate_socket(sock);
+
+	if (rc < 0)
+		return rc;
+	ptr = kzalloc(sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return -ENOMEM;
+	/*
+	 * Subsequent LSM hooks will receive "newsock". Therefore, I mark
+	 * "newsock" as "an accept()ed socket but post accept() permission
+	 * check is not done yet" by allocating memory using inode of the
+	 * "newsock" as a search key.
+	 */
+	ptr->inode = SOCK_INODE(newsock);
+	ptr->status = 1; /* Check post accept() permission later. */
+	spin_lock(&cs_accepted_socket_list_lock);
+	list_add_tail_rcu(&ptr->list, &cs_accepted_socket_list);
+	spin_unlock(&cs_accepted_socket_list_lock);
+	return 0;
+}
+
+/**
+ * cs_socket_listen - Check permission for listen().
+ *
+ * @sock:    Pointer to "struct socket".
+ * @backlog: Backlog parameter.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_listen(struct socket *sock, int backlog)
+{
+	const int rc = cs_validate_socket(sock);
+
+	if (rc < 0)
+		return rc;
+	return cs_socket_listen_permission(sock);
+}
+
+/**
+ * cs_socket_connect - Check permission for connect().
+ *
+ * @sock:     Pointer to "struct socket".
+ * @addr:     Pointer to "struct sockaddr".
+ * @addr_len: Size of @addr.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_connect(struct socket *sock, struct sockaddr *addr,
+			     int addr_len)
+{
+	const int rc = cs_validate_socket(sock);
+
+	if (rc < 0)
+		return rc;
+	return cs_socket_connect_permission(sock, addr, addr_len);
+}
+
+/**
+ * cs_socket_bind - Check permission for bind().
+ *
+ * @sock:     Pointer to "struct socket".
+ * @addr:     Pointer to "struct sockaddr".
+ * @addr_len: Size of @addr.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_bind(struct socket *sock, struct sockaddr *addr,
+			  int addr_len)
+{
+	const int rc = cs_validate_socket(sock);
+
+	if (rc < 0)
+		return rc;
+	return cs_socket_bind_permission(sock, addr, addr_len);
+}
+
+/**
+ * cs_socket_sendmsg - Check permission for sendmsg().
+ *
+ * @sock: Pointer to "struct socket".
+ * @msg:  Pointer to "struct msghdr".
+ * @size: Size of message.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_sendmsg(struct socket *sock, struct msghdr *msg,
+			     int size)
+{
+	const int rc = cs_validate_socket(sock);
+
+	if (rc < 0)
+		return rc;
+	return cs_socket_sendmsg_permission(sock, msg, size);
+}
+
+/**
+ * cs_socket_recvmsg - Check permission for recvmsg().
+ *
+ * @sock:  Pointer to "struct socket".
+ * @msg:   Pointer to "struct msghdr".
+ * @size:  Size of message.
+ * @flags: Flags.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_recvmsg(struct socket *sock, struct msghdr *msg,
+			     int size, int flags)
+{
+	return cs_validate_socket(sock);
+}
+
+/**
+ * cs_socket_getsockname - Check permission for getsockname().
+ *
+ * @sock: Pointer to "struct socket".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_getsockname(struct socket *sock)
+{
+	return cs_validate_socket(sock);
+}
+
+/**
+ * cs_socket_getpeername - Check permission for getpeername().
+ *
+ * @sock: Pointer to "struct socket".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_getpeername(struct socket *sock)
+{
+	return cs_validate_socket(sock);
+}
+
+/**
+ * cs_socket_getsockopt - Check permission for getsockopt().
+ *
+ * @sock:    Pointer to "struct socket".
+ * @level:   Level.
+ * @optname: Option's name,
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_getsockopt(struct socket *sock, int level, int optname)
+{
+	return cs_validate_socket(sock);
+}
+
+/**
+ * cs_socket_setsockopt - Check permission for setsockopt().
+ *
+ * @sock:    Pointer to "struct socket".
+ * @level:   Level.
+ * @optname: Option's name,
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_setsockopt(struct socket *sock, int level, int optname)
+{
+	return cs_validate_socket(sock);
+}
+
+/**
+ * cs_socket_shutdown - Check permission for shutdown().
+ *
+ * @sock: Pointer to "struct socket".
+ * @how:  Shutdown mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_socket_shutdown(struct socket *sock, int how)
+{
+	return cs_validate_socket(sock);
+}
+
+#define SOCKFS_MAGIC 0x534F434B
+
+/**
+ * cs_inode_free_security - Release memory associated with an inode.
+ *
+ * @inode: Pointer to "struct inode".
+ *
+ * Returns nothing.
+ *
+ * We use this hook for releasing memory associated with an accept()ed socket.
+ */
+static void cs_inode_free_security(struct inode *inode)
+{
+	if (inode->i_sb && inode->i_sb->s_magic == SOCKFS_MAGIC)
+		cs_update_socket_tag(inode, 0);
+}
+
+#endif
+
+/**
+ * cs_sb_pivotroot - Check permission for pivot_root().
+ *
+ * @old_path: Pointer to "struct path".
+ * @new_path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_sb_pivotroot(const struct path *old_path,
+			   const struct path *new_path)
+{
+	return cs_pivot_root_permission(old_path, new_path);
+}
+
+/**
+ * cs_sb_mount - Check permission for mount().
+ *
+ * @dev_name:  Name of device file.
+ * @path:      Pointer to "struct path".
+ * @type:      Name of filesystem type. Maybe NULL.
+ * @flags:     Mount options.
+ * @data_page: Optional data. Maybe NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_sb_mount(const char *dev_name, const struct path *path,
+		       const char *type, unsigned long flags, void *data_page)
+{
+	return cs_mount_permission(dev_name, path, type, flags, data_page);
+}
+
+/**
+ * cs_move_mount - Check permission for move_mount().
+ *
+ * @from_path: Pointer to "struct path".
+ * @to_path:   Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_move_mount(const struct path *from_path,
+			 const struct path *to_path)
+{
+	return cs_move_mount_permission(from_path, to_path);
+}
+
+/**
+ * cs_sb_umount - Check permission for umount().
+ *
+ * @mnt:   Pointer to "struct vfsmount".
+ * @flags: Unmount flags.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_sb_umount(struct vfsmount *mnt, int flags)
+{
+	struct path path = { .mnt = mnt, .dentry = mnt->mnt_root };
+
+	return cs_umount_permission(&path, flags);
+}
+
+/**
+ * cs_file_fcntl - Check permission for fcntl().
+ *
+ * @file: Pointer to "struct file".
+ * @cmd:  Command number.
+ * @arg:  Value for @cmd.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_file_fcntl(struct file *file, unsigned int cmd,
+			 unsigned long arg)
+{
+	return cs_fcntl_permission(file, cmd, arg);
+}
+
+/**
+ * cs_file_ioctl - Check permission for ioctl().
+ *
+ * @filp: Pointer to "struct file".
+ * @cmd:  Command number.
+ * @arg:  Value for @cmd.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_file_ioctl(struct file *filp, unsigned int cmd,
+			  unsigned long arg)
+{
+	return cs_ioctl_permission(filp, cmd, arg);
+}
+
+#define MY_HOOK_INIT LSM_HOOK_INIT
+
+static struct security_hook_list caitsith_hooks[] = {
+	/* Security context allocator. */
+	MY_HOOK_INIT(task_free, cs_task_free_security),
+	MY_HOOK_INIT(cred_prepare, cs_cred_prepare),
+	MY_HOOK_INIT(task_alloc, cs_task_alloc_security),
+	/* Security context updater for successful execve(). */
+	MY_HOOK_INIT(bprm_check_security, cs_bprm_check_security),
+	MY_HOOK_INIT(bprm_committing_creds, cs_bprm_committing_creds),
+	/* Various permission checker. */
+	MY_HOOK_INIT(file_open, cs_file_open),
+	MY_HOOK_INIT(file_fcntl, cs_file_fcntl),
+	MY_HOOK_INIT(file_ioctl, cs_file_ioctl),
+	MY_HOOK_INIT(sb_pivotroot, cs_sb_pivotroot),
+	MY_HOOK_INIT(sb_mount, cs_sb_mount),
+	MY_HOOK_INIT(move_mount, cs_move_mount),
+	MY_HOOK_INIT(sb_umount, cs_sb_umount),
+#ifdef CONFIG_SECURITY_PATH
+	MY_HOOK_INIT(path_mknod, cs_path_mknod),
+	MY_HOOK_INIT(path_mkdir, cs_path_mkdir),
+	MY_HOOK_INIT(path_rmdir, cs_path_rmdir),
+	MY_HOOK_INIT(path_unlink, cs_path_unlink),
+	MY_HOOK_INIT(path_symlink, cs_path_symlink),
+	MY_HOOK_INIT(path_rename, cs_path_rename),
+	MY_HOOK_INIT(path_link, cs_path_link),
+	MY_HOOK_INIT(path_truncate, cs_path_truncate),
+	MY_HOOK_INIT(path_chmod, cs_path_chmod),
+	MY_HOOK_INIT(path_chown, cs_path_chown),
+	MY_HOOK_INIT(path_chroot, cs_path_chroot),
+#else
+	MY_HOOK_INIT(inode_mknod, cs_inode_mknod),
+	MY_HOOK_INIT(inode_mkdir, cs_inode_mkdir),
+	MY_HOOK_INIT(inode_rmdir, cs_inode_rmdir),
+	MY_HOOK_INIT(inode_unlink, cs_inode_unlink),
+	MY_HOOK_INIT(inode_symlink, cs_inode_symlink),
+	MY_HOOK_INIT(inode_rename, cs_inode_rename),
+	MY_HOOK_INIT(inode_link, cs_inode_link),
+	MY_HOOK_INIT(inode_create, cs_inode_create),
+	MY_HOOK_INIT(inode_setattr, cs_inode_setattr),
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_GETATTR
+	MY_HOOK_INIT(inode_getattr, cs_inode_getattr),
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	MY_HOOK_INIT(socket_bind, cs_socket_bind),
+	MY_HOOK_INIT(socket_connect, cs_socket_connect),
+	MY_HOOK_INIT(socket_listen, cs_socket_listen),
+	MY_HOOK_INIT(socket_sendmsg, cs_socket_sendmsg),
+	MY_HOOK_INIT(socket_recvmsg, cs_socket_recvmsg),
+	MY_HOOK_INIT(socket_getsockname, cs_socket_getsockname),
+	MY_HOOK_INIT(socket_getpeername, cs_socket_getpeername),
+	MY_HOOK_INIT(socket_getsockopt, cs_socket_getsockopt),
+	MY_HOOK_INIT(socket_setsockopt, cs_socket_setsockopt),
+	MY_HOOK_INIT(socket_shutdown, cs_socket_shutdown),
+	MY_HOOK_INIT(socket_accept, cs_socket_accept),
+	MY_HOOK_INIT(inode_free_security, cs_inode_free_security),
+#endif
+};
+
+static inline void add_hook(struct security_hook_list *hook)
+{
+	hlist_add_tail_rcu(&hook->list, hook->head);
+}
+
+static void __init swap_hook(struct security_hook_list *hook,
+			     union security_list_options *original)
+{
+	struct hlist_head *list = hook->head;
+
+	if (hlist_empty(list)) {
+		add_hook(hook);
+	} else {
+		struct security_hook_list *shp =
+			hlist_entry(list->first, typeof(*shp), list);
+
+		while (shp->list.next)
+			shp = hlist_entry(shp->list.next, typeof(*shp), list);
+		*original = shp->hook;
+		/* Make sure that original callback is saved. */
+		smp_wmb();
+		shp->hook = hook->hook;
+	}
+}
+
+#if defined(CONFIG_STRICT_KERNEL_RWX) && !defined(CONFIG_SECURITY_WRITABLE_HOOKS)
+#include <linux/uaccess.h> /* copy_to_kernel_nofault() */
+#define NEED_TO_CHECK_HOOKS_ARE_WRITABLE
+
+#if defined(CONFIG_X86)
+#define MAX_RO_PAGES 1024
+static struct page *ro_pages[MAX_RO_PAGES] __initdata;
+static unsigned int ro_pages_len __initdata;
+
+static bool __init lsm_test_page_ro(void *addr)
+{
+	unsigned int i;
+	int unused;
+	struct page *page;
+
+	page = (struct page *) lookup_address((unsigned long) addr, &unused);
+	if (!page)
+		return false;
+	if (test_bit(_PAGE_BIT_RW, &(page->flags)))
+		return true;
+	for (i = 0; i < ro_pages_len; i++)
+		if (page == ro_pages[i])
+			return true;
+	if (ro_pages_len == MAX_RO_PAGES)
+		return false;
+	ro_pages[ro_pages_len++] = page;
+	return true;
+}
+
+static bool __init check_ro_pages(struct security_hook_heads *hooks)
+{
+	int i;
+	struct hlist_head *list = &hooks->capable;
+
+	if (!copy_to_kernel_nofault(list, list, sizeof(void *)))
+		return true;
+	for (i = 0; i < ARRAY_SIZE(caitsith_hooks); i++) {
+		struct hlist_head *head = caitsith_hooks[i].head;
+		struct security_hook_list *shp;
+
+		if (!lsm_test_page_ro(&head->first))
+			return false;
+		hlist_for_each_entry(shp, head, list)
+			if (!lsm_test_page_ro(&shp->list.next) ||
+			    !lsm_test_page_ro(&shp->list.pprev))
+				return false;
+	}
+	return true;
+}
+#else
+static bool __init check_ro_pages(struct security_hook_heads *hooks)
+{
+	struct hlist_head *list = &hooks->capable;
+
+	return !copy_to_kernel_nofault(list, list, sizeof(void *));
+}
+#endif
+#endif
+
+/**
+ * cs_init - Initialize this module.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int __init cs_init(void)
+{
+	int idx;
+#if defined(NEED_TO_CHECK_HOOKS_ARE_WRITABLE)
+	if (!check_ro_pages(&security_hook_heads)) {
+		pr_info("Can't update security_hook_heads due to write protected. Retry with rodata=0 kernel command line option added.\n");
+		return -EINVAL;
+	}
+#endif
+	for (idx = 0; idx < CS_MAX_TASK_SECURITY_HASH; idx++)
+		INIT_LIST_HEAD(&cs_task_security_list[idx]);
+	cs_init_module();
+#if defined(NEED_TO_CHECK_HOOKS_ARE_WRITABLE) && defined(CONFIG_X86)
+	for (idx = 0; idx < ro_pages_len; idx++)
+		set_bit(_PAGE_BIT_RW, &(ro_pages[idx]->flags));
+#endif
+	swap_hook(&caitsith_hooks[0], &original_task_free);
+	swap_hook(&caitsith_hooks[1], &original_cred_prepare);
+	swap_hook(&caitsith_hooks[2], &original_task_alloc);
+	for (idx = 3; idx < ARRAY_SIZE(caitsith_hooks); idx++)
+		add_hook(&caitsith_hooks[idx]);
+#if defined(NEED_TO_CHECK_HOOKS_ARE_WRITABLE) && defined(CONFIG_X86)
+	for (idx = 0; idx < ro_pages_len; idx++)
+		clear_bit(_PAGE_BIT_RW, &(ro_pages[idx]->flags));
+#endif
+	return 0;
+}
+
+module_init(cs_init);
+MODULE_LICENSE("GPL");
+
+/**
+ * cs_used_by_cred - Check whether the given domain is in use or not.
+ *
+ * @domain: Pointer to "struct cs_domain_info".
+ *
+ * Returns true if @domain is in use, false otherwise.
+ *
+ * Caller holds rcu_read_lock().
+ */
+bool cs_used_by_cred(const struct cs_domain_info *domain)
+{
+	return false;
+}
+
+/**
+ * cs_add_task_security - Add "struct cs_security" to list.
+ *
+ * @ptr:  Pointer to "struct cs_security".
+ * @list: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ */
+static void cs_add_task_security(struct cs_security *ptr,
+				 struct list_head *list)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&cs_task_security_list_lock, flags);
+	list_add_rcu(&ptr->list, list);
+	spin_unlock_irqrestore(&cs_task_security_list_lock, flags);
+}
+
+/**
+ * __cs_alloc_task_security - Allocate memory for new tasks.
+ *
+ * @task: Pointer to "struct task_struct".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int __cs_alloc_task_security(const struct task_struct *task)
+{
+	struct cs_security *old_security = cs_current_security();
+	struct cs_security *new_security = kzalloc(sizeof(*new_security),
+						   GFP_KERNEL);
+	struct list_head *list = &cs_task_security_list
+		[hash_ptr((void *) task, CS_TASK_SECURITY_HASH_BITS)];
+
+	if (!new_security)
+		return -ENOMEM;
+	new_security->task = task;
+	new_security->cs_domain_info = old_security->cs_domain_info;
+	new_security->cs_flags = old_security->cs_flags;
+	cs_add_task_security(new_security, list);
+	return 0;
+}
+
+/**
+ * cs_find_task_security - Find "struct cs_security" for given task.
+ *
+ * @task: Pointer to "struct task_struct".
+ *
+ * Returns pointer to "struct cs_security" on success, &cs_oom_security on
+ * out of memory, &cs_default_security otherwise.
+ *
+ * If @task is current thread and "struct cs_security" for current thread was
+ * not found, I try to allocate it. But if allocation failed, current thread
+ * will be killed by SIGKILL. Note that if current->pid == 1, sending SIGKILL
+ * won't work.
+ */
+struct cs_security *cs_find_task_security(const struct task_struct *task)
+{
+	struct cs_security *ptr;
+	struct list_head *list = &cs_task_security_list
+		[hash_ptr((void *) task, CS_TASK_SECURITY_HASH_BITS)];
+	/* Make sure INIT_LIST_HEAD() in cs_mm_init() takes effect. */
+	while (!list->next)
+		smp_rmb();
+	rcu_read_lock();
+	list_for_each_entry_rcu(ptr, list, list) {
+		if (ptr->task != task)
+			continue;
+		rcu_read_unlock();
+		/*
+		 * Current thread needs to transit from old domain to new
+		 * domain before do_execve() succeeds in order to check
+		 * permission for interpreters and environment variables using
+		 * new domain's ACL rules. The domain transition has to be
+		 * visible from other CPU in order to allow interactive
+		 * enforcing mode. Also, the domain transition has to be
+		 * reverted if do_execve() failed. However, an LSM hook for
+		 * reverting domain transition is missing.
+		 *
+		 * security_prepare_creds() is called from prepare_creds() from
+		 * prepare_bprm_creds() from do_execve() before setting
+		 * current->in_execve flag, and current->in_execve flag is
+		 * cleared by the time next do_execve() request starts.
+		 * This means that we can emulate the missing LSM hook for
+		 * reverting domain transition, by calling this function from
+		 * security_prepare_creds().
+		 *
+		 * If current->in_execve is not set but ptr->cs_flags has
+		 * CS_TASK_IS_IN_EXECVE set, it indicates that do_execve()
+		 * has failed and reverting domain transition is needed.
+		 */
+		if (task == current &&
+		    (ptr->cs_flags & CS_TASK_IS_IN_EXECVE) &&
+		    !current->in_execve) {
+			cs_debug_trace("1");
+			cs_clear_execve(-1, ptr);
+		}
+		return ptr;
+	}
+	rcu_read_unlock();
+	if (task != current)
+		return &cs_default_security;
+	/* Use GFP_ATOMIC because caller may have called rcu_read_lock(). */
+	ptr = kzalloc(sizeof(*ptr), GFP_ATOMIC);
+	if (!ptr) {
+		pr_warn("Unable to allocate memory for pid=%u\n",
+			task->pid);
+		send_sig(SIGKILL, current, 0);
+		return &cs_oom_security;
+	}
+	*ptr = cs_default_security;
+	ptr->task = task;
+	cs_add_task_security(ptr, list);
+	return ptr;
+}
-- 
2.18.4



More information about the Linux-security-module-archive mailing list