From patchwork Wed Nov 2 17:10:16 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028657 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1CDBAC43217 for ; Wed, 2 Nov 2022 17:11:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230253AbiKBRLr (ORCPT ); Wed, 2 Nov 2022 13:11:47 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37798 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231514AbiKBRLe (ORCPT ); Wed, 2 Nov 2022 13:11:34 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C7D291F9C0 for ; Wed, 2 Nov 2022 10:11:27 -0700 (PDT) Received: from fsav119.sakura.ne.jp (fsav119.sakura.ne.jp [27.133.134.246]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HAtFQ021860; Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav119.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp); Thu, 03 Nov 2022 02:10:55 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkC021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 01/10] security: Export security_hook_heads Date: Thu, 3 Nov 2022 02:10:16 +0900 Message-Id: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 Precedence: bulk List-ID: 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 --- 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; From patchwork Wed Nov 2 17:10:17 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028658 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id ECFF1C4321E for ; Wed, 2 Nov 2022 17:11:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230398AbiKBRLs (ORCPT ); Wed, 2 Nov 2022 13:11:48 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37812 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231558AbiKBRLe (ORCPT ); Wed, 2 Nov 2022 13:11:34 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CF15F1C425 for ; Wed, 2 Nov 2022 10:11:29 -0700 (PDT) Received: from fsav119.sakura.ne.jp (fsav119.sakura.ne.jp [27.133.134.246]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HAtHW021865; Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav119.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp); Thu, 03 Nov 2022 02:10:55 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkD021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 02/10] mm: Export copy_to_kernel_nofault() Date: Thu, 3 Nov 2022 02:10:17 +0900 Message-Id: <20221102171025.126961-2-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> References: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> Precedence: bulk List-ID: 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 --- 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) { From patchwork Wed Nov 2 17:10:18 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028656 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id EA77AC43219 for ; Wed, 2 Nov 2022 17:11:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229996AbiKBRLq (ORCPT ); Wed, 2 Nov 2022 13:11:46 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36722 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231516AbiKBRLe (ORCPT ); Wed, 2 Nov 2022 13:11:34 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 59C92201A8 for ; Wed, 2 Nov 2022 10:11:29 -0700 (PDT) Received: from fsav119.sakura.ne.jp (fsav119.sakura.ne.jp [27.133.134.246]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HAtdL021870; Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav119.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp); Thu, 03 Nov 2022 02:10:55 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkE021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 03/10] fs,kernel: Export d_absolute_path()/find_task_by_pid_ns()/find_task_by_vpid() Date: Thu, 3 Nov 2022 02:10:18 +0900 Message-Id: <20221102171025.126961-3-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> References: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> Precedence: bulk List-ID: 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 --- 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) { From patchwork Wed Nov 2 17:10:19 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028661 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 32EA2C433FE for ; Wed, 2 Nov 2022 17:12:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230006AbiKBRMU (ORCPT ); Wed, 2 Nov 2022 13:12:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37642 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231636AbiKBRLn (ORCPT ); Wed, 2 Nov 2022 13:11:43 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 43E6D1A068 for ; Wed, 2 Nov 2022 10:11:41 -0700 (PDT) Received: from fsav119.sakura.ne.jp (fsav119.sakura.ne.jp [27.133.134.246]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HAt4H021875; Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav119.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp); Thu, 03 Nov 2022 02:10:55 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkF021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 04/10] CaitSith: Add header file. Date: Thu, 3 Nov 2022 02:10:19 +0900 Message-Id: <20221102171025.126961-4-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> References: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> Precedence: bulk List-ID: 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 From patchwork Wed Nov 2 17:10:20 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028663 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 41676C43217 for ; Wed, 2 Nov 2022 17:12:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230320AbiKBRMY (ORCPT ); Wed, 2 Nov 2022 13:12:24 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37910 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231143AbiKBRLx (ORCPT ); Wed, 2 Nov 2022 13:11:53 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 72BAF1B9C4 for ; Wed, 2 Nov 2022 10:11:50 -0700 (PDT) Received: from fsav119.sakura.ne.jp (fsav119.sakura.ne.jp [27.133.134.246]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HAtn7021880; Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav119.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp); Thu, 03 Nov 2022 02:10:55 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav119.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkG021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:55 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 05/10] CaitSith: Add LSM interface management file. Date: Thu, 3 Nov 2022 02:10:20 +0900 Message-Id: <20221102171025.126961-5-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> References: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> Precedence: bulk List-ID: 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 --- 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 + * + * Version: 0.2.10 2021/06/06 + */ + +#include "caitsith.h" +#include + +/* 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 + +/* 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 /* 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; +} From patchwork Wed Nov 2 17:10:22 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028662 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 10BCEC43219 for ; Wed, 2 Nov 2022 17:12:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230179AbiKBRMT (ORCPT ); Wed, 2 Nov 2022 13:12:19 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37040 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231626AbiKBRLi (ORCPT ); Wed, 2 Nov 2022 13:11:38 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 69F891B9D6 for ; Wed, 2 Nov 2022 10:11:34 -0700 (PDT) Received: from fsav413.sakura.ne.jp (fsav413.sakura.ne.jp [133.242.250.112]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HAuiD021888; Thu, 3 Nov 2022 02:10:57 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav413.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav413.sakura.ne.jp); Thu, 03 Nov 2022 02:10:56 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav413.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkI021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:56 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 07/10] CaitSith: Add permission checking functions. Date: Thu, 3 Nov 2022 02:10:22 +0900 Message-Id: <20221102171025.126961-7-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> References: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> Precedence: bulk List-ID: This file implements similar functions provided by many of security/tomoyo/*.c files. Signed-off-by: Tetsuo Handa --- 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; +} From patchwork Wed Nov 2 17:10:23 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028660 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5F20BC43217 for ; Wed, 2 Nov 2022 17:12:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229880AbiKBRMT (ORCPT ); Wed, 2 Nov 2022 13:12:19 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37054 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231624AbiKBRLi (ORCPT ); Wed, 2 Nov 2022 13:11:38 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D683C186FE for ; Wed, 2 Nov 2022 10:11:35 -0700 (PDT) Received: from fsav311.sakura.ne.jp (fsav311.sakura.ne.jp [153.120.85.142]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HAvah021893; Thu, 3 Nov 2022 02:10:57 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav311.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav311.sakura.ne.jp); Thu, 03 Nov 2022 02:10:57 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav311.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkJ021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:56 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 08/10] CaitSith: Add pathname calculation functions. Date: Thu, 3 Nov 2022 02:10:23 +0900 Message-Id: <20221102171025.126961-8-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> References: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> Precedence: bulk List-ID: This file implements similar functions provided by security/tomoyo/realpath.c file. Signed-off-by: Tetsuo Handa --- 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 + +/***** 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("", 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("", 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; +} From patchwork Wed Nov 2 17:10:24 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028659 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 90725C433FE for ; Wed, 2 Nov 2022 17:12:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229516AbiKBRMS (ORCPT ); Wed, 2 Nov 2022 13:12:18 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37554 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231618AbiKBRLh (ORCPT ); Wed, 2 Nov 2022 13:11:37 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 33A7D1D0C3 for ; Wed, 2 Nov 2022 10:11:33 -0700 (PDT) Received: from fsav311.sakura.ne.jp (fsav311.sakura.ne.jp [153.120.85.142]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HAvBv021897; Thu, 3 Nov 2022 02:10:57 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav311.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav311.sakura.ne.jp); Thu, 03 Nov 2022 02:10:57 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav311.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkK021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:57 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 09/10] CaitSith: Add garbage collector functions. Date: Thu, 3 Nov 2022 02:10:24 +0900 Message-Id: <20221102171025.126961-9-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> References: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> Precedence: bulk List-ID: This file implements similar functions provided by security/tomoyo/gc.c file. Signed-off-by: Tetsuo Handa --- 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); + } +} From patchwork Wed Nov 2 17:10:25 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 13028682 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C843EC4332F for ; Wed, 2 Nov 2022 17:16:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230311AbiKBRQu (ORCPT ); Wed, 2 Nov 2022 13:16:50 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43042 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231309AbiKBRQi (ORCPT ); Wed, 2 Nov 2022 13:16:38 -0400 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 62B98248D2 for ; Wed, 2 Nov 2022 10:16:34 -0700 (PDT) Received: from fsav118.sakura.ne.jp (fsav118.sakura.ne.jp [27.133.134.245]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 2A2HG3eC023046; Thu, 3 Nov 2022 02:16:03 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav118.sakura.ne.jp (F-Secure/fsigk_smtp/550/fsav118.sakura.ne.jp); Thu, 03 Nov 2022 02:16:03 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/550/fsav118.sakura.ne.jp) Received: from localhost.localdomain (M106072142033.v4.enabler.ne.jp [106.72.142.33]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 2A2HAnkL021849 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Thu, 3 Nov 2022 02:10:57 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org, Casey Schaufler , Paul Moore , John Johansen , Kees Cook Cc: Tetsuo Handa Subject: [PATCH 10/10] CaitSith: Add Kconfig and Makefile files. Date: Thu, 3 Nov 2022 02:10:25 +0900 Message-Id: <20221102171025.126961-10-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> References: <20221102171025.126961-1-penguin-kernel@I-love.SAKURA.ne.jp> Precedence: bulk List-ID: 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 --- 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