From patchwork Fri Oct 21 12:49:06 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tetsuo Handa X-Patchwork-Id: 9388895 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 64FCC607F0 for ; Fri, 21 Oct 2016 12:50:27 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 54A592A155 for ; Fri, 21 Oct 2016 12:50:27 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 4933F2A183; Fri, 21 Oct 2016 12:50:27 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BDEC82A155 for ; Fri, 21 Oct 2016 12:50:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755414AbcJUMuX (ORCPT ); Fri, 21 Oct 2016 08:50:23 -0400 Received: from www262.sakura.ne.jp ([202.181.97.72]:39033 "EHLO www262.sakura.ne.jp" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932708AbcJUMth (ORCPT ); Fri, 21 Oct 2016 08:49:37 -0400 Received: from fsav301.sakura.ne.jp (fsav301.sakura.ne.jp [153.120.85.132]) by www262.sakura.ne.jp (8.14.5/8.14.5) with ESMTP id u9LCnTKh029097; Fri, 21 Oct 2016 21:49:29 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from www262.sakura.ne.jp (202.181.97.72) by fsav301.sakura.ne.jp (F-Secure/fsigk_smtp/530/fsav301.sakura.ne.jp); Fri, 21 Oct 2016 21:49:29 +0900 (JST) X-Virus-Status: clean(F-Secure/fsigk_smtp/530/fsav301.sakura.ne.jp) Received: from ccsecurity.localdomain (softbank126227147111.bbtec.net [126.227.147.111]) (authenticated bits=0) by www262.sakura.ne.jp (8.14.5/8.14.5) with ESMTP id u9LCnMFo029053 (version=TLSv1/SSLv3 cipher=DHE-RSA-CAMELLIA256-SHA bits=256 verify=NO); Fri, 21 Oct 2016 21:49:29 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) From: Tetsuo Handa To: linux-security-module@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Tetsuo Handa Subject: [PATCH 4/8] CaitSith: Add permission check functions. Date: Fri, 21 Oct 2016 21:49:06 +0900 Message-Id: <1477054150-4772-5-git-send-email-penguin-kernel@I-love.SAKURA.ne.jp> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1477054150-4772-1-git-send-email-penguin-kernel@I-love.SAKURA.ne.jp> References: <1477054150-4772-1-git-send-email-penguin-kernel@I-love.SAKURA.ne.jp> Sender: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: X-Virus-Scanned: ClamAV using ClamSMTP This file corresponds to security/tomoyo/condition.c and security/tomoyo/file.c . Compared to TOMOYO, a new wildcard /\(dir\)/ has been introduced for helping converting from (e.g.) "/tmp/\{\*\}/" to "/tmp/\(\*\)/\*", for directory's pathname (except the root directory itself) no longer ends with / character which previously matched /\{\*\}/ wildcard. Signed-off-by: Tetsuo Handa --- security/caitsith/permission.c | 691 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 691 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 0000000..9b33bea --- /dev/null +++ b/security/caitsith/permission.c @@ -0,0 +1,691 @@ +/* + * security/caitsith/permission.c + * + * Copyright (C) 2005-2012 NTT DATA CORPORATION + */ + +#include "caitsith.h" + +/* Type of condition argument. */ +enum cs_arg_type { + CS_ARG_TYPE_NONE, + CS_ARG_TYPE_NAME, +} __packed; + +/* Structure for holding single condition component. */ +struct cs_cond_arg { + enum cs_arg_type type; + const struct cs_path_info *name; +}; + +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_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 int cs_execute_path(struct linux_binprm *bprm, struct path *path); +static int cs_execute(struct cs_request_info *r); +static void cs_clear_request_info(struct cs_request_info *r); + +/* The list for ACL policy. */ +struct list_head cs_acl_list[CS_MAX_MAC_INDEX]; + +/* NULL value. */ +struct cs_path_info cs_null_name; + +/** + * 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_rcu(ptr, list, list) { + struct cs_acl_info *ptr2; + + if (!cs_check_entry(r, ptr)) { + if (unlikely(r->failed_by_oom)) + goto oom; + continue; + } + r->matched_acl = ptr; + r->result = CS_MATCHING_UNMATCHED; + list_for_each_entry_rcu(ptr2, &ptr->acl_info_list, list) { + if (!cs_check_entry(r, ptr2)) { + if (unlikely(r->failed_by_oom)) + goto oom; + continue; + } + r->result = ptr2->is_deny ? + CS_MATCHING_DENIED : CS_MATCHING_ALLOWED; + break; + } + error = cs_audit_log(r); + /* Ignore out of memory during audit. */ + r->failed_by_oom = false; + if (error) + 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 = get_seconds(); + + if (oom != cs_last_oom) { + cs_last_oom = oom; + printk(KERN_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. + */ +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); + 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; + return cs_check_acl(r, false); +} + +/** + * cs_start_execve - Prepare for execve() operation. + * + * @bprm: Pointer to "struct linux_binprm". + * + * Returns 0 on success, negative value otherwise. + */ +int cs_start_execve(struct linux_binprm *bprm) +{ + int retval; + struct cs_request_info r = { }; + int idx; + + r.tmp = kzalloc(CS_EXEC_TMPSIZE, GFP_NOFS); + if (!r.tmp) + return -ENOMEM; + idx = cs_read_lock(); + r.bprm = bprm; + r.obj.path[0] = bprm->file->f_path; + retval = cs_execute(&r); + 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); + return retval; +} + +/** + * 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 (kern_path(bprm->filename, follow, path)) + return -ENOENT; + return 0; +} + +/** + * 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) +{ + if (!cs_policy_loaded) + return true; + { + struct cs_request_info r = { }; + + r.type = CS_MAC_MODIFY_POLICY; + if (cs_check_acl(&r, true) == 0) + 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(); + + printk(KERN_WARNING "'%s' (pid=%u) is not permitted to update policies.\n", + exe, pid); + cs_last_pid = pid; + kfree(exe); + } + } + return false; +} + +/** + * 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 othwerwise. + * + * 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) +{ + 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_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; + default: + arg->name = NULL; + break; + } + if (!arg->name) + return false; + arg->type = CS_ARG_TYPE_NAME; + 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_NAME) { + if (right.type != CS_ARG_TYPE_NAME) + return false; + if (cs_path_matches_pattern + (left.name, right.name) == match) + continue; + } + return false; + } + return true; +} + +/** + * 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(). + * Their callers do not allocate memory until pathnames becomes needed + * for checking condition. + */ + 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; +}