TOMOYO Linux checks permission for non-POSIX capability
so that the number of capabilities won't be limited to 32 or 64.
TOMOYO Linux uses per-a-domain capability, an approach that associate
capabilities with each domain, and assign a domain for each process.
The advantages of this approach are that
(1) minimum capabilities required for a process is directly given
compared to setting/clearing/inheriting bit fields of a process's
capability value.
(2) it can support as many types of capabilities as you want
without consuming much memory because you don't need to allocate
memory for per-a-task basis.
Although current implementation can handle up to 65536 types of capabilities.
we don't have enough hooks for checking/distinguishing
"What operation is requested.".
Therefore, some of capabilities defined in this module doesn't work.
We want to insert a new hook like operation_capable(syscall_number)
so that we can have enough hooks for checking/distinguishing requests.
May be system call auditing mechanism helps distinguish operations that are
all-combined into capable(CAP_SYS_ADMIN) operation. But to use it,
we think we have to enable system call auditing for all processes,
which may cause performance and log flooding problem?
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <[email protected]>
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/capability.c | 320 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 320 insertions(+)
--- /dev/null
+++ linux-2.6-mm/security/tomoyo/capability.c
@@ -0,0 +1,320 @@
+/*
+ * security/tomoyo/capability.c
+ *
+ * Capability access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+static struct {
+ const char *keyword;
+ unsigned int current_value;
+ const char *capability_name;
+} capability_control_array[TMY_MAX_CAPABILITY_INDEX] = {
+ [TMY_INET_STREAM_SOCKET_CREATE] = /* OK */
+ { "inet_tcp_create", 0, "socket(PF_INET, SOCK_STREAM)" },
+ [TMY_INET_STREAM_SOCKET_LISTEN] = /* OK */
+ { "inet_tcp_listen", 0, "listen(PF_INET, SOCK_STREAM)" },
+ [TMY_INET_STREAM_SOCKET_CONNECT] = /* OK */
+ { "inet_tcp_connect", 0, "connect(PF_INET, SOCK_STREAM)" },
+ [TMY_USE_INET_DGRAM_SOCKET] = /* OK */
+ { "use_inet_udp", 0, "socket(PF_INET, SOCK_DGRAM)" },
+ [TMY_USE_INET_RAW_SOCKET] = /* OK */
+ { "use_inet_ip", 0, "socket(PF_INET, SOCK_RAW)" },
+ [TMY_USE_ROUTE_SOCKET] = /* OK */
+ { "use_route", 0, "socket(PF_ROUTE)" },
+ [TMY_USE_PACKET_SOCKET] = /* OK */
+ { "use_packet", 0, "socket(PF_PACKET)" },
+ [TMY_SYS_MOUNT] = /* OK */
+ { "SYS_MOUNT", 0, "sys_mount()" },
+ [TMY_SYS_UMOUNT] = /* OK */
+ { "SYS_UMOUNT", 0, "sys_umount()" },
+ [TMY_SYS_REBOOT] = /* Too many hooks. */
+ { "SYS_REBOOT", 0, "sys_reboot()" },
+ [TMY_SYS_CHROOT] = /* OK */
+ { "SYS_CHROOT", 0, "sys_chroot()" },
+ [TMY_SYS_KILL] = /* No appropriate hook. */
+ { "SYS_KILL", 0, "sys_kill()" },
+ [TMY_SYS_VHANGUP] = /* Too many hooks. */
+ { "SYS_VHANGUP", 0, "sys_vhangup()" },
+ [TMY_SYS_SETTIME] = /* Too many hooks. */
+ { "SYS_TIME", 0, "sys_settimeofday()" },
+ [TMY_SYS_NICE] = /* No appropriate hook. */
+ { "SYS_NICE", 0, "sys_nice()" },
+ [TMY_SYS_SETHOSTNAME] = /* No appropriate hook. */
+ { "SYS_SETHOSTNAME", 0, "sys_sethostname()" },
+ [TMY_USE_KERNEL_MODULE] = /* Too many hooks. */
+ { "use_kernel_module", 0, "kernel_module" },
+ [TMY_CREATE_FIFO] = /* OK */
+ { "create_fifo", 0, "mknod(FIFO)" },
+ [TMY_CREATE_BLOCK_DEV] = /* OK */
+ { "create_block_dev", 0, "mknod(BDEV)" },
+ [TMY_CREATE_CHAR_DEV] = /* OK */
+ { "create_char_dev", 0, "mknod(CDEV)" },
+ [TMY_CREATE_UNIX_SOCKET] = /* OK */
+ { "create_unix_socket", 0, "mknod(SOCKET)" },
+ [TMY_SYS_LINK] = /* OK */
+ { "SYS_LINK", 0, "sys_link()" },
+ [TMY_SYS_SYMLINK] = /* OK */
+ { "SYS_SYMLINK", 0, "sys_symlink()" },
+ [TMY_SYS_RENAME] = /* OK */
+ { "SYS_RENAME", 0, "sys_rename()" },
+ [TMY_SYS_UNLINK] = /* OK */
+ { "SYS_UNLINK", 0, "sys_unlink()" },
+ [TMY_SYS_CHMOD] = /* OK */
+ { "SYS_CHMOD", 0, "sys_chmod()" },
+ [TMY_SYS_CHOWN] = /* OK */
+ { "SYS_CHOWN", 0, "sys_chown()" },
+ [TMY_SYS_IOCTL] = /* Too many hooks. */
+ { "SYS_IOCTL", 0, "sys_ioctl()" },
+ [TMY_SYS_KEXEC_LOAD] = /* No appropriate hook. */
+ { "SYS_KEXEC_LOAD", 0, "sys_kexec_load()" },
+ [TMY_SYS_PIVOT_ROOT] = /* OK */
+ { "SYS_PIVOT_ROOT", 0, "sys_pivot_root()" },
+};
+
+struct profile {
+ unsigned char value[TMY_MAX_CAPABILITY_INDEX];
+};
+
+static struct profile *profile_ptr[TMY_MAX_PROFILES];
+
+/************************* UTILITY FUNCTIONS *************************/
+
+const char *tmy_capability2keyword(const unsigned int capability)
+{
+ return capability < TMY_MAX_CAPABILITY_INDEX ?
+ capability_control_array[capability].keyword : NULL;
+}
+
+static const char *tmy_capability2name(const unsigned int capability)
+{
+ return capability < TMY_MAX_CAPABILITY_INDEX ?
+ capability_control_array[capability].capability_name : NULL;
+}
+
+/* Check whether the given capability control is enabled. */
+static unsigned int tmy_capability_flags(const unsigned int index)
+{
+ const u8 profile = TMY_SECURITY->domain->profile;
+ /* All operations might sleep. See tmy_supervisor(). */
+ might_sleep();
+ if (in_interrupt())
+ return 0;
+ return sbin_init_started && index < TMY_MAX_CAPABILITY_INDEX
+#if TMY_MAX_PROFILES != 256
+ && profile < TMY_MAX_PROFILES
+#endif
+ && profile_ptr[profile] ?
+ profile_ptr[profile]->value[index] : 0;
+}
+
+static struct profile *tmy_new_capability_profile(const unsigned int profile)
+{
+ static DEFINE_MUTEX(mutex);
+ struct profile *ptr;
+ int i;
+ if (profile >= TMY_MAX_PROFILES)
+ return NULL;
+ mutex_lock(&mutex);
+ ptr = profile_ptr[profile];
+ if (ptr)
+ goto ok;
+ ptr = tmy_alloc_element(sizeof(*ptr));
+ if (!ptr)
+ goto ok;
+ for (i = 0; i < TMY_MAX_CAPABILITY_INDEX; i++)
+ ptr->value[i] = capability_control_array[i].current_value;
+ mb(); /* Avoid out-of-order execution. */
+ profile_ptr[profile] = ptr;
+ok: ;
+ mutex_unlock(&mutex);
+ return ptr;
+}
+
+int tmy_set_capability_profile(const char *data, unsigned int value,
+ const unsigned int profile)
+{
+ int i;
+ struct profile *ptr;
+ ptr = tmy_new_capability_profile(profile);
+ if (!ptr)
+ return -EINVAL;
+ for (i = 0; i < TMY_MAX_CAPABILITY_INDEX; i++) {
+ if (strcmp(data, capability_control_array[i].keyword))
+ continue;
+ if (value > 3)
+ value = 3;
+ ptr->value[i] = value;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+int tmy_read_capability_profile(struct io_buffer *head)
+{
+ int step;
+ for (step = head->read_step;
+ step < TMY_MAX_PROFILES * TMY_MAX_CAPABILITY_INDEX; step++) {
+ const int i = step / TMY_MAX_CAPABILITY_INDEX;
+ const int j = step % TMY_MAX_CAPABILITY_INDEX;
+ const struct profile *profile = profile_ptr[i];
+ head->read_step = step;
+ if (!profile)
+ continue;
+ if (tmy_io_printf(head, "%u-" TMY_MAC_FOR_CAPABILITY "%s=%u\n",
+ i, capability_control_array[j].keyword,
+ profile->value[j]))
+ break;
+ }
+ return step < TMY_MAX_PROFILES * TMY_MAX_CAPABILITY_INDEX ? -ENOMEM : 0;
+}
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_capability_log(const unsigned int capability,
+ const bool is_granted,
+ const u8 profile, const unsigned int mode)
+{
+ char *buf;
+ int len = 64 ;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ buf = tmy_init_audit_log(&len, profile, mode);
+
+ if (!buf)
+ return -ENOMEM;
+
+ tmy_sncatprintf(buf, len - 1, TMY_ALLOW_CAPABILITY "%s\n",
+ tmy_capability2keyword(capability));
+
+ return tmy_write_audit_log(buf, is_granted);
+}
+
+/************************* CAPABILITY ACL HANDLER *************************/
+
+static int tmy_add_capability_acl(const unsigned int capability,
+ struct domain_info *domain,
+ const struct condition_list *condition,
+ const bool is_delete)
+{
+ struct acl_info *ptr;
+ struct capability_acl *acl;
+ int error = -ENOMEM;
+ const u16 hash = capability;
+ if (!domain)
+ return -EINVAL;
+ mutex_lock(&domain_acl_lock);
+ if (is_delete)
+ goto remove;
+
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = container_of(ptr, struct capability_acl, head);
+ if (ptr->type == TMY_TYPE_CAPABILITY_ACL
+ && acl->capability == hash
+ && ptr->cond == condition) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+ goto ok;
+ }
+ }
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ goto ok;
+ acl->head.type = TMY_TYPE_CAPABILITY_ACL;
+ acl->head.cond = condition;
+ acl->capability = hash;
+ error = tmy_add_acl(domain, &acl->head);
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = container_of(ptr, struct capability_acl, head);
+ if (ptr->type != TMY_TYPE_CAPABILITY_ACL || ptr->is_deleted ||
+ acl->capability != hash || ptr->cond != condition)
+ continue;
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ mutex_unlock(&domain_acl_lock);
+ return error;
+}
+
+/**
+ * tmy_capable - check permission for capability.
+ * @capability: capability index.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_capable(const unsigned int capability)
+{
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+ const u8 profile = domain->profile;
+ const unsigned int mode = tmy_capability_flags(capability);
+ const bool is_enforce = (mode == 3);
+ const u16 hash = capability;
+ if (!mode)
+ return 0;
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ struct capability_acl *acl;
+ acl = container_of(ptr, struct capability_acl, head);
+ if (ptr->type != TMY_TYPE_CAPABILITY_ACL || ptr->is_deleted
+ || acl->capability != hash
+ || tmy_check_condition(ptr->cond, NULL))
+ continue;
+ tmy_audit_capability_log(capability, 1, profile, mode);
+ return 0;
+ }
+ if (tmy_flags(TMY_VERBOSE))
+ tmy_audit("TOMOYO-%s: %s denied for %s\n",
+ tmy_getmsg(is_enforce),
+ tmy_capability2name(capability),
+ tmy_lastname(domain));
+ tmy_audit_capability_log(capability, 0, profile, mode);
+ if (is_enforce)
+ return tmy_supervisor("%s\n" TMY_ALLOW_CAPABILITY "%s\n",
+ domain->domainname->name,
+ tmy_capability2keyword(capability));
+ if (mode == 1 && tmy_quota())
+ tmy_add_capability_acl(capability, domain, NULL, 0);
+ return 0;
+}
+
+/**
+ * tmy_add_capability_policy - add or delete capability policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_capability_policy(char *data, struct domain_info *domain,
+ const struct condition_list *cond,
+ const bool is_delete)
+{
+ unsigned int capability;
+ for (capability = 0; capability < TMY_MAX_CAPABILITY_INDEX;
+ capability++) {
+ if (strcmp(data, capability_control_array[capability].keyword))
+ continue;
+ return tmy_add_capability_acl(capability, domain,
+ cond, is_delete);
+ }
+ return -EINVAL;
+}
--