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