[PATCH 07/10] CaitSith: Add permission checking functions.
Tetsuo Handa
penguin-kernel at I-love.SAKURA.ne.jp
Wed Nov 2 17:10:22 UTC 2022
This file implements similar functions provided by many of
security/tomoyo/*.c files.
Signed-off-by: Tetsuo Handa <penguin-kernel at I-love.SAKURA.ne.jp>
---
security/caitsith/permission.c | 2746 ++++++++++++++++++++++++++++++++
1 file changed, 2746 insertions(+)
create mode 100644 security/caitsith/permission.c
diff --git a/security/caitsith/permission.c b/security/caitsith/permission.c
new file mode 100644
index 000000000000..0fd29e7f5d0a
--- /dev/null
+++ b/security/caitsith/permission.c
@@ -0,0 +1,2746 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * permission.c
+ *
+ * Copyright (C) 2005-2012 NTT DATA CORPORATION
+ *
+ * Version: 0.2.10 2021/06/06
+ */
+
+#include "caitsith.h"
+
+/***** SECTION1: Constants definition *****/
+
+/* String table for special mount operations. */
+static const char * const cs_mounts[CS_MAX_SPECIAL_MOUNT] = {
+ [CS_MOUNT_BIND] = "--bind",
+ [CS_MOUNT_MOVE] = "--move",
+ [CS_MOUNT_REMOUNT] = "--remount",
+ [CS_MOUNT_MAKE_UNBINDABLE] = "--make-unbindable",
+ [CS_MOUNT_MAKE_PRIVATE] = "--make-private",
+ [CS_MOUNT_MAKE_SLAVE] = "--make-slave",
+ [CS_MOUNT_MAKE_SHARED] = "--make-shared",
+};
+
+#ifdef CONFIG_SECURITY_CAITSITH_CAPABILITY
+
+/*
+ * Mapping table from "enum cs_capability_acl_index" to "enum cs_mac_index".
+ */
+static const u8 cs_c2mac[CS_MAX_CAPABILITY_INDEX] = {
+ [CS_USE_ROUTE_SOCKET] = CS_MAC_USE_NETLINK_SOCKET,
+ [CS_USE_PACKET_SOCKET] = CS_MAC_USE_PACKET_SOCKET,
+};
+
+#endif
+
+/* Type of condition argument. */
+enum cs_arg_type {
+ CS_ARG_TYPE_NONE,
+ CS_ARG_TYPE_NUMBER,
+ CS_ARG_TYPE_NAME,
+ CS_ARG_TYPE_GROUP,
+ CS_ARG_TYPE_BITOP,
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+ CS_ARG_TYPE_IPV4ADDR,
+ CS_ARG_TYPE_IPV6ADDR,
+#endif
+} __packed;
+
+/***** SECTION2: Structure definition *****/
+
+/* Structure for holding inet domain socket's address. */
+struct cs_inet_addr_info {
+ u16 port; /* In network byte order. */
+ const u8 *address; /* In network byte order. */
+ bool is_ipv6;
+};
+
+/* Structure for holding unix domain socket's address. */
+struct cs_unix_addr_info {
+ u8 *addr; /* This may not be '\0' terminated string. */
+ unsigned int addr_len;
+};
+
+/* Structure for holding socket address. */
+struct cs_addr_info {
+ u8 operation;
+ struct cs_inet_addr_info inet;
+ struct cs_unix_addr_info unix0;
+};
+
+/* Structure for holding single condition component. */
+struct cs_cond_arg {
+ enum cs_arg_type type;
+ unsigned long value[2];
+ const struct cs_path_info *name;
+ const struct cs_group *group;
+ struct in6_addr ip[2];
+};
+
+/***** SECTION3: Prototype definition section *****/
+
+static bool cs_alphabet_char(const char c);
+static bool cs_byte_range(const char *str);
+static bool cs_check_entry(struct cs_request_info *r,
+ const struct cs_acl_info *ptr);
+static bool cs_condition(struct cs_request_info *r,
+ const struct cs_condition *cond);
+static bool cs_file_matches_pattern(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end);
+static bool cs_file_matches_pattern2(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end);
+static bool cs_number_matches_group(const unsigned long min,
+ const unsigned long max,
+ const struct cs_group *group);
+static bool cs_path_matches_pattern(const struct cs_path_info *filename,
+ const struct cs_path_info *pattern);
+static bool cs_path_matches_pattern2(const char *f, const char *p);
+static bool cs_path_matches_group(const struct cs_path_info *pathname,
+ const struct cs_group *group);
+static int cs_execute_path(struct linux_binprm *bprm, struct path *path);
+static int cs_execute(struct cs_request_info *r);
+static int cs_kern_path(const char *pathname, int flags, struct path *path);
+static int cs_mkdev_perm(const u8 operation, const struct path *path,
+ const unsigned int mode, unsigned int dev);
+static int cs_mount_acl(const char *dev_name, const struct path *dir,
+ const char *type, unsigned long flags,
+ const char *data);
+static int cs_path2_perm(const enum cs_mac_index operation,
+ const struct path *path1, const struct path *path2);
+static int cs_path_number_perm(const enum cs_mac_index type,
+ const struct path *path, unsigned long number);
+static int cs_path_perm(const enum cs_mac_index operation,
+ const struct path *path);
+static void cs_check_auto_domain_transition(void);
+static void cs_clear_request_info(struct cs_request_info *r);
+
+#ifdef CONFIG_SECURITY_CAITSITH_ENVIRON
+static int cs_env_perm(struct cs_request_info *r, const char *name,
+ const char *value);
+static int cs_environ(struct cs_request_info *r);
+#endif
+
+#ifdef CONFIG_SECURITY_CAITSITH_CAPABILITY
+static bool cs_kernel_service(void);
+#endif
+
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+static bool cs_ip_matches_group(const bool is_ipv6, const u8 *address,
+ const struct cs_group *group);
+static bool cs_kernel_service(void);
+static int cs_check_inet_address(const struct sockaddr *addr,
+ const unsigned int addr_len, const u16 port,
+ struct cs_addr_info *address);
+static int cs_check_unix_address(struct sockaddr *addr,
+ const unsigned int addr_len,
+ struct cs_addr_info *address);
+static int cs_inet_entry(const struct cs_addr_info *address);
+static int cs_unix_entry(const struct cs_addr_info *address);
+static u8 cs_sock_family(struct sock *sk);
+#endif
+
+/***** SECTION4: Standalone functions section *****/
+
+/**
+ * cs_put_filesystem - Wrapper for put_filesystem().
+ *
+ * @fstype: Pointer to "struct file_system_type".
+ *
+ * Returns nothing.
+ *
+ * Since put_filesystem() is not exported, I embed put_filesystem() here.
+ */
+static inline void cs_put_filesystem(struct file_system_type *fstype)
+{
+ module_put(fstype->owner);
+}
+
+/***** SECTION5: Variables definition section *****/
+
+/* The initial domain. */
+struct cs_domain_info cs_kernel_domain;
+
+/* The list for "struct cs_domain_info". */
+LIST_HEAD(cs_domain_list);
+
+/* The list for ACL policy. */
+struct list_head cs_acl_list[CS_MAX_MAC_INDEX];
+
+/* NULL value. */
+struct cs_path_info cs_null_name;
+
+/***** SECTION6: Dependent functions section *****/
+
+/**
+ * cs_path_matches_group - Check whether the given pathname matches members of the given pathname group.
+ *
+ * @pathname: The name of pathname.
+ * @group: Pointer to "struct cs_string_group".
+ *
+ * Returns true if @pathname matches pathnames in @group, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_path_matches_group(const struct cs_path_info *pathname,
+ const struct cs_group *group)
+{
+ struct cs_string_group *member;
+
+ list_for_each_entry_srcu(member, &group->member_list, head.list,
+ &cs_ss) {
+ if (member->head.is_deleted)
+ continue;
+ if (!cs_path_matches_pattern(pathname, member->member_name))
+ continue;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * cs_number_matches_group - Check whether the given number matches members of the given number group.
+ *
+ * @min: Min number.
+ * @max: Max number.
+ * @group: Pointer to "struct cs_number_group".
+ *
+ * Returns true if @min and @max partially overlaps @group, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_number_matches_group(const unsigned long min,
+ const unsigned long max,
+ const struct cs_group *group)
+{
+ struct cs_number_group *member;
+ bool matched = false;
+
+ list_for_each_entry_srcu(member, &group->member_list, head.list,
+ &cs_ss) {
+ if (member->head.is_deleted)
+ continue;
+ if (min > member->value[1] || max < member->value[0])
+ continue;
+ matched = true;
+ break;
+ }
+ return matched;
+}
+
+/**
+ * cs_check_entry - Do permission check.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @ptr: Pointer to "struct cs_acl_info".
+ *
+ * Returns true on match, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_check_entry(struct cs_request_info *r,
+ const struct cs_acl_info *ptr)
+{
+ return !ptr->is_deleted && cs_condition(r, ptr->cond);
+}
+
+/**
+ * cs_check_acl_list - Do permission check.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_check_acl_list(struct cs_request_info *r)
+{
+ struct cs_acl_info *ptr;
+ int error = 0;
+ struct list_head * const list = &cs_acl_list[r->type];
+
+ r->matched_acl = NULL;
+ list_for_each_entry_srcu(ptr, list, list, &cs_ss) {
+ struct cs_acl_info *ptr2;
+retry:
+ if (!cs_check_entry(r, ptr)) {
+ if (unlikely(r->failed_by_oom))
+ goto oom;
+ continue;
+ }
+ r->matched_acl = ptr;
+ r->audit = ptr->audit;
+ r->result = CS_MATCHING_UNMATCHED;
+ list_for_each_entry_srcu(ptr2, &ptr->acl_info_list, list,
+ &cs_ss) {
+ r->transition_candidate = NULL;
+ if (!cs_check_entry(r, ptr2)) {
+ if (unlikely(r->failed_by_oom))
+ goto oom;
+ continue;
+ }
+ if (ptr2->is_deny) {
+ r->result = CS_MATCHING_DENIED;
+ break;
+ }
+ r->result = CS_MATCHING_ALLOWED;
+ /* Set the first matching domain transition entry. */
+ if (r->transition_candidate && !r->transition)
+ r->transition = r->transition_candidate;
+ break;
+ }
+ error = cs_audit_log(r);
+ /* Ignore out of memory during audit. */
+ r->failed_by_oom = false;
+ if (!error)
+ continue;
+ if (error == CS_RETRY_REQUEST)
+ goto retry;
+ break;
+ }
+ return error;
+oom:
+ /*
+ * If conditions could not be checked due to out of memory,
+ * reject the request with -ENOMEM, for we don't know whether
+ * there was a possibility of matching "deny" lines or not.
+ */
+ {
+ static unsigned long cs_last_oom;
+ unsigned long oom = ktime_get_real_seconds();
+
+ if (oom != cs_last_oom) {
+ cs_last_oom = oom;
+ pr_info("CaitSith: Rejecting access request due to out of memory.\n");
+ }
+ }
+ return -ENOMEM;
+}
+
+/**
+ * cs_check_acl - Do permission check.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @clear: True to cleanup @r before return, false otherwise.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * If "transition=" part was specified to "allow" entries of non "execute" acl
+ * but transition to that domain failed due to e.g. memory quota, the current
+ * thread will be killed by SIGKILL.
+ */
+int cs_check_acl(struct cs_request_info *r, const bool clear)
+{
+ int error;
+ const int idx = cs_read_lock();
+
+ error = cs_check_acl_list(r);
+ if (r->transition && r->transition != &cs_null_name &&
+ r->result == CS_MATCHING_ALLOWED && r->type != CS_MAC_EXECUTE &&
+ !cs_transit_domain(r->transition->name)) {
+ pr_warn("ERROR: Unable to transit to '%s' domain.\n",
+ r->transition->name);
+ force_sig(SIGKILL);
+ }
+ cs_read_unlock(idx);
+ if (clear)
+ cs_clear_request_info(r);
+ return error;
+}
+
+/**
+ * cs_execute - Check permission for "execute".
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_execute(struct cs_request_info *r)
+{
+ int retval;
+
+ /* Get symlink's dentry/vfsmount. */
+ retval = cs_execute_path(r->bprm, &r->obj.path[1]);
+ if (retval < 0)
+ return retval;
+ cs_populate_patharg(r, false);
+ if (!r->param.s[1])
+ return -ENOMEM;
+
+ /* Check execute permission. */
+ r->type = CS_MAC_EXECUTE;
+ retval = cs_check_acl(r, false);
+ if (retval < 0)
+ return retval;
+ /*
+ * Tell GC that I started execve().
+ * Also, tell open_exec() to check read permission.
+ */
+ cs_current_security()->cs_flags |= CS_TASK_IS_IN_EXECVE;
+ if (!r->transition || r->transition == &cs_null_name)
+ /* Keep current domain. */
+ return 0;
+ /*
+ * Make cs_current_security()->cs_flags visible to GC before changing
+ * cs_current_security()->cs_domain_info.
+ */
+ smp_wmb();
+ /*
+ * Transit to the specified domain.
+ * It will be reverted if execve() failed.
+ */
+ if (cs_transit_domain(r->transition->name))
+ return 0;
+ pr_warn("ERROR: Domain '%s' not ready.\n",
+ r->transition->name);
+ return -ENOMEM;
+}
+
+/**
+ * cs_dump_page - Dump a page to buffer.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @pos: Location to dump.
+ * @dump: Pointer to "struct cs_page_dump".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool cs_dump_page(struct linux_binprm *bprm, unsigned long pos,
+ struct cs_page_dump *dump)
+{
+ struct page *page;
+ int ret;
+
+ /* dump->data is released by cs_start_execve(). */
+ if (!dump->data) {
+ dump->data = kzalloc(PAGE_SIZE, GFP_NOFS);
+ if (!dump->data)
+ return false;
+ }
+ /* Same with get_arg_page(bprm, pos, 0) in fs/exec.c */
+#ifdef CONFIG_MMU
+ mmap_read_lock(bprm->mm);
+ ret = get_user_pages_remote(bprm->mm, pos, 1, FOLL_FORCE, &page, NULL, NULL);
+ mmap_read_unlock(bprm->mm);
+ if (ret <= 0)
+ return false;
+#else
+ page = bprm->page[pos / PAGE_SIZE];
+#endif
+ if (page != dump->page) {
+ const unsigned int offset = pos % PAGE_SIZE;
+ /*
+ * Maybe kmap()/kunmap() should be used here.
+ * But remove_arg_zero() uses kmap_atomic()/kunmap_atomic().
+ * So do I.
+ */
+ char *kaddr = kmap_atomic(page);
+
+ dump->page = page;
+ memcpy(dump->data + offset, kaddr + offset,
+ PAGE_SIZE - offset);
+ kunmap_atomic(kaddr);
+ }
+ /* Same with put_arg_page(page) in fs/exec.c */
+#ifdef CONFIG_MMU
+ put_page(page);
+#endif
+ return true;
+}
+
+/**
+ * cs_start_execve - Prepare for execve() operation.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @rp: Pointer to "struct cs_request_info *".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_start_execve(struct linux_binprm *bprm, struct cs_request_info **rp)
+{
+ int retval;
+ struct cs_security *task = cs_current_security();
+ struct cs_request_info *r;
+ int idx;
+ *rp = NULL;
+ r = kzalloc(sizeof(*r), GFP_NOFS);
+ if (!r)
+ return -ENOMEM;
+ r->tmp = kzalloc(CS_EXEC_TMPSIZE, GFP_NOFS);
+ if (!r->tmp) {
+ kfree(r);
+ return -ENOMEM;
+ }
+ idx = cs_read_lock();
+ /* r->dump->data is allocated by cs_dump_page(). */
+ r->previous_domain = task->cs_domain_info;
+ /* Clear manager flag. */
+ task->cs_flags &= ~CS_TASK_IS_MANAGER;
+ *rp = r;
+ r->bprm = bprm;
+ r->obj.path[0] = bprm->file->f_path;
+ retval = cs_execute(r);
+#ifdef CONFIG_SECURITY_CAITSITH_ENVIRON
+ if (!retval && bprm->envc)
+ retval = cs_environ(r);
+#endif
+ cs_clear_request_info(r);
+ /* Drop refcount obtained by cs_execute_path(). */
+ if (r->obj.path[1].dentry) {
+ path_put(&r->obj.path[1]);
+ r->obj.path[1].dentry = NULL;
+ }
+ cs_read_unlock(idx);
+ kfree(r->tmp);
+ r->tmp = NULL;
+ kfree(r->dump.data);
+ r->dump.data = NULL;
+ return retval;
+}
+
+/**
+ * cs_finish_execve - Clean up execve() operation.
+ *
+ * @retval: Return code of an execve() operation.
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns nothing.
+ */
+void cs_finish_execve(int retval, struct cs_request_info *r)
+{
+ struct cs_security *task;
+
+ if (!r)
+ return;
+ task = cs_current_security();
+ if (retval < 0) {
+ task->cs_domain_info = r->previous_domain;
+ /*
+ * Make task->cs_domain_info visible to GC before changing
+ * task->cs_flags.
+ */
+ smp_wmb();
+ }
+ /* Tell GC that I finished execve(). */
+ task->cs_flags &= ~CS_TASK_IS_IN_EXECVE;
+ cs_clear_request_info(r);
+ kfree(r);
+}
+
+/**
+ * cs_kern_path - Wrapper for kern_path().
+ *
+ * @pathname: Pathname to resolve. Maybe NULL.
+ * @flags: Lookup flags.
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_kern_path(const char *pathname, int flags, struct path *path)
+{
+ if (!pathname || kern_path(pathname, flags, path))
+ return -ENOENT;
+ return 0;
+}
+
+/**
+ * cs_execute_path - Get dentry/vfsmount of a program.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_execute_path(struct linux_binprm *bprm, struct path *path)
+{
+ /*
+ * Follow symlinks if the requested pathname is on procfs, for
+ * /proc/\$/exe is meaningless.
+ */
+ const unsigned int follow =
+ (bprm->file->f_path.dentry->d_sb->s_magic == PROC_SUPER_MAGIC)
+ ? LOOKUP_FOLLOW : 0;
+ if (cs_kern_path(bprm->filename, follow, path))
+ return -ENOENT;
+ return 0;
+}
+
+/**
+ * cs_mount_acl - Check permission for mount() operation.
+ *
+ * @dev_name: Name of device file or mount source. Maybe NULL.
+ * @dir: Pointer to "struct path".
+ * @type: Name of filesystem type. Maybe NULL.
+ * @flags: Mount options.
+ * @data: Mount options not in @flags. Maybe NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_mount_acl(const char *dev_name, const struct path *dir,
+ const char *type, unsigned long flags,
+ const char *data)
+{
+ struct cs_request_info r = { };
+ struct cs_path_info rtype = { };
+ struct cs_path_info rdata = { };
+ bool check_dev = false;
+ bool check_data = false;
+ int error;
+
+ /* Compare fstype in order to determine type of dev_name argument. */
+ if (type == cs_mounts[CS_MOUNT_REMOUNT]) {
+ /* do_remount() case. */
+ if (data && !(dir->mnt->mnt_sb->s_type->fs_flags &
+ FS_BINARY_MOUNTDATA))
+ check_data = true;
+ } else if (type == cs_mounts[CS_MOUNT_BIND]) {
+ /* do_loopback() case. */
+ check_dev = true;
+ } else if (type == cs_mounts[CS_MOUNT_MAKE_UNBINDABLE] ||
+ type == cs_mounts[CS_MOUNT_MAKE_PRIVATE] ||
+ type == cs_mounts[CS_MOUNT_MAKE_SLAVE] ||
+ type == cs_mounts[CS_MOUNT_MAKE_SHARED]) {
+ /* do_change_type() case. */
+ } else if (type == cs_mounts[CS_MOUNT_MOVE]) {
+ /* do_move_mount() case. */
+ check_dev = true;
+ } else {
+ /* do_new_mount() case. */
+ struct file_system_type *fstype;
+
+ if (!type)
+ return -EINVAL;
+ fstype = get_fs_type(type);
+ if (!fstype)
+ return -ENODEV;
+ if (fstype->fs_flags & FS_REQUIRES_DEV)
+ check_dev = true;
+ if (data && !(fstype->fs_flags & FS_BINARY_MOUNTDATA))
+ check_data = true;
+ cs_put_filesystem(fstype);
+ }
+ /* Start filling arguments. */
+ r.type = CS_MAC_MOUNT;
+ /* Remember mount options. */
+ r.param.i[0] = flags;
+ /*
+ * Remember mount point.
+ * r.param.s[1] is calculated from r.obj.path[1] as needed.
+ */
+ r.obj.path[1] = *dir;
+ /* Remember fstype. */
+ rtype.name = cs_encode(type);
+ if (!rtype.name)
+ return -ENOMEM;
+ cs_fill_path_info(&rtype);
+ r.param.s[2] = &rtype;
+ if (check_data) {
+ /* Remember data argument. */
+ rdata.name = cs_encode(data);
+ if (!rdata.name) {
+ error = -ENOMEM;
+ goto out;
+ }
+ cs_fill_path_info(&rdata);
+ r.param.s[3] = &rdata;
+ }
+ if (check_dev) {
+ /*
+ * Remember device file or mount source.
+ * r.param.s[0] is calculated from r.obj.path[0] as needed.
+ */
+ if (cs_kern_path(dev_name, LOOKUP_FOLLOW, &r.obj.path[0])) {
+ error = -ENOENT;
+ goto out;
+ }
+ }
+ error = cs_check_acl(&r, false);
+ /* Drop refcount obtained by cs_kern_path(). */
+ if (check_dev)
+ path_put(&r.obj.path[0]);
+out:
+ kfree(rtype.name);
+ kfree(rdata.name);
+ cs_clear_request_info(&r);
+ return error;
+}
+
+/**
+ * cs_mount_permission - Check permission for mount() operation.
+ *
+ * @dev_name: Name of device file. Maybe NULL.
+ * @path: Pointer to "struct path".
+ * @type: Name of filesystem type. Maybe NULL.
+ * @flags: Mount options.
+ * @data_page: Mount options not in @flags. Maybe NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_mount_permission(const char *dev_name, const struct path *path,
+ const char *type, unsigned long flags,
+ void *data_page)
+{
+ if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
+ flags &= ~MS_MGC_MSK;
+ if (flags & MS_REMOUNT) {
+ type = cs_mounts[CS_MOUNT_REMOUNT];
+ flags &= ~MS_REMOUNT;
+ } else if (flags & MS_BIND) {
+ type = cs_mounts[CS_MOUNT_BIND];
+ flags &= ~MS_BIND;
+ } else if (flags & MS_SHARED) {
+ if (flags & (MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
+ return -EINVAL;
+ type = cs_mounts[CS_MOUNT_MAKE_SHARED];
+ flags &= ~MS_SHARED;
+ } else if (flags & MS_PRIVATE) {
+ if (flags & (MS_SHARED | MS_SLAVE | MS_UNBINDABLE))
+ return -EINVAL;
+ type = cs_mounts[CS_MOUNT_MAKE_PRIVATE];
+ flags &= ~MS_PRIVATE;
+ } else if (flags & MS_SLAVE) {
+ if (flags & (MS_SHARED | MS_PRIVATE | MS_UNBINDABLE))
+ return -EINVAL;
+ type = cs_mounts[CS_MOUNT_MAKE_SLAVE];
+ flags &= ~MS_SLAVE;
+ } else if (flags & MS_UNBINDABLE) {
+ if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE))
+ return -EINVAL;
+ type = cs_mounts[CS_MOUNT_MAKE_UNBINDABLE];
+ flags &= ~MS_UNBINDABLE;
+ } else if (flags & MS_MOVE) {
+ type = cs_mounts[CS_MOUNT_MOVE];
+ flags &= ~MS_MOVE;
+ }
+ /*
+ * do_mount() terminates data_page with '\0' if data_page != NULL.
+ * Therefore, it is safe to pass data_page argument to cs_mount_acl()
+ * as "const char *" rather than "void *".
+ */
+ cs_check_auto_domain_transition();
+ return cs_mount_acl(dev_name, path, type, flags, data_page);
+}
+
+/**
+ * cs_move_mount_permission - Check permission for move_mount() operation.
+ *
+ * @from_path: Pointer to "struct path".
+ * @to_path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_move_mount_permission(const struct path *from_path,
+ const struct path *to_path)
+{
+ return 0; /* For now. */
+}
+
+/**
+ * cs_open_permission - Check permission for "read" and "write".
+ *
+ * @path: Pointer to "struct path".
+ * @flag: Flags for open().
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_open_permission(const struct path *path, const int flag)
+{
+ struct cs_request_info r = { };
+ const u32 cs_flags = cs_current_flags();
+ const u8 acc_mode = (flag & 3) == 3 ? 0 : ACC_MODE(flag);
+ int error = 0;
+
+ if (current->in_execve && !(cs_flags & CS_TASK_IS_IN_EXECVE))
+ return 0;
+#ifndef CONFIG_SECURITY_CAITSITH_READDIR
+ if (d_is_dir(path->dentry))
+ return 0;
+#endif
+ r.obj.path[0] = *path;
+ if (!(cs_flags & CS_TASK_IS_IN_EXECVE))
+ cs_check_auto_domain_transition();
+ if (acc_mode & MAY_READ) {
+ r.type = CS_MAC_READ;
+ error = cs_check_acl(&r, false);
+ }
+ if (!error && (acc_mode & MAY_WRITE)) {
+ r.type = (flag & O_APPEND) ? CS_MAC_APPEND : CS_MAC_WRITE;
+ error = cs_check_acl(&r, false);
+ }
+ cs_clear_request_info(&r);
+ return error;
+}
+
+/**
+ * cs_path_perm - Check permission for "unlink", "rmdir", "truncate", "append", "getattr" and "chroot".
+ *
+ * @operation: One of values in "enum cs_mac_index".
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_perm(const enum cs_mac_index operation,
+ const struct path *path)
+{
+ struct cs_request_info r = { };
+
+ cs_check_auto_domain_transition();
+ r.type = operation;
+ r.obj.path[0] = *path;
+ return cs_check_acl(&r, true);
+}
+
+/**
+ * cs_mkdev_perm - Check permission for "mkblock" and "mkchar".
+ *
+ * @operation: Type of operation. (CS_MAC_MKCHAR or CS_MAC_MKBLOCK)
+ * @path: Pointer to "struct path".
+ * @mode: Create mode.
+ * @dev: Device number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_mkdev_perm(const u8 operation, const struct path *path,
+ const unsigned int mode, unsigned int dev)
+{
+ struct cs_request_info r = { };
+
+ cs_check_auto_domain_transition();
+ r.obj.path[0] = *path;
+#ifdef CONFIG_SECURITY_PATH
+ dev = new_decode_dev(dev);
+#endif
+ r.type = operation;
+ r.param.i[0] = mode;
+ r.param.i[1] = MAJOR(dev);
+ r.param.i[2] = MINOR(dev);
+ return cs_check_acl(&r, true);
+}
+
+/**
+ * cs_path2_perm - Check permission for "rename", "link" and "pivot_root".
+ *
+ * @operation: One of values in "enum cs_mac_index".
+ * @path1: Pointer to "struct path".
+ * @path2: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path2_perm(const enum cs_mac_index operation,
+ const struct path *path1, const struct path *path2)
+{
+ struct cs_request_info r = { };
+
+ cs_check_auto_domain_transition();
+ r.type = operation;
+ r.obj.path[0] = *path1;
+ r.obj.path[1] = *path2;
+ return cs_check_acl(&r, true);
+}
+
+/**
+ * cs_symlink_permission - Check permission for "symlink".
+ *
+ * @path: Pointer to "struct path".
+ * @target: Content of symlink.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_symlink_permission(const struct path *path, const char *target)
+{
+ struct cs_request_info r = { };
+
+ cs_check_auto_domain_transition();
+ r.type = CS_MAC_SYMLINK;
+ r.obj.path[0] = *path;
+ r.obj.pathname[1].name = cs_encode(target);
+ if (!r.obj.pathname[1].name)
+ return -ENOMEM;
+ cs_fill_path_info(&r.obj.pathname[1]);
+ r.param.s[1] = &r.obj.pathname[1];
+ return cs_check_acl(&r, true);
+}
+
+/**
+ * cs_path_number_perm - Check permission for "create", "mkdir", "mkfifo", "mksock", "ioctl", "chmod", "chown", "chgrp" and "unmount".
+ *
+ * @type: One of values in "enum cs_mac_index".
+ * @path: Pointer to "struct path".
+ * @number: Number.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_path_number_perm(const enum cs_mac_index type,
+ const struct path *path, unsigned long number)
+{
+ struct cs_request_info r = { };
+
+ cs_check_auto_domain_transition();
+ r.type = type;
+ r.obj.path[0] = *path;
+ r.param.i[0] = number;
+ return cs_check_acl(&r, true);
+}
+
+/**
+ * cs_ioctl_permission - Check permission for "ioctl".
+ *
+ * @filp: Pointer to "struct file".
+ * @cmd: Ioctl command number.
+ * @arg: Param for @cmd.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_ioctl_permission(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ return cs_path_number_perm(CS_MAC_IOCTL, &filp->f_path, cmd);
+}
+
+/**
+ * cs_chmod_permission - Check permission for "chmod".
+ *
+ * @path: Pointer to "struct path".
+ * @mode: Mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_chmod_permission(const struct path *path, mode_t mode)
+{
+ return cs_path_number_perm(CS_MAC_CHMOD, path, mode & S_IALLUGO);
+}
+
+/**
+ * cs_chown_permission - Check permission for "chown/chgrp".
+ *
+ * @path: Pointer to "struct path".
+ * @user: User ID.
+ * @group: Group ID.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_chown_permission(const struct path *path, kuid_t user, kgid_t group)
+{
+ int error = 0;
+
+ if (uid_valid(user))
+ error = cs_path_number_perm(CS_MAC_CHOWN, path,
+ from_kuid(&init_user_ns, user));
+ if (!error && gid_valid(group))
+ error = cs_path_number_perm(CS_MAC_CHGRP, path,
+ from_kgid(&init_user_ns, group));
+ return error;
+}
+
+/**
+ * cs_fcntl_permission - Check permission for changing O_APPEND flag.
+ *
+ * @file: Pointer to "struct file".
+ * @cmd: Command number.
+ * @arg: Value for @cmd.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_fcntl_permission(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ if (!(cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND)))
+ return 0;
+ return cs_open_permission(&file->f_path, O_WRONLY | (arg & O_APPEND));
+}
+
+/**
+ * cs_pivot_root_permission - Check permission for pivot_root().
+ *
+ * @old_path: Pointer to "struct path".
+ * @new_path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_pivot_root_permission(const struct path *old_path,
+ const struct path *new_path)
+{
+ return cs_path2_perm(CS_MAC_PIVOT_ROOT, new_path, old_path);
+}
+
+/**
+ * cs_chroot_permission - Check permission for chroot().
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_chroot_permission(const struct path *path)
+{
+ return cs_path_perm(CS_MAC_CHROOT, path);
+}
+
+/**
+ * cs_umount_permission - Check permission for unmount.
+ *
+ * @path: Pointer to "struct path".
+ * @flags: Unmount flags.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_umount_permission(const struct path *path, int flags)
+{
+ return cs_path_number_perm(CS_MAC_UMOUNT, path, flags);
+}
+
+/**
+ * cs_mknod_permission - Check permission for vfs_mknod().
+ *
+ * @path: Pointer to "struct path".
+ * @mode: Device type and permission.
+ * @dev: Device number for block or character device.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_mknod_permission(const struct path *path, const unsigned int mode,
+ unsigned int dev)
+{
+ int error = 0;
+ const unsigned int perm = mode & S_IALLUGO;
+
+ switch (mode & S_IFMT) {
+ case S_IFCHR:
+ error = cs_mkdev_perm(CS_MAC_MKCHAR, path, perm, dev);
+ break;
+ case S_IFBLK:
+ error = cs_mkdev_perm(CS_MAC_MKBLOCK, path, perm, dev);
+ break;
+ case S_IFIFO:
+ error = cs_path_number_perm(CS_MAC_MKFIFO, path, perm);
+ break;
+ case S_IFSOCK:
+ error = cs_path_number_perm(CS_MAC_MKSOCK, path, perm);
+ break;
+ case 0:
+ case S_IFREG:
+ error = cs_path_number_perm(CS_MAC_CREATE, path, perm);
+ break;
+ }
+ return error;
+}
+
+/**
+ * cs_mkdir_permission - Check permission for vfs_mkdir().
+ *
+ * @path: Pointer to "struct path".
+ * @mode: Create mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_mkdir_permission(const struct path *path, unsigned int mode)
+{
+ return cs_path_number_perm(CS_MAC_MKDIR, path, mode);
+}
+
+/**
+ * cs_rmdir_permission - Check permission for vfs_rmdir().
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_rmdir_permission(const struct path *path)
+{
+ return cs_path_perm(CS_MAC_RMDIR, path);
+}
+
+/**
+ * cs_unlink_permission - Check permission for vfs_unlink().
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_unlink_permission(const struct path *path)
+{
+ return cs_path_perm(CS_MAC_UNLINK, path);
+}
+
+#ifdef CONFIG_SECURITY_CAITSITH_GETATTR
+
+/**
+ * cs_getattr_permission - Check permission for vfs_getattr().
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_getattr_permission(const struct path *path)
+{
+ return cs_path_perm(CS_MAC_GETATTR, path);
+}
+
+#endif
+
+/**
+ * cs_truncate_permission - Check permission for notify_change().
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_truncate_permission(const struct path *path)
+{
+ return cs_path_perm(CS_MAC_TRUNCATE, path);
+}
+
+/**
+ * cs_rename_permission - Check permission for vfs_rename().
+ *
+ * @old: Pointer to "struct path".
+ * @new: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_rename_permission(const struct path *old, const struct path *new)
+{
+ return cs_path2_perm(CS_MAC_RENAME, old, new);
+}
+
+/**
+ * cs_link_permission - Check permission for vfs_link().
+ *
+ * @old: Pointer to "struct path".
+ * @new: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_link_permission(const struct path *old, const struct path *new)
+{
+ return cs_path2_perm(CS_MAC_LINK, old, new);
+}
+
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+
+/**
+ * cs_ip_matches_group - Check whether the given IP address matches members of the given IP group.
+ *
+ * @is_ipv6: True if @address is an IPv6 address.
+ * @address: An IPv4 or IPv6 address.
+ * @group: Pointer to "struct cs_ip_group".
+ *
+ * Returns true if @address matches addresses in @group group, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_ip_matches_group(const bool is_ipv6, const u8 *address,
+ const struct cs_group *group)
+{
+ struct cs_ip_group *member;
+ bool matched = false;
+ const u8 size = is_ipv6 ? 16 : 4;
+
+ list_for_each_entry_srcu(member, &group->member_list, head.list,
+ &cs_ss) {
+ if (member->head.is_deleted)
+ continue;
+ if (member->is_ipv6 != is_ipv6)
+ continue;
+ if (memcmp(&member->ip[0], address, size) > 0 ||
+ memcmp(address, &member->ip[1], size) > 0)
+ continue;
+ matched = true;
+ break;
+ }
+ return matched;
+}
+
+/**
+ * cs_inet_entry - Check permission for INET network operation.
+ *
+ * @address: Pointer to "struct cs_addr_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_inet_entry(const struct cs_addr_info *address)
+{
+ struct cs_request_info r = { };
+
+ cs_check_auto_domain_transition();
+ r.type = address->operation;
+ r.param.is_ipv6 = address->inet.is_ipv6;
+ r.param.ip = address->inet.address;
+ r.param.i[0] = ntohs(address->inet.port);
+ return cs_check_acl(&r, true);
+}
+
+/**
+ * cs_check_inet_address - Check permission for inet domain socket's operation.
+ *
+ * @addr: Pointer to "struct sockaddr".
+ * @addr_len: Size of @addr.
+ * @port: Port number.
+ * @address: Pointer to "struct cs_addr_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_check_inet_address(const struct sockaddr *addr,
+ const unsigned int addr_len, const u16 port,
+ struct cs_addr_info *address)
+{
+ struct cs_inet_addr_info *i = &address->inet;
+
+ if (addr_len < sizeof(addr->sa_family))
+ goto skip;
+ switch (addr->sa_family) {
+ case AF_INET6:
+ if (addr_len < SIN6_LEN_RFC2133)
+ goto skip;
+ i->is_ipv6 = true;
+ i->address =
+ ((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr;
+ i->port = ((struct sockaddr_in6 *) addr)->sin6_port;
+ break;
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ goto skip;
+ i->is_ipv6 = false;
+ i->address = (u8 *) &((struct sockaddr_in *) addr)->sin_addr;
+ i->port = ((struct sockaddr_in *) addr)->sin_port;
+ break;
+ default:
+ goto skip;
+ }
+ if (address->operation == CS_MAC_INET_RAW_BIND ||
+ address->operation == CS_MAC_INET_RAW_SEND)
+ i->port = htons(port);
+ return cs_inet_entry(address);
+skip:
+ return 0;
+}
+
+/**
+ * cs_unix_entry - Check permission for UNIX network operation.
+ *
+ * @address: Pointer to "struct cs_addr_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_unix_entry(const struct cs_addr_info *address)
+{
+ int error;
+ char *buf = address->unix0.addr;
+ int len = address->unix0.addr_len - sizeof(sa_family_t);
+
+ if (len <= 0) {
+ buf = "anonymous";
+ len = 9;
+ } else if (buf[0]) {
+ len = strnlen(buf, len);
+ }
+ buf = cs_encode2(buf, len);
+ if (buf) {
+ struct cs_path_info addr;
+ struct cs_request_info r = { };
+
+ addr.name = buf;
+ cs_fill_path_info(&addr);
+ r.type = address->operation;
+ r.param.s[0] = &addr;
+ error = cs_check_acl(&r, true);
+ kfree(buf);
+ } else
+ error = -ENOMEM;
+ return error;
+}
+
+/**
+ * cs_check_unix_address - Check permission for unix domain socket's operation.
+ *
+ * @addr: Pointer to "struct sockaddr".
+ * @addr_len: Size of @addr.
+ * @address: Pointer to "struct cs_addr_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_check_unix_address(struct sockaddr *addr,
+ const unsigned int addr_len,
+ struct cs_addr_info *address)
+{
+ struct cs_unix_addr_info *u = &address->unix0;
+
+ if (addr_len < sizeof(addr->sa_family))
+ return 0;
+ if (addr->sa_family != AF_UNIX)
+ return 0;
+ u->addr = ((struct sockaddr_un *) addr)->sun_path;
+ u->addr_len = addr_len;
+ return cs_unix_entry(address);
+}
+
+/**
+ * cs_sock_family - Get socket's family.
+ *
+ * @sk: Pointer to "struct sock".
+ *
+ * Returns one of PF_INET, PF_INET6, PF_UNIX or 0.
+ */
+static u8 cs_sock_family(struct sock *sk)
+{
+ u8 family;
+
+ if (cs_kernel_service())
+ return 0;
+ family = sk->sk_family;
+ switch (family) {
+ case PF_INET:
+ case PF_INET6:
+ case PF_UNIX:
+ return family;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * cs_socket_listen_permission - Check permission for listening a socket.
+ *
+ * @sock: Pointer to "struct socket".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_socket_listen_permission(struct socket *sock)
+{
+ struct cs_addr_info address;
+ const u8 family = cs_sock_family(sock->sk);
+ const unsigned int type = sock->type;
+ struct sockaddr_storage addr;
+ int addr_len;
+
+ if (!family || (type != SOCK_STREAM && type != SOCK_SEQPACKET))
+ return 0;
+ addr_len = sock->ops->getname(sock, (struct sockaddr *) &addr, 0);
+ if (addr_len < 0)
+ return addr_len;
+ if (family == PF_INET || family == PF_INET6)
+ address.operation = CS_MAC_INET_STREAM_LISTEN;
+ else if (type == SOCK_STREAM)
+ address.operation = CS_MAC_UNIX_STREAM_LISTEN;
+ else
+ address.operation = CS_MAC_UNIX_SEQPACKET_LISTEN;
+ if (family == PF_UNIX)
+ return cs_check_unix_address((struct sockaddr *) &addr,
+ addr_len, &address);
+ return cs_check_inet_address((struct sockaddr *) &addr, addr_len, 0,
+ &address);
+}
+
+/**
+ * cs_socket_connect_permission - Check permission for setting the remote address of a socket.
+ *
+ * @sock: Pointer to "struct socket".
+ * @addr: Pointer to "struct sockaddr".
+ * @addr_len: Size of @addr.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_socket_connect_permission(struct socket *sock, struct sockaddr *addr,
+ int addr_len)
+{
+ struct cs_addr_info address;
+ const u8 family = cs_sock_family(sock->sk);
+
+ if (!family)
+ return 0;
+ switch (sock->type) {
+ case SOCK_DGRAM:
+ address.operation = family == PF_UNIX ?
+ CS_MAC_UNIX_DGRAM_SEND :
+ CS_MAC_INET_DGRAM_SEND;
+ break;
+ case SOCK_RAW:
+ address.operation = CS_MAC_INET_RAW_SEND;
+ break;
+ case SOCK_STREAM:
+ address.operation = family == PF_UNIX ?
+ CS_MAC_UNIX_STREAM_CONNECT :
+ CS_MAC_INET_STREAM_CONNECT;
+ break;
+ case SOCK_SEQPACKET:
+ address.operation = CS_MAC_UNIX_SEQPACKET_CONNECT;
+ break;
+ default:
+ return 0;
+ }
+ if (family == PF_UNIX)
+ return cs_check_unix_address(addr, addr_len, &address);
+ return cs_check_inet_address(addr, addr_len, sock->sk->sk_protocol,
+ &address);
+}
+
+/**
+ * cs_socket_bind_permission - Check permission for setting the local address of a socket.
+ *
+ * @sock: Pointer to "struct socket".
+ * @addr: Pointer to "struct sockaddr".
+ * @addr_len: Size of @addr.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_socket_bind_permission(struct socket *sock, struct sockaddr *addr,
+ int addr_len)
+{
+ struct cs_addr_info address;
+ const u8 family = cs_sock_family(sock->sk);
+ const unsigned int type = sock->type;
+
+ if (!family)
+ return 0;
+ switch (type) {
+ case SOCK_STREAM:
+ address.operation = family == PF_UNIX ?
+ CS_MAC_UNIX_STREAM_BIND :
+ CS_MAC_INET_STREAM_BIND;
+ break;
+ case SOCK_DGRAM:
+ address.operation = family == PF_UNIX ?
+ CS_MAC_UNIX_DGRAM_BIND :
+ CS_MAC_INET_DGRAM_BIND;
+ break;
+ case SOCK_RAW:
+ address.operation = CS_MAC_INET_RAW_BIND;
+ break;
+ case SOCK_SEQPACKET:
+ address.operation = CS_MAC_UNIX_SEQPACKET_BIND;
+ break;
+ default:
+ return 0;
+ }
+ if (family == PF_UNIX)
+ return cs_check_unix_address(addr, addr_len, &address);
+ return cs_check_inet_address(addr, addr_len, sock->sk->sk_protocol,
+ &address);
+}
+
+/**
+ * cs_socket_sendmsg_permission - Check permission for sending a datagram.
+ *
+ * @sock: Pointer to "struct socket".
+ * @msg: Pointer to "struct msghdr".
+ * @size: Unused.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_socket_sendmsg_permission(struct socket *sock, struct msghdr *msg,
+ int size)
+{
+ struct cs_addr_info address;
+ const u8 family = cs_sock_family(sock->sk);
+ const unsigned int type = sock->type;
+
+ if (!msg->msg_name || !family ||
+ (type != SOCK_DGRAM && type != SOCK_RAW))
+ return 0;
+ if (family == PF_UNIX)
+ address.operation = CS_MAC_UNIX_DGRAM_SEND;
+ else if (type == SOCK_DGRAM)
+ address.operation = CS_MAC_INET_DGRAM_SEND;
+ else
+ address.operation = CS_MAC_INET_RAW_SEND;
+ if (family == PF_UNIX)
+ return cs_check_unix_address((struct sockaddr *)
+ msg->msg_name, msg->msg_namelen,
+ &address);
+ return cs_check_inet_address((struct sockaddr *) msg->msg_name,
+ msg->msg_namelen, sock->sk->sk_protocol,
+ &address);
+}
+
+/**
+ * cs_socket_post_accept_permission - Check permission for accepting a socket.
+ *
+ * @sock: Pointer to "struct socket".
+ * @newsock: Pointer to "struct socket".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_socket_post_accept_permission(struct socket *sock,
+ struct socket *newsock)
+{
+ struct cs_addr_info address;
+ const u8 family = cs_sock_family(sock->sk);
+ const unsigned int type = sock->type;
+ struct sockaddr_storage addr;
+ int addr_len;
+
+ if (!family || (type != SOCK_STREAM && type != SOCK_SEQPACKET))
+ return 0;
+ addr_len = newsock->ops->getname(newsock, (struct sockaddr *) &addr,
+ 2);
+ if (addr_len < 0)
+ return addr_len;
+ if (family == PF_INET || family == PF_INET6)
+ address.operation = CS_MAC_INET_STREAM_ACCEPT;
+ else if (type == SOCK_STREAM)
+ address.operation = CS_MAC_UNIX_STREAM_ACCEPT;
+ else
+ address.operation = CS_MAC_UNIX_SEQPACKET_ACCEPT;
+ if (family == PF_UNIX)
+ return cs_check_unix_address((struct sockaddr *) &addr,
+ addr_len, &address);
+ return cs_check_inet_address((struct sockaddr *) &addr, addr_len, 0,
+ &address);
+}
+
+#endif
+
+#if defined(CONFIG_SECURITY_CAITSITH_CAPABILITY) || defined(CONFIG_SECURITY_CAITSITH_NETWORK)
+
+/**
+ * cs_kernel_service - Check whether I'm kernel service or not.
+ *
+ * Returns true if I'm kernel service, false otherwise.
+ */
+static bool cs_kernel_service(void)
+{
+ /* Nothing to do if I am a kernel service. */
+ return current->flags & PF_KTHREAD;
+}
+
+#endif
+
+#ifdef CONFIG_SECURITY_CAITSITH_CAPABILITY
+
+/**
+ * cs_capable - Check permission for capability.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns true on success, false otherwise.
+ */
+bool cs_capable(const u8 operation)
+{
+ struct cs_request_info r = { };
+
+ r.type = cs_c2mac[operation];
+ return !cs_check_acl(&r, true);
+}
+
+/**
+ * cs_socket_create_permission - Check permission for creating a socket.
+ *
+ * @family: Protocol family.
+ * @type: Unused.
+ * @protocol: Unused.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_socket_create_permission(int family, int type, int protocol)
+{
+ if (cs_kernel_service())
+ return 0;
+ if (family == PF_PACKET && !cs_capable(CS_USE_PACKET_SOCKET))
+ return -EPERM;
+ if (family == PF_NETLINK && !cs_capable(CS_USE_ROUTE_SOCKET))
+ return -EPERM;
+ return 0;
+}
+
+#endif
+
+/**
+ * cs_manager - Check whether the current process is a policy manager.
+ *
+ * Returns true if the current process is permitted to modify policy
+ * via /sys/kernel/security/caitsith/ interface.
+ *
+ * Caller holds cs_read_lock().
+ */
+bool cs_manager(void)
+{
+ struct cs_security *task;
+
+ if (!cs_policy_loaded)
+ return true;
+ task = cs_current_security();
+ if (task->cs_flags & CS_TASK_IS_MANAGER)
+ return true;
+ {
+ struct cs_request_info r = { };
+
+ r.type = CS_MAC_MODIFY_POLICY;
+ if (cs_check_acl(&r, true) == 0) {
+ /* Set manager flag. */
+ task->cs_flags |= CS_TASK_IS_MANAGER;
+ return true;
+ }
+ }
+ { /* Reduce error messages. */
+ static pid_t cs_last_pid;
+ const pid_t pid = current->pid;
+
+ if (cs_last_pid != pid) {
+ const char *exe = cs_get_exe();
+
+ pr_warn("'%s' (pid=%u domain='%s') is not permitted to update policies.\n",
+ exe, pid, task->cs_domain_info->domainname->name);
+ cs_last_pid = pid;
+ kfree(exe);
+ }
+ }
+ return false;
+}
+
+#ifdef CONFIG_SECURITY_CAITSITH_ENVIRON
+
+/**
+ * cs_env_perm - Check permission for environment variable's name.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @name: Name of environment variable. Maybe "".
+ * @value: Value of environment variable. Maybe "".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_env_perm(struct cs_request_info *r, const char *name,
+ const char *value)
+{
+ struct cs_path_info n;
+ struct cs_path_info v;
+
+ n.name = name;
+ cs_fill_path_info(&n);
+ v.name = value;
+ cs_fill_path_info(&v);
+ r->type = CS_MAC_ENVIRON;
+ r->param.s[2] = &n;
+ r->param.s[3] = &v;
+ return cs_check_acl(r, false);
+}
+
+/**
+ * cs_environ - Check permission for environment variable names.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_environ(struct cs_request_info *r)
+{
+ struct linux_binprm *bprm = r->bprm;
+ /* env_page.data is allocated by cs_dump_page(). */
+ struct cs_page_dump env_page = { };
+ char *arg_ptr; /* Size is CS_EXEC_TMPSIZE bytes */
+ int arg_len = 0;
+ unsigned long pos = bprm->p;
+ int offset = pos % PAGE_SIZE;
+ int argv_count = bprm->argc;
+ int envp_count = bprm->envc;
+ int error = -ENOMEM;
+
+ arg_ptr = kzalloc(CS_EXEC_TMPSIZE, GFP_NOFS);
+ if (!arg_ptr) {
+ r->failed_by_oom = true;
+ goto out;
+ }
+ while (error == -ENOMEM) {
+ if (!cs_dump_page(bprm, pos, &env_page)) {
+ r->failed_by_oom = true;
+ goto out;
+ }
+ pos += PAGE_SIZE - offset;
+ /* Read. */
+ while (argv_count && offset < PAGE_SIZE) {
+ if (!env_page.data[offset++])
+ argv_count--;
+ }
+ if (argv_count) {
+ offset = 0;
+ continue;
+ }
+ while (offset < PAGE_SIZE) {
+ char *value;
+ const unsigned char c = env_page.data[offset++];
+
+ if (c && arg_len < CS_EXEC_TMPSIZE - 10) {
+ if (c > ' ' && c < 127 && c != '\\') {
+ arg_ptr[arg_len++] = c;
+ } else {
+ arg_ptr[arg_len++] = '\\';
+ arg_ptr[arg_len++] = (c >> 6) + '0';
+ arg_ptr[arg_len++]
+ = ((c >> 3) & 7) + '0';
+ arg_ptr[arg_len++] = (c & 7) + '0';
+ }
+ } else {
+ arg_ptr[arg_len] = '\0';
+ }
+ if (c)
+ continue;
+ value = strchr(arg_ptr, '=');
+ if (value)
+ *value++ = '\0';
+ else
+ value = "";
+ if (cs_env_perm(r, arg_ptr, value)) {
+ error = -EPERM;
+ break;
+ }
+ if (!--envp_count) {
+ error = 0;
+ break;
+ }
+ arg_len = 0;
+ }
+ offset = 0;
+ }
+out:
+ kfree(env_page.data);
+ kfree(arg_ptr);
+ return error;
+}
+
+#endif
+
+/**
+ * cs_path_matches_group_or_pattern - Check whether the given pathname matches the given group or the given pattern.
+ *
+ * @path: Pointer to "struct cs_path_info".
+ * @group: Pointer to "struct cs_group". Maybe NULL.
+ * @pattern: Pointer to "struct cs_path_info". Maybe NULL.
+ * @match: True if positive match, false otherwise.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_path_matches_group_or_pattern
+(const struct cs_path_info *path, const struct cs_group *group,
+ const struct cs_path_info *pattern, const bool match)
+{
+ if (group)
+ return cs_path_matches_group(path, group) == match;
+ else if (pattern != &cs_null_name)
+ return cs_path_matches_pattern(path, pattern) == match;
+ else
+ return !match;
+}
+
+/**
+ * cs_check_argv - Check argv[] in "struct linux_binbrm".
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @index: Index number to check.
+ * @group: Pointer to "struct cs_group". Maybe NULL.
+ * @value: Pointer to "struct cs_path_info". NULL if @group != NULL.
+ * @match: True if positive match, false otherwise.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_check_argv(struct cs_request_info *r, unsigned long index,
+ const struct cs_group *group,
+ const struct cs_path_info *value,
+ const bool match)
+{
+ struct linux_binprm *bprm = r->bprm;
+ struct cs_page_dump *dump = &r->dump;
+ char *arg_ptr = r->tmp;
+ int arg_len = 0;
+ unsigned long pos = bprm->p;
+ int offset = pos % PAGE_SIZE;
+ struct cs_path_info arg;
+
+ if (index > bprm->argc)
+ return false;
+ while (1) {
+ if (!cs_dump_page(bprm, pos, dump)) {
+ r->failed_by_oom = true;
+ return false;
+ }
+ pos += PAGE_SIZE - offset;
+ while (offset < PAGE_SIZE) {
+ const unsigned char c = dump->data[offset++];
+
+ if (index) {
+ if (!c)
+ index--;
+ continue;
+ }
+ if (c && arg_len < CS_EXEC_TMPSIZE - 10) {
+ if (c > ' ' && c < 127 && c != '\\') {
+ arg_ptr[arg_len++] = c;
+ } else {
+ arg_ptr[arg_len++] = '\\';
+ arg_ptr[arg_len++] = (c >> 6) + '0';
+ arg_ptr[arg_len++] =
+ ((c >> 3) & 7) + '0';
+ arg_ptr[arg_len++] = (c & 7) + '0';
+ }
+ continue;
+ }
+ arg_ptr[arg_len] = '\0';
+ arg.name = arg_ptr;
+ cs_fill_path_info(&arg);
+ return cs_path_matches_group_or_pattern
+ (&arg, group, value, match);
+ }
+ offset = 0;
+ }
+}
+
+/**
+ * cs_check_envp - Check envp[] in "struct linux_binbrm".
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @name: Pointer to "struct cs_path_info".
+ * @group: Pointer to "struct cs_group". Maybe NULL.
+ * @value: Pointer to "struct cs_path_info". NULL if @group != NULL.
+ * @match: True if positive match, false otherwise.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_check_envp(struct cs_request_info *r,
+ const struct cs_path_info *name,
+ const struct cs_group *group,
+ const struct cs_path_info *value,
+ const bool match)
+{
+ struct linux_binprm *bprm = r->bprm;
+ struct cs_page_dump *dump = &r->dump;
+ char *arg_ptr = r->tmp;
+ int arg_len = 0;
+ unsigned long pos = bprm->p;
+ int offset = pos % PAGE_SIZE;
+ int argv_count = bprm->argc;
+ int envp_count = bprm->envc;
+ bool result = false;
+ struct cs_path_info env;
+ char *cp;
+
+ while (envp_count) {
+ if (!cs_dump_page(bprm, pos, dump)) {
+ r->failed_by_oom = true;
+ return false;
+ }
+ pos += PAGE_SIZE - offset;
+ while (envp_count && offset < PAGE_SIZE) {
+ const unsigned char c = dump->data[offset++];
+
+ if (argv_count) {
+ if (!c)
+ argv_count--;
+ continue;
+ }
+ if (c && arg_len < CS_EXEC_TMPSIZE - 10) {
+ if (c > ' ' && c < 127 && c != '\\') {
+ arg_ptr[arg_len++] = c;
+ } else {
+ arg_ptr[arg_len++] = '\\';
+ arg_ptr[arg_len++] = (c >> 6) + '0';
+ arg_ptr[arg_len++] =
+ ((c >> 3) & 7) + '0';
+ arg_ptr[arg_len++] = (c & 7) + '0';
+ }
+ } else {
+ arg_ptr[arg_len] = '\0';
+ }
+ if (c)
+ continue;
+ arg_len = 0;
+ envp_count--;
+ /* Check. */
+ cp = strchr(arg_ptr, '=');
+ if (!cp)
+ cp = "";
+ else
+ *cp++ = '\0';
+ env.name = arg_ptr;
+ cs_fill_path_info(&env);
+ if (!cs_path_matches_pattern(&env, name))
+ continue;
+ result = true;
+ env.name = cp;
+ cs_fill_path_info(&env);
+ if (cs_path_matches_group_or_pattern
+ (&env, group, value, match))
+ continue;
+ return false;
+ }
+ offset = 0;
+ }
+ /*
+ * Return value rule:
+ *
+ * Condition envp["ENV"]=NULL
+ * +----------------------+-------------+------------+-------------+
+ * | environment variable | bool result | bool match | return |
+ * +----------------------+-------------+------------+-------------+
+ * | undefined | false | true | true |
+ * | defined but unmatch | true | true | unreachable |
+ * | defined and match | true | true | unreachable |
+ * +----------------------+-------------+------------+-------------+
+ *
+ * Condition envp["ENV"]!=NULL
+ * +----------------------+-------------+------------+-------------+
+ * | environment variable | bool result | bool match | return |
+ * +----------------------+-------------+------------+-------------+
+ * | undefined | false | false | false |
+ * | defined but unmatch | true | false | true |
+ * | defined and match | true | false | true |
+ * +----------------------+-------------+------------+-------------+
+ *
+ * Condition envp["ENV"]="VALUE" or envp["ENV"]=@GROUP
+ * +----------------------+-------------+------------+-------------+
+ * | environment variable | bool result | bool match | return |
+ * +----------------------+-------------+------------+-------------+
+ * | undefined | false | true | false |
+ * | defined but unmatch | true | true | unreachable |
+ * | defined and match | true | true | true |
+ * +----------------------+-------------+------------+-------------+
+ *
+ * Condition envp["ENV"]!="VALUE" or envp["ENV"]!=@GROUP
+ * +----------------------+-------------+------------+-------------+
+ * | environment variable | bool result | bool match | return |
+ * +----------------------+-------------+------------+-------------+
+ * | undefined | false | false | true |
+ * | defined but unmatch | true | false | true |
+ * | defined and match | true | false | unreachable |
+ * +----------------------+-------------+------------+-------------+
+ *
+ * FIXME: What should I do if multiple values with the same environment
+ * variable name (e.g. HOME=/ and HOME=/root ) are passed in a way
+ * comparison results differ?
+ */
+ return value == &cs_null_name ? result != match : result || !match;
+}
+
+/**
+ * cs_get_attributes - Revalidate "struct inode".
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns nothing.
+ */
+void cs_get_attributes(struct cs_request_info *r)
+{
+ u8 i;
+ struct dentry *dentry = NULL;
+
+ if (r->obj.validate_done)
+ return;
+ for (i = 0; i < CS_MAX_PATH_STAT; i++) {
+ struct inode *inode;
+
+ switch (i) {
+ case CS_PATH1:
+ dentry = r->obj.path[0].dentry;
+ if (!dentry)
+ continue;
+ break;
+ case CS_PATH2:
+ dentry = r->obj.path[1].dentry;
+ if (!dentry)
+ continue;
+ break;
+ default:
+ if (!dentry)
+ continue;
+ dentry = dget_parent(dentry);
+ break;
+ }
+ inode = d_backing_inode(dentry);
+ if (inode) {
+ struct cs_mini_stat *stat = &r->obj.stat[i];
+
+ stat->uid = inode->i_uid;
+ stat->gid = inode->i_gid;
+ stat->ino = inode->i_ino;
+ stat->mode = inode->i_mode;
+ stat->dev = inode->i_sb->s_dev;
+ stat->rdev = inode->i_rdev;
+ stat->fsmagic = dentry->d_sb->s_magic;
+ r->obj.stat_valid[i] = true;
+ }
+ if (i & 1) /* parent directory */
+ dput(dentry);
+ }
+ r->obj.validate_done = true;
+}
+
+/**
+ * cs_populate_patharg - Calculate pathname for permission check and audit logs.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @first: True for first pathname, false for second pathname.
+ *
+ * Returns nothing.
+ */
+void cs_populate_patharg(struct cs_request_info *r, const bool first)
+{
+ struct cs_path_info *buf = &r->obj.pathname[!first];
+ struct path *path = &r->obj.path[!first];
+
+ if (!buf->name && path->dentry) {
+ buf->name = cs_realpath(path);
+ /* Set OOM flag if failed. */
+ if (!buf->name) {
+ r->failed_by_oom = true;
+ return;
+ }
+ cs_fill_path_info(buf);
+ }
+ if (!r->param.s[!first] && buf->name)
+ r->param.s[!first] = buf;
+}
+
+/**
+ * cs_cond2arg - Assign values to condition variables.
+ *
+ * @arg: Pointer to "struct cs_cond_arg".
+ * @cmd: One of values in "enum cs_conditions_index".
+ * @condp: Pointer to "union cs_condition_element *".
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns true on success, false otherwise.
+ *
+ * This function should not fail. But it can fail if (for example) out of
+ * memory has occurred while calculating cs_populate_patharg() or
+ * cs_get_exename().
+ */
+static bool cs_cond2arg(struct cs_cond_arg *arg,
+ const enum cs_conditions_index cmd,
+ const union cs_condition_element **condp,
+ struct cs_request_info *r)
+{
+ struct cs_mini_stat *stat;
+ unsigned long value;
+ const struct linux_binprm *bprm = r->bprm;
+ const struct cs_request_param *param = &r->param;
+
+ arg->type = CS_ARG_TYPE_NUMBER;
+ switch (cmd) {
+ case CS_SELF_UID:
+ value = from_kuid(&init_user_ns, current_uid());
+ break;
+ case CS_SELF_EUID:
+ value = from_kuid(&init_user_ns, current_euid());
+ break;
+ case CS_SELF_SUID:
+ value = from_kuid(&init_user_ns, current_suid());
+ break;
+ case CS_SELF_FSUID:
+ value = from_kuid(&init_user_ns, current_fsuid());
+ break;
+ case CS_SELF_GID:
+ value = from_kgid(&init_user_ns, current_gid());
+ break;
+ case CS_SELF_EGID:
+ value = from_kgid(&init_user_ns, current_egid());
+ break;
+ case CS_SELF_SGID:
+ value = from_kgid(&init_user_ns, current_sgid());
+ break;
+ case CS_SELF_FSGID:
+ value = from_kgid(&init_user_ns, current_fsgid());
+ break;
+ case CS_SELF_PID:
+ value = cs_sys_getpid();
+ break;
+ case CS_SELF_PPID:
+ value = cs_sys_getppid();
+ break;
+ case CS_OBJ_IS_SOCKET:
+ value = S_IFSOCK;
+ break;
+ case CS_OBJ_IS_SYMLINK:
+ value = S_IFLNK;
+ break;
+ case CS_OBJ_IS_FILE:
+ value = S_IFREG;
+ break;
+ case CS_OBJ_IS_BLOCK_DEV:
+ value = S_IFBLK;
+ break;
+ case CS_OBJ_IS_DIRECTORY:
+ value = S_IFDIR;
+ break;
+ case CS_OBJ_IS_CHAR_DEV:
+ value = S_IFCHR;
+ break;
+ case CS_OBJ_IS_FIFO:
+ value = S_IFIFO;
+ break;
+ case CS_EXEC_ARGC:
+ if (!bprm)
+ return false;
+ value = bprm->argc;
+ break;
+ case CS_EXEC_ENVC:
+ if (!bprm)
+ return false;
+ value = bprm->envc;
+ break;
+ case CS_ARGV_ENTRY:
+ case CS_IMM_NUMBER_ENTRY1:
+ value = (*condp)->value;
+ (*condp)++;
+ break;
+ case CS_COND_NARG0:
+ value = param->i[0];
+ break;
+ case CS_COND_NARG1:
+ value = param->i[1];
+ break;
+ case CS_COND_NARG2:
+ value = param->i[2];
+ break;
+ case CS_TRANSIT_DOMAIN:
+ case CS_COND_IPARG:
+ /* Values are loaded by caller. Just return a dummy. */
+ arg->type = CS_ARG_TYPE_NONE;
+ value = 0;
+ break;
+ default:
+ goto not_single_value;
+ }
+ arg->value[0] = value;
+ arg->value[1] = value;
+ return true;
+not_single_value:
+ if (cmd == CS_IMM_NUMBER_ENTRY2) {
+ arg->value[0] = (*condp)->value;
+ (*condp)++;
+ arg->value[1] = (*condp)->value;
+ (*condp)++;
+ return true;
+ }
+ switch (cmd) {
+ case CS_COND_SARG0:
+ if (!r->param.s[0])
+ cs_populate_patharg(r, true);
+ arg->name = r->param.s[0];
+ break;
+ case CS_COND_SARG1:
+ if (!r->param.s[1])
+ cs_populate_patharg(r, false);
+ arg->name = r->param.s[1];
+ break;
+ case CS_COND_SARG2:
+ arg->name = r->param.s[2];
+ break;
+ case CS_COND_SARG3:
+ arg->name = r->param.s[3];
+ break;
+ case CS_ENVP_ENTRY:
+ case CS_IMM_NAME_ENTRY:
+ arg->name = (*condp)->path;
+ (*condp)++;
+ break;
+ case CS_SELF_EXE:
+ if (!r->exename.name) {
+ cs_get_exename(&r->exename);
+ /* Set OOM flag if failed. */
+ if (!r->exename.name)
+ r->failed_by_oom = true;
+ }
+ arg->name = &r->exename;
+ break;
+ case CS_COND_DOMAIN:
+ arg->name = r->param.s[0];
+ break;
+ case CS_SELF_DOMAIN:
+ arg->name = cs_current_domain()->domainname;
+ break;
+ default:
+ goto not_single_name;
+ }
+ if (!arg->name)
+ return false;
+ arg->type = CS_ARG_TYPE_NAME;
+ return true;
+not_single_name:
+ if (cmd == CS_IMM_GROUP) {
+ arg->type = CS_ARG_TYPE_GROUP;
+ arg->group = (*condp)->group;
+ (*condp)++;
+ return true;
+ }
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+ if (cmd == CS_IMM_IPV4ADDR_ENTRY1) {
+ arg->type = CS_ARG_TYPE_IPV4ADDR;
+ memmove(&arg->ip[0], &(*condp)->ip, 4);
+ memmove(&arg->ip[1], &(*condp)->ip, 4);
+ (*condp)++;
+ return true;
+ }
+ if (cmd == CS_IMM_IPV4ADDR_ENTRY2) {
+ arg->type = CS_ARG_TYPE_IPV4ADDR;
+ memmove(&arg->ip[0], &(*condp)->ip, 4);
+ (*condp)++;
+ memmove(&arg->ip[1], &(*condp)->ip, 4);
+ (*condp)++;
+ return true;
+ }
+ if (cmd == CS_IMM_IPV6ADDR_ENTRY1) {
+ arg->type = CS_ARG_TYPE_IPV6ADDR;
+ memmove(&arg->ip[0], &(*condp)->ip, 16);
+ memmove(&arg->ip[1], &(*condp)->ip, 16);
+ *condp = (void *)
+ (((u8 *) *condp) + sizeof(struct in6_addr));
+ return true;
+ }
+ if (cmd == CS_IMM_IPV6ADDR_ENTRY2) {
+ arg->type = CS_ARG_TYPE_IPV6ADDR;
+ memmove(&arg->ip[0], &(*condp)->ip, 16);
+ *condp = (void *)
+ (((u8 *) *condp) + sizeof(struct in6_addr));
+ memmove(&arg->ip[1], &(*condp)->ip, 16);
+ *condp = (void *)
+ (((u8 *) *condp) + sizeof(struct in6_addr));
+ return true;
+ }
+#endif
+ switch (cmd) {
+ case CS_MODE_SETUID:
+ value = S_ISUID;
+ break;
+ case CS_MODE_SETGID:
+ value = S_ISGID;
+ break;
+ case CS_MODE_STICKY:
+ value = S_ISVTX;
+ break;
+ case CS_MODE_OWNER_READ:
+ value = 0400;
+ break;
+ case CS_MODE_OWNER_WRITE:
+ value = 0200;
+ break;
+ case CS_MODE_OWNER_EXECUTE:
+ value = 0100;
+ break;
+ case CS_MODE_GROUP_READ:
+ value = 0040;
+ break;
+ case CS_MODE_GROUP_WRITE:
+ value = 0020;
+ break;
+ case CS_MODE_GROUP_EXECUTE:
+ value = 0010;
+ break;
+ case CS_MODE_OTHERS_READ:
+ value = 0004;
+ break;
+ case CS_MODE_OTHERS_WRITE:
+ value = 0002;
+ break;
+ case CS_MODE_OTHERS_EXECUTE:
+ value = 0001;
+ break;
+ default:
+ goto not_bitop;
+ }
+ arg->type = CS_ARG_TYPE_BITOP;
+ arg->value[0] = value;
+ return true;
+not_bitop:
+ arg->type = CS_ARG_TYPE_NUMBER;
+ if (!r->obj.path[0].dentry && !r->obj.path[1].dentry)
+ return false;
+ cs_get_attributes(r);
+ value = (cmd - CS_PATH_ATTRIBUTE_START) >> 4;
+ if (value > 3)
+ return false;
+ stat = &r->obj.stat[value];
+ if (!stat)
+ return false;
+ switch ((cmd - CS_PATH_ATTRIBUTE_START) & 0xF) {
+ case CS_PATH_ATTRIBUTE_UID:
+ value = from_kuid(&init_user_ns, stat->uid);
+ break;
+ case CS_PATH_ATTRIBUTE_GID:
+ value = from_kgid(&init_user_ns, stat->gid);
+ break;
+ case CS_PATH_ATTRIBUTE_INO:
+ value = stat->ino;
+ break;
+ case CS_PATH_ATTRIBUTE_MAJOR:
+ value = MAJOR(stat->dev);
+ break;
+ case CS_PATH_ATTRIBUTE_MINOR:
+ value = MINOR(stat->dev);
+ break;
+ case CS_PATH_ATTRIBUTE_TYPE:
+ value = stat->mode & S_IFMT;
+ break;
+ case CS_PATH_ATTRIBUTE_DEV_MAJOR:
+ value = MAJOR(stat->rdev);
+ break;
+ case CS_PATH_ATTRIBUTE_DEV_MINOR:
+ value = MINOR(stat->rdev);
+ break;
+ case CS_PATH_ATTRIBUTE_PERM:
+ value = stat->mode & S_IALLUGO;
+ break;
+ case CS_PATH_ATTRIBUTE_FSMAGIC:
+ value = stat->fsmagic;
+ break;
+ default:
+ return false;
+ }
+ arg->value[0] = value;
+ arg->value[1] = value;
+ return true;
+}
+
+/**
+ * cs_condition - Check condition part.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @cond: Pointer to "struct cs_condition". Maybe NULL.
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_condition(struct cs_request_info *r,
+ const struct cs_condition *cond)
+{
+ const union cs_condition_element *condp;
+
+ if (!cond)
+ return true;
+ condp = (typeof(condp)) (cond + 1);
+ while ((void *) condp < (void *) ((u8 *) cond) + cond->size) {
+ struct cs_cond_arg left;
+ struct cs_cond_arg right;
+ const enum cs_conditions_index left_op = condp->left;
+ const enum cs_conditions_index right_op = condp->right;
+ const bool match = !condp->is_not;
+
+ condp++;
+ if (!cs_cond2arg(&left, left_op, &condp, r) ||
+ !cs_cond2arg(&right, right_op, &condp, r))
+ /*
+ * Something wrong (e.g. out of memory or invalid
+ * argument) occurred. We can't check permission.
+ */
+ return false;
+ if (left.type == CS_ARG_TYPE_NUMBER) {
+ if (left_op == CS_ARGV_ENTRY) {
+ if (!r->bprm)
+ return false;
+ else if (right.type == CS_ARG_TYPE_NAME)
+ right.group = NULL;
+ else if (right.type == CS_ARG_TYPE_GROUP)
+ right.name = NULL;
+ else
+ return false;
+ if (cs_check_argv(r, left.value[0],
+ right.group, right.name,
+ match))
+ continue;
+ return false;
+ }
+ if (right.type == CS_ARG_TYPE_NUMBER) {
+ if ((left.value[0] <= right.value[1] &&
+ left.value[1] >= right.value[0]) == match)
+ continue;
+ return false;
+ }
+ if (right.type == CS_ARG_TYPE_GROUP) {
+ if (cs_number_matches_group
+ (left.value[0], left.value[1], right.group)
+ == match)
+ continue;
+ return false;
+ }
+ if (right.type == CS_ARG_TYPE_BITOP) {
+ if (!(left.value[0] & right.value[0]) ==
+ !match)
+ continue;
+ return false;
+ }
+ return false;
+ }
+ if (left.type == CS_ARG_TYPE_NAME) {
+ if (right.type == CS_ARG_TYPE_NAME)
+ right.group = NULL;
+ else if (right.type == CS_ARG_TYPE_GROUP)
+ right.name = NULL;
+ else
+ return false;
+ if (left_op == CS_ENVP_ENTRY) {
+ if (r->bprm && cs_check_envp
+ (r, left.name, right.group, right.name,
+ match))
+ continue;
+ } else if (cs_path_matches_group_or_pattern
+ (left.name, right.group, right.name, match))
+ continue;
+ return false;
+ }
+ if (left.type != CS_ARG_TYPE_NONE)
+ return false;
+ /* Check IPv4 or IPv6 address expressions. */
+ if (left_op == CS_COND_IPARG) {
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+ if (right.type == CS_ARG_TYPE_GROUP) {
+ if (cs_ip_matches_group
+ (r->param.is_ipv6, r->param.ip,
+ right.group) == match)
+ continue;
+ } else if (right.type == CS_ARG_TYPE_IPV6ADDR) {
+ if (r->param.is_ipv6 &&
+ (memcmp(r->param.ip, &right.ip[0],
+ 16) >= 0 &&
+ memcmp(r->param.ip, &right.ip[1],
+ 16) <= 0) == match)
+ continue;
+ } else if (right.type == CS_ARG_TYPE_IPV4ADDR) {
+ if (!r->param.is_ipv6 &&
+ (memcmp(r->param.ip, &right.ip[0],
+ 4) >= 0 &&
+ memcmp(r->param.ip, &right.ip[1],
+ 4) <= 0) == match)
+ continue;
+ }
+#endif
+ return false;
+ }
+ if (left_op == CS_TRANSIT_DOMAIN) {
+ r->transition_candidate = right.name;
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * cs_check_auto_domain_transition - Check "auto_domain_transition" entry.
+ *
+ * Returns nothing.
+ *
+ * If "auto_domain_transition" keyword was specified and transition to that
+ * domain failed, the current thread will be killed by SIGKILL.
+ */
+static void cs_check_auto_domain_transition(void)
+{
+#ifdef CONFIG_SECURITY_CAITSITH_AUTO_DOMAIN_TRANSITION
+ struct cs_request_info r = { };
+
+ r.type = CS_MAC_AUTO_DOMAIN_TRANSITION;
+ cs_check_acl(&r, true);
+#endif
+}
+
+/**
+ * cs_byte_range - Check whether the string is a \ooo style octal value.
+ *
+ * @str: Pointer to the string.
+ *
+ * Returns true if @str is a \ooo style octal value, false otherwise.
+ */
+static bool cs_byte_range(const char *str)
+{
+ return *str >= '0' && *str++ <= '3' &&
+ *str >= '0' && *str++ <= '7' &&
+ *str >= '0' && *str <= '7';
+}
+
+/**
+ * cs_alphabet_char - Check whether the character is an alphabet.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is an alphabet character, false otherwise.
+ */
+static bool cs_alphabet_char(const char c)
+{
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+
+/**
+ * cs_file_matches_pattern2 - Pattern matching without '/' character and "\-" pattern.
+ *
+ * @filename: The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern: The start of pattern to compare.
+ * @pattern_end: The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool cs_file_matches_pattern2(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ while (filename < filename_end && pattern < pattern_end) {
+ char c;
+
+ if (*pattern != '\\') {
+ if (*filename++ != *pattern++)
+ return false;
+ continue;
+ }
+ c = *filename;
+ pattern++;
+ switch (*pattern) {
+ int i;
+ int j;
+ case '?':
+ if (c == '/') {
+ return false;
+ } else if (c == '\\') {
+ if (cs_byte_range(filename + 1))
+ filename += 3;
+ else
+ return false;
+ }
+ break;
+ case '+':
+ if (!isdigit(c))
+ return false;
+ break;
+ case 'x':
+ if (!isxdigit(c))
+ return false;
+ break;
+ case 'a':
+ if (!cs_alphabet_char(c))
+ return false;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ if (c == '\\' && cs_byte_range(filename + 1)
+ && !strncmp(filename + 1, pattern, 3)) {
+ filename += 3;
+ pattern += 2;
+ break;
+ }
+ return false; /* Not matched. */
+ case '*':
+ case '@':
+ for (i = 0; i <= filename_end - filename; i++) {
+ if (cs_file_matches_pattern2(filename + i,
+ filename_end,
+ pattern + 1,
+ pattern_end))
+ return true;
+ c = filename[i];
+ if (c == '.' && *pattern == '@')
+ break;
+ if (c != '\\')
+ continue;
+ if (cs_byte_range(filename + i + 1))
+ i += 3;
+ else
+ break; /* Bad pattern. */
+ }
+ return false; /* Not matched. */
+ default:
+ j = 0;
+ c = *pattern;
+ if (c == '$') {
+ while (isdigit(filename[j]))
+ j++;
+ } else if (c == 'X') {
+ while (isxdigit(filename[j]))
+ j++;
+ } else if (c == 'A') {
+ while (cs_alphabet_char(filename[j]))
+ j++;
+ }
+ for (i = 1; i <= j; i++) {
+ if (cs_file_matches_pattern2(filename + i,
+ filename_end,
+ pattern + 1,
+ pattern_end))
+ return true;
+ }
+ return false; /* Not matched or bad pattern. */
+ }
+ filename++;
+ pattern++;
+ }
+ /* Ignore trailing "\*" and "\@" in @pattern. */
+ while (*pattern == '\\' &&
+ (*(pattern + 1) == '*' || *(pattern + 1) == '@'))
+ pattern += 2;
+ return filename == filename_end && pattern == pattern_end;
+}
+
+/**
+ * cs_file_matches_pattern - Pattern matching without '/' character.
+ *
+ * @filename: The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern: The start of pattern to compare.
+ * @pattern_end: The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool cs_file_matches_pattern(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ const char *pattern_start = pattern;
+ bool first = true;
+ bool result;
+
+ while (pattern < pattern_end - 1) {
+ /* Split at "\-" pattern. */
+ if (*pattern++ != '\\' || *pattern++ != '-')
+ continue;
+ result = cs_file_matches_pattern2(filename, filename_end,
+ pattern_start, pattern - 2);
+ if (first)
+ result = !result;
+ if (result)
+ return false;
+ first = false;
+ pattern_start = pattern;
+ }
+ result = cs_file_matches_pattern2(filename, filename_end,
+ pattern_start, pattern_end);
+ return first ? result : !result;
+}
+
+/**
+ * cs_path_matches_pattern2 - Do pathname pattern matching.
+ *
+ * @f: The start of string to check.
+ * @p: The start of pattern to compare.
+ *
+ * Returns true if @f matches @p, false otherwise.
+ */
+static bool cs_path_matches_pattern2(const char *f, const char *p)
+{
+ const char *f_delimiter;
+ const char *p_delimiter;
+
+ while (*f && *p) {
+ f_delimiter = strchr(f + 1, '/');
+ if (!f_delimiter)
+ f_delimiter = f + strlen(f);
+ p_delimiter = strchr(p + 1, '/');
+ if (!p_delimiter)
+ p_delimiter = p + strlen(p);
+ if (*p == '/' && *(p + 1) == '\\') {
+ if (*(p + 2) == '(') {
+ /* Check zero repetition. */
+ if (cs_path_matches_pattern2(f, p_delimiter))
+ return true;
+ /* Check one or more repetition. */
+ goto repetition;
+ }
+ if (*(p + 2) == '{')
+ goto repetition;
+ }
+ if ((*f == '/' || *p == '/') && *f++ != *p++)
+ return false;
+ if (!cs_file_matches_pattern(f, f_delimiter, p, p_delimiter))
+ return false;
+ f = f_delimiter;
+ p = p_delimiter;
+ }
+ /* Ignore trailing "\*" and "\@" in @pattern. */
+ while (*p == '\\' && (*(p + 1) == '*' || *(p + 1) == '@'))
+ p += 2;
+ return !*f && !*p;
+repetition:
+ do {
+ /* Compare current component with pattern. */
+ if (!cs_file_matches_pattern(f + 1, f_delimiter, p + 3,
+ p_delimiter - 2))
+ break;
+ /* Proceed to next component. */
+ f = f_delimiter;
+ if (!*f)
+ break;
+ /* Continue comparison. */
+ if (cs_path_matches_pattern2(f, p_delimiter))
+ return true;
+ f_delimiter = strchr(f + 1, '/');
+ } while (f_delimiter);
+ return false; /* Not matched. */
+}
+
+/**
+ * cs_path_matches_pattern - Check whether the given filename matches the given pattern.
+ *
+ * @filename: The filename to check.
+ * @pattern: The pattern to compare.
+ *
+ * Returns true if matches, false otherwise.
+ *
+ * The following patterns are available.
+ * \ooo Octal representation of a byte.
+ * \* Zero or more repetitions of characters other than '/'.
+ * \@ Zero or more repetitions of characters other than '/' or '.'.
+ * \? 1 byte character other than '/'.
+ * \$ One or more repetitions of decimal digits.
+ * \+ 1 decimal digit.
+ * \X One or more repetitions of hexadecimal digits.
+ * \x 1 hexadecimal digit.
+ * \A One or more repetitions of alphabet characters.
+ * \a 1 alphabet character.
+ *
+ * \- Subtraction operator.
+ *
+ * /\{dir\}/ '/' + 'One or more repetitions of dir/' (e.g. /dir/ /dir/dir/
+ * /dir/dir/dir/ ).
+ *
+ * /\(dir\)/ '/' + 'Zero or more repetitions of dir/' (e.g. / /dir/
+ * /dir/dir/ ).
+ */
+static bool cs_path_matches_pattern(const struct cs_path_info *filename,
+ const struct cs_path_info *pattern)
+{
+ const char *f = filename->name;
+ const char *p = pattern->name;
+ const int len = pattern->const_len;
+ /* If @pattern doesn't contain pattern, I can use strcmp(). */
+ if (len == pattern->total_len)
+ return !cs_pathcmp(filename, pattern);
+ /* Compare the initial length without patterns. */
+ if (len) {
+ if (strncmp(f, p, len))
+ return false;
+ f += len - 1;
+ p += len - 1;
+ }
+ return cs_path_matches_pattern2(f, p);
+}
+
+/**
+ * cs_clear_request_info - Release memory allocated during permission check.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns nothing.
+ */
+static void cs_clear_request_info(struct cs_request_info *r)
+{
+ u8 i;
+ /*
+ * r->obj.pathname[0] (which is referenced by r->obj.s[0]) and
+ * r->obj.pathname[1] (which is referenced by r->obj.s[1]) may contain
+ * pathnames allocated using cs_populate_patharg() or cs_mount_acl().
+ * Their callers do not allocate memory until pathnames becomes needed
+ * for checking condition or auditing requests.
+ *
+ * r->obj.s[2] and r->obj.s[3] are used by
+ * cs_mount_acl()/cs_env_perm() and are allocated/released by their
+ * callers.
+ */
+ for (i = 0; i < 2; i++) {
+ kfree(r->obj.pathname[i].name);
+ r->obj.pathname[i].name = NULL;
+ }
+ kfree(r->exename.name);
+ r->exename.name = NULL;
+}
--
2.18.4
More information about the Linux-security-module-archive
mailing list