All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 01/10] security: Export security_hook_heads
@ 2022-11-02 17:10 Tetsuo Handa
  2022-11-02 17:10 ` [PATCH 02/10] mm: Export copy_to_kernel_nofault() Tetsuo Handa
                   ` (8 more replies)
  0 siblings, 9 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

LSM modules which can be loaded using /sbin/insmod need to be able to
access security_hook_heads, for security_add_hooks() is marked as __init
function which cannot be accessed from loadable kernel modules.

LSM modules which can be loaded using /sbin/insmod are developed in order
to make it possible to enable LSM modules without replacing or rebuilding
the whole kernel, for distributors disable LSM modules which they cannot
afford supporting via kernel config options while users cannot afford
enabling LSM modules which they want to use by replacing or rebuilding
the whole kernel.

Now that I'm demonstrating CaitSith as one of such LSM modules, export
the security_hook_heads list.

Unlike introducing a LSM manager module which could provide ability to
load/unload loadable LSM modules, exporting only security_hook_heads does
not affect performance of built-in LSM modules and will little increase
the kernel size. Therefore, no kernel config option for this change.

Distributors who believe that loadable LSM modules are unacceptable might
try to make it harder to use loadable LSM modules via unexporting
security_hook_heads. But we cannot hide security_hook_heads perfectly, for
binary code analysis allows loadable kernel modules to identify the
address of security_hook_heads. It is just a matter of cleanness of code.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 security/security.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/security/security.c b/security/security.c
index 79d82cb6e469..fd7b9b1f8348 100644
--- a/security/security.c
+++ b/security/security.c
@@ -75,6 +75,7 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1] = {
 };
 
 struct security_hook_heads security_hook_heads __lsm_ro_after_init;
+EXPORT_SYMBOL_GPL(security_hook_heads);
 static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
 
 static struct kmem_cache *lsm_file_cache;
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 02/10] mm: Export copy_to_kernel_nofault()
  2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
@ 2022-11-02 17:10 ` Tetsuo Handa
  2022-11-02 17:10 ` [PATCH 03/10] fs,kernel: Export d_absolute_path()/find_task_by_pid_ns()/find_task_by_vpid() Tetsuo Handa
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

LSM modules which can be loaded using /sbin/insmod need to be able to
modify security_hook_heads. Since security_hook_heads might be read-only
due to being marked as __lsm_ro_after_init, and writing to read-only memory
crashes the kernel, such LSM modules need to test whether memory pages
containing security_hook_heads is read-only.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 mm/maccess.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/mm/maccess.c b/mm/maccess.c
index 5f4d240f67ec..f75447b13034 100644
--- a/mm/maccess.c
+++ b/mm/maccess.c
@@ -75,6 +75,7 @@ long copy_to_kernel_nofault(void *dst, const void *src, size_t size)
 	pagefault_enable();
 	return -EFAULT;
 }
+EXPORT_SYMBOL_GPL(copy_to_kernel_nofault);
 
 long strncpy_from_kernel_nofault(char *dst, const void *unsafe_addr, long count)
 {
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 03/10] fs,kernel: Export d_absolute_path()/find_task_by_pid_ns()/find_task_by_vpid()
  2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
  2022-11-02 17:10 ` [PATCH 02/10] mm: Export copy_to_kernel_nofault() Tetsuo Handa
@ 2022-11-02 17:10 ` Tetsuo Handa
  2022-11-05 23:51   ` Serge E. Hallyn
  2022-11-02 17:10 ` [PATCH 04/10] CaitSith: Add header file Tetsuo Handa
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

CaitSith module which can be loaded using /sbin/insmod needs to be able to
access these functions. TOMOYO module will also access these functions when
CONFIG_SECURITY_TOMOYO=m becomes possible.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 fs/d_path.c  | 1 +
 kernel/pid.c | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/fs/d_path.c b/fs/d_path.c
index 56a6ee4c6331..417e74414f19 100644
--- a/fs/d_path.c
+++ b/fs/d_path.c
@@ -234,6 +234,7 @@ char *d_absolute_path(const struct path *path,
 		return ERR_PTR(-EINVAL);
 	return extract_string(&b);
 }
+EXPORT_SYMBOL_GPL(d_absolute_path);
 
 static void get_fs_root_rcu(struct fs_struct *fs, struct path *root)
 {
diff --git a/kernel/pid.c b/kernel/pid.c
index 3fbc5e46b721..9e5224d8769c 100644
--- a/kernel/pid.c
+++ b/kernel/pid.c
@@ -416,11 +416,13 @@ struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
 			 "find_task_by_pid_ns() needs rcu_read_lock() protection");
 	return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
 }
+EXPORT_SYMBOL_GPL(find_task_by_pid_ns);
 
 struct task_struct *find_task_by_vpid(pid_t vnr)
 {
 	return find_task_by_pid_ns(vnr, task_active_pid_ns(current));
 }
+EXPORT_SYMBOL_GPL(find_task_by_vpid);
 
 struct task_struct *find_get_task_by_vpid(pid_t nr)
 {
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 04/10] CaitSith: Add header file.
  2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
  2022-11-02 17:10 ` [PATCH 02/10] mm: Export copy_to_kernel_nofault() Tetsuo Handa
  2022-11-02 17:10 ` [PATCH 03/10] fs,kernel: Export d_absolute_path()/find_task_by_pid_ns()/find_task_by_vpid() Tetsuo Handa
@ 2022-11-02 17:10 ` Tetsuo Handa
  2022-11-02 17:57   ` Casey Schaufler
  2022-11-02 17:10 ` [PATCH 05/10] CaitSith: Add LSM interface management file Tetsuo Handa
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

The main point of this submission is to demonstrate how an LSM module
which can be loaded using /sbin/insmod can work, and to provide
consideration points for making changes for LSM stacking in a way that
will not lock out LSM modules which can be loaded using /sbin/insmod .

CaitSith was developed as next version of TOMOYO. But since it became
too different to call as TOMOYO (this was a fundamental change as if
SELinux stops using filesystem's extended attributes), I gave this module
a new name. Background of developing this module is explained in
https://I-love.SAKURA.ne.jp/tomoyo/CaitSith-en.pdf .

This file defines constants, structures, and inline functions used by
CaitSith. Please ignore redundancy, for this submission is just an output
 from repository which supports 2.6.27+ kernels with LINUX_VERSION_CODE
removed.

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

diff --git a/security/caitsith/caitsith.h b/security/caitsith/caitsith.h
new file mode 100644
index 000000000000..b85050d2d5e3
--- /dev/null
+++ b/security/caitsith/caitsith.h
@@ -0,0 +1,919 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * caitsith.h
+ *
+ * Copyright (C) 2005-2012  NTT DATA CORPORATION
+ *
+ * Version: 0.2.10   2021/06/06
+ */
+
+#ifndef _SECURITY_CAITSITH_INTERNAL_H
+#define _SECURITY_CAITSITH_INTERNAL_H
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/utime.h>
+#include <linux/file.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/highmem.h>
+#include <linux/poll.h>
+#include <linux/binfmts.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/dcache.h>
+#include <linux/mount.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/un.h>
+#include <linux/namei.h>
+#include <linux/fs_struct.h>
+#include <linux/hash.h>
+#include <linux/kthread.h>
+#include <linux/magic.h>
+#include <linux/uaccess.h>
+#include <linux/sched/signal.h>
+#include <net/sock.h>
+#include <net/af_unix.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/udp.h>
+#include <linux/ctype.h>
+#include <linux/magic.h>
+#include <uapi/linux/mount.h>
+#include <linux/uidgid.h>
+
+/* Index numbers for Capability Controls. */
+enum cs_capability_acl_index {
+	/* socket(PF_ROUTE, *, *)                                      */
+	CS_USE_ROUTE_SOCKET,
+	/* socket(PF_PACKET, *, *)                                     */
+	CS_USE_PACKET_SOCKET,
+	CS_MAX_CAPABILITY_INDEX
+};
+
+/* Enumeration definition for internal use. */
+
+/* Index numbers for "struct cs_condition". */
+enum cs_conditions_index {
+	CS_INVALID_CONDITION,
+	CS_SELF_UID,             /* current_uid()   */
+	CS_SELF_EUID,            /* current_euid()  */
+	CS_SELF_SUID,            /* current_suid()  */
+	CS_SELF_FSUID,           /* current_fsuid() */
+	CS_SELF_GID,             /* current_gid()   */
+	CS_SELF_EGID,            /* current_egid()  */
+	CS_SELF_SGID,            /* current_sgid()  */
+	CS_SELF_FSGID,           /* current_fsgid() */
+	CS_SELF_PID,             /* sys_getpid()   */
+	CS_SELF_PPID,            /* sys_getppid()  */
+	CS_SELF_DOMAIN,
+	CS_SELF_EXE,
+	CS_EXEC_ARGC,            /* "struct linux_binprm *"->argc */
+	CS_EXEC_ENVC,            /* "struct linux_binprm *"->envc */
+	CS_OBJ_IS_SOCKET,        /* S_IFSOCK */
+	CS_OBJ_IS_SYMLINK,       /* S_IFLNK */
+	CS_OBJ_IS_FILE,          /* S_IFREG */
+	CS_OBJ_IS_BLOCK_DEV,     /* S_IFBLK */
+	CS_OBJ_IS_DIRECTORY,     /* S_IFDIR */
+	CS_OBJ_IS_CHAR_DEV,      /* S_IFCHR */
+	CS_OBJ_IS_FIFO,          /* S_IFIFO */
+	CS_MODE_SETUID,          /* S_ISUID */
+	CS_MODE_SETGID,          /* S_ISGID */
+	CS_MODE_STICKY,          /* S_ISVTX */
+	CS_MODE_OWNER_READ,      /* S_IRUSR */
+	CS_MODE_OWNER_WRITE,     /* S_IWUSR */
+	CS_MODE_OWNER_EXECUTE,   /* S_IXUSR */
+	CS_MODE_GROUP_READ,      /* S_IRGRP */
+	CS_MODE_GROUP_WRITE,     /* S_IWGRP */
+	CS_MODE_GROUP_EXECUTE,   /* S_IXGRP */
+	CS_MODE_OTHERS_READ,     /* S_IROTH */
+	CS_MODE_OTHERS_WRITE,    /* S_IWOTH */
+	CS_MODE_OTHERS_EXECUTE,  /* S_IXOTH */
+	CS_TRANSIT_DOMAIN,
+	CS_COND_SARG0,
+	CS_COND_SARG1,
+	CS_COND_SARG2,
+	CS_COND_SARG3,
+	CS_COND_NARG0,
+	CS_COND_NARG1,
+	CS_COND_NARG2,
+	CS_COND_IPARG,
+	CS_COND_DOMAIN,
+	CS_IMM_GROUP,
+	CS_IMM_NAME_ENTRY,
+	CS_IMM_NUMBER_ENTRY1,
+	CS_IMM_NUMBER_ENTRY2,
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	CS_IMM_IPV4ADDR_ENTRY1,
+	CS_IMM_IPV4ADDR_ENTRY2,
+	CS_IMM_IPV6ADDR_ENTRY1,
+	CS_IMM_IPV6ADDR_ENTRY2,
+#endif
+	CS_ARGV_ENTRY,
+	CS_ENVP_ENTRY,
+	CS_PATH_ATTRIBUTE_START = 192,
+	CS_PATH_ATTRIBUTE_END = 255
+} __packed;
+
+enum cs_path_attribute_index {
+	CS_PATH_ATTRIBUTE_UID,
+	CS_PATH_ATTRIBUTE_GID,
+	CS_PATH_ATTRIBUTE_INO,
+	CS_PATH_ATTRIBUTE_TYPE,
+	CS_PATH_ATTRIBUTE_MAJOR,
+	CS_PATH_ATTRIBUTE_MINOR,
+	CS_PATH_ATTRIBUTE_PERM,
+	CS_PATH_ATTRIBUTE_DEV_MAJOR,
+	CS_PATH_ATTRIBUTE_DEV_MINOR,
+	CS_PATH_ATTRIBUTE_FSMAGIC,
+	CS_MAX_PATH_ATTRIBUTE
+} __packed;
+
+/* Index numbers for group entries. */
+enum cs_group_id {
+	CS_STRING_GROUP,
+	CS_NUMBER_GROUP,
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	CS_IP_GROUP,
+#endif
+	CS_MAX_GROUP
+} __packed;
+
+/* Index numbers for functionality. */
+enum cs_mac_index {
+	CS_MAC_EXECUTE,
+	CS_MAC_READ,
+	CS_MAC_WRITE,
+	CS_MAC_APPEND,
+	CS_MAC_CREATE,
+	CS_MAC_UNLINK,
+#ifdef CONFIG_SECURITY_CAITSITH_GETATTR
+	CS_MAC_GETATTR,
+#endif
+	CS_MAC_MKDIR,
+	CS_MAC_RMDIR,
+	CS_MAC_MKFIFO,
+	CS_MAC_MKSOCK,
+	CS_MAC_TRUNCATE,
+	CS_MAC_SYMLINK,
+	CS_MAC_MKBLOCK,
+	CS_MAC_MKCHAR,
+	CS_MAC_LINK,
+	CS_MAC_RENAME,
+	CS_MAC_CHMOD,
+	CS_MAC_CHOWN,
+	CS_MAC_CHGRP,
+	CS_MAC_IOCTL,
+	CS_MAC_CHROOT,
+	CS_MAC_MOUNT,
+	CS_MAC_UMOUNT,
+	CS_MAC_PIVOT_ROOT,
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	CS_MAC_INET_STREAM_BIND,
+	CS_MAC_INET_STREAM_LISTEN,
+	CS_MAC_INET_STREAM_CONNECT,
+	CS_MAC_INET_STREAM_ACCEPT,
+	CS_MAC_INET_DGRAM_BIND,
+	CS_MAC_INET_DGRAM_SEND,
+	CS_MAC_INET_RAW_BIND,
+	CS_MAC_INET_RAW_SEND,
+	CS_MAC_UNIX_STREAM_BIND,
+	CS_MAC_UNIX_STREAM_LISTEN,
+	CS_MAC_UNIX_STREAM_CONNECT,
+	CS_MAC_UNIX_STREAM_ACCEPT,
+	CS_MAC_UNIX_DGRAM_BIND,
+	CS_MAC_UNIX_DGRAM_SEND,
+	CS_MAC_UNIX_SEQPACKET_BIND,
+	CS_MAC_UNIX_SEQPACKET_LISTEN,
+	CS_MAC_UNIX_SEQPACKET_CONNECT,
+	CS_MAC_UNIX_SEQPACKET_ACCEPT,
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_ENVIRON
+	CS_MAC_ENVIRON,
+#endif
+	CS_MAC_MODIFY_POLICY,
+#ifdef CONFIG_SECURITY_CAITSITH_CAPABILITY
+	CS_MAC_USE_NETLINK_SOCKET,
+	CS_MAC_USE_PACKET_SOCKET,
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_AUTO_DOMAIN_TRANSITION
+	CS_MAC_AUTO_DOMAIN_TRANSITION,
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_MANUAL_DOMAIN_TRANSITION
+	CS_MAC_MANUAL_DOMAIN_TRANSITION,
+#endif
+	CS_MAX_MAC_INDEX,
+	/* Map undefined functions to CS_MAX_MAC_INDEX */
+#ifndef CONFIG_SECURITY_CAITSITH_GETATTR
+	CS_MAC_GETATTR = CS_MAX_MAC_INDEX,
+#endif
+#ifndef CONFIG_SECURITY_CAITSITH_NETWORK
+	CS_MAC_INET_STREAM_BIND = CS_MAX_MAC_INDEX,
+	CS_MAC_INET_STREAM_LISTEN = CS_MAX_MAC_INDEX,
+	CS_MAC_INET_STREAM_CONNECT = CS_MAX_MAC_INDEX,
+	CS_MAC_INET_STREAM_ACCEPT = CS_MAX_MAC_INDEX,
+	CS_MAC_INET_DGRAM_BIND = CS_MAX_MAC_INDEX,
+	CS_MAC_INET_DGRAM_SEND = CS_MAX_MAC_INDEX,
+	CS_MAC_INET_RAW_BIND = CS_MAX_MAC_INDEX,
+	CS_MAC_INET_RAW_SEND = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_STREAM_BIND = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_STREAM_LISTEN = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_STREAM_CONNECT = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_STREAM_ACCEPT = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_DGRAM_BIND = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_DGRAM_SEND = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_SEQPACKET_BIND = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_SEQPACKET_LISTEN = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_SEQPACKET_CONNECT = CS_MAX_MAC_INDEX,
+	CS_MAC_UNIX_SEQPACKET_ACCEPT = CS_MAX_MAC_INDEX,
+#endif
+#ifndef CONFIG_SECURITY_CAITSITH_ENVIRON
+	CS_MAC_ENVIRON = CS_MAX_MAC_INDEX,
+#endif
+#ifndef CONFIG_SECURITY_CAITSITH_CAPABILITY
+	CS_MAC_USE_NETLINK_SOCKET = CS_MAX_MAC_INDEX,
+	CS_MAC_USE_PACKET_SOCKET = CS_MAX_MAC_INDEX,
+#endif
+#ifndef CONFIG_SECURITY_CAITSITH_AUTO_DOMAIN_TRANSITION
+	CS_MAC_AUTO_DOMAIN_TRANSITION = CS_MAX_MAC_INDEX,
+#endif
+#ifndef CONFIG_SECURITY_CAITSITH_MANUAL_DOMAIN_TRANSITION
+	CS_MAC_MANUAL_DOMAIN_TRANSITION = CS_MAX_MAC_INDEX,
+#endif
+} __packed;
+
+/* Index numbers for statistic information. */
+enum cs_memory_stat_type {
+	CS_MEMORY_POLICY,
+	CS_MEMORY_AUDIT,
+	CS_MEMORY_QUERY,
+	CS_MAX_MEMORY_STAT
+} __packed;
+
+enum cs_matching_result {
+	CS_MATCHING_UNMATCHED,
+	CS_MATCHING_ALLOWED,
+	CS_MATCHING_DENIED,
+	CS_MAX_MATCHING
+} __packed;
+
+/* Index numbers for stat(). */
+enum cs_path_stat_index {
+	/* Do not change this order. */
+	CS_PATH1,
+	CS_PATH1_PARENT,
+	CS_PATH2,
+	CS_PATH2_PARENT,
+	CS_MAX_PATH_STAT
+} __packed;
+
+/* Index numbers for entry type. */
+enum cs_policy_id {
+	CS_ID_GROUP,
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	CS_ID_IP_GROUP,
+#endif
+	CS_ID_STRING_GROUP,
+	CS_ID_NUMBER_GROUP,
+	CS_ID_CONDITION,
+	CS_ID_NAME,
+	CS_ID_ACL,
+	CS_ID_DOMAIN,
+	CS_MAX_POLICY
+} __packed;
+
+/* Index numbers for statistic information. */
+enum cs_policy_stat_type {
+	CS_STAT_POLICY_UPDATES,
+	CS_STAT_REQUEST_DENIED,
+	CS_MAX_POLICY_STAT
+} __packed;
+
+/* Index numbers for /sys/kernel/security/caitsith/ interfaces. */
+enum cs_securityfs_interface_index {
+	CS_POLICY,
+	CS_PROCESS_STATUS,
+	CS_AUDIT,
+	CS_VERSION,
+	CS_QUERY,
+} __packed;
+
+/* Index numbers for special mount operations. */
+enum cs_special_mount {
+	CS_MOUNT_BIND,            /* mount --bind /source /dest   */
+	CS_MOUNT_MOVE,            /* mount --move /old /new       */
+	CS_MOUNT_REMOUNT,         /* mount -o remount /dir        */
+	CS_MOUNT_MAKE_UNBINDABLE, /* mount --make-unbindable /dir */
+	CS_MOUNT_MAKE_PRIVATE,    /* mount --make-private /dir    */
+	CS_MOUNT_MAKE_SLAVE,      /* mount --make-slave /dir      */
+	CS_MOUNT_MAKE_SHARED,     /* mount --make-shared /dir     */
+	CS_MAX_SPECIAL_MOUNT
+} __packed;
+
+/* Index numbers for type of numeric values. */
+enum cs_value_type {
+	CS_VALUE_TYPE_INVALID,
+	CS_VALUE_TYPE_DECIMAL,
+	CS_VALUE_TYPE_OCTAL,
+	CS_VALUE_TYPE_HEXADECIMAL,
+} __packed;
+
+/* Constants definition for internal use. */
+
+/*
+ * CaitSith uses this hash only when appending a string into the string table.
+ * Frequency of appending strings is very low. So we don't need large (e.g.
+ * 64k) hash size. 256 will be sufficient.
+ */
+#define CS_HASH_BITS 8
+#define CS_MAX_HASH (1u << CS_HASH_BITS)
+
+/*
+ * CaitSith checks only SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET.
+ * Therefore, we don't need SOCK_MAX.
+ */
+#define CS_SOCK_MAX 6
+
+/* Size of temporary buffer for execve() operation. */
+#define CS_EXEC_TMPSIZE     4096
+
+/* Patterns for auditing logs quota. */
+#define CS_MAX_LOG_QUOTA 256
+
+/* Garbage collector is trying to kfree() this element. */
+#define CS_GC_IN_PROGRESS -1
+
+/* Current thread is doing open(3) ? */
+#define CS_OPEN_FOR_IOCTL_ONLY           2
+/* Current thread is doing do_execve() ? */
+#define CS_TASK_IS_IN_EXECVE             4
+/*
+ * Current thread is allowed to modify policy via
+ * /sys/kernel/security/caitsith/ interface?
+ */
+#define CS_TASK_IS_MANAGER               8
+
+/*
+ * Retry this request. Returned by cs_supervisor() if policy violation has
+ * occurred in enforcing mode and the userspace daemon decided to retry.
+ *
+ * We must choose a positive value in order to distinguish "granted" (which is
+ * 0) and "rejected" (which is a negative value) and "retry".
+ */
+#define CS_RETRY_REQUEST 1
+
+/* Size of read buffer for /sys/kernel/security/caitsith/ interface. */
+#define CS_MAX_IO_READ_QUEUE 64
+
+/* Structure definition for internal use. */
+
+/* Common header for holding ACL entries. */
+struct cs_acl_head {
+	struct list_head list;
+	s8 is_deleted; /* true or false or CS_GC_IN_PROGRESS */
+} __packed;
+
+/* Common header for shared entries. */
+struct cs_shared_acl_head {
+	struct list_head list;
+	atomic_t users;
+} __packed;
+
+/* Common header for individual entries. */
+struct cs_acl_info {
+	struct list_head list;
+	struct list_head acl_info_list;
+	struct cs_condition *cond; /* Maybe NULL. */
+	bool is_deleted;
+	bool is_deny;
+	u16 priority;
+	u8 audit;
+};
+
+/* Structure for "string_group"/"number_group"/"ip_group" directive. */
+struct cs_group {
+	struct cs_shared_acl_head head;
+	/* Name of group (without leading "@"). */
+	const struct cs_path_info *group_name;
+	/*
+	 * List of "struct cs_string_group" or "struct cs_number_group" or
+	 * "struct cs_ip_group".
+	 */
+	struct list_head member_list;
+};
+
+/* Structure for "string_group" directive. */
+struct cs_string_group {
+	struct cs_acl_head head;
+	const struct cs_path_info *member_name;
+};
+
+/* Structure for "number_group" directive. */
+struct cs_number_group {
+	struct cs_acl_head head;
+	u8 radix;
+	unsigned long value[2];
+};
+
+/* Structure for "ip_group" directive. */
+struct cs_ip_group {
+	struct cs_acl_head head;
+	bool is_ipv6;
+	/* Structure for holding an IP address. */
+	struct in6_addr ip[2]; /* Big endian. */
+};
+
+/* Subset of "struct stat". Used by conditional ACL and audit logs. */
+struct cs_mini_stat {
+	kuid_t uid;
+	kgid_t gid;
+	ino_t ino;
+	umode_t mode;
+	dev_t dev;
+	dev_t rdev;
+	unsigned long fsmagic;
+};
+
+/* Structure for dumping argv[] and envp[] of "struct linux_binprm". */
+struct cs_page_dump {
+	struct page *page;    /* Previously dumped page. */
+	char *data;           /* Contents of "page". Size is PAGE_SIZE. */
+};
+
+/* Structure for entries which follows "struct cs_condition". */
+union cs_condition_element {
+	struct {
+		enum cs_conditions_index left;
+		enum cs_conditions_index right;
+		bool is_not;
+		u8 radix;
+	};
+	struct cs_group *group;
+	const struct cs_path_info *path;
+	u32 ip; /* Repeat 4 times if IPv6 address. */
+	unsigned long value;
+};
+
+/* Structure for optional arguments. */
+struct cs_condition {
+	struct cs_shared_acl_head head;
+	u32 size; /* Memory size allocated for this entry. */
+	/* union cs_condition_element condition[]; */
+};
+
+/* Structure for holding a token. */
+struct cs_path_info {
+	const char *name;
+	u32 hash;          /* = full_name_hash(name, strlen(name)) */
+	u32 total_len;     /* = strlen(name)                       */
+	u32 const_len;     /* = cs_const_part_length(name)        */
+};
+
+/* Structure for request info. */
+struct cs_request_info {
+	/* For holding parameters. */
+	struct cs_request_param {
+		const struct cs_path_info *s[4];
+		unsigned long i[3];
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+		const u8 *ip; /* Big endian. */
+		bool is_ipv6;
+#endif
+	} param;
+	/* For holding pathnames and attributes. */
+	struct {
+		/*
+		 * True if cs_get_attributes() was already called, false
+		 * otherwise.
+		 */
+		bool validate_done;
+		/* True if @stat[] is valid. */
+		bool stat_valid[CS_MAX_PATH_STAT];
+		/* Pointer to file objects. */
+		struct path path[2];
+		/*
+		 * Information on @path[0], @path[0]'s parent directory,
+		 * @path[1] and @path[1]'s parent directory.
+		 */
+		struct cs_mini_stat stat[CS_MAX_PATH_STAT];
+		/*
+		 * Name of @path[0] and @path[1].
+		 * Cleared by cs_clear_request_info().
+		 */
+		struct cs_path_info pathname[2];
+	} obj;
+	struct {
+		struct linux_binprm *bprm;
+		struct cs_domain_info *previous_domain;
+		/* For dumping argv[] and envp[]. */
+		struct cs_page_dump dump;
+		/* For temporary use. Size is CS_EXEC_TMPSIZE bytes. */
+		char *tmp;
+	};
+	/*
+	 * Name of current thread's executable.
+	 * Cleared by cs_clear_request_info().
+	 */
+	struct cs_path_info exename;
+	/*
+	 * Matching "struct cs_acl_info" is copied. Used for caitsith-queryd.
+	 * Valid until cs_read_unlock().
+	 */
+	struct cs_acl_info *matched_acl;
+	/*
+	 * Matching domain transition is copied.
+	 * Valid until cs_read_unlock().
+	 */
+	const struct cs_path_info *transition;
+	const struct cs_path_info *transition_candidate;
+	/*
+	 * For holding operation index used for this request.
+	 * One of values in "enum cs_mac_index".
+	 */
+	enum cs_mac_index type;
+	/* For holding matching result. */
+	enum cs_matching_result result;
+	/*
+	 * For counting number of retries made for this request.
+	 * This counter is incremented whenever cs_supervisor() returned
+	 * CS_RETRY_REQUEST.
+	 */
+	u8 retry;
+	/* For holding max audit log count for this matching entry. */
+	u8 audit;
+	/*
+	 * Set to true if condition could not be checked due to out of memory.
+	 * This flag is used for returning out of memory flag back to
+	 * cs_check_acl_list(). Thus, this flag will not be set if out of
+	 * memory occurred before cs_check_acl_list() is called.
+	 */
+	bool failed_by_oom;
+};
+
+/* Structure for domain information. */
+struct cs_domain_info {
+	struct list_head list;
+	/* Name of this domain. Never NULL. */
+	const struct cs_path_info *domainname;
+};
+
+/* Structure for holding string data. */
+struct cs_name {
+	struct cs_shared_acl_head head;
+	int size; /* Memory size allocated for this entry. */
+	struct cs_path_info entry;
+};
+
+/*
+ * Structure for reading/writing policy via /sys/kernel/security/caitsith/
+ * interfaces.
+ */
+struct cs_io_buffer {
+	/* Exclusive lock for this structure.   */
+	struct mutex io_sem;
+	char __user *read_user_buf;
+	size_t read_user_buf_avail;
+	struct {
+		struct list_head *group;
+		struct list_head *acl;
+		struct list_head *subacl;
+		const union cs_condition_element *cond;
+		size_t avail;
+		unsigned int step;
+		unsigned int query_index;
+		u16 index;
+		u8 cond_step;
+		u8 w_pos;
+		enum cs_mac_index acl_index;
+		bool eof;
+		bool print_this_acl_only;
+		bool version_done;
+		bool stat_done;
+		bool quota_done;
+		bool group_done;
+		const char *w[CS_MAX_IO_READ_QUEUE];
+	} r;
+	struct {
+		char *data;
+		struct cs_acl_info *acl;
+		size_t avail;
+		enum cs_mac_index acl_index;
+		bool is_delete;
+		bool is_deny;
+		u16 priority;
+	} w;
+	/* Buffer for reading.                  */
+	char *read_buf;
+	/* Size of read buffer.                 */
+	size_t readbuf_size;
+	/* Buffer for writing.                  */
+	char *write_buf;
+	/* Size of write buffer.                */
+	size_t writebuf_size;
+	/* Type of interface. */
+	enum cs_securityfs_interface_index type;
+	/* Users counter protected by cs_io_buffer_list_lock. */
+	u8 users;
+	/* List for telling GC not to kfree() elements. */
+	struct list_head list;
+};
+
+/* Structure for representing YYYY/MM/DD hh/mm/ss. */
+struct cs_time {
+	u16 year;
+	u8 month;
+	u8 day;
+	u8 hour;
+	u8 min;
+	u8 sec;
+};
+
+/* Prototype definition for internal use. */
+
+int __init cs_init_module(void);
+void cs_check_profile(void);
+bool cs_dump_page(struct linux_binprm *bprm, unsigned long pos,
+		  struct cs_page_dump *dump);
+bool cs_get_exename(struct cs_path_info *buf);
+bool cs_manager(void);
+bool cs_transit_domain(const char *domainname);
+char *cs_encode(const char *str);
+char *cs_encode2(const char *str, int str_len);
+char *cs_realpath(const struct path *path);
+char *cs_get_exe(void);
+int cs_audit_log(struct cs_request_info *r);
+int cs_check_acl(struct cs_request_info *r, const bool clear);
+void cs_del_condition(struct list_head *element);
+void cs_fill_path_info(struct cs_path_info *ptr);
+void cs_get_attributes(struct cs_request_info *r);
+void cs_notify_gc(struct cs_io_buffer *head, const bool is_register);
+void cs_populate_patharg(struct cs_request_info *r, const bool first);
+void cs_transition_failed(const char *domainname);
+void cs_warn_oom(const char *function);
+int cs_chroot_permission(const struct path *path);
+int cs_chmod_permission(const struct path *path, mode_t mode);
+int cs_chown_permission(const struct path *path, kuid_t user, kgid_t group);
+int cs_fcntl_permission(struct file *file, unsigned int cmd,
+			unsigned long arg);
+int cs_ioctl_permission(struct file *filp, unsigned int cmd,
+			unsigned long arg);
+int cs_link_permission(const struct path *old, const struct path *new);
+int cs_mkdir_permission(const struct path *path, unsigned int mode);
+int cs_mknod_permission(const struct path *path, const unsigned int mode,
+			unsigned int dev);
+int cs_mount_permission(const char *dev_name, const struct path *path,
+			const char *type, unsigned long flags,
+			void *data_page);
+int cs_move_mount_permission(const struct path *from_path,
+			     const struct path *to_path);
+int cs_pivot_root_permission(const struct path *old_path,
+			     const struct path *new_path);
+int cs_rename_permission(const struct path *old, const struct path *new);
+int cs_symlink_permission(const struct path *path, const char *from);
+int cs_truncate_permission(const struct path *path);
+int cs_umount_permission(const struct path *path, int flags);
+int cs_unlink_permission(const struct path *path);
+int cs_socket_create_permission(int family, int type, int protocol);
+int cs_socket_bind_permission(struct socket *sock, struct sockaddr *addr,
+			      int addr_len);
+int cs_socket_connect_permission(struct socket *sock, struct sockaddr *addr,
+				 int addr_len);
+int cs_socket_listen_permission(struct socket *sock);
+int cs_socket_post_accept_permission(struct socket *sock,
+				     struct socket *newsock);
+int cs_socket_sendmsg_permission(struct socket *sock,
+				 struct msghdr *msg, int size);
+int cs_rmdir_permission(const struct path *path);
+int cs_getattr_permission(const struct path *path);
+bool cs_capable(const u8 operation);
+int cs_open_permission(const struct path *path, const int flag);
+
+/* Variable definition for internal use. */
+
+extern bool cs_policy_loaded;
+extern struct cs_domain_info cs_kernel_domain;
+extern struct cs_path_info cs_null_name;
+extern struct list_head cs_acl_list[CS_MAX_MAC_INDEX];
+extern struct list_head cs_condition_list;
+extern struct list_head cs_domain_list;
+extern struct list_head cs_group_list[CS_MAX_GROUP];
+extern struct list_head cs_name_list[CS_MAX_HASH];
+extern struct mutex cs_policy_lock;
+extern struct srcu_struct cs_ss;
+extern unsigned int cs_memory_used[CS_MAX_MEMORY_STAT];
+
+/* Inlined functions for internal use. */
+
+/**
+ * cs_pathcmp - strcmp() for "struct cs_path_info" structure.
+ *
+ * @a: Pointer to "struct cs_path_info".
+ * @b: Pointer to "struct cs_path_info".
+ *
+ * Returns true if @a != @b, false otherwise.
+ */
+static inline bool cs_pathcmp(const struct cs_path_info *a,
+			      const struct cs_path_info *b)
+{
+	return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+/**
+ * cs_read_lock - Take lock for protecting policy.
+ *
+ * Returns index number for cs_read_unlock().
+ */
+static inline int cs_read_lock(void)
+{
+	return srcu_read_lock(&cs_ss);
+}
+
+/**
+ * cs_read_unlock - Release lock for protecting policy.
+ *
+ * @idx: Index number returned by cs_read_lock().
+ *
+ * Returns nothing.
+ */
+static inline void cs_read_unlock(const int idx)
+{
+	srcu_read_unlock(&cs_ss, idx);
+}
+
+/**
+ * cs_sys_getppid - Copy of getppid().
+ *
+ * Returns parent process's PID.
+ *
+ * Alpha does not have getppid() defined. To be able to build this module on
+ * Alpha, I have to copy getppid() from kernel/timer.c.
+ */
+static inline pid_t cs_sys_getppid(void)
+{
+	pid_t pid;
+
+	rcu_read_lock();
+	pid = task_tgid_vnr(rcu_dereference(current->real_parent));
+	rcu_read_unlock();
+	return pid;
+}
+
+/**
+ * cs_sys_getpid - Copy of getpid().
+ *
+ * Returns current thread's PID.
+ *
+ * Alpha does not have getpid() defined. To be able to build this module on
+ * Alpha, I have to copy getpid() from kernel/timer.c.
+ */
+static inline pid_t cs_sys_getpid(void)
+{
+	return task_tgid_vnr(current);
+}
+
+/**
+ * cs_put_condition - Drop reference on "struct cs_condition".
+ *
+ * @cond: Pointer to "struct cs_condition". Maybe NULL.
+ *
+ * Returns nothing.
+ */
+static inline void cs_put_condition(struct cs_condition *cond)
+{
+	if (cond)
+		atomic_dec(&cond->head.users);
+}
+
+/**
+ * cs_put_group - Drop reference on "struct cs_group".
+ *
+ * @group: Pointer to "struct cs_group". Maybe NULL.
+ *
+ * Returns nothing.
+ */
+static inline void cs_put_group(struct cs_group *group)
+{
+	if (group)
+		atomic_dec(&group->head.users);
+}
+
+/**
+ * cs_put_name - Drop reference on "struct cs_name".
+ *
+ * @name: Pointer to "struct cs_path_info". Maybe NULL.
+ *
+ * Returns nothing.
+ */
+static inline void cs_put_name(const struct cs_path_info *name)
+{
+	if (name)
+		atomic_dec(&container_of(name, struct cs_name, entry)->
+			   head.users);
+}
+
+/*
+ * Structure for holding "struct cs_domain_info *" and "u32 cs_flags" for
+ * each "struct task_struct".
+ *
+ * "struct cs_domain_info *" and "u32 cs_flags" for each "struct task_struct"
+ * are maintained outside that "struct task_struct". Therefore, cs_security
+ * != task_struct . This keeps KABI for distributor's prebuilt kernels but
+ * entails slow access.
+ *
+ * Memory for this structure is allocated when current thread tries to access
+ * it. Therefore, if memory allocation failed, current thread will be killed by
+ * SIGKILL. Note that if current->pid == 1, sending SIGKILL won't work.
+ */
+struct cs_security {
+	struct list_head list;
+	const struct task_struct *task;
+	struct cs_domain_info *cs_domain_info;
+	u32 cs_flags;
+	struct cs_request_info *r; /* Maybe NULL. */
+	struct rcu_head rcu;
+};
+
+void __init cs_main_init(void);
+bool cs_used_by_cred(const struct cs_domain_info *domain);
+int cs_start_execve(struct linux_binprm *bprm, struct cs_request_info **rp);
+void cs_finish_execve(int retval, struct cs_request_info *r);
+int cs_sysctl_permission(enum cs_mac_index type,
+			 const struct cs_path_info *filename);
+
+#define CS_TASK_SECURITY_HASH_BITS 12
+#define CS_MAX_TASK_SECURITY_HASH (1u << CS_TASK_SECURITY_HASH_BITS)
+extern struct list_head cs_task_security_list[CS_MAX_TASK_SECURITY_HASH];
+
+struct cs_security *cs_find_task_security(const struct task_struct *task);
+
+/**
+ * cs_current_security - Get "struct cs_security" for current thread.
+ *
+ * Returns pointer to "struct cs_security" for current thread.
+ */
+static inline struct cs_security *cs_current_security(void)
+{
+	return cs_find_task_security(current);
+}
+
+/**
+ * cs_task_domain - Get "struct cs_domain_info" for specified thread.
+ *
+ * @task: Pointer to "struct task_struct".
+ *
+ * Returns pointer to "struct cs_security" for specified thread.
+ */
+static inline struct cs_domain_info *cs_task_domain(struct task_struct *task)
+{
+	struct cs_domain_info *domain;
+
+	rcu_read_lock();
+	domain = cs_find_task_security(task)->cs_domain_info;
+	rcu_read_unlock();
+	return domain;
+}
+
+/**
+ * cs_current_domain - Get "struct cs_domain_info" for current thread.
+ *
+ * Returns pointer to "struct cs_domain_info" for current thread.
+ */
+static inline struct cs_domain_info *cs_current_domain(void)
+{
+	return cs_find_task_security(current)->cs_domain_info;
+}
+
+/**
+ * cs_task_flags - Get flags for specified thread.
+ *
+ * @task: Pointer to "struct task_struct".
+ *
+ * Returns flags for specified thread.
+ */
+static inline u32 cs_task_flags(struct task_struct *task)
+{
+	u32 cs_flags;
+
+	rcu_read_lock();
+	cs_flags = cs_find_task_security(task)->cs_flags;
+	rcu_read_unlock();
+	return cs_flags;
+}
+
+/**
+ * cs_current_flags - Get flags for current thread.
+ *
+ * Returns flags for current thread.
+ */
+static inline u32 cs_current_flags(void)
+{
+	return cs_find_task_security(current)->cs_flags;
+}
+
+#endif
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 05/10] CaitSith: Add LSM interface management file.
  2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
                   ` (2 preceding siblings ...)
  2022-11-02 17:10 ` [PATCH 04/10] CaitSith: Add header file Tetsuo Handa
@ 2022-11-02 17:10 ` Tetsuo Handa
  2022-11-02 19:05   ` Kees Cook
  2022-11-02 17:10 ` [PATCH 07/10] CaitSith: Add permission checking functions Tetsuo Handa
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

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@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@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


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 07/10] CaitSith: Add permission checking functions.
  2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
                   ` (3 preceding siblings ...)
  2022-11-02 17:10 ` [PATCH 05/10] CaitSith: Add LSM interface management file Tetsuo Handa
@ 2022-11-02 17:10 ` Tetsuo Handa
  2022-11-02 17:10 ` [PATCH 08/10] CaitSith: Add pathname calculation functions Tetsuo Handa
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

This file implements similar functions provided by many of
security/tomoyo/*.c files.

Signed-off-by: Tetsuo Handa <penguin-kernel@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


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 08/10] CaitSith: Add pathname calculation functions.
  2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
                   ` (4 preceding siblings ...)
  2022-11-02 17:10 ` [PATCH 07/10] CaitSith: Add permission checking functions Tetsuo Handa
@ 2022-11-02 17:10 ` Tetsuo Handa
  2022-11-02 17:10 ` [PATCH 09/10] CaitSith: Add garbage collector functions Tetsuo Handa
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

This file implements similar functions provided by
security/tomoyo/realpath.c file.

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

diff --git a/security/caitsith/realpath.c b/security/caitsith/realpath.c
new file mode 100644
index 000000000000..ed6b1407a54d
--- /dev/null
+++ b/security/caitsith/realpath.c
@@ -0,0 +1,415 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * realpath.c
+ *
+ * Copyright (C) 2005-2012  NTT DATA CORPORATION
+ *
+ * Version: 0.2.10   2021/06/06
+ */
+
+#include "caitsith.h"
+
+#include <linux/proc_fs.h>
+
+/***** SECTION1: Constants definition *****/
+
+/***** SECTION2: Structure definition *****/
+
+/***** SECTION3: Prototype definition section *****/
+
+static char *cs_get_absolute_path(const struct path *path, char * const buffer,
+				  const int buflen);
+static char *cs_get_dentry_path(struct dentry *dentry, char * const buffer,
+				const int buflen);
+static char *cs_get_local_path(struct dentry *dentry, char * const buffer,
+			       const int buflen);
+static int cs_const_part_length(const char *filename);
+
+/***** SECTION4: Standalone functions section *****/
+
+/**
+ * cs_realpath_lock - Take locks for __d_path().
+ *
+ * Returns nothing.
+ */
+static inline void cs_realpath_lock(void)
+{
+	/* dcache_lock is locked by __d_path(). */
+	/* vfsmount_lock is locked by __d_path(). */
+}
+
+/**
+ * cs_realpath_unlock - Release locks for __d_path().
+ *
+ * Returns nothing.
+ */
+static inline void cs_realpath_unlock(void)
+{
+	/* vfsmount_lock is unlocked by __d_path(). */
+	/* dcache_lock is unlocked by __d_path(). */
+}
+
+/***** SECTION5: Variables definition section *****/
+
+/***** SECTION6: Dependent functions section *****/
+
+/**
+ * cs_get_absolute_path - Get the path of a dentry but ignores chroot'ed root.
+ *
+ * @path:   Pointer to "struct path".
+ * @buffer: Pointer to buffer to return value in.
+ * @buflen: Sizeof @buffer.
+ *
+ * Returns the buffer on success, an error code otherwise.
+ *
+ * Caller holds the dcache_lock and vfsmount_lock.
+ * Based on __d_path() in fs/dcache.c
+ */
+static char *cs_get_absolute_path(const struct path *path, char * const buffer,
+				  const int buflen)
+{
+	if (buflen < 256)
+		return ERR_PTR(-ENOMEM);
+	return d_absolute_path(path, buffer, buflen - 1);
+}
+
+/**
+ * cs_get_dentry_path - Get the path of a dentry.
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @buffer: Pointer to buffer to return value in.
+ * @buflen: Sizeof @buffer.
+ *
+ * Returns the buffer on success, an error code otherwise.
+ *
+ * Based on dentry_path() in fs/dcache.c
+ */
+static char *cs_get_dentry_path(struct dentry *dentry, char * const buffer,
+				const int buflen)
+{
+	if (buflen < 256)
+		return ERR_PTR(-ENOMEM);
+	/* rename_lock is locked/unlocked by dentry_path_raw(). */
+	return dentry_path_raw(dentry, buffer, buflen - 1);
+}
+
+/**
+ * cs_get_local_path - Get the path of a dentry.
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @buffer: Pointer to buffer to return value in.
+ * @buflen: Sizeof @buffer.
+ *
+ * Returns the buffer on success, an error code otherwise.
+ */
+static char *cs_get_local_path(struct dentry *dentry, char * const buffer,
+			       const int buflen)
+{
+	struct super_block *sb = dentry->d_sb;
+	char *pos = cs_get_dentry_path(dentry, buffer, buflen);
+
+	if (IS_ERR(pos))
+		return pos;
+	/* Convert from $PID to self if $PID is current thread. */
+	if (sb->s_magic == PROC_SUPER_MAGIC && *pos == '/') {
+		char *ep;
+		const pid_t pid = (pid_t) simple_strtoul(pos + 1, &ep, 10);
+
+		if (*ep == '/' && pid && pid ==
+		    task_tgid_nr_ns(current, proc_pid_ns(sb))) {
+			pos = ep - 5;
+			if (pos < buffer)
+				goto out;
+			memmove(pos, "/self", 5);
+		}
+		goto prepend_filesystem_name;
+	}
+	/* Use filesystem name for unnamed devices. */
+	if (!MAJOR(sb->s_dev))
+		goto prepend_filesystem_name;
+	{
+		struct inode *inode = d_backing_inode(sb->s_root);
+
+		/*
+		 * Use filesystem name if filesystems does not support rename()
+		 * operation.
+		 */
+		if (!inode->i_op->rename)
+			goto prepend_filesystem_name;
+	}
+	/* Prepend device name. */
+	{
+		char name[64];
+		int name_len;
+		const dev_t dev = sb->s_dev;
+
+		name[sizeof(name) - 1] = '\0';
+		snprintf(name, sizeof(name) - 1, "dev(%u,%u):", MAJOR(dev),
+			 MINOR(dev));
+		name_len = strlen(name);
+		pos -= name_len;
+		if (pos < buffer)
+			goto out;
+		memmove(pos, name, name_len);
+		return pos;
+	}
+	/* Prepend filesystem name. */
+prepend_filesystem_name:
+	{
+		const char *name = sb->s_type->name;
+		const int name_len = strlen(name);
+
+		pos -= name_len + 1;
+		if (pos < buffer)
+			goto out;
+		memmove(pos, name, name_len);
+		pos[name_len] = ':';
+	}
+	return pos;
+out:
+	return ERR_PTR(-ENOMEM);
+}
+
+/**
+ * cs_realpath - Returns realpath(3) of the given pathname but ignores chroot'ed root.
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns the realpath of the given @path on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+char *cs_realpath(const struct path *path)
+{
+	char *buf = NULL;
+	char *name = NULL;
+	unsigned int buf_len = PAGE_SIZE / 2;
+	struct dentry *dentry = path->dentry;
+	struct super_block *sb;
+
+	if (!dentry)
+		return NULL;
+	sb = dentry->d_sb;
+	while (1) {
+		char *pos;
+		struct inode *inode;
+
+		buf_len <<= 1;
+		kfree(buf);
+		buf = kmalloc(buf_len, GFP_NOFS);
+		if (!buf)
+			break;
+		/* To make sure that pos is '\0' terminated. */
+		buf[buf_len - 1] = '\0';
+		/* For "pipe:[\$]" and "socket:[\$]". */
+		if (dentry->d_op && dentry->d_op->d_dname) {
+			pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1);
+			goto encode;
+		}
+		inode = d_backing_inode(sb->s_root);
+		/*
+		 * Use local name for "filesystems without rename() operation
+		 * and device file" or "path without vfsmount" or "absolute
+		 * name is unavailable" cases.
+		 */
+		if (!path->mnt ||
+		    (!inode->i_op->rename &&
+		     !(sb->s_type->fs_flags & FS_REQUIRES_DEV)))
+			pos = ERR_PTR(-EINVAL);
+		else
+			pos = cs_get_absolute_path(path, buf, buf_len - 1);
+		if (pos == ERR_PTR(-EINVAL))
+			pos = cs_get_local_path(path->dentry, buf,
+						buf_len - 1);
+encode:
+		if (IS_ERR(pos))
+			continue;
+		name = cs_encode(pos);
+		break;
+	}
+	kfree(buf);
+	if (!name)
+		cs_warn_oom(__func__);
+	return name;
+}
+
+/**
+ * cs_encode2 - Encode binary string to ascii string.
+ *
+ * @str:     String in binary format. Maybe NULL.
+ * @str_len: Size of @str in byte.
+ *
+ * Returns pointer to @str in ascii format on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+char *cs_encode2(const char *str, int str_len)
+{
+	int i;
+	int len;
+	const char *p = str;
+	char *cp;
+	char *cp0;
+
+	if (!p)
+		return NULL;
+	len = str_len;
+	for (i = 0; i < str_len; i++) {
+		const unsigned char c = p[i];
+
+		if (!(c > ' ' && c < 127 && c != '\\'))
+			len += 3;
+	}
+	len++;
+	cp = kzalloc(len, GFP_NOFS);
+	if (!cp)
+		return NULL;
+	cp0 = cp;
+	p = str;
+	for (i = 0; i < str_len; i++) {
+		const unsigned char c = p[i];
+
+		if (c > ' ' && c < 127 && c != '\\') {
+			*cp++ = c;
+		} else {
+			*cp++ = '\\';
+			*cp++ = (c >> 6) + '0';
+			*cp++ = ((c >> 3) & 7) + '0';
+			*cp++ = (c & 7) + '0';
+		}
+	}
+	return cp0;
+}
+
+/**
+ * cs_encode - Encode binary string to ascii string.
+ *
+ * @str: String in binary format. Maybe NULL.
+ *
+ * Returns pointer to @str in ascii format on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+char *cs_encode(const char *str)
+{
+	return str ? cs_encode2(str, strlen(str)) : NULL;
+}
+
+/**
+ * cs_const_part_length - Evaluate the initial length without a pattern in a token.
+ *
+ * @filename: The string to evaluate. Maybe NULL.
+ *
+ * Returns the initial length without a pattern in @filename.
+ */
+static int cs_const_part_length(const char *filename)
+{
+	char c;
+	int len = 0;
+
+	if (!filename)
+		return 0;
+	while (1) {
+		c = *filename++;
+		if (!c)
+			break;
+		if (c != '\\') {
+			len++;
+			continue;
+		}
+		c = *filename++;
+		switch (c) {
+		case '0':   /* "\ooo" */
+		case '1':
+		case '2':
+		case '3':
+			c = *filename++;
+			if (c < '0' || c > '7')
+				break;
+			c = *filename++;
+			if (c < '0' || c > '7')
+				break;
+			len += 4;
+			continue;
+		}
+		break;
+	}
+	return len;
+}
+
+/**
+ * cs_fill_path_info - Fill in "struct cs_path_info" members.
+ *
+ * @ptr: Pointer to "struct cs_path_info" to fill in.
+ *
+ * Returns nothing.
+ *
+ * The caller sets "struct cs_path_info"->name.
+ */
+void cs_fill_path_info(struct cs_path_info *ptr)
+{
+	const char *name = ptr->name;
+	const int len = strlen(name);
+
+	ptr->total_len = len;
+	ptr->const_len = cs_const_part_length(name);
+	ptr->hash = full_name_hash(NULL, name, len);
+}
+
+/**
+ * cs_get_exe - Get cs_realpath() of current process.
+ *
+ * Returns the cs_realpath() of current process on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so the caller must kfree()
+ * if this function didn't return NULL.
+ */
+char *cs_get_exe(void)
+{
+	struct mm_struct *mm;
+	struct file *exe_file;
+
+	if (current->flags & PF_KTHREAD)
+		return kstrdup("<kernel>", GFP_NOFS);
+	mm = current->mm;
+	if (!mm)
+		goto task_has_no_mm;
+	/* Not using get_mm_exe_file() as it is not exported. */
+	rcu_read_lock();
+	exe_file = rcu_dereference(mm->exe_file);
+	if (exe_file && !get_file_rcu(exe_file))
+		exe_file = NULL;
+	rcu_read_unlock();
+	if (exe_file) {
+		char *cp = cs_realpath(&exe_file->f_path);
+
+		fput(exe_file);
+		return cp;
+	}
+task_has_no_mm:
+	/* I'don't know. */
+	return kstrdup("<unknown>", GFP_NOFS);
+}
+
+/**
+ * cs_get_exename - Get cs_realpath() of current process.
+ *
+ * @buf: Pointer to "struct cs_path_info".
+ *
+ * Returns true on success, false otherwise.
+ *
+ * This function uses kzalloc(), so the caller must kfree()
+ * if this function returned true.
+ */
+bool cs_get_exename(struct cs_path_info *buf)
+{
+	buf->name = cs_get_exe();
+	if (buf->name) {
+		cs_fill_path_info(buf);
+		return true;
+	}
+	return false;
+}
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 09/10] CaitSith: Add garbage collector functions.
  2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
                   ` (5 preceding siblings ...)
  2022-11-02 17:10 ` [PATCH 08/10] CaitSith: Add pathname calculation functions Tetsuo Handa
@ 2022-11-02 17:10 ` Tetsuo Handa
  2022-11-02 17:10 ` [PATCH 10/10] CaitSith: Add Kconfig and Makefile files Tetsuo Handa
       [not found] ` <20221102171025.126961-6-penguin-kernel@I-love.SAKURA.ne.jp>
  8 siblings, 0 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

This file implements similar functions provided by
security/tomoyo/gc.c file.

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

diff --git a/security/caitsith/gc.c b/security/caitsith/gc.c
new file mode 100644
index 000000000000..85d75f22fa2c
--- /dev/null
+++ b/security/caitsith/gc.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * gc.c
+ *
+ * Copyright (C) 2005-2012  NTT DATA CORPORATION
+ *
+ * Version: 0.2.10   2021/06/06
+ */
+
+#include "caitsith.h"
+
+/***** SECTION1: Constants definition *****/
+
+/* The list for "struct cs_io_buffer". */
+static LIST_HEAD(cs_io_buffer_list);
+/* Lock for protecting cs_io_buffer_list. */
+static DEFINE_SPINLOCK(cs_io_buffer_list_lock);
+
+/***** SECTION2: Structure definition *****/
+
+/***** SECTION3: Prototype definition section *****/
+
+static bool cs_domain_used_by_task(struct cs_domain_info *domain);
+static bool cs_name_used_by_io_buffer(const char *string, const size_t size);
+static bool cs_struct_used_by_io_buffer(const struct list_head *element);
+static int cs_gc_thread(void *unused);
+static void cs_collect_acl(struct list_head *list);
+static void cs_collect_entry(void);
+static void cs_collect_member(const enum cs_policy_id id,
+			      struct list_head *member_list);
+static void cs_try_to_gc(const enum cs_policy_id type,
+			 struct list_head *element);
+
+/***** SECTION4: Standalone functions section *****/
+
+/***** SECTION5: Variables definition section *****/
+
+/*
+ * Lock for syscall users.
+ *
+ * This lock is held for only protecting single SRCU section.
+ */
+struct srcu_struct cs_ss;
+
+/***** SECTION6: Dependent functions section *****/
+
+/**
+ * cs_struct_used_by_io_buffer - Check whether the list element is used by /sys/kernel/security/caitsith/ users or not.
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns true if @element is used by /sys/kernel/security/caitsith/ users,
+ * false otherwise.
+ */
+static bool cs_struct_used_by_io_buffer(const struct list_head *element)
+{
+	struct cs_io_buffer *head;
+	bool in_use = false;
+
+	spin_lock(&cs_io_buffer_list_lock);
+	list_for_each_entry(head, &cs_io_buffer_list, list) {
+		head->users++;
+		spin_unlock(&cs_io_buffer_list_lock);
+		mutex_lock(&head->io_sem);
+		if (head->r.acl == element || head->r.subacl == element ||
+		    head->r.group == element || &head->w.acl->list == element)
+			in_use = true;
+		mutex_unlock(&head->io_sem);
+		spin_lock(&cs_io_buffer_list_lock);
+		head->users--;
+		if (in_use)
+			break;
+	}
+	spin_unlock(&cs_io_buffer_list_lock);
+	return in_use;
+}
+
+/**
+ * cs_name_used_by_io_buffer - Check whether the string is used by /sys/kernel/security/caitsith/ users or not.
+ *
+ * @string: String to check.
+ * @size:   Memory allocated for @string .
+ *
+ * Returns true if @string is used by /sys/kernel/security/caitsith/ users,
+ * false otherwise.
+ */
+static bool cs_name_used_by_io_buffer(const char *string, const size_t size)
+{
+	struct cs_io_buffer *head;
+	bool in_use = false;
+
+	spin_lock(&cs_io_buffer_list_lock);
+	list_for_each_entry(head, &cs_io_buffer_list, list) {
+		int i;
+
+		head->users++;
+		spin_unlock(&cs_io_buffer_list_lock);
+		mutex_lock(&head->io_sem);
+		for (i = 0; i < CS_MAX_IO_READ_QUEUE; i++) {
+			const char *w = head->r.w[i];
+
+			if (w < string || w > string + size)
+				continue;
+			in_use = true;
+			break;
+		}
+		mutex_unlock(&head->io_sem);
+		spin_lock(&cs_io_buffer_list_lock);
+		head->users--;
+		if (in_use)
+			break;
+	}
+	spin_unlock(&cs_io_buffer_list_lock);
+	return in_use;
+}
+
+/**
+ * cs_domain_used_by_task - Check whether the given pointer is referenced by a task.
+ *
+ * @domain: Pointer to "struct cs_domain_info".
+ *
+ * Returns true if @domain is in use, false otherwise.
+ */
+static bool cs_domain_used_by_task(struct cs_domain_info *domain)
+{
+	bool in_use = false;
+	/*
+	 * Don't delete this domain if somebody is doing execve().
+	 *
+	 * Since cs_finish_execve() first reverts cs_domain_info and then
+	 * updates cs_flags, we need smp_rmb() to make sure that GC first
+	 * checks cs_flags and then checks cs_domain_info.
+	 */
+	int idx;
+
+	rcu_read_lock();
+	for (idx = 0; idx < CS_MAX_TASK_SECURITY_HASH; idx++) {
+		struct cs_security *ptr;
+		struct list_head *list = &cs_task_security_list[idx];
+
+		list_for_each_entry_rcu(ptr, list, list) {
+			if (!(ptr->cs_flags & CS_TASK_IS_IN_EXECVE)) {
+				smp_rmb(); /* Avoid out of order execution. */
+				if (ptr->cs_domain_info != domain)
+					continue;
+			}
+			in_use = true;
+			goto out;
+		}
+	}
+	in_use = cs_used_by_cred(domain);
+out:
+	rcu_read_unlock();
+	return in_use;
+}
+
+/**
+ * cs_acl_info_has_sub_acl - Clear "struct cs_acl_info"->acl_info.
+ *
+ * @list: Pointer to "struct list_head".
+ *
+ * Returns true if @list is not empty, false otherwise.
+ */
+static bool cs_acl_info_has_sub_acl(struct list_head *list)
+{
+	struct cs_acl_info *acl;
+	struct cs_acl_info *tmp;
+
+	if (list_empty(list))
+		return false;
+	mutex_lock(&cs_policy_lock);
+	list_for_each_entry_safe(acl, tmp, list, list) {
+		cs_try_to_gc(CS_ID_ACL, &acl->list);
+	}
+	mutex_unlock(&cs_policy_lock);
+	return !list_empty(list);
+}
+
+/**
+ * cs_del_acl - Delete members in "struct cs_acl_info".
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ */
+static inline void cs_del_acl(struct list_head *element)
+{
+	struct cs_acl_info *acl = container_of(element, typeof(*acl), list);
+
+	cs_put_condition(acl->cond);
+}
+
+/**
+ * cs_del_domain - Delete members in "struct cs_domain_info".
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static inline void cs_del_domain(struct list_head *element)
+{
+	struct cs_domain_info *domain =
+		container_of(element, typeof(*domain), list);
+	cs_put_name(domain->domainname);
+}
+
+/**
+ * cs_del_string_group - Delete members in "struct cs_string_group".
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ */
+static inline void cs_del_string_group(struct list_head *element)
+{
+	struct cs_string_group *member =
+		container_of(element, typeof(*member), head.list);
+	cs_put_name(member->member_name);
+}
+
+/**
+ * cs_del_group - Delete "struct cs_group".
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ */
+static inline void cs_del_group(struct list_head *element)
+{
+	struct cs_group *group =
+		container_of(element, typeof(*group), head.list);
+	cs_put_name(group->group_name);
+}
+
+/**
+ * cs_del_condition - Delete members in "struct cs_condition".
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ */
+void cs_del_condition(struct list_head *element)
+{
+	struct cs_condition *cond = container_of(element, typeof(*cond),
+						 head.list);
+	const union cs_condition_element *condp = (typeof(condp)) (cond + 1);
+
+	while ((void *) condp < (void *) ((u8 *) cond) + cond->size) {
+		const enum cs_conditions_index left = condp->left;
+		const enum cs_conditions_index right = condp->right;
+
+		condp++;
+		if (left == CS_ARGV_ENTRY)
+			condp++;
+		else if (left == CS_ENVP_ENTRY) {
+			cs_put_name(condp->path);
+			condp++;
+		}
+		if (right == CS_IMM_GROUP) {
+			cs_put_group(condp->group);
+			condp++;
+		} else if (right == CS_IMM_NAME_ENTRY) {
+			if (condp->path != &cs_null_name)
+				cs_put_name(condp->path);
+			condp++;
+		} else if (right == CS_IMM_NUMBER_ENTRY1)
+			condp++;
+		else if (right == CS_IMM_NUMBER_ENTRY2)
+			condp += 2;
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+		else if (right == CS_IMM_IPV6ADDR_ENTRY1)
+			condp = (void *)
+				(((u8 *) condp) + sizeof(struct in6_addr));
+		else if (right == CS_IMM_IPV6ADDR_ENTRY2)
+			condp = (void *)
+				(((u8 *) condp) + sizeof(struct in6_addr) * 2);
+#endif
+	}
+}
+
+/**
+ * cs_try_to_gc - Try to kfree() an entry.
+ *
+ * @type:    One of values in "enum cs_policy_id".
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static void cs_try_to_gc(const enum cs_policy_id type,
+			 struct list_head *element)
+{
+	/*
+	 * __list_del_entry() guarantees that the list element became no longer
+	 * reachable from the list which the element was originally on (e.g.
+	 * cs_domain_list). Also, synchronize_srcu() guarantees that the list
+	 * element became no longer referenced by syscall users.
+	 */
+	__list_del_entry(element);
+	mutex_unlock(&cs_policy_lock);
+	synchronize_srcu(&cs_ss);
+	/*
+	 * However, there are two users which may still be using the list
+	 * element. We need to defer until both users forget this element.
+	 *
+	 * Don't kfree() until "struct cs_io_buffer"->r.{group,acl,subacl} and
+	 * "struct cs_io_buffer"->w.acl forget this element.
+	 */
+	if (cs_struct_used_by_io_buffer(element))
+		goto reinject;
+	switch (type) {
+	case CS_ID_GROUP:
+		cs_del_group(element);
+		break;
+	case CS_ID_STRING_GROUP:
+		cs_del_string_group(element);
+		break;
+	case CS_ID_CONDITION:
+		cs_del_condition(element);
+		break;
+	case CS_ID_NAME:
+		/*
+		 * Don't kfree() until all "struct cs_io_buffer"->r.w[] forget
+		 * this element.
+		 */
+		if (cs_name_used_by_io_buffer
+		    (container_of(element, typeof(struct cs_name),
+				  head.list)->entry.name,
+		     container_of(element, typeof(struct cs_name),
+				  head.list)->size))
+			goto reinject;
+		break;
+	case CS_ID_ACL:
+		/*
+		 * Don't kfree() until "struct cs_acl_info"->acl_info_list
+		 * becomes empty.
+		 */
+		if (cs_acl_info_has_sub_acl
+		    (&container_of(element, typeof(struct cs_acl_info),
+				   list)->acl_info_list))
+			goto reinject;
+		cs_del_acl(element);
+		break;
+	case CS_ID_DOMAIN:
+		/*
+		 * Don't kfree() until all "struct task_struct" forget this
+		 * element.
+		 */
+		if (cs_domain_used_by_task
+		    (container_of(element, typeof(struct cs_domain_info),
+				  list)))
+			goto reinject;
+		cs_del_domain(element);
+		break;
+	default:
+		break;
+	}
+	mutex_lock(&cs_policy_lock);
+	cs_memory_used[CS_MEMORY_POLICY] -= ksize(element);
+	kfree(element);
+	return;
+reinject:
+	/*
+	 * We can safely reinject this element here because
+	 * (1) Appending list elements and removing list elements are protected
+	 *     by cs_policy_lock mutex.
+	 * (2) Only this function removes list elements and this function is
+	 *     exclusively executed by cs_gc_mutex mutex.
+	 * are true.
+	 */
+	mutex_lock(&cs_policy_lock);
+	list_add_rcu(element, element->prev);
+}
+
+/**
+ * cs_collect_member - Delete elements with "struct cs_acl_head".
+ *
+ * @id:          One of values in "enum cs_policy_id".
+ * @member_list: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static void cs_collect_member(const enum cs_policy_id id,
+			      struct list_head *member_list)
+{
+	struct cs_acl_head *member;
+	struct cs_acl_head *tmp;
+
+	list_for_each_entry_safe(member, tmp, member_list, list) {
+		if (!member->is_deleted)
+			continue;
+		member->is_deleted = CS_GC_IN_PROGRESS;
+		cs_try_to_gc(id, &member->list);
+	}
+}
+
+/**
+ * cs_collect_acl - Delete elements in "struct cs_acl_info".
+ *
+ * @list: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static void cs_collect_acl(struct list_head *list)
+{
+	struct cs_acl_info *acl;
+	struct cs_acl_info *tmp;
+
+	list_for_each_entry_safe(acl, tmp, list, list) {
+		if (!acl->is_deleted)
+			continue;
+		cs_try_to_gc(CS_ID_ACL, &acl->list);
+	}
+}
+
+/**
+ * cs_collect_entry - Try to kfree() deleted elements.
+ *
+ * Returns nothing.
+ */
+static void cs_collect_entry(void)
+{
+	int i;
+
+	mutex_lock(&cs_policy_lock);
+	{
+		struct cs_domain_info *domain;
+		struct cs_domain_info *tmp;
+
+		list_for_each_entry_safe(domain, tmp, &cs_domain_list, list) {
+			if (domain == &cs_kernel_domain ||
+			    cs_domain_used_by_task(domain))
+				continue;
+			cs_try_to_gc(CS_ID_DOMAIN, &domain->list);
+		}
+	}
+	for (i = 0; i < CS_MAX_MAC_INDEX; i++) {
+		struct cs_acl_info *ptr;
+		struct cs_acl_info *tmp;
+		struct list_head * const list = &cs_acl_list[i];
+
+		list_for_each_entry_safe(ptr, tmp, list, list) {
+			cs_collect_acl(&ptr->acl_info_list);
+			if (!ptr->is_deleted)
+				continue;
+			/* ptr->is_deleted = CS_GC_IN_PROGRESS; */
+			cs_try_to_gc(CS_ID_ACL, &ptr->list);
+		}
+	}
+	{
+		struct cs_shared_acl_head *ptr;
+		struct cs_shared_acl_head *tmp;
+
+		list_for_each_entry_safe(ptr, tmp, &cs_condition_list, list) {
+			if (atomic_read(&ptr->users) > 0)
+				continue;
+			atomic_set(&ptr->users, CS_GC_IN_PROGRESS);
+			cs_try_to_gc(CS_ID_CONDITION, &ptr->list);
+		}
+	}
+	for (i = 0; i < CS_MAX_GROUP; i++) {
+		struct list_head *list = &cs_group_list[i];
+		struct cs_group *group;
+		struct cs_group *tmp;
+		enum cs_policy_id id = CS_ID_STRING_GROUP;
+
+		if (i == CS_NUMBER_GROUP)
+			id = CS_ID_NUMBER_GROUP;
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+		else if (i == CS_IP_GROUP)
+			id = CS_ID_IP_GROUP;
+#endif
+		list_for_each_entry_safe(group, tmp, list, head.list) {
+			cs_collect_member(id, &group->member_list);
+			if (!list_empty(&group->member_list) ||
+			    atomic_read(&group->head.users) > 0)
+				continue;
+			atomic_set(&group->head.users, CS_GC_IN_PROGRESS);
+			cs_try_to_gc(CS_ID_GROUP, &group->head.list);
+		}
+	}
+	for (i = 0; i < CS_MAX_HASH; i++) {
+		struct list_head *list = &cs_name_list[i];
+		struct cs_shared_acl_head *ptr;
+		struct cs_shared_acl_head *tmp;
+
+		list_for_each_entry_safe(ptr, tmp, list, list) {
+			if (atomic_read(&ptr->users) > 0)
+				continue;
+			atomic_set(&ptr->users, CS_GC_IN_PROGRESS);
+			cs_try_to_gc(CS_ID_NAME, &ptr->list);
+		}
+	}
+	mutex_unlock(&cs_policy_lock);
+}
+
+/**
+ * cs_gc_thread - Garbage collector thread function.
+ *
+ * @unused: Unused.
+ *
+ * Returns 0.
+ */
+static int cs_gc_thread(void *unused)
+{
+	/* Garbage collector thread is exclusive. */
+	static DEFINE_MUTEX(cs_gc_mutex);
+
+	if (!mutex_trylock(&cs_gc_mutex))
+		goto out;
+	cs_collect_entry();
+	{
+		struct cs_io_buffer *head;
+		struct cs_io_buffer *tmp;
+
+		spin_lock(&cs_io_buffer_list_lock);
+		list_for_each_entry_safe(head, tmp, &cs_io_buffer_list,
+					 list) {
+			if (head->users)
+				continue;
+			list_del(&head->list);
+			kfree(head->read_buf);
+			kfree(head->write_buf);
+			kfree(head);
+		}
+		spin_unlock(&cs_io_buffer_list_lock);
+	}
+	mutex_unlock(&cs_gc_mutex);
+out:
+	/* This acts as do_exit(0). */
+	return 0;
+}
+
+/**
+ * cs_notify_gc - Register/unregister /sys/kernel/security/caitsith/ users.
+ *
+ * @head:        Pointer to "struct cs_io_buffer".
+ * @is_register: True if register, false if unregister.
+ *
+ * Returns nothing.
+ */
+void cs_notify_gc(struct cs_io_buffer *head, const bool is_register)
+{
+	bool is_write = false;
+
+	spin_lock(&cs_io_buffer_list_lock);
+	if (is_register) {
+		head->users = 1;
+		list_add(&head->list, &cs_io_buffer_list);
+	} else {
+		is_write = head->write_buf != NULL;
+		if (!--head->users) {
+			list_del(&head->list);
+			kfree(head->read_buf);
+			kfree(head->write_buf);
+			kfree(head);
+		}
+	}
+	spin_unlock(&cs_io_buffer_list_lock);
+	if (is_write) {
+		struct task_struct *task = kthread_create(cs_gc_thread, NULL,
+							  "CaitSith's GC");
+		if (!IS_ERR(task))
+			wake_up_process(task);
+	}
+}
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 10/10] CaitSith: Add Kconfig and Makefile files.
  2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
                   ` (6 preceding siblings ...)
  2022-11-02 17:10 ` [PATCH 09/10] CaitSith: Add garbage collector functions Tetsuo Handa
@ 2022-11-02 17:10 ` Tetsuo Handa
       [not found] ` <20221102171025.126961-6-penguin-kernel@I-love.SAKURA.ne.jp>
  8 siblings, 0 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:10 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook
  Cc: Tetsuo Handa

The point of CaitSith is that you can choose CONFIG_SECURITY_CAITSITH=m .
But please don't choose CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER=y
unless you understood how to prepare built-in policy configuration.
If you choose CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER=y without
built-in policy configuration, the kernel will panic().

For more information, please follow instructions at "2.1.6. Install the
userspace tools" and afterwards in https://caitsith.osdn.jp/index.html .

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 security/Kconfig           |   1 +
 security/Makefile          |   1 +
 security/caitsith/Kconfig  | 112 +++++++++++++++++++++++++++++++++++++
 security/caitsith/Makefile |  11 ++++
 4 files changed, 125 insertions(+)
 create mode 100644 security/caitsith/Kconfig
 create mode 100644 security/caitsith/Makefile

diff --git a/security/Kconfig b/security/Kconfig
index e6db09a779b7..a2f3ba29d63b 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -209,6 +209,7 @@ source "security/lockdown/Kconfig"
 source "security/landlock/Kconfig"
 
 source "security/integrity/Kconfig"
+source "security/caitsith/Kconfig"
 
 choice
 	prompt "First legacy 'major LSM' to be initialized"
diff --git a/security/Makefile b/security/Makefile
index 18121f8f85cd..ef03c490e099 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_SECURITY_LOCKDOWN_LSM)	+= lockdown/
 obj-$(CONFIG_CGROUPS)			+= device_cgroup.o
 obj-$(CONFIG_BPF_LSM)			+= bpf/
 obj-$(CONFIG_SECURITY_LANDLOCK)		+= landlock/
+obj-$(CONFIG_SECURITY_CAITSITH)		+= caitsith/
 
 # Object integrity file lists
 obj-$(CONFIG_INTEGRITY)			+= integrity/
diff --git a/security/caitsith/Kconfig b/security/caitsith/Kconfig
new file mode 100644
index 000000000000..0bae4f2d8b7f
--- /dev/null
+++ b/security/caitsith/Kconfig
@@ -0,0 +1,112 @@
+config SECURITY_CAITSITH
+	tristate "CaitSith support"
+	default n
+	help
+	  Say Y or M here to support CaitSith.
+	  https://caitsith.osdn.jp/
+
+config SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+	bool "Activate without calling userspace policy loader."
+	default n
+	depends on SECURITY_CAITSITH
+	help
+	  Say Y here if you want to activate access control as soon as built-in
+	  policy was loaded. This option will be useful for systems where
+	  operations which can lead to the hijacking of the boot sequence are
+	  needed before loading the policy. For example, you can activate
+	  immediately after loading the fixed part of policy which will allow
+	  only operations needed for mounting a partition which contains the
+	  variant part of policy and verifying (e.g. running GPG check) and
+	  loading the variant part of policy. Since you can start using
+	  enforcing mode from the beginning, you can reduce the possibility of
+	  hijacking the boot sequence.
+
+	  If you say Y to both "Compile as loadable kernel module" option and
+	  "Activate without calling userspace policy loader." option, be sure
+	  to excplicitly load the kernel module from the userspace, for
+	  the kernel will not call /sbin/caitsith-init when /sbin/init starts.
+
+config SECURITY_CAITSITH_POLICY_LOADER
+	string "Location of userspace policy loader"
+	default "/sbin/caitsith-init"
+	depends on SECURITY_CAITSITH
+	depends on !SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+	help
+	  This is the default pathname of policy loader which is called before
+	  activation. You can override this setting via CS_loader= kernel
+	  command line option.
+
+config SECURITY_CAITSITH_ACTIVATION_TRIGGER
+	string "Trigger for calling userspace policy loader"
+	default "/sbin/init"
+	depends on SECURITY_CAITSITH
+	depends on !SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+	help
+	  This is the default pathname of activation trigger.
+	  You can override this setting via CS_trigger= kernel command line
+	  option. For example, if you pass init=/bin/systemd option, you may
+	  want to also pass CS_trigger=/bin/systemd option.
+
+	  Say Y here if you want to enable only specific functionality in order
+	  to reduce object file size.
+
+config SECURITY_CAITSITH_READDIR
+	bool "Enable readdir operation restriction."
+	default y
+	depends on SECURITY_CAITSITH
+	help
+	  Say Y here if you want to enable restriction of opening directories
+	  for reading. Reading directory entries is a commonly requested
+	  operation and damage caused by not restricting it might be acceptable
+	  for you.
+
+config SECURITY_CAITSITH_GETATTR
+	bool "Enable getattr operation restriction."
+	default y
+	depends on SECURITY_CAITSITH
+	help
+	  Say Y here if you want to enable restriction of getting information
+	  of files. Getting file's information is a commonly requested
+	  operation and damage caused by not restricting it might be acceptable
+	  for you.
+
+config SECURITY_CAITSITH_NETWORK
+	bool "Enable socket operation restriction."
+	default y
+	depends on SECURITY_NETWORK
+	depends on SECURITY_CAITSITH
+	help
+	  Say Y here if you want to enable restriction of INET/INET6/UNIX
+	  socket's operations.
+
+config SECURITY_CAITSITH_CAPABILITY
+	bool "Enable non-POSIX capability operation restriction."
+	default y
+	depends on SECURITY_CAITSITH
+	help
+	  Say Y here if you want to enable restriction of non-POSIX
+	  capabilities.
+
+config SECURITY_CAITSITH_ENVIRON
+	bool "Enable environment variable names/values restriction."
+	default y
+	depends on SECURITY_CAITSITH
+	help
+	  Say Y here if you want to enable restriction of environment variable
+	  names/values passed upon program execution request.
+
+config SECURITY_CAITSITH_MANUAL_DOMAIN_TRANSITION
+	bool "Enable domain transition without program execution request."
+	default y
+	depends on SECURITY_CAITSITH
+	help
+	  Say Y here if you want to enable domain transition without involving
+	  program execution request.
+
+config SECURITY_CAITSITH_AUTO_DOMAIN_TRANSITION
+	bool "Enable automatic domain transition."
+	default y
+	depends on SECURITY_CAITSITH
+	help
+	  Say Y here if you want to enable automatic domain transition when
+	  conditions are met.
diff --git a/security/caitsith/Makefile b/security/caitsith/Makefile
new file mode 100644
index 000000000000..4fb86ed45df0
--- /dev/null
+++ b/security/caitsith/Makefile
@@ -0,0 +1,11 @@
+caitsith-objs := permission.o gc.o policy_io.o realpath.o lsm.o
+obj-$(CONFIG_SECURITY_CAITSITH) += caitsith.o
+
+targets += builtin-policy.h
+quiet_cmd_policy = Generating built-in policy for CaitSith 0.2.
+cmd_policy = ( echo "static char cs_builtin_policy[] __initdata ="; sed -e 's/\\/\\134/g' -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/'; echo "\"\";" ) < $< > $@
+
+$(obj)/builtin-policy.h: $(wildcard $(obj)/policy.conf $(srctree)/$(src)/policy.conf) /dev/null FORCE
+	$(call if_changed,policy)
+
+$(obj)/policy_io.o: $(obj)/builtin-policy.h
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 6a/10] CaitSith: Add policy management functions.
       [not found] ` <20221102171025.126961-6-penguin-kernel@I-love.SAKURA.ne.jp>
@ 2022-11-02 17:29   ` Tetsuo Handa
  2022-11-02 17:29   ` [PATCH 6b/10] " Tetsuo Handa
  1 sibling, 0 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:29 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook

This file implements similar functions provided by security/tomoyo/common.c
and security/tomoyo/securityfs_if.c files.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
Reposting "[PATCH 06/10] CaitSith: Add policy management functions." as two patches due to
"BOUNCE linux-security-module@vger.kernel.org: Message too long (>100000 chars)" failure.
This is the former part.

 security/caitsith/policy_io.c | 1901 +++++++++++++++++++++++++++++++++
 1 file changed, 1901 insertions(+)
 create mode 100644 security/caitsith/policy_io.c

diff --git a/security/caitsith/policy_io.c b/security/caitsith/policy_io.c
new file mode 100644
index 000000000000..27e2ec57f3b8
--- /dev/null
+++ b/security/caitsith/policy_io.c
@@ -0,0 +1,1901 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * policy_io.c
+ *
+ * Copyright (C) 2005-2012  NTT DATA CORPORATION
+ *
+ * Version: 0.2.10   2021/06/06
+ */
+
+#include "caitsith.h"
+
+/***** SECTION1: Constants definition *****/
+
+/* Define this to enable debug mode. */
+/* #define DEBUG_CONDITION */
+
+#ifdef DEBUG_CONDITION
+#define dprintk printk
+#else
+#define dprintk(...) do { } while (0)
+#endif
+
+/* String table for operation. */
+static const char * const cs_mac_keywords[CS_MAX_MAC_INDEX] = {
+	[CS_MAC_EXECUTE]    = "execute",
+	[CS_MAC_READ]       = "read",
+	[CS_MAC_WRITE]      = "write",
+	[CS_MAC_APPEND]     = "append",
+	[CS_MAC_CREATE]     = "create",
+	[CS_MAC_UNLINK]     = "unlink",
+#ifdef CONFIG_SECURITY_CAITSITH_GETATTR
+	[CS_MAC_GETATTR]    = "getattr",
+#endif
+	[CS_MAC_MKDIR]      = "mkdir",
+	[CS_MAC_RMDIR]      = "rmdir",
+	[CS_MAC_MKFIFO]     = "mkfifo",
+	[CS_MAC_MKSOCK]     = "mksock",
+	[CS_MAC_TRUNCATE]   = "truncate",
+	[CS_MAC_SYMLINK]    = "symlink",
+	[CS_MAC_MKBLOCK]    = "mkblock",
+	[CS_MAC_MKCHAR]     = "mkchar",
+	[CS_MAC_LINK]       = "link",
+	[CS_MAC_RENAME]     = "rename",
+	[CS_MAC_CHMOD]      = "chmod",
+	[CS_MAC_CHOWN]      = "chown",
+	[CS_MAC_CHGRP]      = "chgrp",
+	[CS_MAC_IOCTL]      = "ioctl",
+	[CS_MAC_CHROOT]     = "chroot",
+	[CS_MAC_MOUNT]      = "mount",
+	[CS_MAC_UMOUNT]     = "unmount",
+	[CS_MAC_PIVOT_ROOT] = "pivot_root",
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	[CS_MAC_INET_STREAM_BIND]       = "inet_stream_bind",
+	[CS_MAC_INET_STREAM_LISTEN]     = "inet_stream_listen",
+	[CS_MAC_INET_STREAM_CONNECT]    = "inet_stream_connect",
+	[CS_MAC_INET_STREAM_ACCEPT]     = "inet_stream_accept",
+	[CS_MAC_INET_DGRAM_BIND]        = "inet_dgram_bind",
+	[CS_MAC_INET_DGRAM_SEND]        = "inet_dgram_send",
+	[CS_MAC_INET_RAW_BIND]          = "inet_raw_bind",
+	[CS_MAC_INET_RAW_SEND]          = "inet_raw_send",
+	[CS_MAC_UNIX_STREAM_BIND]       = "unix_stream_bind",
+	[CS_MAC_UNIX_STREAM_LISTEN]     = "unix_stream_listen",
+	[CS_MAC_UNIX_STREAM_CONNECT]    = "unix_stream_connect",
+	[CS_MAC_UNIX_STREAM_ACCEPT]     = "unix_stream_accept",
+	[CS_MAC_UNIX_DGRAM_BIND]        = "unix_dgram_bind",
+	[CS_MAC_UNIX_DGRAM_SEND]        = "unix_dgram_send",
+	[CS_MAC_UNIX_SEQPACKET_BIND]    = "unix_seqpacket_bind",
+	[CS_MAC_UNIX_SEQPACKET_LISTEN]  = "unix_seqpacket_listen",
+	[CS_MAC_UNIX_SEQPACKET_CONNECT] = "unix_seqpacket_connect",
+	[CS_MAC_UNIX_SEQPACKET_ACCEPT]  = "unix_seqpacket_accept",
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_ENVIRON
+	[CS_MAC_ENVIRON] = "environ",
+#endif
+	[CS_MAC_MODIFY_POLICY]      = "modify_policy",
+#ifdef CONFIG_SECURITY_CAITSITH_CAPABILITY
+	[CS_MAC_USE_NETLINK_SOCKET] = "use_netlink_socket",
+	[CS_MAC_USE_PACKET_SOCKET]  = "use_packet_socket",
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_AUTO_DOMAIN_TRANSITION
+	[CS_MAC_AUTO_DOMAIN_TRANSITION]   = "auto_domain_transition",
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_MANUAL_DOMAIN_TRANSITION
+	[CS_MAC_MANUAL_DOMAIN_TRANSITION] = "manual_domain_transition",
+#endif
+};
+
+/* String table for grouping keywords. */
+static const char * const cs_group_name[CS_MAX_GROUP] = {
+	[CS_STRING_GROUP] = "string_group",
+	[CS_NUMBER_GROUP] = "number_group",
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	[CS_IP_GROUP]     = "ip_group",
+#endif
+};
+
+/* String table for stat info. */
+static const char * const cs_memory_headers[CS_MAX_MEMORY_STAT] = {
+	[CS_MEMORY_POLICY] = "policy",
+	[CS_MEMORY_AUDIT]  = "audit",
+	[CS_MEMORY_QUERY]  = "query",
+};
+
+#define F(bit) (1ULL << bit)
+
+#define CS_ALL_OK				\
+	(F(CS_MAC_EXECUTE) |			\
+	 F(CS_MAC_READ) |			\
+	 F(CS_MAC_WRITE) |			\
+	 F(CS_MAC_APPEND) |			\
+	 F(CS_MAC_CREATE) |			\
+	 F(CS_MAC_UNLINK) |			\
+	 F(CS_MAC_GETATTR) |			\
+	 F(CS_MAC_MKDIR) |			\
+	 F(CS_MAC_RMDIR) |			\
+	 F(CS_MAC_MKFIFO) |			\
+	 F(CS_MAC_MKSOCK) |			\
+	 F(CS_MAC_TRUNCATE) |			\
+	 F(CS_MAC_SYMLINK) |			\
+	 F(CS_MAC_MKBLOCK) |			\
+	 F(CS_MAC_MKCHAR) |			\
+	 F(CS_MAC_LINK) |			\
+	 F(CS_MAC_RENAME) |			\
+	 F(CS_MAC_CHMOD) |			\
+	 F(CS_MAC_CHOWN) |			\
+	 F(CS_MAC_CHGRP) |			\
+	 F(CS_MAC_IOCTL) |			\
+	 F(CS_MAC_CHROOT) |			\
+	 F(CS_MAC_MOUNT) |			\
+	 F(CS_MAC_UMOUNT) |			\
+	 F(CS_MAC_PIVOT_ROOT) |			\
+	 F(CS_MAC_INET_STREAM_BIND) |		\
+	 F(CS_MAC_INET_STREAM_LISTEN) |		\
+	 F(CS_MAC_INET_STREAM_CONNECT) |	\
+	 F(CS_MAC_INET_STREAM_ACCEPT) |		\
+	 F(CS_MAC_INET_DGRAM_BIND) |		\
+	 F(CS_MAC_INET_DGRAM_SEND) |		\
+	 F(CS_MAC_INET_RAW_BIND) |		\
+	 F(CS_MAC_INET_RAW_SEND) |		\
+	 F(CS_MAC_UNIX_STREAM_BIND) |		\
+	 F(CS_MAC_UNIX_STREAM_LISTEN) |		\
+	 F(CS_MAC_UNIX_STREAM_CONNECT) |	\
+	 F(CS_MAC_UNIX_STREAM_ACCEPT) |		\
+	 F(CS_MAC_UNIX_DGRAM_BIND) |		\
+	 F(CS_MAC_UNIX_DGRAM_SEND) |		\
+	 F(CS_MAC_UNIX_SEQPACKET_BIND) |	\
+	 F(CS_MAC_UNIX_SEQPACKET_LISTEN) |	\
+	 F(CS_MAC_UNIX_SEQPACKET_CONNECT) |	\
+	 F(CS_MAC_UNIX_SEQPACKET_ACCEPT) |	\
+	 F(CS_MAC_ENVIRON) |			\
+	 F(CS_MAC_MODIFY_POLICY) |		\
+	 F(CS_MAC_USE_NETLINK_SOCKET) |		\
+	 F(CS_MAC_USE_PACKET_SOCKET) |		\
+	 F(CS_MAC_AUTO_DOMAIN_TRANSITION) |	\
+	 F(CS_MAC_MANUAL_DOMAIN_TRANSITION))
+
+#define CS_PATH_SELF_OK				\
+	(F(CS_MAC_EXECUTE) |			\
+	 F(CS_MAC_READ) |			\
+	 F(CS_MAC_WRITE) |			\
+	 F(CS_MAC_APPEND) |			\
+	 F(CS_MAC_UNLINK) |			\
+	 F(CS_MAC_GETATTR) |			\
+	 F(CS_MAC_RMDIR) |			\
+	 F(CS_MAC_TRUNCATE) |			\
+	 F(CS_MAC_CHMOD) |			\
+	 F(CS_MAC_CHOWN) |			\
+	 F(CS_MAC_CHGRP) |			\
+	 F(CS_MAC_IOCTL) |			\
+	 F(CS_MAC_CHROOT) |			\
+	 F(CS_MAC_UMOUNT) |			\
+	 F(CS_MAC_ENVIRON))
+
+#define CS_PATH_PARENT_OK			\
+	(F(CS_MAC_CREATE) |			\
+	 F(CS_MAC_MKDIR) |			\
+	 F(CS_MAC_MKFIFO) |			\
+	 F(CS_MAC_MKSOCK) |			\
+	 F(CS_MAC_SYMLINK) |			\
+	 F(CS_MAC_MKBLOCK) |			\
+	 F(CS_MAC_MKCHAR))
+
+#define CS_PATH_OK (CS_PATH_SELF_OK | CS_PATH_PARENT_OK)
+
+#define CS_RENAME_OR_LINK_OK (F(CS_MAC_LINK) | F(CS_MAC_RENAME))
+
+#define CS_EXECUTE_OR_ENVIRON_OK (F(CS_MAC_EXECUTE) | F(CS_MAC_ENVIRON))
+
+#define CS_MKDEV_OK (F(CS_MAC_MKBLOCK) | F(CS_MAC_MKCHAR))
+
+#define CS_PATH_PERM_OK				\
+	(F(CS_MAC_MKDIR) |			\
+	 F(CS_MAC_MKBLOCK) |			\
+	 F(CS_MAC_MKCHAR) |			\
+	 F(CS_MAC_MKFIFO) |			\
+	 F(CS_MAC_MKSOCK) |			\
+	 F(CS_MAC_CREATE) |			\
+	 F(CS_MAC_CHMOD))
+
+#define CS_IP_SOCKET_OK				\
+	(F(CS_MAC_INET_STREAM_BIND) |		\
+	 F(CS_MAC_INET_STREAM_LISTEN) |		\
+	 F(CS_MAC_INET_STREAM_CONNECT) |	\
+	 F(CS_MAC_INET_STREAM_ACCEPT) |		\
+	 F(CS_MAC_INET_DGRAM_BIND) |		\
+	 F(CS_MAC_INET_DGRAM_SEND))
+
+#define CS_RAW_SOCKET_OK			\
+	(F(CS_MAC_INET_RAW_BIND) |		\
+	 F(CS_MAC_INET_RAW_SEND))
+
+#define CS_INET_SOCKET_OK (CS_IP_SOCKET_OK | CS_RAW_SOCKET_OK)
+
+#define CS_UNIX_SOCKET_OK			\
+	(F(CS_MAC_UNIX_STREAM_BIND) |		\
+	 F(CS_MAC_UNIX_STREAM_LISTEN) |		\
+	 F(CS_MAC_UNIX_STREAM_CONNECT) |	\
+	 F(CS_MAC_UNIX_STREAM_ACCEPT) |		\
+	 F(CS_MAC_UNIX_DGRAM_BIND) |		\
+	 F(CS_MAC_UNIX_DGRAM_SEND) |		\
+	 F(CS_MAC_UNIX_SEQPACKET_BIND) |	\
+	 F(CS_MAC_UNIX_SEQPACKET_LISTEN) |	\
+	 F(CS_MAC_UNIX_SEQPACKET_CONNECT) |	\
+	 F(CS_MAC_UNIX_SEQPACKET_ACCEPT))
+
+enum cs_var_type {
+	CS_TYPE_INVALID,
+	CS_TYPE_NUMBER,
+	CS_TYPE_STRING,
+	CS_TYPE_IPADDR,
+	CS_TYPE_FILEPERM,
+	CS_TYPE_FILETYPE,
+	CS_TYPE_ASSIGN,
+} __packed;
+
+/* String table for conditions. */
+static const struct {
+	const char * const keyword;
+	const enum cs_var_type left_type;
+	const enum cs_var_type right_type;
+	const enum cs_conditions_index cmd;
+	const u64 available;
+} cs_conditions[] = {
+	{ "addr",                    CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG0,            CS_UNIX_SOCKET_OK },
+	{ "argc",                    CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_EXEC_ARGC,             CS_EXECUTE_OR_ENVIRON_OK },
+	{ "block",                   CS_TYPE_INVALID,  CS_TYPE_FILETYPE,
+	  CS_OBJ_IS_BLOCK_DEV,      CS_ALL_OK },
+	{ "char",                    CS_TYPE_INVALID,  CS_TYPE_FILETYPE,
+	  CS_OBJ_IS_CHAR_DEV,       CS_ALL_OK },
+	{ "cmd",                     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_COND_NARG0,            F(CS_MAC_IOCTL) },
+	{ "data",                    CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG3,            F(CS_MAC_MOUNT) },
+	{ "dev_major",               CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_COND_NARG1,            CS_MKDEV_OK },
+	{ "dev_minor",               CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_COND_NARG2,            CS_MKDEV_OK },
+	{ "directory",               CS_TYPE_INVALID,  CS_TYPE_FILETYPE,
+	  CS_OBJ_IS_DIRECTORY,      CS_ALL_OK },
+	{ "domain",                  CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG0,            F(CS_MAC_MANUAL_DOMAIN_TRANSITION) },
+	{ "envc",                    CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_EXEC_ENVC,             CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec",                    CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG1,            CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.fsmagic",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.gid",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_GID,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.ino",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_INO,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.major",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_MAJOR,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.minor",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_MINOR,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.parent.fsmagic",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.parent.gid",         CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_GID,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.parent.ino",         CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_INO,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.parent.major",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_MAJOR,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.parent.minor",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_MINOR,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.parent.perm",        CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_PERM,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.parent.uid",         CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_UID,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.perm",               CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_PERM,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.type",               CS_TYPE_FILETYPE, CS_TYPE_FILETYPE,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_TYPE,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "exec.uid",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_UID,
+	  CS_EXECUTE_OR_ENVIRON_OK },
+	{ "fifo",                    CS_TYPE_INVALID,  CS_TYPE_FILETYPE,
+	  CS_OBJ_IS_FIFO,           CS_ALL_OK },
+	{ "file",                    CS_TYPE_INVALID,  CS_TYPE_FILETYPE,
+	  CS_OBJ_IS_FILE,           CS_ALL_OK },
+	{ "flags",                   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_COND_NARG0,            F(CS_MAC_MOUNT) | F(CS_MAC_UMOUNT) },
+	{ "fstype",                  CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG2,            F(CS_MAC_MOUNT) },
+	{ "gid",                     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_COND_NARG0,            F(CS_MAC_CHGRP) },
+	{ "group_execute",           CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_GROUP_EXECUTE,    CS_ALL_OK },
+	{ "group_read",              CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_GROUP_READ,       CS_ALL_OK },
+	{ "group_write",             CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_GROUP_WRITE,      CS_ALL_OK },
+	{ "ip",                      CS_TYPE_IPADDR,   CS_TYPE_INVALID,
+	  CS_COND_IPARG,            CS_INET_SOCKET_OK },
+	{ "name",                    CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG2,            F(CS_MAC_ENVIRON) },
+	{ "new_path",                CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG1,            CS_RENAME_OR_LINK_OK },
+	{ "new_path.dev_major",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_DEV_MAJOR,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.dev_minor",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_DEV_MINOR,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.fsmagic",        CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.gid",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.ino",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.major",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.minor",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.parent.fsmagic", CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  CS_RENAME_OR_LINK_OK },
+	{ "new_path.parent.gid",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_GID,
+	  CS_RENAME_OR_LINK_OK },
+	{ "new_path.parent.ino",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_INO,
+	  CS_RENAME_OR_LINK_OK },
+	{ "new_path.parent.major",   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_MAJOR,
+	  CS_RENAME_OR_LINK_OK },
+	{ "new_path.parent.minor",   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_MINOR,
+	  CS_RENAME_OR_LINK_OK },
+	{ "new_path.parent.perm",    CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_PERM,
+	  CS_RENAME_OR_LINK_OK },
+	{ "new_path.parent.uid",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_UID,
+	  CS_RENAME_OR_LINK_OK },
+	{ "new_path.perm",           CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.type",           CS_TYPE_FILETYPE, CS_TYPE_FILETYPE,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_TYPE,
+	  F(CS_MAC_RENAME) },
+	{ "new_path.uid",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_RENAME) },
+	{ "new_root",                CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG0,            F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.dev_major",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_DEV_MAJOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.dev_minor",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_DEV_MINOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.fsmagic",        CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.gid",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.ino",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.major",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.minor",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.parent.fsmagic", CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.parent.gid",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.parent.ino",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.parent.major",   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.parent.minor",   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.parent.perm",    CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.parent.uid",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.perm",           CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.type",           CS_TYPE_FILETYPE, CS_TYPE_FILETYPE,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_TYPE,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "new_root.uid",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "old_path",                CS_TYPE_STRING,    CS_TYPE_STRING,
+	  CS_COND_SARG0,            CS_RENAME_OR_LINK_OK },
+	{ "old_path.dev_major",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_DEV_MAJOR,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.dev_minor",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_DEV_MINOR,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.fsmagic",        CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.gid",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_GID,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.ino",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_INO,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.major",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_MAJOR,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.minor",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_MINOR,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.parent.fsmagic", CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.parent.gid",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_GID,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.parent.ino",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_INO,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.parent.major",   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_MAJOR,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.parent.minor",   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_MINOR,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.parent.perm",    CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_PERM,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.parent.uid",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_UID,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.perm",           CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_PERM,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.type",           CS_TYPE_FILETYPE, CS_TYPE_FILETYPE,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_TYPE,
+	  CS_RENAME_OR_LINK_OK },
+	{ "old_path.uid",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_UID,
+	  CS_RENAME_OR_LINK_OK },
+	{ "others_execute",          CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_OTHERS_EXECUTE,   CS_ALL_OK },
+	{ "others_read",             CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_OTHERS_READ,      CS_ALL_OK },
+	{ "others_write",            CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_OTHERS_WRITE,     CS_ALL_OK },
+	{ "owner_execute",           CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_OWNER_EXECUTE,    CS_ALL_OK },
+	{ "owner_read",              CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_OWNER_READ,       CS_ALL_OK },
+	{ "owner_write",             CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_OWNER_WRITE,      CS_ALL_OK },
+	{ "path",                    CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG0,            CS_PATH_OK },
+	{ "path.dev_major",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_DEV_MAJOR,
+	  CS_PATH_SELF_OK },
+	{ "path.dev_minor",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_DEV_MINOR,
+	  CS_PATH_SELF_OK },
+	{ "path.fsmagic",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  CS_PATH_SELF_OK },
+	{ "path.gid",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_GID,
+	  CS_PATH_SELF_OK },
+	{ "path.ino",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_INO,
+	  CS_PATH_SELF_OK },
+	{ "path.major",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_MAJOR,
+	  CS_PATH_SELF_OK },
+	{ "path.minor",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_MINOR,
+	  CS_PATH_SELF_OK },
+	{ "path.parent.fsmagic",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  CS_PATH_OK },
+	{ "path.parent.gid",         CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_GID,
+	  CS_PATH_OK },
+	{ "path.parent.ino",         CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_INO,
+	  CS_PATH_OK },
+	{ "path.parent.major",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_MAJOR,
+	  CS_PATH_OK },
+	{ "path.parent.minor",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_MINOR,
+	  CS_PATH_OK },
+	{ "path.parent.perm",        CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_PERM,
+	  CS_PATH_OK },
+	{ "path.parent.uid",         CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_UID,
+	  CS_PATH_OK },
+	{ "path.perm",               CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_PERM,
+	  CS_PATH_SELF_OK },
+	{ "path.type",               CS_TYPE_FILETYPE, CS_TYPE_FILETYPE,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_TYPE,
+	  CS_PATH_SELF_OK },
+	{ "path.uid",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_UID,
+	  CS_PATH_SELF_OK },
+	{ "perm",                    CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_COND_NARG0,            CS_PATH_PERM_OK },
+	{ "port",                    CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_COND_NARG0,            CS_IP_SOCKET_OK },
+	{ "proto",                   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_COND_NARG0,            CS_RAW_SOCKET_OK },
+	{ "put_old",                 CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG1,            F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.dev_major",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_DEV_MAJOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.dev_minor",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_DEV_MINOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.fsmagic",         CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.gid",             CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.ino",             CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.major",           CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.minor",           CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.parent.fsmagic",  CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.parent.gid",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.parent.ino",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.parent.major",    CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.parent.minor",    CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.parent.perm",     CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.parent.uid",      CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.perm",            CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.type",            CS_TYPE_FILETYPE, CS_TYPE_FILETYPE,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_TYPE,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "put_old.uid",             CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_PIVOT_ROOT) },
+	{ "setgid",                  CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_SETGID,           CS_ALL_OK },
+	{ "setuid",                  CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_SETUID,           CS_ALL_OK },
+	{ "socket",                  CS_TYPE_INVALID,  CS_TYPE_FILETYPE,
+	  CS_OBJ_IS_SOCKET,         CS_ALL_OK },
+	{ "source",                  CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG0,            F(CS_MAC_MOUNT) },
+	{ "source.dev_major",        CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_DEV_MAJOR,
+	  F(CS_MAC_MOUNT) },
+	{ "source.dev_minor",        CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_DEV_MINOR,
+	  F(CS_MAC_MOUNT) },
+	{ "source.fsmagic",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_MOUNT) },
+	{ "source.gid",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_MOUNT) },
+	{ "source.ino",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_MOUNT) },
+	{ "source.major",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_MOUNT) },
+	{ "source.minor",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_MOUNT) },
+	{ "source.parent.fsmagic",   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_MOUNT) },
+	{ "source.parent.gid",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_MOUNT) },
+	{ "source.parent.ino",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_MOUNT) },
+	{ "source.parent.major",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_MOUNT) },
+	{ "source.parent.minor",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_MOUNT) },
+	{ "source.parent.perm",      CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_MOUNT) },
+	{ "source.parent.uid",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 16 + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_MOUNT) },
+	{ "source.perm",             CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_MOUNT) },
+	{ "source.type",             CS_TYPE_FILETYPE, CS_TYPE_FILETYPE,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_TYPE,
+	  F(CS_MAC_MOUNT) },
+	{ "source.uid",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_MOUNT) },
+	{ "sticky",                  CS_TYPE_INVALID,  CS_TYPE_FILEPERM,
+	  CS_MODE_STICKY,           CS_ALL_OK },
+	{ "symlink",                 CS_TYPE_INVALID,  CS_TYPE_FILETYPE,
+	  CS_OBJ_IS_SYMLINK,        CS_ALL_OK },
+	{ "target",                  CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG1,            F(CS_MAC_MOUNT) | F(CS_MAC_SYMLINK) },
+	{ "target.dev_major",        CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_DEV_MAJOR,
+	  F(CS_MAC_MOUNT) },
+	{ "target.dev_minor",        CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_DEV_MINOR,
+	  F(CS_MAC_MOUNT) },
+	{ "target.fsmagic",          CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_MOUNT) },
+	{ "target.gid",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_MOUNT) },
+	{ "target.ino",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_MOUNT) },
+	{ "target.major",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_MOUNT) },
+	{ "target.minor",            CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_MOUNT) },
+	{ "target.parent.fsmagic",   CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_FSMAGIC,
+	  F(CS_MAC_MOUNT) },
+	{ "target.parent.gid",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_GID,
+	  F(CS_MAC_MOUNT) },
+	{ "target.parent.ino",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_INO,
+	  F(CS_MAC_MOUNT) },
+	{ "target.parent.major",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_MAJOR,
+	  F(CS_MAC_MOUNT) },
+	{ "target.parent.minor",     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_MINOR,
+	  F(CS_MAC_MOUNT) },
+	{ "target.parent.perm",      CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_MOUNT) },
+	{ "target.parent.uid",       CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 48 + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_MOUNT) },
+	{ "target.perm",             CS_TYPE_FILEPERM, CS_TYPE_FILEPERM,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_PERM,
+	  F(CS_MAC_MOUNT) },
+	{ "target.type",             CS_TYPE_FILETYPE, CS_TYPE_FILETYPE,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_TYPE,
+	  F(CS_MAC_MOUNT) },
+	{ "target.uid",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_PATH_ATTRIBUTE_START + 32 + CS_PATH_ATTRIBUTE_UID,
+	  F(CS_MAC_MOUNT) },
+	{ "task.domain",             CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_SELF_DOMAIN,           CS_ALL_OK },
+	{ "task.egid",               CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_EGID,             CS_ALL_OK },
+	{ "task.euid",               CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_EUID,             CS_ALL_OK },
+	{ "task.exe",                CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_SELF_EXE,              CS_ALL_OK },
+	{ "task.fsgid",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_FSGID,            CS_ALL_OK },
+	{ "task.fsuid",              CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_FSUID,            CS_ALL_OK },
+	{ "task.gid",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_GID,              CS_ALL_OK },
+	{ "task.pid",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_PID,              CS_ALL_OK },
+	{ "task.ppid",               CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_PPID,             CS_ALL_OK },
+	{ "task.sgid",               CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_SGID,             CS_ALL_OK },
+	{ "task.suid",               CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_SUID,             CS_ALL_OK },
+	{ "task.uid",                CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_SELF_UID,              CS_ALL_OK },
+	{ "transition",              CS_TYPE_ASSIGN,   CS_TYPE_INVALID,
+	  CS_TRANSIT_DOMAIN,
+	  F(CS_MAC_EXECUTE) | F(CS_MAC_AUTO_DOMAIN_TRANSITION) |
+	  CS_INET_SOCKET_OK | CS_UNIX_SOCKET_OK },
+	{ "uid",                     CS_TYPE_NUMBER,   CS_TYPE_NUMBER,
+	  CS_COND_NARG0,            F(CS_MAC_CHOWN) },
+	{ "value",                   CS_TYPE_STRING,   CS_TYPE_STRING,
+	  CS_COND_SARG3,            F(CS_MAC_ENVIRON) },
+};
+
+/***** SECTION2: Structure definition *****/
+
+struct iattr;
+
+/* Structure for query. */
+struct cs_query {
+	struct list_head list;
+	struct cs_acl_info *acl;
+	char *query;
+	size_t query_len;
+	unsigned int serial;
+	u8 timer;
+	u8 answer;
+	u8 retry;
+	enum cs_mac_index acl_type;
+};
+
+/* Structure for audit log. */
+struct cs_log {
+	struct list_head list;
+	char *log;
+	int size;
+	enum cs_matching_result result;
+};
+
+/* Structure for holding single condition component. */
+struct cs_cond_tmp {
+	enum cs_conditions_index left;
+	enum cs_conditions_index right;
+	bool is_not;
+	u8 radix;
+	enum cs_var_type type;
+	struct cs_group *group;
+	const struct cs_path_info *path;
+	struct in6_addr ipv6[2];
+	unsigned long value[2];
+	unsigned long argv;
+	const struct cs_path_info *envp;
+};
+
+/***** SECTION3: Prototype definition section *****/
+
+static bool cs_correct_domain(const unsigned char *domainname);
+static bool cs_correct_word(const char *string);
+static bool cs_flush(struct cs_io_buffer *head);
+static bool cs_print_condition(struct cs_io_buffer *head,
+			       const struct cs_condition *cond);
+static bool cs_memory_ok(const void *ptr);
+static bool cs_read_acl(struct cs_io_buffer *head,
+			const struct cs_acl_info *acl);
+static bool cs_read_group(struct cs_io_buffer *head);
+static bool cs_select_acl(struct cs_io_buffer *head, const char *data);
+static bool cs_set_lf(struct cs_io_buffer *head);
+static bool cs_str_starts(char **src, const char *find);
+static char *cs_init_log(struct cs_request_info *r);
+static char *cs_print_bprm(struct linux_binprm *bprm,
+			   struct cs_page_dump *dump);
+static char *cs_print_trailer(struct cs_request_info *r);
+static char *cs_read_token(struct cs_io_buffer *head);
+static const char *cs_yesno(const unsigned int value);
+static const struct cs_path_info *cs_get_dqword(char *start);
+static const struct cs_path_info *cs_get_name(const char *name);
+static int cs_open(struct inode *inode, struct file *file);
+static int cs_parse_policy(struct cs_io_buffer *head, char *line);
+static int cs_release(struct inode *inode, struct file *file);
+static int cs_supervisor(struct cs_request_info *r);
+static int cs_update_group(struct cs_io_buffer *head,
+			   const enum cs_group_id type);
+static int cs_write_answer(struct cs_io_buffer *head);
+static int cs_write_audit_quota(char *data);
+static int cs_write_memory_quota(char *data);
+static int cs_write_pid(struct cs_io_buffer *head);
+static int cs_write_policy(struct cs_io_buffer *head);
+static ssize_t cs_read(struct file *file, char __user *buf, size_t count,
+		       loff_t *ppos);
+static ssize_t cs_read_self(struct file *file, char __user *buf, size_t count,
+			    loff_t *ppos);
+static ssize_t cs_write(struct file *file, const char __user *buf,
+			size_t count, loff_t *ppos);
+static struct cs_condition *cs_get_condition(struct cs_io_buffer *head);
+static struct cs_domain_info *cs_find_domain(const char *domainname);
+static struct cs_acl_info *cs_find_acl_by_qid(unsigned int serial,
+					      enum cs_mac_index *type);
+static struct cs_group *cs_get_group(struct cs_io_buffer *head,
+				     const enum cs_group_id idx);
+static enum cs_value_type cs_parse_ulong(unsigned long *result, char **str);
+static unsigned int cs_poll(struct file *file, poll_table *wait);
+static void __init cs_create_entry(const char *name, const umode_t mode,
+				   struct dentry *parent, const u8 key);
+static void __init cs_load_builtin_policy(void);
+static void __init cs_securityfs_init(void);
+static void cs_convert_time(time64_t time, struct cs_time *stamp);
+static void cs_io_printf(struct cs_io_buffer *head, const char *fmt, ...)
+	__printf(2, 3);
+static void cs_normalize_line(unsigned char *buffer);
+static void cs_read_log(struct cs_io_buffer *head);
+static void cs_read_pid(struct cs_io_buffer *head);
+static void cs_read_policy(struct cs_io_buffer *head);
+static void cs_read_query(struct cs_io_buffer *head);
+static void *cs_commit_ok(void *data, const unsigned int size);
+static bool cs_read_quota(struct cs_io_buffer *head);
+static void cs_read_stat(struct cs_io_buffer *head);
+static void cs_read_version(struct cs_io_buffer *head);
+static void cs_set_space(struct cs_io_buffer *head);
+static void cs_set_string(struct cs_io_buffer *head, const char *string);
+static void cs_update_stat(const u8 index);
+static void cs_write_log(struct cs_request_info *r);
+
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+static enum cs_conditions_index cs_parse_ipaddr(char *address,
+						struct in6_addr ipv6[2]);
+static void cs_print_ipv4(struct cs_io_buffer *head, const u32 *ip);
+static void cs_print_ipv6(struct cs_io_buffer *head,
+			  const struct in6_addr *ip);
+static void cs_print_ip(struct cs_io_buffer *head,
+			struct cs_ip_group *member);
+#endif
+
+#ifdef CONFIG_SECURITY_CAITSITH_MANUAL_DOMAIN_TRANSITION
+static ssize_t cs_write_self(struct file *file, const char __user *buf,
+			     size_t count, loff_t *ppos);
+#endif
+
+/***** SECTION4: Standalone functions section *****/
+
+/**
+ * cs_convert_time - Convert time_t to YYYY/MM/DD hh/mm/ss.
+ *
+ * @time:  Seconds since 1970/01/01 00:00:00.
+ * @stamp: Pointer to "struct cs_time".
+ *
+ * Returns nothing.
+ */
+static void cs_convert_time(time64_t time, struct cs_time *stamp)
+{
+	struct tm tm;
+
+	time64_to_tm(time, 0, &tm);
+	stamp->sec = tm.tm_sec;
+	stamp->min = tm.tm_min;
+	stamp->hour = tm.tm_hour;
+	stamp->day = tm.tm_mday;
+	stamp->month = tm.tm_mon + 1;
+	stamp->year = tm.tm_year + 1900;
+}
+
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+
+/**
+ * cs_print_ipv4 - Print an IPv4 address.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @ip:   Pointer to "u32" in network byte order.
+ *
+ * Returns nothing.
+ */
+static void cs_print_ipv4(struct cs_io_buffer *head, const u32 *ip)
+{
+	cs_io_printf(head, "%pI4", ip);
+}
+
+/**
+ * cs_print_ipv6 - Print an IPv6 address.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @ip:   Pointer to "struct in6_addr".
+ *
+ * Returns nothing.
+ */
+static void cs_print_ipv6(struct cs_io_buffer *head,
+			  const struct in6_addr *ip)
+{
+	cs_io_printf(head, "%pI6c", ip);
+}
+
+/**
+ * cs_print_ip - Print an IP address.
+ *
+ * @head:   Pointer to "struct cs_io_buffer".
+ * @member: Pointer to "struct cs_ip_group".
+ *
+ * Returns nothing.
+ */
+static void cs_print_ip(struct cs_io_buffer *head,
+			struct cs_ip_group *member)
+{
+	u8 i;
+
+	for (i = 0; i < 2; i++) {
+		if (member->is_ipv6)
+			cs_print_ipv6(head, &member->ip[i]);
+		else
+			cs_print_ipv4(head, (const u32 *) &member->ip[i]);
+		if (i)
+			break;
+		if (!memcmp(&member->ip[0], &member->ip[1], 16))
+			break;
+		cs_set_string(head, "-");
+	}
+}
+
+#endif
+
+/***** SECTION5: Variables definition section *****/
+
+/* Lock for protecting policy. */
+DEFINE_MUTEX(cs_policy_lock);
+
+/* Has /sbin/init started? */
+bool cs_policy_loaded;
+
+/* List of "struct cs_group". */
+struct list_head cs_group_list[CS_MAX_GROUP];
+/* Policy version. Currently only 20120401 is defined. */
+static unsigned int cs_policy_version;
+
+/* List of "struct cs_condition". */
+LIST_HEAD(cs_condition_list);
+
+/* Wait queue for kernel -> userspace notification. */
+static DECLARE_WAIT_QUEUE_HEAD(cs_query_wait);
+/* Wait queue for userspace -> kernel notification. */
+static DECLARE_WAIT_QUEUE_HEAD(cs_answer_wait);
+
+/* The list for "struct cs_query". */
+static LIST_HEAD(cs_query_list);
+
+/* Lock for manipulating cs_query_list. */
+static DEFINE_SPINLOCK(cs_query_list_lock);
+
+/*
+ * Number of "struct file" referring /sys/kernel/security/caitsith/query
+ * interface.
+ */
+static atomic_t cs_query_observers = ATOMIC_INIT(0);
+
+/* Wait queue for /sys/kernel/security/caitsith/audit interface. */
+static DECLARE_WAIT_QUEUE_HEAD(cs_log_wait);
+
+/* The list for "struct cs_log". */
+static LIST_HEAD(cs_log);
+
+/* Lock for "struct list_head cs_log". */
+static DEFINE_SPINLOCK(cs_log_lock);
+
+/* Length of "struct list_head cs_log". */
+static unsigned int cs_log_count[CS_MAX_MATCHING];
+/* Quota for audit logs. */
+static unsigned int cs_log_quota[CS_MAX_LOG_QUOTA][CS_MAX_MATCHING];
+
+/* Memoy currently used by policy/audit log/query. */
+unsigned int cs_memory_used[CS_MAX_MEMORY_STAT];
+
+/* Memory quota for "policy"/"audit log"/"query". */
+static unsigned int cs_memory_quota[CS_MAX_MEMORY_STAT];
+
+/* The list for "struct cs_name". */
+struct list_head cs_name_list[CS_MAX_HASH];
+
+/* Counter for number of updates. */
+static atomic_t cs_stat_updated[CS_MAX_POLICY_STAT];
+
+/* Timestamp counter for last updated. */
+static time64_t cs_stat_modified[CS_MAX_POLICY_STAT];
+
+/* Operations for /sys/kernel/security/caitsith/self_domain interface. */
+static const struct file_operations cs_self_operations = {
+#ifdef CONFIG_SECURITY_CAITSITH_MANUAL_DOMAIN_TRANSITION
+	.write = cs_write_self,
+#endif
+	.read  = cs_read_self,
+};
+
+/* Operations for /sys/kernel/security/caitsith/ interface. */
+static const struct file_operations cs_operations = {
+	.open    = cs_open,
+	.release = cs_release,
+	.poll    = cs_poll,
+	.read    = cs_read,
+	.write   = cs_write,
+};
+
+/***** SECTION6: Dependent functions section *****/
+
+/**
+ * list_for_each_cookie - iterate over a list with cookie.
+ *
+ * @pos:  Pointer to "struct list_head".
+ * @head: Pointer to "struct list_head".
+ */
+#define list_for_each_cookie(pos, head)					\
+	for (pos = pos ? pos : srcu_dereference((head)->next, &cs_ss);	\
+	     pos != (head); pos = srcu_dereference(pos->next, &cs_ss))
+
+/**
+ * cs_warn_oom - Print out of memory warning message.
+ *
+ * @function: Function's name.
+ *
+ * Returns nothing.
+ */
+void cs_warn_oom(const char *function)
+{
+	/* Reduce error messages. */
+	static pid_t cs_last_pid;
+	const pid_t pid = current->pid;
+
+	if (cs_last_pid != pid) {
+		pr_warn("ERROR: Out of memory at %s.\n", function);
+		cs_last_pid = pid;
+	}
+	if (!cs_policy_loaded)
+		panic("MAC Initialization failed.\n");
+}
+
+/**
+ * cs_memory_ok - Check memory quota.
+ *
+ * @ptr:  Pointer to allocated memory. Maybe NULL.
+ *
+ * Returns true if @ptr is not NULL and quota not exceeded, false otherwise.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static bool cs_memory_ok(const void *ptr)
+{
+	if (ptr) {
+		const size_t s = ksize(ptr);
+
+		cs_memory_used[CS_MEMORY_POLICY] += s;
+		if (!cs_memory_quota[CS_MEMORY_POLICY] ||
+		    cs_memory_used[CS_MEMORY_POLICY] <=
+		    cs_memory_quota[CS_MEMORY_POLICY])
+			return true;
+		cs_memory_used[CS_MEMORY_POLICY] -= s;
+	}
+	cs_warn_oom(__func__);
+	return false;
+}
+
+/**
+ * cs_get_name - Allocate memory for string data.
+ *
+ * @name: The string to store into the permernent memory. Maybe NULL.
+ *
+ * Returns pointer to "struct cs_path_info" on success, NULL otherwise.
+ */
+static const struct cs_path_info *cs_get_name(const char *name)
+{
+	struct cs_name *ptr;
+	unsigned int hash;
+	int len;
+	int allocated_len;
+	struct list_head *head;
+
+	if (!name)
+		return NULL;
+	len = strlen(name) + 1;
+	hash = full_name_hash(NULL, name, len - 1);
+	head = &cs_name_list[hash_long(hash, CS_HASH_BITS)];
+	if (mutex_lock_killable(&cs_policy_lock))
+		return NULL;
+	list_for_each_entry(ptr, head, head.list) {
+		if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name) ||
+		    atomic_read(&ptr->head.users) == CS_GC_IN_PROGRESS)
+			continue;
+		atomic_inc(&ptr->head.users);
+		goto out;
+	}
+	allocated_len = sizeof(*ptr) + len;
+	ptr = kzalloc(allocated_len, GFP_NOFS);
+	if (cs_memory_ok(ptr)) {
+		ptr->entry.name = ((char *) ptr) + sizeof(*ptr);
+		memmove((char *) ptr->entry.name, name, len);
+		atomic_set(&ptr->head.users, 1);
+		cs_fill_path_info(&ptr->entry);
+		ptr->size = allocated_len;
+		list_add_tail(&ptr->head.list, head);
+	} else {
+		kfree(ptr);
+		ptr = NULL;
+	}
+out:
+	mutex_unlock(&cs_policy_lock);
+	return ptr ? &ptr->entry : NULL;
+}
+
+/**
+ * cs_read_token - Read a word from a line.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns a word on success, "" otherwise.
+ *
+ * To allow the caller to skip NULL check, this function returns "" rather than
+ * NULL if there is no more words to read.
+ */
+static char *cs_read_token(struct cs_io_buffer *head)
+{
+	char *pos = head->w.data;
+	char *del = strchr(pos, ' ');
+
+	if (del)
+		*del++ = '\0';
+	else
+		del = pos + strlen(pos);
+	head->w.data = del;
+	return pos;
+}
+
+/**
+ * cs_correct_word - Check whether the given string follows the naming rules.
+ *
+ * @string: The string to check.
+ *
+ * Returns true if @string follows the naming rules, false otherwise.
+ */
+static bool cs_correct_word(const char *string)
+{
+	u8 recursion = 20;
+	const char *const start = string;
+	u8 in_repetition = 0;
+
+	if (!*string)
+		goto out;
+	while (*string) {
+		unsigned char c = *string++;
+
+		if (in_repetition && c == '/')
+			goto out;
+		if (c <= ' ' || c >= 127)
+			goto out;
+		if (c != '\\')
+			continue;
+		c = *string++;
+		if (c >= '0' && c <= '3') {
+			unsigned char d;
+			unsigned char e;
+
+			d = *string++;
+			if (d < '0' || d > '7')
+				goto out;
+			e = *string++;
+			if (e < '0' || e > '7')
+				goto out;
+			c = ((c - '0') << 6) + ((d - '0') << 3) + (e - '0');
+			if (c <= ' ' || c >= 127 || c == '\\')
+				continue;
+			goto out;
+		}
+		switch (c) {
+		case '+':   /* "\+" */
+		case '?':   /* "\?" */
+		case 'x':   /* "\x" */
+		case 'a':   /* "\a" */
+		case '-':   /* "\-" */
+			continue;
+		}
+		/* Reject too deep wildcard that consumes too much stack. */
+		if (!recursion--)
+			goto out;
+		switch (c) {
+		case '*':   /* "\*" */
+		case '@':   /* "\@" */
+		case '$':   /* "\$" */
+		case 'X':   /* "\X" */
+		case 'A':   /* "\A" */
+			continue;
+		case '{':   /* "/\{" */
+			if (string - 3 < start || *(string - 3) != '/')
+				goto out;
+			in_repetition = 1;
+			continue;
+		case '}':   /* "\}/" */
+			if (in_repetition != 1 || *string++ != '/')
+				goto out;
+			in_repetition = 0;
+			continue;
+		case '(':   /* "/\(" */
+			if (string - 3 < start || *(string - 3) != '/')
+				goto out;
+			in_repetition = 2;
+			continue;
+		case ')':   /* "\)/" */
+			if (in_repetition != 2 || *string++ != '/')
+				goto out;
+			in_repetition = 0;
+			continue;
+		}
+		goto out;
+	}
+	if (in_repetition)
+		goto out;
+	return true;
+out:
+	return false;
+}
+
+/**
+ * cs_commit_ok - Allocate memory and check memory quota.
+ *
+ * @data: Data to copy from.
+ * @size: Size in byte.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ * @data is zero-cleared on success.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static void *cs_commit_ok(void *data, const unsigned int size)
+{
+	void *ptr = kmalloc(size, GFP_NOFS);
+
+	if (cs_memory_ok(ptr)) {
+		memmove(ptr, data, size);
+		memset(data, 0, size);
+		return ptr;
+	}
+	kfree(ptr);
+	return NULL;
+}
+
+/**
+ * cs_get_group - Allocate memory for "struct cs_string_group"/"struct cs_number_group"/"struct cs_ip_group".
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @idx:  Index number.
+ *
+ * Returns pointer to "struct cs_group" on success, NULL otherwise.
+ */
+static struct cs_group *cs_get_group(struct cs_io_buffer *head,
+				     const enum cs_group_id idx)
+{
+	struct cs_group e = { };
+	struct cs_group *group = NULL;
+	struct list_head *list;
+	const char *group_name = cs_read_token(head);
+	bool found = false;
+
+	if (!cs_correct_word(group_name) || idx >= CS_MAX_GROUP)
+		return NULL;
+	e.group_name = cs_get_name(group_name);
+	if (!e.group_name)
+		return NULL;
+	if (mutex_lock_killable(&cs_policy_lock))
+		goto out;
+	list = &cs_group_list[idx];
+	list_for_each_entry(group, list, head.list) {
+		if (e.group_name != group->group_name ||
+		    atomic_read(&group->head.users) == CS_GC_IN_PROGRESS)
+			continue;
+		atomic_inc(&group->head.users);
+		found = true;
+		break;
+	}
+	if (!found) {
+		struct cs_group *entry = cs_commit_ok(&e, sizeof(e));
+
+		if (entry) {
+			INIT_LIST_HEAD(&entry->member_list);
+			atomic_set(&entry->head.users, 1);
+			list_add_tail_rcu(&entry->head.list, list);
+			group = entry;
+			found = true;
+		}
+	}
+	mutex_unlock(&cs_policy_lock);
+out:
+	cs_put_name(e.group_name);
+	return found ? group : NULL;
+}
+
+/**
+ * cs_parse_ulong - Parse an "unsigned long" value.
+ *
+ * @result: Pointer to "unsigned long".
+ * @str:    Pointer to string to parse.
+ *
+ * Returns one of values in "enum cs_value_type".
+ *
+ * The @src is updated to point the first character after the value
+ * on success.
+ */
+static enum cs_value_type cs_parse_ulong(unsigned long *result, char **str)
+{
+	const char *cp = *str;
+	char *ep;
+	int base = 10;
+
+	if (*cp == '0') {
+		char c = *(cp + 1);
+
+		if (c == 'x' || c == 'X') {
+			base = 16;
+			cp += 2;
+		} else if (c >= '0' && c <= '7') {
+			base = 8;
+			cp++;
+		}
+	}
+	*result = simple_strtoul(cp, &ep, base);
+	if (cp == ep)
+		return CS_VALUE_TYPE_INVALID;
+	*str = ep;
+	switch (base) {
+	case 16:
+		return CS_VALUE_TYPE_HEXADECIMAL;
+	case 8:
+		return CS_VALUE_TYPE_OCTAL;
+	default:
+		return CS_VALUE_TYPE_DECIMAL;
+	}
+}
+
+/**
+ * cs_get_dqword - cs_get_name() for a quoted string.
+ *
+ * @start: String to parse.
+ *
+ * Returns pointer to "struct cs_path_info" on success, NULL otherwise.
+ */
+static const struct cs_path_info *cs_get_dqword(char *start)
+{
+	char *cp = start + strlen(start) - 1;
+
+	if (cp == start || *start++ != '"' || *cp != '"')
+		return NULL;
+	*cp = '\0';
+	if (*start && !cs_correct_word(start))
+		return NULL;
+	return cs_get_name(start);
+}
+
+/**
+ * cs_same_condition - Check for duplicated "struct cs_condition" entry.
+ *
+ * @a: Pointer to "struct cs_condition".
+ * @b: Pointer to "struct cs_condition".
+ *
+ * Returns true if @a == @b, false otherwise.
+ */
+static inline bool cs_same_condition(const struct cs_condition *a,
+				     const struct cs_condition *b)
+{
+	return a->size == b->size &&
+		!memcmp(a + 1, b + 1, a->size - sizeof(*a));
+}
+
+/**
+ * cs_commit_condition - Commit "struct cs_condition".
+ *
+ * @entry: Pointer to "struct cs_condition".
+ *
+ * Returns pointer to "struct cs_condition" on success, NULL otherwise.
+ *
+ * This function merges duplicated entries. This function returns NULL if
+ * @entry is not duplicated but memory quota for policy has exceeded.
+ */
+static struct cs_condition *cs_commit_condition(struct cs_condition *entry)
+{
+	struct cs_condition *ptr = kmemdup(entry, entry->size, GFP_NOFS);
+	bool found = false;
+
+	if (ptr) {
+		kfree(entry);
+		entry = ptr;
+	}
+	if (mutex_lock_killable(&cs_policy_lock)) {
+		dprintk(KERN_WARNING "%u: %s failed\n", __LINE__, __func__);
+		ptr = NULL;
+		found = true;
+		goto out;
+	}
+	list_for_each_entry(ptr, &cs_condition_list, head.list) {
+		if (!cs_same_condition(ptr, entry) ||
+		    atomic_read(&ptr->head.users) == CS_GC_IN_PROGRESS)
+			continue;
+		/* Same entry found. Share this entry. */
+		atomic_inc(&ptr->head.users);
+		found = true;
+		break;
+	}
+	if (!found) {
+		if (cs_memory_ok(entry)) {
+			atomic_set(&entry->head.users, 1);
+			list_add(&entry->head.list, &cs_condition_list);
+		} else {
+			found = true;
+			ptr = NULL;
+		}
+	}
+	mutex_unlock(&cs_policy_lock);
+out:
+	if (found) {
+		cs_del_condition(&entry->head.list);
+		kfree(entry);
+		entry = ptr;
+	}
+	return entry;
+}
+
+/**
+ * cs_correct_domain - Check whether the given domainname follows the naming rules.
+ *
+ * @domainname: The domainname to check.
+ *
+ * Returns true if @domainname follows the naming rules, false otherwise.
+ */
+static bool cs_correct_domain(const unsigned char *domainname)
+{
+	if (!cs_correct_word(domainname))
+		return false;
+	while (*domainname) {
+		if (*domainname++ != '\\')
+			continue;
+		if (*domainname < '0' || *domainname++ > '3')
+			return false;
+	}
+	return true;
+}
+
+/**
+ * cs_normalize_line - Format string.
+ *
+ * @buffer: The line to normalize.
+ *
+ * Returns nothing.
+ *
+ * Leading and trailing whitespaces are removed.
+ * Multiple whitespaces are packed into single space.
+ */
+static void cs_normalize_line(unsigned char *buffer)
+{
+	unsigned char *sp = buffer;
+	unsigned char *dp = buffer;
+	bool first = true;
+
+	while (*sp && (*sp <= ' ' || *sp >= 127))
+		sp++;
+	while (*sp) {
+		if (!first)
+			*dp++ = ' ';
+		first = false;
+		while (*sp > ' ' && *sp < 127)
+			*dp++ = *sp++;
+		while (*sp && (*sp <= ' ' || *sp >= 127))
+			sp++;
+	}
+	*dp = '\0';
+}
+
+/**
+ * cs_parse_values - Parse an numeric argument.
+ *
+ * @value: Values to parse.
+ * @v:     Pointer to "unsigned long".
+ *
+ * Returns "enum cs_value_type" if @value is a single value, bitwise-OR-ed
+ * value if @value is value range.
+ */
+static u8 cs_parse_values(char *value, unsigned long v[2])
+{
+	enum cs_value_type radix1 = cs_parse_ulong(&v[0], &value);
+	enum cs_value_type radix2;
+
+	if (radix1 == CS_VALUE_TYPE_INVALID)
+		return CS_VALUE_TYPE_INVALID;
+	if (!*value) {
+		v[1] = v[0];
+		return radix1;
+	}
+	if (*value++ != '-')
+		return CS_VALUE_TYPE_INVALID;
+	radix2 = cs_parse_ulong(&v[1], &value);
+	if (radix2 == CS_VALUE_TYPE_INVALID || *value || v[0] > v[1])
+		return CS_VALUE_TYPE_INVALID;
+	return radix1 | (radix2 << 2);
+}
+
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+
+/**
+ * cs_parse_ipaddr - Parse an IP address.
+ *
+ * @address: Address to parse.
+ * @ipv6:    Pointer to "struct in6_addr".
+ *
+ * Returns one of values in "enum cs_conditions_index".
+ */
+static enum cs_conditions_index cs_parse_ipaddr(char *address,
+						struct in6_addr ipv6[2])
+{
+	const char *end;
+
+	if (!strchr(address, ':') &&
+	    in4_pton(address, -1, ipv6[0].s6_addr, '-', &end) > 0) {
+		if (!*end) {
+			ipv6[0].s6_addr32[0] = ipv6[0].s6_addr32[0];
+			ipv6[1].s6_addr32[0] = ipv6[0].s6_addr32[0];
+			return CS_IMM_IPV4ADDR_ENTRY1;
+		}
+		if (*end++ != '-' ||
+		    in4_pton(end, -1, ipv6[1].s6_addr, '\0', &end) <= 0 ||
+		    *end || memcmp(&ipv6[0], &ipv6[1], 4) >= 0)
+			return CS_INVALID_CONDITION;
+		return CS_IMM_IPV4ADDR_ENTRY2;
+	}
+	if (in6_pton(address, -1, ipv6[0].s6_addr, '-', &end) > 0) {
+		if (!*end) {
+			ipv6[1] = ipv6[0];
+			return CS_IMM_IPV6ADDR_ENTRY1;
+		}
+		if (*end++ != '-' ||
+		    in6_pton(end, -1, ipv6[1].s6_addr, '\0', &end) <= 0 ||
+		    *end || memcmp(&ipv6[0], &ipv6[1], 16) >= 0)
+			return CS_INVALID_CONDITION;
+		return CS_IMM_IPV6ADDR_ENTRY2;
+	}
+	return CS_INVALID_CONDITION;
+}
+
+#endif
+
+/**
+ * cs_parse_lefthand - Parse special lefthand conditions.
+ *
+ * @word: Keyword to search.
+ * @mac:  One of values in "enum cs_mac_index".
+ * @tmp:  Pointer to "struct cs_cond_tmp".
+ *
+ * Returns one of values in "enum cs_conditions_index".
+ */
+static enum cs_conditions_index cs_parse_lefthand
+(char *word, const enum cs_mac_index mac, struct cs_cond_tmp *tmp)
+{
+	if (mac == CS_MAC_EXECUTE || mac == CS_MAC_ENVIRON) {
+		tmp->type = CS_TYPE_STRING;
+		if (!strncmp(word, "argv[", 5)) {
+			word += 5;
+			if (cs_parse_ulong(&tmp->argv, &word) ==
+			    CS_VALUE_TYPE_DECIMAL && !strcmp(word, "]"))
+				return CS_ARGV_ENTRY;
+		} else if (!strncmp(word, "envp[\"", 6)) {
+			char *end = word + strlen(word) - 2;
+
+			if (!strcmp(end, "\"]")) {
+				*end = '\0';
+				tmp->envp = cs_get_name(word + 6);
+				if (tmp->envp)
+					return CS_ENVP_ENTRY;
+			}
+		}
+	}
+	return CS_INVALID_CONDITION;
+}
+
+/**
+ * cs_parse_righthand - Parse special righthand conditions.
+ *
+ * @word: Keyword to search.
+ * @head: Pointer to "struct cs_io_buffer".
+ * @tmp:  Pointer to "struct cs_cond_tmp".
+ *
+ * Returns one of values in "enum cs_conditions_index".
+ */
+static enum cs_conditions_index cs_parse_righthand
+(char *word, struct cs_io_buffer *head, struct cs_cond_tmp *tmp)
+{
+	const enum cs_var_type type = tmp->type;
+
+	dprintk(KERN_WARNING "%u: tmp->left=%u type=%u\n",
+		__LINE__, tmp->left, type);
+	if (type == CS_TYPE_ASSIGN) {
+		if (tmp->is_not)
+			goto out;
+		if (!strcmp(word, "NULL"))
+			goto null_word;
+		tmp->path = cs_get_dqword(word);
+		if (tmp->path && tmp->path->const_len == tmp->path->total_len)
+			return CS_IMM_NAME_ENTRY;
+		goto out;
+	}
+	if (word[0] == '@' && word[1]) {
+		enum cs_group_id g;
+
+		if (type == CS_TYPE_NUMBER || type == CS_TYPE_FILEPERM)
+			g = CS_NUMBER_GROUP;
+		else if (type == CS_TYPE_STRING)
+			g = CS_STRING_GROUP;
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+		else if (type == CS_TYPE_IPADDR)
+			g = CS_IP_GROUP;
+#endif
+		else
+			goto out;
+		head->w.data = word + 1;
+		tmp->group = cs_get_group(head, g);
+		if (tmp->group)
+			return CS_IMM_GROUP;
+		goto out;
+	}
+	if (type == CS_TYPE_NUMBER || type == CS_TYPE_FILEPERM) {
+		tmp->radix = cs_parse_values(word, tmp->value);
+		if (tmp->radix == CS_VALUE_TYPE_INVALID)
+			goto out;
+		if (tmp->radix >> 2)
+			return CS_IMM_NUMBER_ENTRY2;
+		else
+			return CS_IMM_NUMBER_ENTRY1;
+	}
+	if (type == CS_TYPE_STRING) {
+		dprintk(KERN_WARNING "%u: word='%s'\n", __LINE__, word);
+		if (!strcmp(word, "NULL"))
+			goto null_word;
+		tmp->path = cs_get_dqword(word);
+		dprintk(KERN_WARNING "%u: tmp->path=%p\n", __LINE__,
+			tmp->path);
+		if (tmp->path)
+			return CS_IMM_NAME_ENTRY;
+		goto out;
+	}
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	if (type == CS_TYPE_IPADDR)
+		return cs_parse_ipaddr(word, tmp->ipv6);
+#endif
+out:
+	dprintk(KERN_WARNING "%u: righthand failed\n", __LINE__);
+	return CS_INVALID_CONDITION;
+null_word:
+	tmp->path = &cs_null_name;
+	return CS_IMM_NAME_ENTRY;
+}
+
+/**
+ * cs_condindex - Get condition's index.
+ *
+ * @word: Name of condition.
+ * @mac:  One of values in "enum cs_mac_index".
+ * @tmp:  Pointer to "struct cs_cond_tmp".
+ * @left: True if lefthand part, false otherwise.
+ *
+ * Returns one of values in "enum cs_condition_index".
+ */
+static enum cs_conditions_index cs_condindex(const char *word,
+					     const enum cs_mac_index mac,
+					     struct cs_cond_tmp *tmp,
+					     const bool lefthand)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs_conditions); i++) {
+		if (!(cs_conditions[i].available & F(mac)) ||
+		    strcmp(cs_conditions[i].keyword, word))
+			continue;
+		tmp->type = lefthand ? cs_conditions[i].left_type :
+			cs_conditions[i].right_type;
+		if (tmp->type != CS_TYPE_INVALID)
+			return cs_conditions[i].cmd;
+		break;
+	}
+	return CS_INVALID_CONDITION;
+}
+
+/**
+ * cs_parse_cond - Parse single condition.
+ *
+ * @tmp:  Pointer to "struct cs_cond_tmp".
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_parse_cond(struct cs_cond_tmp *tmp,
+			  struct cs_io_buffer *head)
+{
+	char *left = head->w.data;
+	char *right;
+	const enum cs_mac_index mac = head->w.acl_index;
+	enum cs_var_type type = CS_TYPE_STRING;
+
+	dprintk(KERN_WARNING "%u: type=%u word='%s'\n",
+		__LINE__, mac, left);
+	right = strchr(left, '=');
+	if (!right || right == left)
+		return false;
+	*right++ = '\0';
+	tmp->is_not = (*(right - 2) == '!');
+	if (tmp->is_not)
+		*(right - 2) = '\0';
+	if (!*left || !*right)
+		return false;
+	tmp->left = cs_condindex(left, mac, tmp, true);
+	dprintk(KERN_WARNING "%u: tmp->left=%u\n", __LINE__, tmp->left);
+	if (tmp->left == CS_INVALID_CONDITION) {
+		tmp->left = cs_parse_lefthand(left, mac, tmp);
+		dprintk(KERN_WARNING "%u: tmp->left=%u\n", __LINE__,
+			tmp->left);
+		if (tmp->left == CS_INVALID_CONDITION)
+			return false;
+	} else {
+		type = tmp->type;
+	}
+	dprintk(KERN_WARNING "%u: tmp->type=%u\n", __LINE__, tmp->type);
+	tmp->right = cs_condindex(right, mac, tmp, false);
+	dprintk(KERN_WARNING "%u: tmp->right=%u tmp->type=%u\n",
+		__LINE__, tmp->right, tmp->type);
+	if (tmp->right != CS_INVALID_CONDITION && type != tmp->type &&
+	    !(type == CS_TYPE_FILEPERM && tmp->type == CS_TYPE_NUMBER))
+		return false;
+	if (tmp->right == CS_INVALID_CONDITION)
+		tmp->right = cs_parse_righthand(right, head, tmp);
+	dprintk(KERN_WARNING "%u: tmp->right=%u tmp->type=%u\n",
+		__LINE__, tmp->right, tmp->type);
+	return tmp->right != CS_INVALID_CONDITION;
+}
+
+/**
+ * cs_get_condition - Parse condition part.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns pointer to "struct cs_condition" on success, NULL otherwise.
+ */
+static struct cs_condition *cs_get_condition(struct cs_io_buffer *head)
+{
+	struct cs_condition *entry = kzalloc(PAGE_SIZE, GFP_NOFS);
+	union cs_condition_element *condp;
+	struct cs_cond_tmp tmp;
+	const enum cs_mac_index type = head->w.acl_index;
+	bool transit_domain_done = head->w.is_deny ||
+		!(F(type) &
+		  (F(CS_MAC_EXECUTE) | F(CS_MAC_AUTO_DOMAIN_TRANSITION) |
+		   CS_INET_SOCKET_OK | CS_UNIX_SOCKET_OK));
+	char *pos = head->w.data;
+
+	if (!entry)
+		return NULL;
+	condp = (union cs_condition_element *) (entry + 1);
+	while (1) {
+		memset(&tmp, 0, sizeof(tmp));
+		/*
+		 * tmp.left = CS_INVALID_CONDITION;
+		 * tmp.right = CS_INVALID_CONDITION;
+		 */
+		while (*pos == ' ')
+			pos++;
+		if (!*pos)
+			break;
+		if ((u8 *) condp >= ((u8 *) entry) + PAGE_SIZE
+		    - (sizeof(*condp) + sizeof(struct in6_addr) * 2))
+			goto out;
+		{
+			char *next = strchr(pos, ' ');
+
+			if (next)
+				*next++ = '\0';
+			else
+				next = "";
+			head->w.data = pos;
+			pos = next;
+		}
+		if (!cs_parse_cond(&tmp, head))
+			goto out;
+		if (tmp.left == CS_TRANSIT_DOMAIN) {
+			if (transit_domain_done)
+				goto out;
+			transit_domain_done = true;
+		}
+		condp->is_not = tmp.is_not;
+		condp->left = tmp.left;
+		condp->right = tmp.right;
+		condp->radix = tmp.radix;
+		condp++;
+		if (tmp.left == CS_ARGV_ENTRY) {
+			condp->value = tmp.argv;
+			condp++;
+		} else if (tmp.left == CS_ENVP_ENTRY) {
+			condp->path = tmp.envp;
+			condp++;
+		}
+		if (tmp.right == CS_IMM_GROUP) {
+			condp->group = tmp.group;
+			condp++;
+		} else if (tmp.right == CS_IMM_NAME_ENTRY) {
+			condp->path = tmp.path;
+			condp++;
+		} else if (tmp.right == CS_IMM_NUMBER_ENTRY1 ||
+			   tmp.right == CS_IMM_NUMBER_ENTRY2) {
+			condp->value = tmp.value[0];
+			condp++;
+			if (tmp.right == CS_IMM_NUMBER_ENTRY2) {
+				condp->value = tmp.value[1];
+				condp++;
+			}
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+		} else if (tmp.right == CS_IMM_IPV4ADDR_ENTRY1 ||
+			   tmp.right == CS_IMM_IPV4ADDR_ENTRY2) {
+			condp->ip = *(u32 *) &tmp.ipv6[0];
+			condp++;
+			if (tmp.right == CS_IMM_IPV4ADDR_ENTRY2) {
+				condp->ip = *(u32 *) &tmp.ipv6[1];
+				condp++;
+			}
+		} else if (tmp.right == CS_IMM_IPV6ADDR_ENTRY1 ||
+			   tmp.right == CS_IMM_IPV6ADDR_ENTRY2) {
+			*(struct in6_addr *) condp = tmp.ipv6[0];
+			condp = (void *) (((u8 *) condp) +
+					  sizeof(struct in6_addr));
+			if (tmp.right == CS_IMM_IPV6ADDR_ENTRY2) {
+				*(struct in6_addr *) condp = tmp.ipv6[1];
+				condp = (void *) (((u8 *) condp) +
+						  sizeof(struct in6_addr));
+			}
+#endif
+		}
+	}
+#ifdef CONFIG_SECURITY_CAITSITH_AUTO_DOMAIN_TRANSITION
+	if (!transit_domain_done && type == CS_MAC_AUTO_DOMAIN_TRANSITION)
+		goto out;
+#endif
+	entry->size = (void *) condp - (void *) entry;
+	return cs_commit_condition(entry);
+out:
+	dprintk(KERN_WARNING
+		"%u: bad condition: type=%u env='%s' path='%s' group='%s'\n",
+		__LINE__, type, tmp.envp ? tmp.envp->name : "",
+		tmp.path ? tmp.path->name : "",
+		tmp.group ? tmp.group->group_name->name : "");
+	cs_put_name(tmp.envp);
+	if (tmp.path != &cs_null_name)
+		cs_put_name(tmp.path);
+	cs_put_group(tmp.group);
+	entry->size = (void *) condp - (void *) entry;
+	cs_del_condition(&entry->head.list);
+	kfree(entry);
+	return NULL;
+}
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 6b/10] CaitSith: Add policy management functions.
       [not found] ` <20221102171025.126961-6-penguin-kernel@I-love.SAKURA.ne.jp>
  2022-11-02 17:29   ` [PATCH 6a/10] CaitSith: Add policy management functions Tetsuo Handa
@ 2022-11-02 17:29   ` Tetsuo Handa
  1 sibling, 0 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-02 17:29 UTC (permalink / raw)
  To: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook

This file implements similar functions provided by security/tomoyo/common.c
and security/tomoyo/securityfs_if.c files.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
Reposting "[PATCH 06/10] CaitSith: Add policy management functions." as two patches due to
"BOUNCE linux-security-module@vger.kernel.org: Message too long (>100000 chars)" failure.
This is the latter part.

 security/caitsith/policy_io.c | 2360 +++++++++++++++++++++++++++++++++
 1 file changed, 2360 insertions(+)

diff --git a/security/caitsith/policy_io.c b/security/caitsith/policy_io.c
index 27e2ec57f3b8..36acc994b679 100644
--- a/security/caitsith/policy_io.c
+++ b/security/caitsith/policy_io.c
@@ -1899,3 +1899,2363 @@ static struct cs_condition *cs_get_condition(struct cs_io_buffer *head)
 	kfree(entry);
 	return NULL;
 }
+
+/**
+ * cs_yesno - Return "yes" or "no".
+ *
+ * @value: Bool value.
+ *
+ * Returns "yes" if @value is not 0, "no" otherwise.
+ */
+static const char *cs_yesno(const unsigned int value)
+{
+	return value ? "yes" : "no";
+}
+
+/**
+ * cs_flush - Flush queued string to userspace's buffer.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns true if all data was flushed, false otherwise.
+ */
+static bool cs_flush(struct cs_io_buffer *head)
+{
+	while (head->r.w_pos) {
+		const char *w = head->r.w[0];
+		size_t len = strlen(w);
+
+		if (len) {
+			if (len > head->read_user_buf_avail)
+				len = head->read_user_buf_avail;
+			if (!len)
+				return false;
+			if (copy_to_user(head->read_user_buf, w, len))
+				return false;
+			head->read_user_buf_avail -= len;
+			head->read_user_buf += len;
+			w += len;
+		}
+		head->r.w[0] = w;
+		if (*w)
+			return false;
+		/* Add '\0' for audit logs and query. */
+		if (head->type == CS_AUDIT || head->type == CS_QUERY) {
+			if (!head->read_user_buf_avail ||
+			    copy_to_user(head->read_user_buf, "", 1))
+				return false;
+			head->read_user_buf_avail--;
+			head->read_user_buf++;
+		}
+		head->r.w_pos--;
+		for (len = 0; len < head->r.w_pos; len++)
+			head->r.w[len] = head->r.w[len + 1];
+	}
+	head->r.avail = 0;
+	return true;
+}
+
+/**
+ * cs_set_string - Queue string to "struct cs_io_buffer" structure.
+ *
+ * @head:   Pointer to "struct cs_io_buffer".
+ * @string: String to print.
+ *
+ * Returns nothing.
+ *
+ * Note that @string has to be kept valid until @head is kfree()d.
+ * This means that char[] allocated on stack memory cannot be passed to
+ * this function. Use cs_io_printf() for char[] allocated on stack memory.
+ */
+static void cs_set_string(struct cs_io_buffer *head, const char *string)
+{
+	if (head->r.w_pos < CS_MAX_IO_READ_QUEUE) {
+		head->r.w[head->r.w_pos++] = string;
+		cs_flush(head);
+	} else
+		pr_warn("Too many words in a line.\n");
+}
+
+/**
+ * cs_io_printf - printf() to "struct cs_io_buffer" structure.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @fmt:  The printf()'s format string, followed by parameters.
+ *
+ * Returns nothing.
+ */
+static void cs_io_printf(struct cs_io_buffer *head, const char *fmt, ...)
+{
+	va_list args;
+	size_t len;
+	size_t pos = head->r.avail;
+	int size = head->readbuf_size - pos;
+
+	if (size <= 0)
+		return;
+	va_start(args, fmt);
+	len = vsnprintf(head->read_buf + pos, size, fmt, args) + 1;
+	va_end(args);
+	if (pos + len >= head->readbuf_size) {
+		pr_warn("Too many words in a line.\n");
+		return;
+	}
+	head->r.avail += len;
+	cs_set_string(head, head->read_buf + pos);
+}
+
+/**
+ * cs_set_space - Put a space to "struct cs_io_buffer" structure.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ */
+static void cs_set_space(struct cs_io_buffer *head)
+{
+	cs_set_string(head, " ");
+}
+
+/**
+ * cs_set_lf - Put a line feed to "struct cs_io_buffer" structure.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns true if all data was flushed, false otherwise.
+ */
+static bool cs_set_lf(struct cs_io_buffer *head)
+{
+	cs_set_string(head, "\n");
+	return !head->r.w_pos;
+}
+
+/**
+ * cs_check_profile - Check policy is loaded.
+ *
+ * Returns nothing.
+ */
+void cs_check_profile(void)
+{
+	cs_policy_loaded = true;
+	pr_info("CaitSith (LSM): 0.2.10   2021/06/06\n");
+	if (cs_policy_version == 20120401) {
+		pr_info("CaitSith module activated.\n");
+		return;
+	}
+	pr_err("Policy version %u is not supported.\n", cs_policy_version);
+	pr_err("Userland tools for CaitSith must be installed and policy must be initialized.\n");
+	pr_err("Please see https://caitsith.osdn.jp/ for more information.\n");
+	panic("STOP!");
+}
+
+/**
+ * cs_str_starts - Check whether the given string starts with the given keyword.
+ *
+ * @src:  Pointer to pointer to the string.
+ * @find: Pointer to the keyword.
+ *
+ * Returns true if @src starts with @find, false otherwise.
+ *
+ * The @src is updated to point the first character after the @find
+ * if @src starts with @find.
+ */
+static bool cs_str_starts(char **src, const char *find)
+{
+	const int len = strlen(find);
+	char *tmp = *src;
+
+	if (strncmp(tmp, find, len))
+		return false;
+	tmp += len;
+	*src = tmp;
+	return true;
+}
+
+/**
+ * cs_find_domain - Find a domain by the given name.
+ *
+ * @domainname: The domainname to find.
+ *
+ * Returns pointer to "struct cs_domain_info" if found, NULL otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static struct cs_domain_info *cs_find_domain(const char *domainname)
+{
+	struct cs_domain_info *domain;
+	struct cs_path_info name;
+
+	name.name = domainname;
+	cs_fill_path_info(&name);
+	list_for_each_entry_srcu(domain, &cs_domain_list, list, &cs_ss) {
+		if (!cs_pathcmp(&name, domain->domainname))
+			return domain;
+	}
+	return NULL;
+}
+
+/**
+ * cs_select_acl - Parse select command.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @data: String to parse.
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_select_acl(struct cs_io_buffer *head, const char *data)
+{
+	unsigned int qid;
+	enum cs_mac_index type;
+	struct cs_acl_info *acl;
+
+	if (sscanf(data, "Q=%u", &qid) != 1)
+		return false;
+	acl = cs_find_acl_by_qid(qid, &type);
+	head->w.acl = acl;
+	/* Accessing read_buf is safe because head->io_sem is held. */
+	if (!head->read_buf)
+		return true; /* Do nothing if open(O_WRONLY). */
+	memset(&head->r, 0, sizeof(head->r));
+	head->r.print_this_acl_only = true;
+	if (acl) {
+		head->r.acl = &acl->list;
+		head->r.acl_index = type;
+	} else
+		head->r.eof = true;
+	cs_io_printf(head, "# Q=%u\n", qid);
+	return true;
+}
+
+/**
+ * cs_update_acl - Update "struct cs_acl_info" entry.
+ *
+ * @list:   Pointer to "struct list_head".
+ * @head:   Pointer to "struct cs_io_buffer".
+ * @update: True to store matching entry, false otherwise.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_update_acl(struct list_head * const list,
+			 struct cs_io_buffer *head, const bool update)
+{
+	struct cs_acl_info *ptr;
+	struct cs_acl_info new_entry = { };
+	const bool is_delete = head->w.is_delete;
+	int error = is_delete ? -ENOENT : -ENOMEM;
+
+	new_entry.priority = head->w.priority;
+	new_entry.is_deny = head->w.is_deny;
+	if (head->w.data[0]) {
+		new_entry.cond = cs_get_condition(head);
+		if (!new_entry.cond)
+			return -EINVAL;
+	}
+	if (mutex_lock_killable(&cs_policy_lock))
+		goto out;
+	list_for_each_entry_srcu(ptr, list, list, &cs_ss) {
+		if (ptr->priority > new_entry.priority)
+			break;
+		/*
+		 * We cannot reuse deleted "struct cs_acl_info" entry because
+		 * somebody might be referencing children of this deleted entry
+		 * from srcu section. We cannot delete children of this deleted
+		 * entry until all children are no longer referenced. Thus, let
+		 * the garbage collector wait and delete rather than trying to
+		 * reuse this deleted entry.
+		 */
+		if (ptr->is_deleted || ptr->cond != new_entry.cond ||
+		    ptr->priority != new_entry.priority ||
+		    ptr->is_deny != new_entry.is_deny)
+			continue;
+		ptr->is_deleted = is_delete;
+		if (!is_delete && update)
+			head->w.acl = ptr;
+		error = 0;
+		break;
+	}
+	if (error && !is_delete) {
+		struct cs_acl_info *entry =
+			cs_commit_ok(&new_entry, sizeof(new_entry));
+
+		if (entry) {
+			INIT_LIST_HEAD(&entry->acl_info_list);
+			list_add_tail_rcu(&entry->list, &ptr->list);
+			if (update)
+				head->w.acl = entry;
+		}
+	}
+	mutex_unlock(&cs_policy_lock);
+out:
+	cs_put_condition(new_entry.cond);
+	return error;
+}
+
+/**
+ * cs_parse_entry - Update ACL entry.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_parse_entry(struct cs_io_buffer *head)
+{
+	enum cs_mac_index type;
+	const char *operation = cs_read_token(head);
+
+	for (type = 0; type < CS_MAX_MAC_INDEX; type++) {
+		if (strcmp(operation, cs_mac_keywords[type]))
+			continue;
+		head->w.acl_index = type;
+		/*
+		 * This is_deny is for rejecting transition= argument
+		 * in "acl" line, for that argument is accepted for
+		 * only "allow" line.
+		 */
+		head->w.is_deny = true;
+		return cs_update_acl(&cs_acl_list[type], head, true);
+	}
+	return -EINVAL;
+}
+
+/**
+ * cs_print_number - Print number argument.
+ *
+ * @head:  Pointer to "struct cs_io_buffer".
+ * @radix: One of values in "enum cs_value_type".
+ * @value: Value to print.
+ *
+ * Returns nothing.
+ */
+static void cs_print_number(struct cs_io_buffer *head,
+			    const enum cs_value_type radix,
+			    const unsigned long value)
+{
+	switch (radix) {
+	case CS_VALUE_TYPE_HEXADECIMAL:
+		cs_io_printf(head, "0x%lX", value);
+		break;
+	case CS_VALUE_TYPE_OCTAL:
+		cs_io_printf(head, "0%lo", value);
+		break;
+	default:
+		cs_io_printf(head, "%lu", value);
+	}
+}
+
+/**
+ * cs_condword - Get condition's name.
+ *
+ * @type: One of values in "enum cs_mac_index".
+ * @cond: One of values in "enum cs_condition_index".
+ *
+ * Returns condition's name.
+ */
+static const char *cs_condword(const enum cs_mac_index type,
+			       const enum cs_conditions_index cond)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs_conditions); i++) {
+		if (!(cs_conditions[i].available & F(type)) ||
+		    cs_conditions[i].cmd != cond)
+			continue;
+		return cs_conditions[i].keyword;
+	}
+	return "unknown"; /* This should not happen. */
+}
+
+/**
+ * cs_print_condition_loop - Print condition part.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @cond: Pointer to "struct cs_condition".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_print_condition_loop(struct cs_io_buffer *head,
+				    const struct cs_condition *cond)
+{
+	const enum cs_mac_index type = head->r.acl_index;
+	const union cs_condition_element *condp = head->r.cond;
+
+	while ((void *) condp < (void *) ((u8 *) cond) + cond->size) {
+		const bool is_not = condp->is_not;
+		const enum cs_conditions_index left = condp->left;
+		const enum cs_conditions_index right = condp->right;
+		const u8 radix = condp->radix;
+
+		if (!cs_flush(head)) {
+			head->r.cond = condp;
+			return false;
+		}
+		condp++;
+		cs_set_space(head);
+		switch (left) {
+		case CS_ARGV_ENTRY:
+			cs_io_printf(head, "argv[%lu]", condp->value);
+			condp++;
+			break;
+		case CS_ENVP_ENTRY:
+			cs_set_string(head, "envp[\"");
+			cs_set_string(head, condp->path->name);
+			condp++;
+			cs_set_string(head, "\"]");
+			break;
+		default:
+			cs_set_string(head, cs_condword(type, left));
+		}
+		cs_set_string(head, is_not ? "!=" : "=");
+		switch (right) {
+		case CS_IMM_GROUP:
+			cs_set_string(head, "@");
+			cs_set_string(head, condp->group->group_name->name);
+			condp++;
+			break;
+		case CS_IMM_NAME_ENTRY:
+			if (condp->path != &cs_null_name) {
+				cs_set_string(head, "\"");
+				cs_set_string(head, condp->path->name);
+				cs_set_string(head, "\"");
+			} else {
+				cs_set_string(head, "NULL");
+			}
+			condp++;
+			break;
+		case CS_IMM_NUMBER_ENTRY1:
+		case CS_IMM_NUMBER_ENTRY2:
+			cs_print_number(head, radix & 3, condp->value);
+			condp++;
+			if (right == CS_IMM_NUMBER_ENTRY1)
+				break;
+			cs_set_string(head, "-");
+			cs_print_number(head, (radix >> 2) & 3, condp->value);
+			condp++;
+			break;
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+		case CS_IMM_IPV4ADDR_ENTRY1:
+		case CS_IMM_IPV4ADDR_ENTRY2:
+			cs_print_ipv4(head, &condp->ip);
+			condp++;
+			if (right == CS_IMM_IPV4ADDR_ENTRY1)
+				break;
+			cs_set_string(head, "-");
+			cs_print_ipv4(head, &condp->ip);
+			condp++;
+			break;
+		case CS_IMM_IPV6ADDR_ENTRY1:
+		case CS_IMM_IPV6ADDR_ENTRY2:
+			cs_print_ipv6(head, (const struct in6_addr *) condp);
+			condp = (void *)
+				((u8 *) condp) + sizeof(struct in6_addr);
+			if (right == CS_IMM_IPV6ADDR_ENTRY1)
+				break;
+			cs_set_string(head, "-");
+			cs_print_ipv6(head, (const struct in6_addr *) condp);
+			condp = (void *)
+				((u8 *) condp) + sizeof(struct in6_addr);
+			break;
+#endif
+		default:
+			cs_set_string(head, cs_condword(type, right));
+		}
+	}
+	head->r.cond = NULL;
+	return true;
+}
+
+/**
+ * cs_print_condition - Print condition part.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @cond: Pointer to "struct cs_condition".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_print_condition(struct cs_io_buffer *head,
+			       const struct cs_condition *cond)
+{
+	switch (head->r.cond_step) {
+	case 0:
+		head->r.cond = (const union cs_condition_element *)
+			(cond + 1);
+		head->r.cond_step++;
+		fallthrough;
+	case 1:
+		if (!cs_print_condition_loop(head, cond))
+			return false;
+		head->r.cond_step++;
+		fallthrough;
+	case 2:
+		head->r.cond = NULL;
+		return true;
+	}
+	return false;
+}
+
+/**
+ * cs_read_acl - Print an ACL entry.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @acl:  Pointer to an ACL entry.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_read_acl(struct cs_io_buffer *head,
+			const struct cs_acl_info *acl)
+{
+	const enum cs_mac_index type = head->r.acl_index;
+
+	if (head->r.cond)
+		goto print_cond_part;
+	if (acl->is_deleted)
+		return true;
+	if (!cs_flush(head))
+		return false;
+	cs_io_printf(head, "%u ", acl->priority);
+	cs_set_string(head, "acl ");
+	cs_set_string(head, cs_mac_keywords[type]);
+	if (acl->cond) {
+		head->r.cond_step = 0;
+print_cond_part:
+		if (!cs_print_condition(head, acl->cond))
+			return false;
+	}
+	cs_set_lf(head);
+	return true;
+}
+
+/**
+ * cs_write_pid - Specify PID to obtain domainname.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns 0.
+ */
+static int cs_write_pid(struct cs_io_buffer *head)
+{
+	head->r.eof = false;
+	return 0;
+}
+
+/**
+ * cs_read_pid - Read information of a process.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ *
+ * Reads the domainname which the specified PID is in or
+ * process information of the specified PID on success.
+ *
+ * Caller holds cs_read_lock().
+ */
+static void cs_read_pid(struct cs_io_buffer *head)
+{
+	char *buf = head->write_buf;
+	bool task_info = false;
+	bool global_pid = false;
+	unsigned int pid;
+	struct task_struct *p;
+	struct cs_domain_info *domain = NULL;
+	u32 cs_flags = 0;
+
+	/* Accessing write_buf is safe because head->io_sem is held. */
+	if (!buf) {
+		head->r.eof = true;
+		return; /* Do nothing if open(O_RDONLY). */
+	}
+	if (head->r.w_pos || head->r.eof)
+		return;
+	head->r.eof = true;
+	if (cs_str_starts(&buf, "info "))
+		task_info = true;
+	if (cs_str_starts(&buf, "global-pid "))
+		global_pid = true;
+	pid = (unsigned int) simple_strtoul(buf, NULL, 10);
+	rcu_read_lock();
+	if (global_pid)
+		p = find_task_by_pid_ns(pid, &init_pid_ns);
+	else
+		p = find_task_by_vpid(pid);
+	if (p) {
+		domain = cs_task_domain(p);
+		cs_flags = cs_task_flags(p);
+	}
+	rcu_read_unlock();
+	if (!domain)
+		return;
+	if (!task_info) {
+		cs_io_printf(head, "%u ", pid);
+		cs_set_string(head, domain->domainname->name);
+	} else {
+		cs_io_printf(head, "%u manager=%s ", pid,
+			     cs_yesno(cs_flags &
+				      CS_TASK_IS_MANAGER));
+	}
+}
+
+/**
+ * cs_update_group - Update "struct cs_string_group"/"struct cs_number_group"/"struct cs_ip_group" list.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @type: Type of this group.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_update_group(struct cs_io_buffer *head,
+			   const enum cs_group_id type)
+{
+	u8 size;
+	const bool is_delete = head->w.is_delete;
+	int error = is_delete ? -ENOENT : -ENOMEM;
+	struct cs_group *group = cs_get_group(head, type);
+	char *word = cs_read_token(head);
+	union {
+		struct cs_acl_head head;
+		struct cs_string_group path;
+		struct cs_number_group number;
+		struct cs_ip_group address;
+	} e = { };
+
+	if (!group)
+		return -ENOMEM;
+	if (!*word) {
+		error = -EINVAL;
+		goto out;
+	}
+	if (type == CS_STRING_GROUP) {
+		if (!cs_correct_word(word)) {
+			error = -EINVAL;
+			goto out;
+		}
+		e.path.member_name = cs_get_name(word);
+		if (!e.path.member_name) {
+			error = -ENOMEM;
+			goto out;
+		}
+		size = sizeof(e.path);
+	} else if (type == CS_NUMBER_GROUP) {
+		e.number.radix = cs_parse_values(word, e.number.value);
+		if (e.number.radix == CS_VALUE_TYPE_INVALID)
+			goto out;
+		size = sizeof(e.number);
+	} else {
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+		switch (cs_parse_ipaddr(word, e.address.ip)) {
+		case CS_IMM_IPV4ADDR_ENTRY1:
+		case CS_IMM_IPV4ADDR_ENTRY2:
+			e.address.is_ipv6 = false;
+			break;
+		case CS_IMM_IPV6ADDR_ENTRY1:
+		case CS_IMM_IPV6ADDR_ENTRY2:
+			e.address.is_ipv6 = true;
+			break;
+		default:
+			goto out;
+		}
+		size = sizeof(e.address);
+#else
+		goto out;
+#endif
+	}
+	if (mutex_lock_killable(&cs_policy_lock) == 0) {
+		struct cs_acl_head *entry;
+
+		list_for_each_entry_srcu(entry, &group->member_list,
+					 list, &cs_ss) {
+			if (entry->is_deleted == CS_GC_IN_PROGRESS ||
+			    memcmp(entry + 1, &e.head + 1,
+				   size - sizeof(*entry)))
+				continue;
+			entry->is_deleted = is_delete;
+			error = 0;
+			break;
+		}
+		if (error && !is_delete) {
+			entry = cs_commit_ok(&e, size);
+			if (entry) {
+				list_add_tail_rcu(&entry->list,
+						  &group->member_list);
+				error = 0;
+			}
+		}
+		mutex_unlock(&cs_policy_lock);
+	}
+	if (type == CS_STRING_GROUP)
+		cs_put_name(e.path.member_name);
+out:
+	cs_put_group(group);
+	return error;
+}
+
+/**
+ * cs_write_policy - Write policy.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_write_policy(struct cs_io_buffer *head)
+{
+	enum cs_group_id i;
+	unsigned int priority;
+	char *word = cs_read_token(head);
+
+	if (sscanf(word, "%u", &priority) == 1)
+		word = cs_read_token(head);
+	else
+		priority = 1000;
+	if (priority >= 65536 || !*word)
+		return -EINVAL;
+	head->w.priority = priority;
+	if (!head->w.acl)
+		goto no_acl_selected;
+	head->w.is_deny = !strcmp(word, "deny");
+	if (head->w.is_deny || !strcmp(word, "allow"))
+		return cs_update_acl(&head->w.acl->acl_info_list, head,
+				     false);
+	if (!strcmp(word, "audit")) {
+		head->w.acl->audit = simple_strtoul(head->w.data, NULL, 10);
+		return 0;
+	}
+	head->w.acl = NULL;
+no_acl_selected:
+	if (cs_select_acl(head, word))
+		return 0;
+	if (!strcmp(word, "acl"))
+		return cs_parse_entry(head);
+	for (i = 0; i < CS_MAX_GROUP; i++)
+		if (!strcmp(word, cs_group_name[i]))
+			return cs_update_group(head, i);
+	if (sscanf(word, "POLICY_VERSION=%u", &cs_policy_version) == 1)
+		return 0;
+	if (strcmp(word, "quota"))
+		return -EINVAL;
+	if (cs_str_starts(&head->w.data, "memory "))
+		return cs_write_memory_quota(head->w.data);
+	return cs_write_audit_quota(head->w.data);
+}
+
+/**
+ * cs_read_subgroup - Read "struct cs_string_group"/"struct cs_number_group"/"struct cs_ip_group" list.
+ *
+ * @head:  Pointer to "struct cs_io_buffer".
+ * @group: Pointer to "struct cs_group".
+ * @idx:   One of values in "enum cs_group_id".
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_read_subgroup(struct cs_io_buffer *head,
+			     struct cs_group *group,
+			     const enum cs_group_id idx)
+{
+	list_for_each_cookie(head->r.acl, &group->member_list) {
+		struct cs_acl_head *ptr =
+			list_entry(head->r.acl, typeof(*ptr), list);
+
+		if (ptr->is_deleted)
+			continue;
+		if (!cs_flush(head))
+			return false;
+		cs_set_string(head, cs_group_name[idx]);
+		cs_set_space(head);
+		cs_set_string(head, group->group_name->name);
+		cs_set_space(head);
+		if (idx == CS_STRING_GROUP) {
+			cs_set_string(head, container_of
+				      (ptr, struct cs_string_group,
+				       head)->member_name->name);
+		} else if (idx == CS_NUMBER_GROUP) {
+			struct cs_number_group *e =
+				container_of(ptr, typeof(*e), head);
+
+			cs_print_number(head, e->radix & 3, e->value[0]);
+			if (e->radix >> 2) {
+				cs_set_string(head, "-");
+				cs_print_number(head, (e->radix >> 2) & 3,
+						e->value[1]);
+			}
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+		} else if (idx == CS_IP_GROUP) {
+			cs_print_ip(head, container_of
+				    (ptr, struct cs_ip_group, head));
+#endif
+		}
+		cs_set_lf(head);
+	}
+	head->r.acl = NULL;
+	return true;
+}
+
+/**
+ * cs_read_group - Read "struct cs_string_group"/"struct cs_number_group"/"struct cs_ip_group" list.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_read_group(struct cs_io_buffer *head)
+{
+	while (head->r.step < CS_MAX_GROUP) {
+		const enum cs_group_id idx = head->r.step;
+		struct list_head *list = &cs_group_list[idx];
+
+		list_for_each_cookie(head->r.group, list) {
+			struct cs_group *group =
+				list_entry(head->r.group, typeof(*group),
+					   head.list);
+
+			if (!cs_read_subgroup(head, group, idx))
+				return false;
+		}
+		head->r.group = NULL;
+		head->r.step++;
+	}
+	head->r.step = 0;
+	return true;
+}
+
+/**
+ * cs_supervisor - Ask for the supervisor's decision.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns 0 if the supervisor decided to permit the access request,
+ * CS_RETRY_REQUEST if the supervisor decided to retry the access request,
+ * -EPERM otherwise.
+ */
+static int cs_supervisor(struct cs_request_info *r)
+{
+	int error = -EPERM;
+	int len;
+	static unsigned int cs_serial;
+	struct cs_query entry = { };
+	bool quota_exceeded = false;
+
+	if (!r->matched_acl)
+		return -EPERM;
+	/* Get message. */
+	entry.query = cs_init_log(r);
+	if (!entry.query)
+		return -EPERM;
+	entry.query_len = strlen(entry.query) + 1;
+	len = ksize(entry.query);
+	entry.acl = r->matched_acl;
+	entry.acl_type = r->type;
+	spin_lock(&cs_query_list_lock);
+	if (cs_memory_quota[CS_MEMORY_QUERY] &&
+	    cs_memory_used[CS_MEMORY_QUERY] + len
+	    >= cs_memory_quota[CS_MEMORY_QUERY]) {
+		quota_exceeded = true;
+	} else {
+		entry.serial = cs_serial++;
+		entry.retry = r->retry;
+		cs_memory_used[CS_MEMORY_QUERY] += len;
+		list_add_tail(&entry.list, &cs_query_list);
+	}
+	spin_unlock(&cs_query_list_lock);
+	if (quota_exceeded)
+		goto out;
+	/* Give 10 seconds for supervisor's opinion. */
+	while (entry.timer < 10) {
+		wake_up_all(&cs_query_wait);
+		if (wait_event_killable_timeout
+		    (cs_answer_wait, entry.answer ||
+		     !atomic_read(&cs_query_observers), HZ))
+			break;
+		entry.timer++;
+	}
+	spin_lock(&cs_query_list_lock);
+	list_del(&entry.list);
+	cs_memory_used[CS_MEMORY_QUERY] -= len;
+	spin_unlock(&cs_query_list_lock);
+	switch (entry.answer) {
+	case 3: /* Asked to retry by administrator. */
+		error = CS_RETRY_REQUEST;
+		r->retry++;
+		break;
+	case 1:
+		/* Granted by administrator. */
+		error = 0;
+		break;
+	default:
+		/* Timed out or rejected by administrator. */
+		break;
+	}
+out:
+	kfree(entry.query);
+	return error;
+}
+
+/**
+ * cs_audit_log - Audit permission check log.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns 0 to grant the request, CS_RETRY_REQUEST to retry the permission
+ * check, -EPERM otherwise.
+ */
+int cs_audit_log(struct cs_request_info *r)
+{
+	/* Do not reject if not yet activated. */
+	if (!cs_policy_loaded)
+		return 0;
+	/* Write /sys/kernel/security/caitsith/audit unless quota exceeded. */
+	if (cs_log_count[r->result] < cs_log_quota[r->audit][r->result])
+		cs_write_log(r);
+	/* Nothing more to do unless denied. */
+	if (r->result != CS_MATCHING_DENIED)
+		return 0;
+	/* Update policy violation counter if denied. */
+	cs_update_stat(CS_STAT_REQUEST_DENIED);
+	/* Nothing more to do unless caitsith-queryd is running. */
+	if (!atomic_read(&cs_query_observers))
+		return -EPERM;
+	/* Ask the caitsith-queryd for decision. */
+	return cs_supervisor(r);
+}
+
+/**
+ * cs_find_acl_by_qid - Get ACL by query id.
+ *
+ * @serial: Query ID assigned by cs_supervisor().
+ * @type:   Pointer to "enum cs_mac_index".
+ *
+ * Returns pointer to "struct cs_acl_info" if found, NULL otherwise.
+ *
+ * @type holds "enum cs_mac_index" value if found.
+ */
+static struct cs_acl_info *cs_find_acl_by_qid(unsigned int serial,
+					      enum cs_mac_index *type)
+{
+	struct cs_query *ptr;
+	struct cs_acl_info *acl = NULL;
+
+	spin_lock(&cs_query_list_lock);
+	list_for_each_entry(ptr, &cs_query_list, list) {
+		if (ptr->serial != serial)
+			continue;
+		acl = ptr->acl;
+		*type = ptr->acl_type;
+		break;
+	}
+	spin_unlock(&cs_query_list_lock);
+	return acl;
+}
+
+/**
+ * cs_read_query - Read access requests which violated policy in enforcing mode.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ */
+static void cs_read_query(struct cs_io_buffer *head)
+{
+	struct list_head *tmp;
+	unsigned int pos = 0;
+	size_t len = 0;
+	char *buf;
+
+	if (head->r.w_pos)
+		return;
+	kfree(head->read_buf);
+	head->read_buf = NULL;
+	spin_lock(&cs_query_list_lock);
+	list_for_each(tmp, &cs_query_list) {
+		struct cs_query *ptr = list_entry(tmp, typeof(*ptr), list);
+
+		if (pos++ != head->r.query_index)
+			continue;
+		len = ptr->query_len;
+		break;
+	}
+	spin_unlock(&cs_query_list_lock);
+	if (!len) {
+		head->r.query_index = 0;
+		return;
+	}
+	buf = kzalloc(len + 32, GFP_NOFS);
+	if (!buf)
+		return;
+	pos = 0;
+	spin_lock(&cs_query_list_lock);
+	list_for_each(tmp, &cs_query_list) {
+		struct cs_query *ptr = list_entry(tmp, typeof(*ptr), list);
+
+		if (pos++ != head->r.query_index)
+			continue;
+		/*
+		 * Some query can be skipped because cs_query_list
+		 * can change, but I don't care.
+		 */
+		if (len == ptr->query_len)
+			snprintf(buf, len + 31, "Q%u-%hu\n%s", ptr->serial,
+				 ptr->retry, ptr->query);
+		break;
+	}
+	spin_unlock(&cs_query_list_lock);
+	if (buf[0]) {
+		head->read_buf = buf;
+		head->r.w[head->r.w_pos++] = buf;
+		head->r.query_index++;
+	} else {
+		kfree(buf);
+	}
+}
+
+/**
+ * cs_write_answer - Write the supervisor's decision.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int cs_write_answer(struct cs_io_buffer *head)
+{
+	char *data = head->write_buf;
+	struct list_head *tmp;
+	unsigned int serial;
+	unsigned int answer;
+
+	spin_lock(&cs_query_list_lock);
+	list_for_each(tmp, &cs_query_list) {
+		struct cs_query *ptr = list_entry(tmp, typeof(*ptr), list);
+
+		ptr->timer = 0;
+	}
+	spin_unlock(&cs_query_list_lock);
+	if (sscanf(data, "A%u=%u", &serial, &answer) != 2)
+		return -EINVAL;
+	spin_lock(&cs_query_list_lock);
+	list_for_each(tmp, &cs_query_list) {
+		struct cs_query *ptr = list_entry(tmp, typeof(*ptr), list);
+
+		if (ptr->serial != serial)
+			continue;
+		ptr->answer = (u8) answer;
+		/* Remove from cs_query_list. */
+		if (ptr->answer) {
+			list_del(&ptr->list);
+			INIT_LIST_HEAD(&ptr->list);
+		}
+		break;
+	}
+	spin_unlock(&cs_query_list_lock);
+	wake_up_all(&cs_answer_wait);
+	return 0;
+}
+
+/**
+ * cs_read_version - Get version.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ */
+static void cs_read_version(struct cs_io_buffer *head)
+{
+	if (head->r.eof)
+		return;
+	cs_set_string(head, "0.2");
+	head->r.eof = true;
+}
+
+/**
+ * cs_update_stat - Update statistic counters.
+ *
+ * @index: Index for policy type.
+ *
+ * Returns nothing.
+ */
+static void cs_update_stat(const u8 index)
+{
+	atomic_inc(&cs_stat_updated[index]);
+	cs_stat_modified[index] = ktime_get_real_seconds();
+}
+
+/**
+ * cs_read_stat - Read statistic data.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ */
+static void cs_read_stat(struct cs_io_buffer *head)
+{
+	u8 i;
+
+	for (i = 0; i < CS_MAX_POLICY_STAT; i++) {
+		static const char * const k[CS_MAX_POLICY_STAT] = {
+			[CS_STAT_POLICY_UPDATES] = "Policy updated:",
+			[CS_STAT_REQUEST_DENIED] = "Requests denied:",
+		};
+
+		cs_io_printf(head, "stat %s %u", k[i],
+			     atomic_read(&cs_stat_updated[i]));
+		if (cs_stat_modified[i]) {
+			struct cs_time stamp;
+
+			cs_convert_time(cs_stat_modified[i], &stamp);
+			cs_io_printf(head,
+				     " (Last: %04u/%02u/%02u %02u:%02u:%02u)",
+				     stamp.year, stamp.month, stamp.day,
+				     stamp.hour, stamp.min, stamp.sec);
+		}
+		cs_set_lf(head);
+	}
+	for (i = 0; i < CS_MAX_MEMORY_STAT; i++)
+		cs_io_printf(head, "stat Memory used by %s: %u\n",
+			     cs_memory_headers[i], cs_memory_used[i]);
+}
+
+/**
+ * cs_read_quota - Read quota data.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_read_quota(struct cs_io_buffer *head)
+{
+	unsigned int i;
+
+	while (head->r.step < CS_MAX_MEMORY_STAT) {
+		i = head->r.step++;
+		if (!cs_memory_quota[i])
+			continue;
+		cs_io_printf(head, "quota memory %s %u\n",
+			     cs_memory_headers[i], cs_memory_quota[i]);
+	}
+	while (head->r.step < CS_MAX_LOG_QUOTA + CS_MAX_MEMORY_STAT) {
+		unsigned int a;
+		unsigned int d;
+		unsigned int u;
+
+		if (!cs_flush(head))
+			return false;
+		i = head->r.step - CS_MAX_MEMORY_STAT;
+		a = cs_log_quota[i][CS_MATCHING_ALLOWED];
+		d = cs_log_quota[i][CS_MATCHING_DENIED];
+		u = cs_log_quota[i][CS_MATCHING_UNMATCHED];
+		if (a || d || u)
+			cs_io_printf(head,
+				     "quota audit[%u] allowed=%u denied=%u unmatched=%u\n",
+				     i, a, d, u);
+		head->r.step++;
+	}
+	head->r.step = 0;
+	return true;
+}
+
+/**
+ * cs_write_memory_quota - Set memory quota.
+ *
+ * @data: Line to parse.
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int cs_write_memory_quota(char *data)
+{
+	u8 i;
+
+	for (i = 0; i < CS_MAX_MEMORY_STAT; i++)
+		if (cs_str_starts(&data, cs_memory_headers[i])) {
+			if (*data == ' ')
+				data++;
+			cs_memory_quota[i] =
+				simple_strtoul(data, NULL, 10);
+			return 0;
+		}
+	return -EINVAL;
+}
+
+/**
+ * cs_write_audit_quota - Set audit log quota.
+ *
+ * @data: Line to parse.
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int cs_write_audit_quota(char *data)
+{
+	unsigned int i;
+
+	if (sscanf(data, "audit[%u]", &i) != 1 || i >= CS_MAX_LOG_QUOTA)
+		return -EINVAL;
+	data = strchr(data, ' ');
+	if (!data++)
+		return -EINVAL;
+	while (1) {
+		unsigned int logs;
+		char *cp = strchr(data, ' ');
+
+		if (cp)
+			*cp++ = '\0';
+		if (sscanf(data, "allowed=%u", &logs) == 1)
+			cs_log_quota[i][CS_MATCHING_ALLOWED] = logs;
+		else if (sscanf(data, "denied=%u", &logs) == 1)
+			cs_log_quota[i][CS_MATCHING_DENIED] = logs;
+		else if (sscanf(data, "unmatched=%u", &logs) == 1)
+			cs_log_quota[i][CS_MATCHING_UNMATCHED] = logs;
+		if (!cp)
+			break;
+		data = cp;
+	}
+	return 0;
+}
+
+/**
+ * cs_print_bprm - Print "struct linux_binprm" for auditing.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @dump: Pointer to "struct cs_page_dump".
+ *
+ * Returns the contents of @bprm on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+static char *cs_print_bprm(struct linux_binprm *bprm,
+			   struct cs_page_dump *dump)
+{
+	static const int cs_buffer_len = 4096 * 2;
+	char *buffer = kzalloc(cs_buffer_len, GFP_NOFS);
+	char *cp;
+	char *last_start;
+	unsigned long pos = bprm->p;
+	int offset = pos % PAGE_SIZE;
+	int argv_count = bprm->argc;
+	int envp_count = bprm->envc;
+	bool skip = false;
+	bool env_value = false;
+
+	if (!buffer)
+		return NULL;
+	cp = buffer + snprintf(buffer, cs_buffer_len - 1, " argc=%d envc=%d",
+			       argv_count, envp_count);
+	last_start = cp;
+	while (argv_count || envp_count) {
+		if (!cs_dump_page(bprm, pos, dump)) {
+			kfree(buffer);
+			return NULL;
+		}
+		pos += PAGE_SIZE - offset;
+		/* Read. */
+		while (offset < PAGE_SIZE) {
+			const char *kaddr = dump->data;
+			const unsigned char c = kaddr[offset++];
+			int len;
+
+			/* Check for end of buffer. */
+			if (skip) {
+				if (c)
+					continue;
+				goto reset;
+			}
+			len = buffer + cs_buffer_len - cp - 1;
+			if (len <= 32 && c) {
+				cp = last_start;
+				skip = true;
+				continue;
+			}
+			/* Print argv[$index]=" or envp[" part. */
+			if (cp == last_start) {
+				int l;
+
+				if (argv_count)
+					l = snprintf(cp, len, " argv[%u]=\"",
+						     bprm->argc - argv_count);
+				else
+					l = snprintf(cp, len, " envp[\"");
+				cp += l;
+				len -= l;
+			}
+			if (c > ' ' && c < 127 && c != '\\') {
+				/* Print "]=" part if printing environ. */
+				if (c == '=' && !argv_count && !env_value) {
+					cp += snprintf(cp, len, "\"]=\"");
+					env_value = true;
+				} else
+					*cp++ = c;
+				continue;
+			}
+			if (c) {
+				*cp++ = '\\';
+				*cp++ = (c >> 6) + '0';
+				*cp++ = ((c >> 3) & 7) + '0';
+				*cp++ = (c & 7) + '0';
+				continue;
+			}
+			/* Print "]=" part if not yet printed. */
+			if (!argv_count && !env_value)
+				cp += snprintf(cp, len, "\"]=\"");
+			*cp++ = '"';
+			last_start = cp;
+reset:
+			skip = false;
+			env_value = false;
+			if (argv_count)
+				argv_count--;
+			else if (envp_count)
+				envp_count--;
+			if (!argv_count && !envp_count)
+				break;
+		}
+		offset = 0;
+	}
+	*cp = '\0';
+	return buffer;
+}
+
+/**
+ * cs_filetype - Get string representation of file type.
+ *
+ * @mode: Mode value for stat().
+ *
+ * Returns file type string.
+ */
+static inline const char *cs_filetype(const umode_t mode)
+{
+	switch (mode & S_IFMT) {
+	case S_IFREG:
+	case 0:
+		return "file";
+	case S_IFDIR:
+		return "directory";
+	case S_IFLNK:
+		return "symlink";
+	case S_IFIFO:
+		return "fifo";
+	case S_IFSOCK:
+		return "socket";
+	case S_IFBLK:
+		return "block";
+	case S_IFCHR:
+		return "char";
+	}
+	return "unknown"; /* This should not happen. */
+}
+
+/**
+ * cs_print_trailer - Get misc info of audit log.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns string representation.
+ *
+ * This function uses kmalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+static char *cs_print_trailer(struct cs_request_info *r)
+{
+	const char *exe = r->exename.name;
+	const char *domain = cs_current_domain()->domainname->name;
+	const int cs_buffer_len = 2000 + strlen(exe) + strlen(domain);
+	char *buffer = kmalloc(cs_buffer_len, GFP_NOFS);
+	int pos;
+	u8 i;
+
+	if (!buffer)
+		return NULL;
+	pos = snprintf(buffer, cs_buffer_len - 1,
+		       " task.pid=%u task.ppid=%u task.uid=%u task.gid=%u task.euid=%u task.egid=%u task.suid=%u task.sgid=%u task.fsuid=%u task.fsgid=%u task.exe=\"%s\" task.domain=\"%s\"",
+		       cs_sys_getpid(), cs_sys_getppid(),
+		       from_kuid(&init_user_ns, current_uid()),
+		       from_kgid(&init_user_ns, current_gid()),
+		       from_kuid(&init_user_ns, current_euid()),
+		       from_kgid(&init_user_ns, current_egid()),
+		       from_kuid(&init_user_ns, current_suid()),
+		       from_kgid(&init_user_ns, current_sgid()),
+		       from_kuid(&init_user_ns, current_fsuid()),
+		       from_kgid(&init_user_ns, current_fsgid()),
+		       exe, domain);
+	if (!r->obj.path[0].dentry && !r->obj.path[1].dentry)
+		goto no_obj_info;
+	cs_get_attributes(r);
+	for (i = 0; i < CS_MAX_PATH_STAT; i++) {
+		const enum cs_conditions_index cond =
+			CS_PATH_ATTRIBUTE_START + (i * 16);
+		struct cs_mini_stat *stat;
+		unsigned int dev;
+		umode_t mode;
+
+		if (!r->obj.stat_valid[i])
+			continue;
+		stat = &r->obj.stat[i];
+		mode = stat->mode;
+		pos += snprintf(buffer + pos, cs_buffer_len - 1 - pos,
+				" %s=%u %s=%u %s=0%o",
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_UID),
+				from_kuid(&init_user_ns, stat->uid),
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_GID),
+				from_kgid(&init_user_ns, stat->gid),
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_PERM),
+				mode & S_IALLUGO);
+		/* No need to audit if parent directory. */
+		if (i & 1)
+			goto skip;
+		pos += snprintf(buffer + pos, cs_buffer_len - 1 - pos,
+				" %s=%s",
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_TYPE),
+				cs_filetype(mode));
+		/* No need to audit unless block or char. */
+		if (!S_ISCHR(mode) && !S_ISBLK(mode))
+			goto skip;
+		dev = stat->rdev;
+		pos += snprintf(buffer + pos, cs_buffer_len - 1 - pos,
+				" %s=%u %s=%u",
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_DEV_MAJOR),
+				MAJOR(dev),
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_DEV_MINOR),
+				MINOR(dev));
+skip:
+		dev = stat->dev;
+		pos += snprintf(buffer + pos, cs_buffer_len - 1 - pos,
+				" %s=%lu %s=%u %s=%u %s=0x%lX",
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_INO),
+				(unsigned long) stat->ino,
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_MAJOR),
+				MAJOR(dev),
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_MINOR),
+				MINOR(dev),
+				cs_condword(r->type, cond +
+					    CS_PATH_ATTRIBUTE_FSMAGIC),
+				stat->fsmagic);
+	}
+no_obj_info:
+	if (pos < cs_buffer_len - 1)
+		return buffer;
+	kfree(buffer);
+	return NULL;
+}
+
+/**
+ * cs_print_param -  Get arg info of audit log.
+ *
+ * @r:   Pointer to "struct cs_request_info".
+ * @buf: Buffer to write.
+ * @len: Size of @buf in bytes.
+ */
+static int cs_print_param(struct cs_request_info *r, char *buf, int len)
+{
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	/* Make sure that IP address argument is ready. */
+	char ip[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")];
+
+	switch (r->type) {
+	case CS_MAC_INET_STREAM_BIND:
+	case CS_MAC_INET_STREAM_LISTEN:
+	case CS_MAC_INET_STREAM_CONNECT:
+	case CS_MAC_INET_STREAM_ACCEPT:
+	case CS_MAC_INET_DGRAM_BIND:
+	case CS_MAC_INET_DGRAM_SEND:
+	case CS_MAC_INET_RAW_BIND:
+	case CS_MAC_INET_RAW_SEND:
+		if (!r->param.ip)
+			return 0;
+		if (r->param.is_ipv6) {
+			snprintf(ip, sizeof(ip), "%pI6c",
+				 (const struct in6_addr *) r->param.ip);
+		} else {
+			snprintf(ip, sizeof(ip), "%pI4", r->param.ip);
+		}
+		break;
+	default:
+		break;
+	}
+#endif
+	/* Make sure that string arguments are ready. */
+	if (!r->param.s[0] && r->obj.path[0].dentry) {
+		cs_populate_patharg(r, true);
+		if (!r->param.s[0])
+			return 0;
+	}
+	if (!r->param.s[1] && r->obj.path[1].dentry) {
+		cs_populate_patharg(r, false);
+		if (!r->param.s[1])
+			return 0;
+	}
+	switch (r->type) {
+		int pos;
+		u8 i;
+	case CS_MAC_EXECUTE:
+		return snprintf(buf, len, " exec=\"%s\" path=\"%s\"",
+				r->param.s[1]->name, r->param.s[0]->name);
+	case CS_MAC_READ:
+	case CS_MAC_WRITE:
+	case CS_MAC_APPEND:
+	case CS_MAC_UNLINK:
+#ifdef CONFIG_SECURITY_CAITSITH_GETATTR
+	case CS_MAC_GETATTR:
+#endif
+	case CS_MAC_RMDIR:
+	case CS_MAC_TRUNCATE:
+	case CS_MAC_CHROOT:
+		return snprintf(buf, len, " path=\"%s\"", r->param.s[0]->name);
+	case CS_MAC_CREATE:
+	case CS_MAC_MKDIR:
+	case CS_MAC_MKFIFO:
+	case CS_MAC_MKSOCK:
+		return snprintf(buf, len, " path=\"%s\" perm=0%lo",
+				r->param.s[0]->name, r->param.i[0]);
+	case CS_MAC_SYMLINK:
+		return snprintf(buf, len, " path=\"%s\" target=\"%s\"",
+				r->param.s[0]->name, r->param.s[1]->name);
+	case CS_MAC_MKBLOCK:
+	case CS_MAC_MKCHAR:
+		return snprintf(buf, len,
+				" path=\"%s\" perm=0%lo dev_major=%lu dev_minor=%lu",
+				r->param.s[0]->name, r->param.i[0],
+				r->param.i[1], r->param.i[2]);
+	case CS_MAC_LINK:
+	case CS_MAC_RENAME:
+		return snprintf(buf, len, " old_path=\"%s\" new_path=\"%s\"",
+				r->param.s[0]->name, r->param.s[1]->name);
+	case CS_MAC_CHMOD:
+		return snprintf(buf, len, " path=\"%s\" perm=0%lo",
+				r->param.s[0]->name, r->param.i[0]);
+	case CS_MAC_CHOWN:
+		return snprintf(buf, len, " path=\"%s\" uid=%lu",
+				r->param.s[0]->name, r->param.i[0]);
+	case CS_MAC_CHGRP:
+		return snprintf(buf, len, " path=\"%s\" gid=%lu",
+				r->param.s[0]->name, r->param.i[0]);
+	case CS_MAC_IOCTL:
+		return snprintf(buf, len, " path=\"%s\" cmd=0x%lX",
+				r->param.s[0]->name, r->param.i[0]);
+	case CS_MAC_MOUNT:
+		pos = 0;
+		for (i = 0; i < 4; i++) {
+			static const char * const cs_names[4] = {
+				"source", "target", "fstype", "data"
+			};
+			if (i == 3)
+				pos += snprintf(buf + pos, pos < len ?
+						len - pos : 0, " flags=0x%lX",
+						r->param.i[0]);
+			if (!r->param.s[i])
+				continue;
+			pos += snprintf(buf + pos, pos < len ? len - pos : 0,
+					" %s=\"%s\"", cs_names[i],
+					r->param.s[i]->name);
+		}
+		return pos;
+	case CS_MAC_UMOUNT:
+		return snprintf(buf, len, " path=\"%s\" flags=0x%lX",
+				r->param.s[0]->name, r->param.i[0]);
+	case CS_MAC_PIVOT_ROOT:
+		return snprintf(buf, len, " new_root=\"%s\" put_old=\"%s\"",
+				r->param.s[0]->name, r->param.s[1]->name);
+#ifdef CONFIG_SECURITY_CAITSITH_ENVIRON
+	case CS_MAC_ENVIRON:
+		return snprintf(buf, len,
+				" name=\"%s\" value=\"%s\" exec=\"%s\" path=\"%s\"",
+				r->param.s[2]->name, r->param.s[3]->name,
+				r->param.s[1]->name, r->param.s[0]->name);
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_NETWORK
+	case CS_MAC_INET_STREAM_BIND:
+	case CS_MAC_INET_STREAM_LISTEN:
+	case CS_MAC_INET_STREAM_CONNECT:
+	case CS_MAC_INET_STREAM_ACCEPT:
+	case CS_MAC_INET_DGRAM_BIND:
+	case CS_MAC_INET_DGRAM_SEND:
+		return snprintf(buf, len, " ip=%s port=%lu", ip,
+				r->param.i[0]);
+	case CS_MAC_INET_RAW_BIND:
+	case CS_MAC_INET_RAW_SEND:
+		return snprintf(buf, len, " ip=%s proto=%lu", ip,
+				r->param.i[0]);
+	case CS_MAC_UNIX_STREAM_BIND:
+	case CS_MAC_UNIX_STREAM_LISTEN:
+	case CS_MAC_UNIX_STREAM_CONNECT:
+	case CS_MAC_UNIX_STREAM_ACCEPT:
+	case CS_MAC_UNIX_DGRAM_BIND:
+	case CS_MAC_UNIX_DGRAM_SEND:
+	case CS_MAC_UNIX_SEQPACKET_BIND:
+	case CS_MAC_UNIX_SEQPACKET_LISTEN:
+	case CS_MAC_UNIX_SEQPACKET_CONNECT:
+	case CS_MAC_UNIX_SEQPACKET_ACCEPT:
+		return snprintf(buf, len, " addr=\"%s\"", r->param.s[0]->name);
+#endif
+#ifdef CONFIG_SECURITY_CAITSITH_MANUAL_DOMAIN_TRANSITION
+	case CS_MAC_MANUAL_DOMAIN_TRANSITION:
+		return snprintf(buf, len, " domain=\"%s\"",
+				r->param.s[0]->name);
+#endif
+	default:
+		break;
+	}
+	return 0;
+}
+
+/**
+ * cs_init_log - Allocate buffer for audit logs.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns pointer to allocated memory.
+ *
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+static char *cs_init_log(struct cs_request_info *r)
+{
+	const pid_t gpid = task_pid_nr(current);
+	struct cs_time stamp;
+	static const char * const k[CS_MAX_MATCHING] = {
+		[CS_MATCHING_UNMATCHED] = "unmatched",
+		[CS_MATCHING_ALLOWED] = "allowed",
+		[CS_MATCHING_DENIED] = "denied",
+	};
+	char *buf;
+	const char *bprm_info;
+	const char *trailer;
+	int len;
+
+	if (!r->exename.name && !cs_get_exename(&r->exename))
+		return NULL;
+	cs_convert_time(ktime_get_real_seconds(), &stamp);
+	trailer = cs_print_trailer(r);
+	if (r->bprm)
+		bprm_info = cs_print_bprm(r->bprm, &r->dump);
+	else
+		bprm_info = NULL;
+	len = 0;
+	while (1) {
+		int pos;
+
+		buf = kzalloc(len, GFP_NOFS);
+		if (!buf)
+			break;
+		pos = snprintf(buf, len,
+			       "#%04u/%02u/%02u %02u:%02u:%02u# global-pid=%u result=%s priority=%u / %s",
+			       stamp.year, stamp.month, stamp.day, stamp.hour,
+			       stamp.min, stamp.sec, gpid, k[r->result],
+			       r->matched_acl ? r->matched_acl->priority : 0,
+			       cs_mac_keywords[r->type]);
+		pos += cs_print_param(r, buf + pos,
+				      pos < len ? len - pos : 0);
+		if (bprm_info)
+			pos += snprintf(buf + pos, pos < len ? len - pos : 0,
+					"%s", bprm_info);
+		if (trailer)
+			pos += snprintf(buf + pos, pos < len ? len - pos : 0,
+					"%s", trailer);
+		pos += snprintf(buf + pos, pos < len ? len - pos : 0,
+				"\n") + 1;
+		if (pos <= len)
+			break;
+		kfree(buf);
+		len = pos;
+	}
+	kfree(bprm_info);
+	kfree(trailer);
+	return buf;
+}
+
+/**
+ * cs_write_log - Write an audit log.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns nothing.
+ */
+static void cs_write_log(struct cs_request_info *r)
+{
+	struct cs_log *entry;
+	bool quota_exceeded = false;
+	char *buf = cs_init_log(r);
+
+	if (!buf)
+		return;
+	entry = kzalloc(sizeof(*entry), GFP_NOFS);
+	if (!entry) {
+		kfree(buf);
+		return;
+	}
+	entry->log = buf;
+	/*
+	 * The entry->size is used for memory quota checks.
+	 * Don't go beyond strlen(entry->log).
+	 */
+	entry->size = ksize(buf) + ksize(entry);
+	entry->result = r->result;
+	spin_lock(&cs_log_lock);
+	if (cs_memory_quota[CS_MEMORY_AUDIT] &&
+	    cs_memory_used[CS_MEMORY_AUDIT] + entry->size >=
+	    cs_memory_quota[CS_MEMORY_AUDIT]) {
+		quota_exceeded = true;
+	} else {
+		cs_memory_used[CS_MEMORY_AUDIT] += entry->size;
+		list_add_tail(&entry->list, &cs_log);
+		cs_log_count[entry->result]++;
+	}
+	spin_unlock(&cs_log_lock);
+	if (quota_exceeded) {
+		kfree(buf);
+		kfree(entry);
+		return;
+	}
+	wake_up(&cs_log_wait);
+}
+
+/**
+ * cs_read_log - Read an audit log.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ */
+static void cs_read_log(struct cs_io_buffer *head)
+{
+	struct cs_log *ptr = NULL;
+
+	if (head->r.w_pos)
+		return;
+	kfree(head->read_buf);
+	head->read_buf = NULL;
+	spin_lock(&cs_log_lock);
+	if (!list_empty(&cs_log)) {
+		ptr = list_entry(cs_log.next, typeof(*ptr), list);
+		list_del(&ptr->list);
+		cs_log_count[ptr->result]--;
+		cs_memory_used[CS_MEMORY_AUDIT] -= ptr->size;
+	}
+	spin_unlock(&cs_log_lock);
+	if (ptr) {
+		head->read_buf = ptr->log;
+		head->r.w[head->r.w_pos++] = head->read_buf;
+		kfree(ptr);
+	}
+}
+
+/**
+ * cs_transit_domain - Transit to other domain.
+ *
+ * @domainname: The name of domain.
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+bool cs_transit_domain(const char *domainname)
+{
+	struct cs_security *security = cs_current_security();
+	struct cs_domain_info e = { };
+	struct cs_domain_info *entry = cs_find_domain(domainname);
+
+	if (entry) {
+		security->cs_domain_info = entry;
+		return true;
+	}
+	/* Requested domain does not exist. */
+	/* Don't create requested domain if domainname is invalid. */
+	if (!cs_correct_domain(domainname))
+		return false;
+	e.domainname = cs_get_name(domainname);
+	if (!e.domainname)
+		return false;
+	if (mutex_lock_killable(&cs_policy_lock))
+		goto out;
+	entry = cs_find_domain(domainname);
+	if (entry)
+		goto done;
+	entry = cs_commit_ok(&e, sizeof(e));
+	if (!entry)
+		goto done;
+	list_add_tail_rcu(&entry->list, &cs_domain_list);
+done:
+	mutex_unlock(&cs_policy_lock);
+out:
+	cs_put_name(e.domainname);
+	if (entry)
+		security->cs_domain_info = entry;
+	return entry != NULL;
+}
+
+/**
+ * cs_parse_policy - Parse a policy line.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @line: Line to parse.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_parse_policy(struct cs_io_buffer *head, char *line)
+{
+	/* Set current line's content. */
+	head->w.data = line;
+	head->w.is_deny = false;
+	head->w.priority = 0;
+	/* Delete request? */
+	head->w.is_delete = !strncmp(line, "delete ", 7);
+	if (head->w.is_delete)
+		memmove(line, line + 7, strlen(line + 7) + 1);
+	/* Do the update. */
+	switch (head->type) {
+	case CS_PROCESS_STATUS:
+		return cs_write_pid(head);
+	case CS_QUERY:
+		return cs_write_answer(head);
+	case CS_POLICY:
+		return cs_write_policy(head);
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * cs_load_builtin_policy - Load built-in policy.
+ *
+ * Returns nothing.
+ */
+static void __init cs_load_builtin_policy(void)
+{
+	/*
+	 * This include file is manually created and contains built-in policy.
+	 *
+	 * static char [] __initdata cs_builtin_policy = { ... };
+	 */
+#include "builtin-policy.h"
+	const int idx = cs_read_lock();
+	struct cs_io_buffer head = { };
+	char *start = cs_builtin_policy;
+
+	head.type = CS_POLICY;
+	while (1) {
+		char *end = strchr(start, '\n');
+
+		if (!end)
+			break;
+		*end = '\0';
+		cs_normalize_line(start);
+		head.write_buf = start;
+		cs_parse_policy(&head, start);
+		start = end + 1;
+	}
+	cs_read_unlock(idx);
+#ifdef CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+	cs_check_profile();
+#endif
+}
+
+/**
+ * cs_read_self - read() for /sys/kernel/security/caitsith/self_domain interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Domainname which current thread belongs to.
+ * @count: Size of @buf.
+ * @ppos:  Bytes read by now.
+ *
+ * Returns read size on success, negative value otherwise.
+ */
+static ssize_t cs_read_self(struct file *file, char __user *buf, size_t count,
+			    loff_t *ppos)
+{
+	const char *domain = cs_current_domain()->domainname->name;
+	loff_t len = strlen(domain);
+	loff_t pos = *ppos;
+
+	if (pos >= len || !count)
+		return 0;
+	len -= pos;
+	if (count < len)
+		len = count;
+	if (copy_to_user(buf, domain + pos, len))
+		return -EFAULT;
+	*ppos += len;
+	return len;
+}
+
+/**
+ * cs_read_subacl - Read sub ACL in ACL entry.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @list: Pointer to "struct list_head".
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_read_subacl(struct cs_io_buffer *head,
+			   const struct list_head *list)
+{
+	list_for_each_cookie(head->r.subacl, list) {
+		struct cs_acl_info *acl =
+			list_entry(head->r.subacl, typeof(*acl), list);
+
+		switch (head->r.step) {
+		case 3:
+			if (acl->is_deleted)
+				continue;
+			if (!cs_flush(head))
+				return false;
+			cs_io_printf(head, "    %u ", acl->priority);
+			if (acl->is_deny)
+				cs_set_string(head, "deny");
+			else
+				cs_set_string(head, "allow");
+			head->r.cond_step = 0;
+			head->r.step++;
+			fallthrough;
+		case 4:
+			if (!cs_flush(head))
+				return false;
+			if (acl->cond &&
+			    !cs_print_condition(head, acl->cond))
+				return false;
+			cs_set_lf(head);
+			head->r.step--;
+		}
+	}
+	head->r.subacl = NULL;
+	return true;
+}
+
+/**
+ * cs_read_policy - Read policy.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Caller holds cs_read_lock().
+ */
+static void cs_read_policy(struct cs_io_buffer *head)
+{
+	if (head->r.eof)
+		return;
+	if (head->r.print_this_acl_only)
+		goto skip;
+	if (!head->r.version_done) {
+		cs_io_printf(head, "POLICY_VERSION=%u\n", cs_policy_version);
+		head->r.version_done = true;
+	}
+	if (!head->r.stat_done) {
+		cs_read_stat(head);
+		head->r.stat_done = true;
+	}
+	if (!head->r.quota_done) {
+		if (!cs_read_quota(head))
+			return;
+		head->r.quota_done = true;
+	}
+	if (!head->r.group_done) {
+		if (!cs_read_group(head))
+			return;
+		head->r.group_done = true;
+		cs_set_lf(head);
+	}
+	while (head->r.acl_index < CS_MAX_MAC_INDEX) {
+		list_for_each_cookie(head->r.acl,
+				     &cs_acl_list[head->r.acl_index]) {
+			struct cs_acl_info *ptr;
+
+skip:
+			ptr = list_entry(head->r.acl, typeof(*ptr), list);
+			switch (head->r.step) {
+			case 0:
+				if (ptr->is_deleted &&
+				    !head->r.print_this_acl_only)
+					continue;
+				head->r.step++;
+				fallthrough;
+			case 1:
+				if (!cs_read_acl(head, ptr))
+					return;
+				head->r.step++;
+				fallthrough;
+			case 2:
+				if (!cs_flush(head))
+					return;
+				cs_io_printf(head, "    audit %u\n",
+					     ptr->audit);
+				head->r.step++;
+				fallthrough;
+			case 3:
+			case 4:
+				if (!cs_read_subacl(head,
+						    &ptr->acl_info_list))
+					return;
+				head->r.step = 5;
+				fallthrough;
+			case 5:
+				if (!cs_flush(head))
+					return;
+				cs_set_lf(head);
+				head->r.step = 0;
+				if (head->r.print_this_acl_only)
+					goto done;
+			}
+		}
+		head->r.acl = NULL;
+		head->r.acl_index++;
+	}
+done:
+	head->r.eof = true;
+}
+
+/**
+ * cs_open - open() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file:  Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_open(struct inode *inode, struct file *file)
+{
+	const u8 type = (unsigned long) inode->i_private;
+	struct cs_io_buffer *head = kzalloc(sizeof(*head), GFP_NOFS);
+
+	if (!head)
+		return -ENOMEM;
+	mutex_init(&head->io_sem);
+	head->type = type;
+	if ((file->f_mode & FMODE_READ) && type != CS_AUDIT &&
+	    type != CS_QUERY) {
+		/* Don't allocate read_buf for poll() access. */
+		head->readbuf_size = 4096;
+		head->read_buf = kzalloc(head->readbuf_size, GFP_NOFS);
+		if (!head->read_buf) {
+			kfree(head);
+			return -ENOMEM;
+		}
+	}
+	if (file->f_mode & FMODE_WRITE) {
+		head->writebuf_size = 4096;
+		head->write_buf = kzalloc(head->writebuf_size, GFP_NOFS);
+		if (!head->write_buf) {
+			kfree(head->read_buf);
+			kfree(head);
+			return -ENOMEM;
+		}
+	}
+	/*
+	 * If the file is /sys/kernel/security/caitsith/query , increment the
+	 * observer counter. The obserber counter is used by cs_supervisor() to
+	 * see if there is some process monitoring
+	 * /sys/kernel/security/caitsith/query .
+	 */
+	if (type == CS_QUERY)
+		atomic_inc(&cs_query_observers);
+	file->private_data = head;
+	cs_notify_gc(head, true);
+	return 0;
+}
+
+/**
+ * cs_release - close() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file:  Pointer to "struct file".
+ *
+ * Returns 0.
+ */
+static int cs_release(struct inode *inode, struct file *file)
+{
+	struct cs_io_buffer *head = file->private_data;
+
+	/*
+	 * If the file is /sys/kernel/security/caitsith/query ,
+	 * decrement the observer counter.
+	 */
+	if (head->type == CS_QUERY &&
+	    atomic_dec_and_test(&cs_query_observers))
+		wake_up_all(&cs_answer_wait);
+	cs_notify_gc(head, false);
+	return 0;
+}
+
+/**
+ * cs_poll - poll() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @file: Pointer to "struct file".
+ * @wait: Pointer to "poll_table". Maybe NULL.
+ *
+ * Returns POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM if ready to read/write,
+ * POLLOUT | POLLWRNORM otherwise.
+ */
+static unsigned int cs_poll(struct file *file, poll_table *wait)
+{
+	struct cs_io_buffer *head = file->private_data;
+
+	if (head->type == CS_AUDIT) {
+		if (!cs_memory_used[CS_MEMORY_AUDIT]) {
+			poll_wait(file, &cs_log_wait, wait);
+			if (!cs_memory_used[CS_MEMORY_AUDIT])
+				return POLLOUT | POLLWRNORM;
+		}
+	} else if (head->type == CS_QUERY) {
+		if (list_empty(&cs_query_list)) {
+			poll_wait(file, &cs_query_wait, wait);
+			if (list_empty(&cs_query_list))
+				return POLLOUT | POLLWRNORM;
+		}
+	}
+	return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
+}
+
+/**
+ * cs_read - read() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+static ssize_t cs_read(struct file *file, char __user *buf, size_t count,
+		       loff_t *ppos)
+{
+	struct cs_io_buffer *head = file->private_data;
+	int len;
+	int idx;
+
+	if (mutex_lock_killable(&head->io_sem))
+		return -EINTR;
+	head->read_user_buf = buf;
+	head->read_user_buf_avail = count;
+	idx = cs_read_lock();
+	if (cs_flush(head)) {
+		/* Call the policy handler. */
+		switch (head->type) {
+		case CS_AUDIT:
+			cs_read_log(head);
+			break;
+		case CS_PROCESS_STATUS:
+			cs_read_pid(head);
+			break;
+		case CS_VERSION:
+			cs_read_version(head);
+			break;
+		case CS_QUERY:
+			cs_read_query(head);
+			break;
+		case CS_POLICY:
+			cs_read_policy(head);
+			break;
+		}
+		cs_flush(head);
+	}
+	cs_read_unlock(idx);
+	len = head->read_user_buf - buf;
+	mutex_unlock(&head->io_sem);
+	return len;
+}
+
+#ifdef CONFIG_SECURITY_CAITSITH_MANUAL_DOMAIN_TRANSITION
+
+/**
+ * cs_write_self - write() for /sys/kernel/security/caitsith/self_domain interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Domainname to transit to.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns @count on success, negative value otherwise.
+ *
+ * If domain transition was permitted but the domain transition failed, this
+ * function returns error rather than terminating current thread with SIGKILL.
+ */
+static ssize_t cs_write_self(struct file *file, const char __user *buf,
+			     size_t count, loff_t *ppos)
+{
+	char *data;
+	int error;
+
+	if (!count || count >= CS_EXEC_TMPSIZE - 10)
+		return -ENOMEM;
+	data = memdup_user_nul(buf, count);
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+	cs_normalize_line(data);
+	if (cs_correct_domain(data)) {
+		const int idx = cs_read_lock();
+		struct cs_path_info name;
+		struct cs_request_info r = { };
+
+		name.name = data;
+		cs_fill_path_info(&name);
+		/* Check "manual_domain_transition" permission. */
+		r.type = CS_MAC_MANUAL_DOMAIN_TRANSITION;
+		r.param.s[0] = &name;
+		cs_check_acl(&r, true);
+		if (r.result != CS_MATCHING_ALLOWED)
+			error = -EPERM;
+		else
+			error = cs_transit_domain(data) ? 0 : -ENOENT;
+		cs_read_unlock(idx);
+	} else
+		error = -EINVAL;
+	kfree(data);
+	return error ? error : count;
+}
+
+#endif
+
+/**
+ * cs_write - write() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns @count on success, negative value otherwise.
+ */
+static ssize_t cs_write(struct file *file, const char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	struct cs_io_buffer *head = file->private_data;
+	int error = count;
+	char *cp0 = head->write_buf;
+	int idx;
+
+	if (mutex_lock_killable(&head->io_sem))
+		return -EINTR;
+	head->read_user_buf_avail = 0;
+	idx = cs_read_lock();
+	/* Read a line and dispatch it to the policy handler. */
+	while (count) {
+		char c;
+
+		if (head->w.avail >= head->writebuf_size - 1) {
+			const int len = head->writebuf_size * 2;
+			char *cp = kzalloc(len, GFP_NOFS);
+
+			if (!cp) {
+				error = -ENOMEM;
+				break;
+			}
+			memmove(cp, cp0, head->w.avail);
+			kfree(cp0);
+			head->write_buf = cp;
+			cp0 = cp;
+			head->writebuf_size = len;
+		}
+		if (get_user(c, buf)) {
+			error = -EFAULT;
+			break;
+		}
+		buf++;
+		count--;
+		cp0[head->w.avail++] = c;
+		if (c != '\n')
+			continue;
+		cp0[head->w.avail - 1] = '\0';
+		head->w.avail = 0;
+		cs_normalize_line(cp0);
+		/* Don't allow updating policies by non manager programs. */
+		if (head->type != CS_PROCESS_STATUS && !cs_manager()) {
+			error = -EPERM;
+			goto out;
+		}
+		switch (cs_parse_policy(head, cp0)) {
+		case -EPERM:
+			error = -EPERM;
+			goto out;
+		case 0:
+			/* Update statistics. */
+			if (head->type == CS_POLICY)
+				cs_update_stat(CS_STAT_POLICY_UPDATES);
+			break;
+		}
+	}
+out:
+	cs_read_unlock(idx);
+	mutex_unlock(&head->io_sem);
+	return error;
+}
+
+/**
+ * cs_create_entry - Create interface files under /sys/kernel/security/caitsith/ directory.
+ *
+ * @name:   The name of the interface file.
+ * @mode:   The permission of the interface file.
+ * @parent: The parent directory.
+ * @key:    Type of interface.
+ *
+ * Returns nothing.
+ */
+static void __init cs_create_entry(const char *name, const umode_t mode,
+				   struct dentry *parent, const u8 key)
+{
+	securityfs_create_file(name, S_IFREG | mode, parent,
+			       (void *) (unsigned long) key, &cs_operations);
+}
+
+/**
+ * cs_securityfs_init - Initialize /sys/kernel/security/caitsith/ interface.
+ *
+ * Returns nothing.
+ */
+static void __init cs_securityfs_init(void)
+{
+	struct dentry *cs_dir = securityfs_create_dir("caitsith", NULL);
+
+	cs_create_entry("query",            0600, cs_dir, CS_QUERY);
+	cs_create_entry("audit",            0400, cs_dir, CS_AUDIT);
+	cs_create_entry(".process_status",  0600, cs_dir, CS_PROCESS_STATUS);
+	cs_create_entry("version",          0400, cs_dir, CS_VERSION);
+	cs_create_entry("policy",           0600, cs_dir, CS_POLICY);
+	securityfs_create_file("self_domain", S_IFREG | 0666, cs_dir, NULL,
+			       &cs_self_operations);
+}
+
+/**
+ * cs_init_module - Initialize this module.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int __init cs_init_module(void)
+{
+	u16 idx;
+
+#ifdef DEBUG_CONDITION
+	for (idx = 0; idx < CS_MAX_MAC_INDEX; idx++) {
+		if (cs_mac_keywords[idx])
+			continue;
+		pr_info("cs_mac_keywords[%u]==NULL\n", idx);
+		return -EINVAL;
+	}
+#endif
+	if (init_srcu_struct(&cs_ss))
+		panic("Out of memory.");
+	for (idx = 0; idx < CS_MAX_MAC_INDEX; idx++)
+		INIT_LIST_HEAD(&cs_acl_list[idx]);
+	for (idx = 0; idx < CS_MAX_GROUP; idx++)
+		INIT_LIST_HEAD(&cs_group_list[idx]);
+	for (idx = 0; idx < CS_MAX_HASH; idx++)
+		INIT_LIST_HEAD(&cs_name_list[idx]);
+	cs_null_name.name = "NULL";
+	cs_fill_path_info(&cs_null_name);
+	cs_kernel_domain.domainname = cs_get_name("<kernel>");
+	list_add_tail_rcu(&cs_kernel_domain.list, &cs_domain_list);
+	cs_securityfs_init();
+	cs_load_builtin_policy();
+	return 0;
+}
-- 
2.18.4


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-02 17:10 ` [PATCH 04/10] CaitSith: Add header file Tetsuo Handa
@ 2022-11-02 17:57   ` Casey Schaufler
  2022-11-05  2:43     ` Serge E. Hallyn
  0 siblings, 1 reply; 28+ messages in thread
From: Casey Schaufler @ 2022-11-02 17:57 UTC (permalink / raw)
  To: Tetsuo Handa, linux-security-module, Paul Moore, John Johansen,
	Kees Cook, casey

On 11/2/2022 10:10 AM, Tetsuo Handa wrote:
> The main point of this submission is to demonstrate how an LSM module
> which can be loaded using /sbin/insmod can work, and to provide
> consideration points for making changes for LSM stacking in a way that
> will not lock out LSM modules which can be loaded using /sbin/insmod .

CaitSith could readily be done as an in-tree LSM. The implementation
of loadable module infrastructure is unnecessary.


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 05/10] CaitSith: Add LSM interface management file.
  2022-11-02 17:10 ` [PATCH 05/10] CaitSith: Add LSM interface management file Tetsuo Handa
@ 2022-11-02 19:05   ` Kees Cook
  0 siblings, 0 replies; 28+ messages in thread
From: Kees Cook @ 2022-11-02 19:05 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook

On Thu, Nov 03, 2022 at 02:10:20AM +0900, Tetsuo Handa wrote:
> 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@I-love.SAKURA.ne.jp>
> [...]
> +#define cs_debug_trace(pos)						\
> +	do {								\
> +		static bool done;					\
> +		if (!done) {						\
> +			pr_info("CAITSITH: Debug trace: " pos " of 2\n"); \
> +			done = true;					\
> +		}							\
> +	} while (0)

This can be replaced by pr_info_once().

> [...]
> +#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;
> +}

I'm sorry, but absolutely not. One of the most basic elements of the
LSM infrastructure is that it is read-only. Even __lsm_ro_after_init is
a grand-fathered behavior that is supposed to be removed once all the
old SELinux disable-at-runtime users are gone.

I don't see any _justification_ for why any of this is needed. Yes,
it is technically possible to make an LSM loadable, but there needs to
be a convincing rationale for _why_.

-Kees

-- 
Kees Cook

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-02 17:57   ` Casey Schaufler
@ 2022-11-05  2:43     ` Serge E. Hallyn
  2022-11-05  4:05       ` Tetsuo Handa
  0 siblings, 1 reply; 28+ messages in thread
From: Serge E. Hallyn @ 2022-11-05  2:43 UTC (permalink / raw)
  To: Casey Schaufler
  Cc: Tetsuo Handa, linux-security-module, Paul Moore, John Johansen,
	Kees Cook

On Wed, Nov 02, 2022 at 10:57:48AM -0700, Casey Schaufler wrote:
> On 11/2/2022 10:10 AM, Tetsuo Handa wrote:
> > The main point of this submission is to demonstrate how an LSM module
> > which can be loaded using /sbin/insmod can work, and to provide
> > consideration points for making changes for LSM stacking in a way that
> > will not lock out LSM modules which can be loaded using /sbin/insmod .
> 
> CaitSith could readily be done as an in-tree LSM. The implementation
> of loadable module infrastructure is unnecessary.

Sorry, I'm getting confused.  But in-tree and loadable are not related,
right?

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-05  2:43     ` Serge E. Hallyn
@ 2022-11-05  4:05       ` Tetsuo Handa
  2022-11-05 23:46         ` Serge E. Hallyn
  2022-11-07 19:22         ` Paul Moore
  0 siblings, 2 replies; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-05  4:05 UTC (permalink / raw)
  To: Serge E. Hallyn, Casey Schaufler
  Cc: linux-security-module, Paul Moore, John Johansen, Kees Cook

On 2022/11/05 11:43, Serge E. Hallyn wrote:
> On Wed, Nov 02, 2022 at 10:57:48AM -0700, Casey Schaufler wrote:
>> On 11/2/2022 10:10 AM, Tetsuo Handa wrote:
>>> The main point of this submission is to demonstrate how an LSM module
>>> which can be loaded using /sbin/insmod can work, and to provide
>>> consideration points for making changes for LSM stacking in a way that
>>> will not lock out LSM modules which can be loaded using /sbin/insmod .
>>
>> CaitSith could readily be done as an in-tree LSM. The implementation
>> of loadable module infrastructure is unnecessary.
> 
> Sorry, I'm getting confused.  But in-tree and loadable are not related,
> right?

Very much related. My goal is to get CaitSith in-tree as a loadable LSM module
which can be loaded using /sbin/insmod .

Lessons I learned from TOMOYO are that:

  Most of Linux users, unlike Linux kernel developers, consider the Linux kernel
  as something black-box program. While the source code is open, they cannot
  afford understanding how updating the Linux kernel via "yum update" or
  "apt dist-upgrade" affects their systems.

  I can't force Linux users to replace their kernel packages in order to
  allow them to use TOMOYO, for replacing the kernel package makes their
  replaced kernel packages incompatible (kABI breakage) with other kernel
  modules (e.g. device drivers provided by hardware vendors) and/or makes
  their kernel completely unsupported. I can support TOMOYO part but I can't
  support the rest of the kernel, which makes it impossible for Linux users
  using supported kernels to try TOMOYO. Being in-tree helps catching up
  the upstream kernel changes, but being in-tree does not help allowing
  Linux distribution users to use TOMOYO.

  Linux distributors cannot afford enabling all LSM modules, due to the
  "enabled" == "supported" spell. There are out-of-tree kernel modules
  which are built as a loadable kernel module (i.e. not included into
  the main kernel package which is supported by Linux distributors), but
  the LSM framework does not legally allow use of LSM modules which are
  not built into the bzImage file.

If the LSM framework legally allows use of LSM modules which are not built
into the bzImage file, we can allow users to use whatever LSM modules they
want (and "support" is provided by the module authors rather than by Linux
distributors).

This submission is to ask LSM framework "please don't make it impossible to
allow users to use whatever LSM modules they want". That is more important
than "complete LSM stacking".

Casey's proposal towards "complete LSM stacking" is making it impossible to
allow users to use whatever LSM modules they want, due to the requirement of
having an LSM id integer value and using fixed sized array.


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-05  4:05       ` Tetsuo Handa
@ 2022-11-05 23:46         ` Serge E. Hallyn
  2022-11-06  0:56           ` Tetsuo Handa
  2022-11-07 19:22         ` Paul Moore
  1 sibling, 1 reply; 28+ messages in thread
From: Serge E. Hallyn @ 2022-11-05 23:46 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: Serge E. Hallyn, Casey Schaufler, linux-security-module,
	Paul Moore, John Johansen, Kees Cook

On Sat, Nov 05, 2022 at 01:05:44PM +0900, Tetsuo Handa wrote:
> On 2022/11/05 11:43, Serge E. Hallyn wrote:
> > On Wed, Nov 02, 2022 at 10:57:48AM -0700, Casey Schaufler wrote:
> >> On 11/2/2022 10:10 AM, Tetsuo Handa wrote:
> >>> The main point of this submission is to demonstrate how an LSM module
> >>> which can be loaded using /sbin/insmod can work, and to provide
> >>> consideration points for making changes for LSM stacking in a way that
> >>> will not lock out LSM modules which can be loaded using /sbin/insmod .
> >>
> >> CaitSith could readily be done as an in-tree LSM. The implementation
> >> of loadable module infrastructure is unnecessary.
> > 
> > Sorry, I'm getting confused.  But in-tree and loadable are not related,
> > right?
> 
> Very much related. My goal is to get CaitSith in-tree as a loadable LSM module
> which can be loaded using /sbin/insmod .

Great.  I support that.  But the sentence

> >> CaitSith could readily be done as an in-tree LSM. The implementation
> >> of loadable module infrastructure is unnecessary.

suggests that because CaitSith could be done in-tree, it doesn't need
to be loadable.  I'm saying that is a non sequitur.  It sounded like
that setence was meant to say "Because CaitSith could be in-tree, it
doesn't need to be =m.  Only out of tree modules need to be loadable."

-serge

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 03/10] fs,kernel: Export d_absolute_path()/find_task_by_pid_ns()/find_task_by_vpid()
  2022-11-02 17:10 ` [PATCH 03/10] fs,kernel: Export d_absolute_path()/find_task_by_pid_ns()/find_task_by_vpid() Tetsuo Handa
@ 2022-11-05 23:51   ` Serge E. Hallyn
  0 siblings, 0 replies; 28+ messages in thread
From: Serge E. Hallyn @ 2022-11-05 23:51 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: linux-security-module, Casey Schaufler, Paul Moore,
	John Johansen, Kees Cook

On Thu, Nov 03, 2022 at 02:10:18AM +0900, Tetsuo Handa wrote:
> CaitSith module which can be loaded using /sbin/insmod needs to be able to
> access these functions. TOMOYO module will also access these functions when
> CONFIG_SECURITY_TOMOYO=m becomes possible.
> 
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> ---
>  fs/d_path.c  | 1 +
>  kernel/pid.c | 2 ++
>  2 files changed, 3 insertions(+)
> 
> diff --git a/fs/d_path.c b/fs/d_path.c
> index 56a6ee4c6331..417e74414f19 100644
> --- a/fs/d_path.c
> +++ b/fs/d_path.c
> @@ -234,6 +234,7 @@ char *d_absolute_path(const struct path *path,
>  		return ERR_PTR(-EINVAL);
>  	return extract_string(&b);
>  }
> +EXPORT_SYMBOL_GPL(d_absolute_path);
>  
>  static void get_fs_root_rcu(struct fs_struct *fs, struct path *root)
>  {
> diff --git a/kernel/pid.c b/kernel/pid.c
> index 3fbc5e46b721..9e5224d8769c 100644
> --- a/kernel/pid.c
> +++ b/kernel/pid.c
> @@ -416,11 +416,13 @@ struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
>  			 "find_task_by_pid_ns() needs rcu_read_lock() protection");
>  	return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
>  }
> +EXPORT_SYMBOL_GPL(find_task_by_pid_ns);
>  
>  struct task_struct *find_task_by_vpid(pid_t vnr)
>  {
>  	return find_task_by_pid_ns(vnr, task_active_pid_ns(current));
>  }
> +EXPORT_SYMBOL_GPL(find_task_by_vpid);

It would be neat if you could avoid the need for these by instead using
pidfds.

>  struct task_struct *find_get_task_by_vpid(pid_t nr)
>  {
> -- 
> 2.18.4

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-05 23:46         ` Serge E. Hallyn
@ 2022-11-06  0:56           ` Tetsuo Handa
  2022-11-07 18:59             ` Casey Schaufler
  0 siblings, 1 reply; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-06  0:56 UTC (permalink / raw)
  To: Serge E. Hallyn
  Cc: Casey Schaufler, linux-security-module, Paul Moore,
	John Johansen, Kees Cook

On 2022/11/06 8:46, Serge E. Hallyn wrote:
> On Sat, Nov 05, 2022 at 01:05:44PM +0900, Tetsuo Handa wrote:
>> On 2022/11/05 11:43, Serge E. Hallyn wrote:
>>> On Wed, Nov 02, 2022 at 10:57:48AM -0700, Casey Schaufler wrote:
>>>> On 11/2/2022 10:10 AM, Tetsuo Handa wrote:
>>>>> The main point of this submission is to demonstrate how an LSM module
>>>>> which can be loaded using /sbin/insmod can work, and to provide
>>>>> consideration points for making changes for LSM stacking in a way that
>>>>> will not lock out LSM modules which can be loaded using /sbin/insmod .
>>>>
>>>> CaitSith could readily be done as an in-tree LSM. The implementation
>>>> of loadable module infrastructure is unnecessary.
>>>
>>> Sorry, I'm getting confused.  But in-tree and loadable are not related,
>>> right?
>>
>> Very much related. My goal is to get CaitSith in-tree as a loadable LSM module
>> which can be loaded using /sbin/insmod .
> 
> Great.  I support that.  But the sentence

Thank you.

> 
>>>> CaitSith could readily be done as an in-tree LSM. The implementation
>>>> of loadable module infrastructure is unnecessary.
> 
> suggests that because CaitSith could be done in-tree, it doesn't need
> to be loadable.  I'm saying that is a non sequitur.  It sounded like
> that setence was meant to say "Because CaitSith could be in-tree, it
> doesn't need to be =m.  Only out of tree modules need to be loadable."

Unfortunately, I don't think that my intended Linux distributor (namely, Red Hat)
will support LSMs other than SELinux.

  https://bugzilla.redhat.com/show_bug.cgi?id=542986

Therefore, not only out of tree modules but also in-tree modules which cannot be
enabled by Linux distributors need to be implemented as loadable kernel modules.


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-06  0:56           ` Tetsuo Handa
@ 2022-11-07 18:59             ` Casey Schaufler
  2022-11-08 10:18               ` Tetsuo Handa
  0 siblings, 1 reply; 28+ messages in thread
From: Casey Schaufler @ 2022-11-07 18:59 UTC (permalink / raw)
  To: Tetsuo Handa, Serge E. Hallyn
  Cc: linux-security-module, Paul Moore, John Johansen, Kees Cook, casey

On 11/5/2022 5:56 PM, Tetsuo Handa wrote:
> On 2022/11/06 8:46, Serge E. Hallyn wrote:
>> On Sat, Nov 05, 2022 at 01:05:44PM +0900, Tetsuo Handa wrote:
>>> On 2022/11/05 11:43, Serge E. Hallyn wrote:
>>>> On Wed, Nov 02, 2022 at 10:57:48AM -0700, Casey Schaufler wrote:
>>>>> On 11/2/2022 10:10 AM, Tetsuo Handa wrote:
>>>>>> The main point of this submission is to demonstrate how an LSM module
>>>>>> which can be loaded using /sbin/insmod can work, and to provide
>>>>>> consideration points for making changes for LSM stacking in a way that
>>>>>> will not lock out LSM modules which can be loaded using /sbin/insmod .
>>>>> CaitSith could readily be done as an in-tree LSM. The implementation
>>>>> of loadable module infrastructure is unnecessary.
>>>> Sorry, I'm getting confused.  But in-tree and loadable are not related,
>>>> right?
>>> Very much related. My goal is to get CaitSith in-tree as a loadable LSM module
>>> which can be loaded using /sbin/insmod .
>> Great.  I support that.  But the sentence
> Thank you.
>
>>>>> CaitSith could readily be done as an in-tree LSM. The implementation
>>>>> of loadable module infrastructure is unnecessary.
>> suggests that because CaitSith could be done in-tree, it doesn't need
>> to be loadable.  I'm saying that is a non sequitur.  It sounded like
>> that setence was meant to say "Because CaitSith could be in-tree, it
>> doesn't need to be =m.  Only out of tree modules need to be loadable."
> Unfortunately, I don't think that my intended Linux distributor (namely, Red Hat)
> will support LSMs other than SELinux.

I also doubt that even if you came up with a 100% perfect implementation
of loadable module support it would be accepted upstream. If you somehow
got it upstream I really, really think it would be required to be optional.
There's no way Redhat would enable loadable module support if were available.
I am perfectly willing to be corrected if I've made a statement here that
isn't true, but I'll bet a refreshing beverage on it.

>
>   https://bugzilla.redhat.com/show_bug.cgi?id=542986
>
> Therefore, not only out of tree modules but also in-tree modules which cannot be
> enabled by Linux distributors need to be implemented as loadable kernel modules.

Today you cannot use SELinux and AppArmor together on Redhat. Someday
"soon ;)" you won't be allowed to run them together on Redhat. If there's
market demand (I'm not holding my breath) it could happen in the future.
But that's up to Redhat to decide. I don't see Redhat as the customer for
LSM improvements. They are happy with what they have.

You have to take a different approach. Find a distribution that does want
loadable modules. You'll need a viable implementation to convince them to
help with the effort. Even then, you'll have a tough row to hoe.



^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-05  4:05       ` Tetsuo Handa
  2022-11-05 23:46         ` Serge E. Hallyn
@ 2022-11-07 19:22         ` Paul Moore
  1 sibling, 0 replies; 28+ messages in thread
From: Paul Moore @ 2022-11-07 19:22 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: Serge E. Hallyn, Casey Schaufler, linux-security-module,
	John Johansen, Kees Cook

On Sat, Nov 5, 2022 at 12:06 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
> On 2022/11/05 11:43, Serge E. Hallyn wrote:
> > On Wed, Nov 02, 2022 at 10:57:48AM -0700, Casey Schaufler wrote:
> >> On 11/2/2022 10:10 AM, Tetsuo Handa wrote:
> >>> The main point of this submission is to demonstrate how an LSM module
> >>> which can be loaded using /sbin/insmod can work, and to provide
> >>> consideration points for making changes for LSM stacking in a way that
> >>> will not lock out LSM modules which can be loaded using /sbin/insmod .
> >>
> >> CaitSith could readily be done as an in-tree LSM. The implementation
> >> of loadable module infrastructure is unnecessary.
> >
> > Sorry, I'm getting confused.  But in-tree and loadable are not related,
> > right?
>
> Very much related. My goal is to get CaitSith in-tree as a loadable LSM module
> which can be loaded using /sbin/insmod .

At this point in time I don't want to support dynamically loadable LSM modules.

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-07 18:59             ` Casey Schaufler
@ 2022-11-08 10:18               ` Tetsuo Handa
  2022-11-09  2:20                 ` Paul Moore
  0 siblings, 1 reply; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-08 10:18 UTC (permalink / raw)
  To: Casey Schaufler, Serge E. Hallyn
  Cc: linux-security-module, Paul Moore, John Johansen, Kees Cook,
	Linus Torvalds

On 2022/11/08 3:59, Casey Schaufler wrote:
> On 11/5/2022 5:56 PM, Tetsuo Handa wrote:
>> On 2022/11/06 8:46, Serge E. Hallyn wrote:
>>> On Sat, Nov 05, 2022 at 01:05:44PM +0900, Tetsuo Handa wrote:
>>>> On 2022/11/05 11:43, Serge E. Hallyn wrote:
>>>>> On Wed, Nov 02, 2022 at 10:57:48AM -0700, Casey Schaufler wrote:
>>>>>> On 11/2/2022 10:10 AM, Tetsuo Handa wrote:
>>>>>>> The main point of this submission is to demonstrate how an LSM module
>>>>>>> which can be loaded using /sbin/insmod can work, and to provide
>>>>>>> consideration points for making changes for LSM stacking in a way that
>>>>>>> will not lock out LSM modules which can be loaded using /sbin/insmod .
>>>>>> CaitSith could readily be done as an in-tree LSM. The implementation
>>>>>> of loadable module infrastructure is unnecessary.
>>>>> Sorry, I'm getting confused.  But in-tree and loadable are not related,
>>>>> right?
>>>> Very much related. My goal is to get CaitSith in-tree as a loadable LSM module
>>>> which can be loaded using /sbin/insmod .
>>> Great.  I support that.  But the sentence
>> Thank you.
>>
>>>>>> CaitSith could readily be done as an in-tree LSM. The implementation
>>>>>> of loadable module infrastructure is unnecessary.
>>> suggests that because CaitSith could be done in-tree, it doesn't need
>>> to be loadable.  I'm saying that is a non sequitur.  It sounded like
>>> that setence was meant to say "Because CaitSith could be in-tree, it
>>> doesn't need to be =m.  Only out of tree modules need to be loadable."
>> Unfortunately, I don't think that my intended Linux distributor (namely, Red Hat)
>> will support LSMs other than SELinux.
> 
> I also doubt that even if you came up with a 100% perfect implementation
> of loadable module support it would be accepted upstream. If you somehow
> got it upstream I really, really think it would be required to be optional.
> There's no way Redhat would enable loadable module support if were available.
> I am perfectly willing to be corrected if I've made a statement here that
> isn't true, but I'll bet a refreshing beverage on it.
> 
>>
>>   https://bugzilla.redhat.com/show_bug.cgi?id=542986
>>
>> Therefore, not only out of tree modules but also in-tree modules which cannot be
>> enabled by Linux distributors need to be implemented as loadable kernel modules.
> 
> Today you cannot use SELinux and AppArmor together on Redhat. Someday
> "soon ;)" you won't be allowed to run them together on Redhat. If there's
> market demand (I'm not holding my breath) it could happen in the future.
> But that's up to Redhat to decide. I don't see Redhat as the customer for
> LSM improvements. They are happy with what they have.
> 
> You have to take a different approach. Find a distribution that does want
> loadable modules. You'll need a viable implementation to convince them to
> help with the effort. Even then, you'll have a tough row to hoe.
> 

You are proving yourself that you are completely trapped into
"only in-tree and supported by distributors is correct" crap.

I am an individual who don't belong to specific distribution. Most of Linux users
in companies I relate with are using RHEL. Company's financial support for TOMOYO
terminated in March 2012 because I was not able to establish support/consult business
because I was not able to get TOMOYO enabled in RHEL (even in Fedora).

My support for TOMOYO is still continued, but unless loadable LSMs becomes legally
possible, I can't introduce TOMOYO/CaitSith to Linux users in companies I relate with.

Even if your LSM stacking work completes some day, I can imagine that there will be
no LSMs I can stack (unless loadable LSMs becomes legally possible).
Finding a distribution who want loadable LSMs does not help.
Only that loadable LSMs becomes legally possible without kernel config option helps.

What I'm asking you are that:

  Please don't lock out out-of-tree LSM modules (by requiring an LSM id integer value
  which are assigned to only in-tree LSM modules) because we can't accept whatever LSM
  modules as in-tree.

  Please don't lock out loadable LSM modules (by using fixed sized array) because
  locking out loadable LSM modules reduces the value of your LSM stacking work.

Quite simple.


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-08 10:18               ` Tetsuo Handa
@ 2022-11-09  2:20                 ` Paul Moore
  2022-11-09 10:13                   ` Tetsuo Handa
  0 siblings, 1 reply; 28+ messages in thread
From: Paul Moore @ 2022-11-09  2:20 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: Casey Schaufler, Serge E. Hallyn, linux-security-module,
	John Johansen, Kees Cook, Linus Torvalds

On Tue, Nov 8, 2022 at 5:20 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
> What I'm asking you are that:
>
>   Please don't lock out out-of-tree LSM modules (by requiring an LSM id integer value
>   which are assigned to only in-tree LSM modules) because we can't accept whatever LSM
>   modules as in-tree.
>
>   Please don't lock out loadable LSM modules (by using fixed sized array) because
>   locking out loadable LSM modules reduces the value of your LSM stacking work.
>
> Quite simple.

Tetsuo, at this point I think we all understand your concern and I
appreciate and respect the passion you have for your argument.
However, I think the rest of the developers, including myself, have
also made our points very clear.  While there may still be revisions
to the syscall patches, I believe identifying LSMs via a token value
as opposed to a string value is the better option for the upstream
Linux Kernel.  This alone should not prevent dynamically loadable LSMs
in the future, if we decide to pursue that, but I do recognize that it
will present more of a challenge for the long term maintenance of
out-of-tree LSMs.  I see no reason to trade off what I believe as a
better API choice (LSM ID tokens) for something that is explicitly not
supported by the Linux Kernel as a whole (out-of-tree kernel code).

Thank you for your comments, but I'm considering this settled.

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-09  2:20                 ` Paul Moore
@ 2022-11-09 10:13                   ` Tetsuo Handa
  2022-11-09 14:48                     ` Paul Moore
  0 siblings, 1 reply; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-09 10:13 UTC (permalink / raw)
  To: Paul Moore
  Cc: Casey Schaufler, Serge E. Hallyn, linux-security-module,
	John Johansen, Kees Cook, Linus Torvalds

On 2022/11/09 11:20, Paul Moore wrote:
> On Tue, Nov 8, 2022 at 5:20 AM Tetsuo Handa
> <penguin-kernel@i-love.sakura.ne.jp> wrote:
>> What I'm asking you are that:
>>
>>   Please don't lock out out-of-tree LSM modules (by requiring an LSM id integer value
>>   which are assigned to only in-tree LSM modules) because we can't accept whatever LSM
>>   modules as in-tree.
>>
>>   Please don't lock out loadable LSM modules (by using fixed sized array) because
>>   locking out loadable LSM modules reduces the value of your LSM stacking work.
>>
>> Quite simple.
> 
> Tetsuo, at this point I think we all understand your concern and I
> appreciate and respect the passion you have for your argument.
> However, I think the rest of the developers, including myself, have
> also made our points very clear.  While there may still be revisions
> to the syscall patches, I believe identifying LSMs via a token value
> as opposed to a string value is the better option for the upstream
> Linux Kernel.

I'm not opposing to identifying LSMs via a token value. I'm opposing to who can
obtain a token value, for I haven't heard a clear promise that we shall allow
whatever LSM modules to obtain a token value.

>                This alone should not prevent dynamically loadable LSMs
> in the future, if we decide to pursue that, but I do recognize that it
> will present more of a challenge for the long term maintenance of
> out-of-tree LSMs.

Who can obtain a token value affects both built-in LSM modules and dynamically
loadable LSM modules. A "code that is being developed in the open with the
*intention* to be submitted to be in-tree" has to be able to obtain a token
value as soon as starting development (which is much earlier stage than getting
that code in-tree).

Do you remember that you said

  >> Currently anyone can start writing new LSM modules using name as identifier. But
  >> you are trying to forbid using name as identifier, and trying to force using integer
  >> as identifier, but that integer will not be provided unless new LSM modules get
  >> upstream.
  > 
  > That is correct.  In order to have a LSM identifier token the LSM must
  > be upstream.

at https://lkml.kernel.org/r/CAHC9VhT2Azg1F-G3RQ4xL7JgA3OAtHafzS1_nvUyEUFsCJ9+SA@mail.gmail.com ?

If in-tree kernel refuses to assign a token value, no new LSM module would be developed
in the open, for not-yet-in-tree code (in whatever stage) cannot use a token value for
registering into the LSM framework.

>                    I see no reason to trade off what I believe as a
> better API choice (LSM ID tokens) for something that is explicitly not
> supported by the Linux Kernel as a whole (out-of-tree kernel code).

And even if a "code is being developed in the open with the intention to be
submitted to be in-tree, and actually submitted for in-tree", we know that we
cannot accept all submitted LSM modules. LSM modules which were not accepted for
in-tree have to remain out-of-tree.

We don't make patches for out-of-tree code in order to catch up to upstream kernel
changes. But we didn't and must not try to forbid out-of-tree code to exist.

Although I'm not a fan of proprietary / closed source code, I have to resist
Casey's attempt to lock out out-of-tree code and new code.

> 
> Thank you for your comments, but I'm considering this settled.
> 

Never settled, unless you all promise that we shall assign a token value to whatever
LSM modules (regardless of whether that LSM module is already in-tree or not).


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-09 10:13                   ` Tetsuo Handa
@ 2022-11-09 14:48                     ` Paul Moore
  2022-11-09 23:57                       ` Tetsuo Handa
  0 siblings, 1 reply; 28+ messages in thread
From: Paul Moore @ 2022-11-09 14:48 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: Casey Schaufler, Serge E. Hallyn, linux-security-module,
	John Johansen, Kees Cook, Linus Torvalds

On Wed, Nov 9, 2022 at 5:14 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
> On 2022/11/09 11:20, Paul Moore wrote:
> > On Tue, Nov 8, 2022 at 5:20 AM Tetsuo Handa
> > <penguin-kernel@i-love.sakura.ne.jp> wrote:
> >> What I'm asking you are that:
> >>
> >>   Please don't lock out out-of-tree LSM modules (by requiring an LSM id integer value
> >>   which are assigned to only in-tree LSM modules) because we can't accept whatever LSM
> >>   modules as in-tree.
> >>
> >>   Please don't lock out loadable LSM modules (by using fixed sized array) because
> >>   locking out loadable LSM modules reduces the value of your LSM stacking work.
> >>
> >> Quite simple.
> >
> > Tetsuo, at this point I think we all understand your concern and I
> > appreciate and respect the passion you have for your argument.
> > However, I think the rest of the developers, including myself, have
> > also made our points very clear.  While there may still be revisions
> > to the syscall patches, I believe identifying LSMs via a token value
> > as opposed to a string value is the better option for the upstream
> > Linux Kernel.
>
> I'm not opposing to identifying LSMs via a token value. I'm opposing to who can
> obtain a token value, for I haven't heard a clear promise that we shall allow
> whatever LSM modules to obtain a token value.

In that case, let me be very clear on this issue: at this point in
time I will not merge patches which assign LSM ID tokens in the
upstream Linux Kernel to out-of-tree LSMs.  If there is a significant
change, e.g. the overall kernel policy towards out-of-tree code, we
can reconsider this policy but as of right now only upstream LSMs will
have LSM ID tokens assigned to them; in-development LSMs are free to
temporarily assign themselves an ID token (which may change when the
LSM is merged upstream), and out-of-tree LSMs are free to do whatever
they like with respect to their code, just as they do now.

> > Thank you for your comments, but I'm considering this settled.
> >
>
> Never settled, unless you all promise that we shall assign a token value to whatever
> LSM modules (regardless of whether that LSM module is already in-tree or not).

As the person who ultimately is responsible for supporting,
maintaining, and merging LSM related patches into the upstream Linux
Kernel I am considering this issue settled until there is some other
significant change that warrants reconsideration of the policy
mentioned above.  Of course you are welcome to disagree, and you can
continue to voice your objections if you like, but the issue is
settled in my mind.

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-09 14:48                     ` Paul Moore
@ 2022-11-09 23:57                       ` Tetsuo Handa
  2022-11-10  2:22                         ` Kees Cook
  0 siblings, 1 reply; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-09 23:57 UTC (permalink / raw)
  To: Paul Moore
  Cc: Casey Schaufler, Serge E. Hallyn, linux-security-module,
	John Johansen, Kees Cook, Linus Torvalds

On 2022/11/09 23:48, Paul Moore wrote:
>                                             If there is a significant
> change, e.g. the overall kernel policy towards out-of-tree code, we
> can reconsider this policy but as of right now only upstream LSMs will
> have LSM ID tokens assigned to them; in-development LSMs are free to
> temporarily assign themselves an ID token (which may change when the
> LSM is merged upstream), and out-of-tree LSMs are free to do whatever
> they like with respect to their code, just as they do now.

If in-development LSMs and out-of-tree LSMs cannot get a stable ID token,
developers cannot write and publish userspace tools which make use of ID
token. If ID collision happens by use of temporarily ID token, this token
is no longer an identifier. That is a pointless and needless constraint
for getting LSM modules created / tested / used.


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-09 23:57                       ` Tetsuo Handa
@ 2022-11-10  2:22                         ` Kees Cook
  2022-11-10  4:10                           ` Tetsuo Handa
  0 siblings, 1 reply; 28+ messages in thread
From: Kees Cook @ 2022-11-10  2:22 UTC (permalink / raw)
  To: Tetsuo Handa, Paul Moore
  Cc: Casey Schaufler, Serge E. Hallyn, linux-security-module,
	John Johansen, Linus Torvalds

On November 9, 2022 3:57:06 PM PST, Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> wrote:
>On 2022/11/09 23:48, Paul Moore wrote:
>>                                             If there is a significant
>> change, e.g. the overall kernel policy towards out-of-tree code, we
>> can reconsider this policy but as of right now only upstream LSMs will
>> have LSM ID tokens assigned to them; in-development LSMs are free to
>> temporarily assign themselves an ID token (which may change when the
>> LSM is merged upstream), and out-of-tree LSMs are free to do whatever
>> they like with respect to their code, just as they do now.
>
>If in-development LSMs and out-of-tree LSMs cannot get a stable ID token,
>developers cannot write and publish userspace tools which make use of ID
>token. If ID collision happens by use of temporarily ID token, this token
>is no longer an identifier. That is a pointless and needless constraint
>for getting LSM modules created / tested / used.

You have to let this go. You aren't hearing us: this ID reservation process is not a problem for anyone but you. It is the same for all the syscalls that get added, and all the prctls, etc etc. This isn't a problem for userspace tools using those, and there won't be a problem here either.

We will not support out of tree code, so needing ID stability for out-of-tree LSMs isn't a valid argument.

Additionally, not having an LSM built into a distro kernel is a distro kernel problem. Open a bug with them and get it fixed. E.g. Ubuntu has no problem with multiple LSMs:

$ grep 'CONFIG_SECURITY_[^_]*[ =]' /boot/config-$(
uname -r)
...
CONFIG_SECURITY_SELINUX=y
CONFIG_SECURITY_SMACK=y
CONFIG_SECURITY_TOMOYO=y
CONFIG_SECURITY_APPARMOR=y
# CONFIG_SECURITY_LOADPIN is not set
CONFIG_SECURITY_YAMA=y
CONFIG_SECURITY_SAFESETID=y
CONFIG_SECURITY_LANDLOCK=y

And they just leave the "exclusive"s disabled at runtime:
CONFIG_LSM="landlock,lockdown,yama,integrity,apparmor"

-Kees


-- 
Kees Cook

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-10  2:22                         ` Kees Cook
@ 2022-11-10  4:10                           ` Tetsuo Handa
  2022-11-10  4:45                             ` Paul Moore
  0 siblings, 1 reply; 28+ messages in thread
From: Tetsuo Handa @ 2022-11-10  4:10 UTC (permalink / raw)
  To: Kees Cook, Paul Moore
  Cc: Casey Schaufler, Serge E. Hallyn, linux-security-module,
	John Johansen, Linus Torvalds

On 2022/11/10 11:22, Kees Cook wrote:
> On November 9, 2022 3:57:06 PM PST, Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> wrote:
>> On 2022/11/09 23:48, Paul Moore wrote:
>>>                                             If there is a significant
>>> change, e.g. the overall kernel policy towards out-of-tree code, we
>>> can reconsider this policy but as of right now only upstream LSMs will
>>> have LSM ID tokens assigned to them; in-development LSMs are free to
>>> temporarily assign themselves an ID token (which may change when the
>>> LSM is merged upstream), and out-of-tree LSMs are free to do whatever
>>> they like with respect to their code, just as they do now.
>>
>> If in-development LSMs and out-of-tree LSMs cannot get a stable ID token,
>> developers cannot write and publish userspace tools which make use of ID
>> token. If ID collision happens by use of temporarily ID token, this token
>> is no longer an identifier. That is a pointless and needless constraint
>> for getting LSM modules created / tested / used.
> 
> You have to let this go. You aren't hearing us: this ID reservation process
> is not a problem for anyone but you. It is the same for all the syscalls
> that get added, and all the prctls, etc etc. This isn't a problem for userspace
> tools using those, and there won't be a problem here either.
> 
> We will not support out of tree code, so needing ID stability for out-of-tree
> LSMs isn't a valid argument.
> 

This ID reservation process is very much a problem for you all.

In https://lkml.kernel.org/r/CAHC9VhQGnEcoYeGpwbbXbMrG1dOvJ=2ohd4zPYoqBJK9p1mSjQ@mail.gmail.com ,
Paul said that it is becoming increasingly difficult for people to find time to
review potential new LSMs.

  >> Many modules
  >>
  >>     SimpleFlow ( 2016/04/21 https://lwn.net/Articles/684825/ )
  >>     HardChroot ( 2016/07/29 https://lwn.net/Articles/695984/ )
  >>     Checmate ( 2016/08/04 https://lwn.net/Articles/696344/ )
  >>     LandLock ( 2016/08/25 https://lwn.net/Articles/698226/ )
  >>     PTAGS ( 2016/09/29 https://lwn.net/Articles/702639/ )
  >>     CaitSith ( 2016/10/21 https://lwn.net/Articles/704262/ )
  >>     SafeName ( 2016/05/03 https://lwn.net/Articles/686021/ )
  >>     WhiteEgret ( 2017/05/30 https://lwn.net/Articles/724192/ )
  >>     shebang ( 2017/06/09 https://lwn.net/Articles/725285/ )
  >>     S.A.R.A. ( 2017/06/13 https://lwn.net/Articles/725230/ )
  >>
  >> are proposed 5 or 6 years ago, but mostly became silent...
  > 
  > At least one of those, Landlock, has been merged upstream and is now
  > available in modern released Linux Kernels.  As far as the other LSMs
  > are concerned, I don't recall there ever being significant interest
  > among other developers or users to warrant their inclusion upstream.
  > If the authors believe that has changed, or is simply not true, they
  > are always welcome to post their patches again for discussion, review,
  > and potential upstreaming.  However, I will caution that it is
  > becoming increasingly difficult for people to find time to review
  > potential new LSMs so it may a while to attract sufficient comments
  > and feedback.

Actually, previous CaitSith proposal in 2016 at
https://lkml.kernel.org/r/1477054150-4772-1-git-send-email-penguin-kernel@I-love.SAKURA.ne.jp
died because nobody except John Johansen showed interest, and John could not
find time to review.

We are getting more and more difficult to get new LSM module in-tree.
An in-development code being remaining out-of-tree shall happen. And you are
declaring that we ignore LSM modules which cannot get enough interests.
That's horrible and unacceptable.

What I'm repeating since
https://lkml.kernel.org/r/2225aec6-f0f3-d38e-ee3c-6139a7c25a37@I-love.SAKURA.ne.jp is that

  You are making LSM interface more and more unattractive. The consequence would be
  "The LSM interface is dead. We will give up implementing as LSMs."

Quite simple.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 04/10] CaitSith: Add header file.
  2022-11-10  4:10                           ` Tetsuo Handa
@ 2022-11-10  4:45                             ` Paul Moore
  0 siblings, 0 replies; 28+ messages in thread
From: Paul Moore @ 2022-11-10  4:45 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: Kees Cook, Casey Schaufler, Serge E. Hallyn,
	linux-security-module, John Johansen, Linus Torvalds

On Wed, Nov 9, 2022 at 11:12 PM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
> On 2022/11/10 11:22, Kees Cook wrote:
> > On November 9, 2022 3:57:06 PM PST, Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> wrote:
> >> On 2022/11/09 23:48, Paul Moore wrote:
> >>>                                             If there is a significant
> >>> change, e.g. the overall kernel policy towards out-of-tree code, we
> >>> can reconsider this policy but as of right now only upstream LSMs will
> >>> have LSM ID tokens assigned to them; in-development LSMs are free to
> >>> temporarily assign themselves an ID token (which may change when the
> >>> LSM is merged upstream), and out-of-tree LSMs are free to do whatever
> >>> they like with respect to their code, just as they do now.
> >>
> >> If in-development LSMs and out-of-tree LSMs cannot get a stable ID token,
> >> developers cannot write and publish userspace tools which make use of ID
> >> token. If ID collision happens by use of temporarily ID token, this token
> >> is no longer an identifier. That is a pointless and needless constraint
> >> for getting LSM modules created / tested / used.
> >
> > You have to let this go. You aren't hearing us: this ID reservation process
> > is not a problem for anyone but you. It is the same for all the syscalls
> > that get added, and all the prctls, etc etc. This isn't a problem for userspace
> > tools using those, and there won't be a problem here either.
> >
> > We will not support out of tree code, so needing ID stability for out-of-tree
> > LSMs isn't a valid argument.
> >
>
> This ID reservation process is very much a problem for you all.
>
> In https://lkml.kernel.org/r/CAHC9VhQGnEcoYeGpwbbXbMrG1dOvJ=2ohd4zPYoqBJK9p1mSjQ@mail.gmail.com ,
> Paul said that it is becoming increasingly difficult for people to find time to
> review potential new LSMs.

It is important that you understand that difficult does not mean
impossible.  As Kees pointed out the rate of LSM acceptance is roughly
at the same level as the rest of the kernel.  The rate of upstream LSM
review also has very little, if anything, to do with the LSM ID
token/string argument.

Beyond that, I'm trying to be polite, but I completely agree with
Kees' statement that you need to let this go.  We are going to be
moving forward with the LSM ID token over the string values.  I'm
sorry we couldn't come to an agreement that satisfies you, but I'm
done discussing this at this point in time.  We can revisit this
decision if something changes, but in the meantime please let this go;
a decision has been made and I'm no longer considering arguments on
this topic.

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2022-11-10  4:45 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-11-02 17:10 [PATCH 01/10] security: Export security_hook_heads Tetsuo Handa
2022-11-02 17:10 ` [PATCH 02/10] mm: Export copy_to_kernel_nofault() Tetsuo Handa
2022-11-02 17:10 ` [PATCH 03/10] fs,kernel: Export d_absolute_path()/find_task_by_pid_ns()/find_task_by_vpid() Tetsuo Handa
2022-11-05 23:51   ` Serge E. Hallyn
2022-11-02 17:10 ` [PATCH 04/10] CaitSith: Add header file Tetsuo Handa
2022-11-02 17:57   ` Casey Schaufler
2022-11-05  2:43     ` Serge E. Hallyn
2022-11-05  4:05       ` Tetsuo Handa
2022-11-05 23:46         ` Serge E. Hallyn
2022-11-06  0:56           ` Tetsuo Handa
2022-11-07 18:59             ` Casey Schaufler
2022-11-08 10:18               ` Tetsuo Handa
2022-11-09  2:20                 ` Paul Moore
2022-11-09 10:13                   ` Tetsuo Handa
2022-11-09 14:48                     ` Paul Moore
2022-11-09 23:57                       ` Tetsuo Handa
2022-11-10  2:22                         ` Kees Cook
2022-11-10  4:10                           ` Tetsuo Handa
2022-11-10  4:45                             ` Paul Moore
2022-11-07 19:22         ` Paul Moore
2022-11-02 17:10 ` [PATCH 05/10] CaitSith: Add LSM interface management file Tetsuo Handa
2022-11-02 19:05   ` Kees Cook
2022-11-02 17:10 ` [PATCH 07/10] CaitSith: Add permission checking functions Tetsuo Handa
2022-11-02 17:10 ` [PATCH 08/10] CaitSith: Add pathname calculation functions Tetsuo Handa
2022-11-02 17:10 ` [PATCH 09/10] CaitSith: Add garbage collector functions Tetsuo Handa
2022-11-02 17:10 ` [PATCH 10/10] CaitSith: Add Kconfig and Makefile files Tetsuo Handa
     [not found] ` <20221102171025.126961-6-penguin-kernel@I-love.SAKURA.ne.jp>
2022-11-02 17:29   ` [PATCH 6a/10] CaitSith: Add policy management functions Tetsuo Handa
2022-11-02 17:29   ` [PATCH 6b/10] " Tetsuo Handa

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.