TOMOYO: Add pathname grouping support.
This patch adds pathname grouping support, which is useful for grouping pathnames that cannot be represented using /\{dir\}/ pattern. Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> Signed-off-by: James Morris <jmorris@namei.org>
This commit is contained in:
parent
ba0c1709f4
commit
7762fbfffd
|
@ -1 +1 @@
|
||||||
obj-y = common.o realpath.o tomoyo.o domain.o file.o gc.o
|
obj-y = common.o realpath.o tomoyo.o domain.o file.o gc.o path_group.o
|
||||||
|
|
|
@ -75,6 +75,49 @@ static int tomoyo_read_control(struct file *file, char __user *buffer,
|
||||||
static int tomoyo_write_control(struct file *file, const char __user *buffer,
|
static int tomoyo_write_control(struct file *file, const char __user *buffer,
|
||||||
const int buffer_len);
|
const int buffer_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tomoyo_parse_name_union - Parse a tomoyo_name_union.
|
||||||
|
*
|
||||||
|
* @filename: Name or name group.
|
||||||
|
* @ptr: Pointer to "struct tomoyo_name_union".
|
||||||
|
*
|
||||||
|
* Returns true on success, false otherwise.
|
||||||
|
*/
|
||||||
|
bool tomoyo_parse_name_union(const char *filename,
|
||||||
|
struct tomoyo_name_union *ptr)
|
||||||
|
{
|
||||||
|
if (!tomoyo_is_correct_path(filename, 0, 0, 0))
|
||||||
|
return false;
|
||||||
|
if (filename[0] == '@') {
|
||||||
|
ptr->group = tomoyo_get_path_group(filename + 1);
|
||||||
|
ptr->is_group = true;
|
||||||
|
return ptr->group != NULL;
|
||||||
|
}
|
||||||
|
ptr->filename = tomoyo_get_name(filename);
|
||||||
|
ptr->is_group = false;
|
||||||
|
return ptr->filename != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tomoyo_print_name_union - Print a tomoyo_name_union.
|
||||||
|
*
|
||||||
|
* @head: Pointer to "struct tomoyo_io_buffer".
|
||||||
|
* @ptr: Pointer to "struct tomoyo_name_union".
|
||||||
|
*
|
||||||
|
* Returns true on success, false otherwise.
|
||||||
|
*/
|
||||||
|
static bool tomoyo_print_name_union(struct tomoyo_io_buffer *head,
|
||||||
|
const struct tomoyo_name_union *ptr)
|
||||||
|
{
|
||||||
|
int pos = head->read_avail;
|
||||||
|
if (pos && head->read_buf[pos - 1] == ' ')
|
||||||
|
head->read_avail--;
|
||||||
|
if (ptr->is_group)
|
||||||
|
return tomoyo_io_printf(head, " @%s",
|
||||||
|
ptr->group->group_name->name);
|
||||||
|
return tomoyo_io_printf(head, " %s", ptr->filename->name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tomoyo_is_byte_range - Check whether the string isa \ooo style octal value.
|
* tomoyo_is_byte_range - Check whether the string isa \ooo style octal value.
|
||||||
*
|
*
|
||||||
|
@ -171,6 +214,33 @@ static void tomoyo_normalize_line(unsigned char *buffer)
|
||||||
*dp = '\0';
|
*dp = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tomoyo_tokenize - Tokenize string.
|
||||||
|
*
|
||||||
|
* @buffer: The line to tokenize.
|
||||||
|
* @w: Pointer to "char *".
|
||||||
|
* @size: Sizeof @w .
|
||||||
|
*
|
||||||
|
* Returns true on success, false otherwise.
|
||||||
|
*/
|
||||||
|
bool tomoyo_tokenize(char *buffer, char *w[], size_t size)
|
||||||
|
{
|
||||||
|
int count = size / sizeof(char *);
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
w[i] = "";
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
char *cp = strchr(buffer, ' ');
|
||||||
|
if (cp)
|
||||||
|
*cp = '\0';
|
||||||
|
w[i] = buffer;
|
||||||
|
if (!cp)
|
||||||
|
break;
|
||||||
|
buffer = cp + 1;
|
||||||
|
}
|
||||||
|
return i < count || !*buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tomoyo_is_correct_path - Validate a pathname.
|
* tomoyo_is_correct_path - Validate a pathname.
|
||||||
* @filename: The pathname to check.
|
* @filename: The pathname to check.
|
||||||
|
@ -1368,21 +1438,20 @@ static bool tomoyo_print_path_acl(struct tomoyo_io_buffer *head,
|
||||||
{
|
{
|
||||||
int pos;
|
int pos;
|
||||||
u8 bit;
|
u8 bit;
|
||||||
const char *filename;
|
|
||||||
const u32 perm = ptr->perm | (((u32) ptr->perm_high) << 16);
|
const u32 perm = ptr->perm | (((u32) ptr->perm_high) << 16);
|
||||||
|
|
||||||
filename = ptr->filename->name;
|
|
||||||
for (bit = head->read_bit; bit < TOMOYO_MAX_PATH_OPERATION; bit++) {
|
for (bit = head->read_bit; bit < TOMOYO_MAX_PATH_OPERATION; bit++) {
|
||||||
const char *msg;
|
|
||||||
if (!(perm & (1 << bit)))
|
if (!(perm & (1 << bit)))
|
||||||
continue;
|
continue;
|
||||||
/* Print "read/write" instead of "read" and "write". */
|
/* Print "read/write" instead of "read" and "write". */
|
||||||
if ((bit == TOMOYO_TYPE_READ || bit == TOMOYO_TYPE_WRITE)
|
if ((bit == TOMOYO_TYPE_READ || bit == TOMOYO_TYPE_WRITE)
|
||||||
&& (perm & (1 << TOMOYO_TYPE_READ_WRITE)))
|
&& (perm & (1 << TOMOYO_TYPE_READ_WRITE)))
|
||||||
continue;
|
continue;
|
||||||
msg = tomoyo_path2keyword(bit);
|
|
||||||
pos = head->read_avail;
|
pos = head->read_avail;
|
||||||
if (!tomoyo_io_printf(head, "allow_%s %s\n", msg, filename))
|
if (!tomoyo_io_printf(head, "allow_%s ",
|
||||||
|
tomoyo_path2keyword(bit)) ||
|
||||||
|
!tomoyo_print_name_union(head, &ptr->name) ||
|
||||||
|
!tomoyo_io_printf(head, "\n"))
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
head->read_bit = 0;
|
head->read_bit = 0;
|
||||||
|
@ -1405,21 +1474,18 @@ static bool tomoyo_print_path2_acl(struct tomoyo_io_buffer *head,
|
||||||
struct tomoyo_path2_acl *ptr)
|
struct tomoyo_path2_acl *ptr)
|
||||||
{
|
{
|
||||||
int pos;
|
int pos;
|
||||||
const char *filename1;
|
|
||||||
const char *filename2;
|
|
||||||
const u8 perm = ptr->perm;
|
const u8 perm = ptr->perm;
|
||||||
u8 bit;
|
u8 bit;
|
||||||
|
|
||||||
filename1 = ptr->filename1->name;
|
|
||||||
filename2 = ptr->filename2->name;
|
|
||||||
for (bit = head->read_bit; bit < TOMOYO_MAX_PATH2_OPERATION; bit++) {
|
for (bit = head->read_bit; bit < TOMOYO_MAX_PATH2_OPERATION; bit++) {
|
||||||
const char *msg;
|
|
||||||
if (!(perm & (1 << bit)))
|
if (!(perm & (1 << bit)))
|
||||||
continue;
|
continue;
|
||||||
msg = tomoyo_path22keyword(bit);
|
|
||||||
pos = head->read_avail;
|
pos = head->read_avail;
|
||||||
if (!tomoyo_io_printf(head, "allow_%s %s %s\n", msg,
|
if (!tomoyo_io_printf(head, "allow_%s ",
|
||||||
filename1, filename2))
|
tomoyo_path22keyword(bit)) ||
|
||||||
|
!tomoyo_print_name_union(head, &ptr->name1) ||
|
||||||
|
!tomoyo_print_name_union(head, &ptr->name2) ||
|
||||||
|
!tomoyo_io_printf(head, "\n"))
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
head->read_bit = 0;
|
head->read_bit = 0;
|
||||||
|
@ -1682,6 +1748,8 @@ static int tomoyo_write_exception_policy(struct tomoyo_io_buffer *head)
|
||||||
return tomoyo_write_pattern_policy(data, is_delete);
|
return tomoyo_write_pattern_policy(data, is_delete);
|
||||||
if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DENY_REWRITE))
|
if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DENY_REWRITE))
|
||||||
return tomoyo_write_no_rewrite_policy(data, is_delete);
|
return tomoyo_write_no_rewrite_policy(data, is_delete);
|
||||||
|
if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_PATH_GROUP))
|
||||||
|
return tomoyo_write_path_group_policy(data, is_delete);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1738,6 +1806,12 @@ static int tomoyo_read_exception_policy(struct tomoyo_io_buffer *head)
|
||||||
head->read_var2 = NULL;
|
head->read_var2 = NULL;
|
||||||
head->read_step = 9;
|
head->read_step = 9;
|
||||||
case 9:
|
case 9:
|
||||||
|
if (!tomoyo_read_path_group_policy(head))
|
||||||
|
break;
|
||||||
|
head->read_var1 = NULL;
|
||||||
|
head->read_var2 = NULL;
|
||||||
|
head->read_step = 10;
|
||||||
|
case 10:
|
||||||
head->read_eof = true;
|
head->read_eof = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -54,6 +54,7 @@ struct linux_binprm;
|
||||||
#define TOMOYO_KEYWORD_KEEP_DOMAIN "keep_domain "
|
#define TOMOYO_KEYWORD_KEEP_DOMAIN "keep_domain "
|
||||||
#define TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN "no_initialize_domain "
|
#define TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN "no_initialize_domain "
|
||||||
#define TOMOYO_KEYWORD_NO_KEEP_DOMAIN "no_keep_domain "
|
#define TOMOYO_KEYWORD_NO_KEEP_DOMAIN "no_keep_domain "
|
||||||
|
#define TOMOYO_KEYWORD_PATH_GROUP "path_group "
|
||||||
#define TOMOYO_KEYWORD_SELECT "select "
|
#define TOMOYO_KEYWORD_SELECT "select "
|
||||||
#define TOMOYO_KEYWORD_USE_PROFILE "use_profile "
|
#define TOMOYO_KEYWORD_USE_PROFILE "use_profile "
|
||||||
#define TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "ignore_global_allow_read"
|
#define TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "ignore_global_allow_read"
|
||||||
|
@ -204,6 +205,27 @@ struct tomoyo_path_info_with_data {
|
||||||
char barrier2[16]; /* Safeguard for overrun. */
|
char barrier2[16]; /* Safeguard for overrun. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct tomoyo_name_union {
|
||||||
|
const struct tomoyo_path_info *filename;
|
||||||
|
struct tomoyo_path_group *group;
|
||||||
|
u8 is_group;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Structure for "path_group" directive. */
|
||||||
|
struct tomoyo_path_group {
|
||||||
|
struct list_head list;
|
||||||
|
const struct tomoyo_path_info *group_name;
|
||||||
|
struct list_head member_list;
|
||||||
|
atomic_t users;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Structure for "path_group" directive. */
|
||||||
|
struct tomoyo_path_group_member {
|
||||||
|
struct list_head list;
|
||||||
|
bool is_deleted;
|
||||||
|
const struct tomoyo_path_info *member_name;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tomoyo_acl_info is a structure which is used for holding
|
* tomoyo_acl_info is a structure which is used for holding
|
||||||
*
|
*
|
||||||
|
@ -274,7 +296,7 @@ struct tomoyo_domain_info {
|
||||||
*
|
*
|
||||||
* (1) "head" which is a "struct tomoyo_acl_info".
|
* (1) "head" which is a "struct tomoyo_acl_info".
|
||||||
* (2) "perm" which is a bitmask of permitted operations.
|
* (2) "perm" which is a bitmask of permitted operations.
|
||||||
* (3) "filename" is the pathname.
|
* (3) "name" is the pathname.
|
||||||
*
|
*
|
||||||
* Directives held by this structure are "allow_read/write", "allow_execute",
|
* Directives held by this structure are "allow_read/write", "allow_execute",
|
||||||
* "allow_read", "allow_write", "allow_create", "allow_unlink", "allow_mkdir",
|
* "allow_read", "allow_write", "allow_create", "allow_unlink", "allow_mkdir",
|
||||||
|
@ -287,8 +309,7 @@ struct tomoyo_path_acl {
|
||||||
struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH_ACL */
|
struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH_ACL */
|
||||||
u8 perm_high;
|
u8 perm_high;
|
||||||
u16 perm;
|
u16 perm;
|
||||||
/* Pointer to single pathname. */
|
struct tomoyo_name_union name;
|
||||||
const struct tomoyo_path_info *filename;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -298,8 +319,8 @@ struct tomoyo_path_acl {
|
||||||
*
|
*
|
||||||
* (1) "head" which is a "struct tomoyo_acl_info".
|
* (1) "head" which is a "struct tomoyo_acl_info".
|
||||||
* (2) "perm" which is a bitmask of permitted operations.
|
* (2) "perm" which is a bitmask of permitted operations.
|
||||||
* (3) "filename1" is the source/old pathname.
|
* (3) "name1" is the source/old pathname.
|
||||||
* (4) "filename2" is the destination/new pathname.
|
* (4) "name2" is the destination/new pathname.
|
||||||
*
|
*
|
||||||
* Directives held by this structure are "allow_rename", "allow_link" and
|
* Directives held by this structure are "allow_rename", "allow_link" and
|
||||||
* "allow_pivot_root".
|
* "allow_pivot_root".
|
||||||
|
@ -307,10 +328,8 @@ struct tomoyo_path_acl {
|
||||||
struct tomoyo_path2_acl {
|
struct tomoyo_path2_acl {
|
||||||
struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH2_ACL */
|
struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH2_ACL */
|
||||||
u8 perm;
|
u8 perm;
|
||||||
/* Pointer to single pathname. */
|
struct tomoyo_name_union name1;
|
||||||
const struct tomoyo_path_info *filename1;
|
struct tomoyo_name_union name2;
|
||||||
/* Pointer to single pathname. */
|
|
||||||
const struct tomoyo_path_info *filename2;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -514,6 +533,9 @@ struct tomoyo_policy_manager_entry {
|
||||||
|
|
||||||
/********** Function prototypes. **********/
|
/********** Function prototypes. **********/
|
||||||
|
|
||||||
|
/* Check whether the given name matches the given name_union. */
|
||||||
|
bool tomoyo_compare_name_union(const struct tomoyo_path_info *name,
|
||||||
|
const struct tomoyo_name_union *ptr);
|
||||||
/* Check whether the domain has too many ACL entries to hold. */
|
/* Check whether the domain has too many ACL entries to hold. */
|
||||||
bool tomoyo_domain_quota_is_ok(struct tomoyo_domain_info * const domain);
|
bool tomoyo_domain_quota_is_ok(struct tomoyo_domain_info * const domain);
|
||||||
/* Transactional sprintf() for policy dump. */
|
/* Transactional sprintf() for policy dump. */
|
||||||
|
@ -526,6 +548,12 @@ bool tomoyo_is_correct_path(const char *filename, const s8 start_type,
|
||||||
const s8 pattern_type, const s8 end_type);
|
const s8 pattern_type, const s8 end_type);
|
||||||
/* Check whether the token can be a domainname. */
|
/* Check whether the token can be a domainname. */
|
||||||
bool tomoyo_is_domain_def(const unsigned char *buffer);
|
bool tomoyo_is_domain_def(const unsigned char *buffer);
|
||||||
|
bool tomoyo_parse_name_union(const char *filename,
|
||||||
|
struct tomoyo_name_union *ptr);
|
||||||
|
/* Check whether the given filename matches the given path_group. */
|
||||||
|
bool tomoyo_path_matches_group(const struct tomoyo_path_info *pathname,
|
||||||
|
const struct tomoyo_path_group *group,
|
||||||
|
const bool may_use_pattern);
|
||||||
/* Check whether the given filename matches the given pattern. */
|
/* Check whether the given filename matches the given pattern. */
|
||||||
bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename,
|
bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename,
|
||||||
const struct tomoyo_path_info *pattern);
|
const struct tomoyo_path_info *pattern);
|
||||||
|
@ -540,10 +568,14 @@ bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head);
|
||||||
bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head);
|
bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head);
|
||||||
/* Read "file_pattern" entry in exception policy. */
|
/* Read "file_pattern" entry in exception policy. */
|
||||||
bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head);
|
bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head);
|
||||||
|
/* Read "path_group" entry in exception policy. */
|
||||||
|
bool tomoyo_read_path_group_policy(struct tomoyo_io_buffer *head);
|
||||||
/* Read "allow_read" entry in exception policy. */
|
/* Read "allow_read" entry in exception policy. */
|
||||||
bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head);
|
bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head);
|
||||||
/* Read "deny_rewrite" entry in exception policy. */
|
/* Read "deny_rewrite" entry in exception policy. */
|
||||||
bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head);
|
bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head);
|
||||||
|
/* Tokenize a line. */
|
||||||
|
bool tomoyo_tokenize(char *buffer, char *w[], size_t size);
|
||||||
/* Write domain policy violation warning message to console? */
|
/* Write domain policy violation warning message to console? */
|
||||||
bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain);
|
bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain);
|
||||||
/* Convert double path operation to operation name. */
|
/* Convert double path operation to operation name. */
|
||||||
|
@ -580,12 +612,18 @@ int tomoyo_write_globally_readable_policy(char *data, const bool is_delete);
|
||||||
int tomoyo_write_no_rewrite_policy(char *data, const bool is_delete);
|
int tomoyo_write_no_rewrite_policy(char *data, const bool is_delete);
|
||||||
/* Create "file_pattern" entry in exception policy. */
|
/* Create "file_pattern" entry in exception policy. */
|
||||||
int tomoyo_write_pattern_policy(char *data, const bool is_delete);
|
int tomoyo_write_pattern_policy(char *data, const bool is_delete);
|
||||||
|
/* Create "path_group" entry in exception policy. */
|
||||||
|
int tomoyo_write_path_group_policy(char *data, const bool is_delete);
|
||||||
/* Find a domain by the given name. */
|
/* Find a domain by the given name. */
|
||||||
struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname);
|
struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname);
|
||||||
/* Find or create a domain by the given name. */
|
/* Find or create a domain by the given name. */
|
||||||
struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
|
struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
|
||||||
domainname,
|
domainname,
|
||||||
const u8 profile);
|
const u8 profile);
|
||||||
|
|
||||||
|
/* Allocate memory for "struct tomoyo_path_group". */
|
||||||
|
struct tomoyo_path_group *tomoyo_get_path_group(const char *group_name);
|
||||||
|
|
||||||
/* Check mode for specified functionality. */
|
/* Check mode for specified functionality. */
|
||||||
unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain,
|
unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain,
|
||||||
const u8 index);
|
const u8 index);
|
||||||
|
@ -642,6 +680,9 @@ int tomoyo_path2_perm(const u8 operation, struct path *path1,
|
||||||
int tomoyo_check_rewrite_permission(struct file *filp);
|
int tomoyo_check_rewrite_permission(struct file *filp);
|
||||||
int tomoyo_find_next_domain(struct linux_binprm *bprm);
|
int tomoyo_find_next_domain(struct linux_binprm *bprm);
|
||||||
|
|
||||||
|
/* Drop refcount on tomoyo_name_union. */
|
||||||
|
void tomoyo_put_name_union(struct tomoyo_name_union *ptr);
|
||||||
|
|
||||||
/* Run garbage collector. */
|
/* Run garbage collector. */
|
||||||
void tomoyo_run_gc(void);
|
void tomoyo_run_gc(void);
|
||||||
|
|
||||||
|
@ -655,6 +696,7 @@ extern struct srcu_struct tomoyo_ss;
|
||||||
/* The list for "struct tomoyo_domain_info". */
|
/* The list for "struct tomoyo_domain_info". */
|
||||||
extern struct list_head tomoyo_domain_list;
|
extern struct list_head tomoyo_domain_list;
|
||||||
|
|
||||||
|
extern struct list_head tomoyo_path_group_list;
|
||||||
extern struct list_head tomoyo_domain_initializer_list;
|
extern struct list_head tomoyo_domain_initializer_list;
|
||||||
extern struct list_head tomoyo_domain_keeper_list;
|
extern struct list_head tomoyo_domain_keeper_list;
|
||||||
extern struct list_head tomoyo_alias_list;
|
extern struct list_head tomoyo_alias_list;
|
||||||
|
@ -725,6 +767,12 @@ static inline void tomoyo_put_name(const struct tomoyo_path_info *name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void tomoyo_put_path_group(struct tomoyo_path_group *group)
|
||||||
|
{
|
||||||
|
if (group)
|
||||||
|
atomic_dec(&group->users);
|
||||||
|
}
|
||||||
|
|
||||||
static inline struct tomoyo_domain_info *tomoyo_domain(void)
|
static inline struct tomoyo_domain_info *tomoyo_domain(void)
|
||||||
{
|
{
|
||||||
return current_cred()->security;
|
return current_cred()->security;
|
||||||
|
@ -736,6 +784,34 @@ static inline struct tomoyo_domain_info *tomoyo_real_domain(struct task_struct
|
||||||
return task_cred_xxx(task, security);
|
return task_cred_xxx(task, security);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool tomoyo_is_same_acl_head(const struct tomoyo_acl_info *p1,
|
||||||
|
const struct tomoyo_acl_info *p2)
|
||||||
|
{
|
||||||
|
return p1->type == p2->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool tomoyo_is_same_name_union
|
||||||
|
(const struct tomoyo_name_union *p1, const struct tomoyo_name_union *p2)
|
||||||
|
{
|
||||||
|
return p1->filename == p2->filename && p1->group == p2->group &&
|
||||||
|
p1->is_group == p2->is_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool tomoyo_is_same_path_acl(const struct tomoyo_path_acl *p1,
|
||||||
|
const struct tomoyo_path_acl *p2)
|
||||||
|
{
|
||||||
|
return tomoyo_is_same_acl_head(&p1->head, &p2->head) &&
|
||||||
|
tomoyo_is_same_name_union(&p1->name, &p2->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool tomoyo_is_same_path2_acl(const struct tomoyo_path2_acl *p1,
|
||||||
|
const struct tomoyo_path2_acl *p2)
|
||||||
|
{
|
||||||
|
return tomoyo_is_same_acl_head(&p1->head, &p2->head) &&
|
||||||
|
tomoyo_is_same_name_union(&p1->name1, &p2->name1) &&
|
||||||
|
tomoyo_is_same_name_union(&p1->name2, &p2->name2);
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool tomoyo_is_same_domain_initializer_entry
|
static inline bool tomoyo_is_same_domain_initializer_entry
|
||||||
(const struct tomoyo_domain_initializer_entry *p1,
|
(const struct tomoyo_domain_initializer_entry *p1,
|
||||||
const struct tomoyo_domain_initializer_entry *p2)
|
const struct tomoyo_domain_initializer_entry *p2)
|
||||||
|
|
|
@ -45,6 +45,37 @@ static const char *tomoyo_path2_keyword[TOMOYO_MAX_PATH2_OPERATION] = {
|
||||||
[TOMOYO_TYPE_PIVOT_ROOT] = "pivot_root",
|
[TOMOYO_TYPE_PIVOT_ROOT] = "pivot_root",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void tomoyo_put_name_union(struct tomoyo_name_union *ptr)
|
||||||
|
{
|
||||||
|
if (!ptr)
|
||||||
|
return;
|
||||||
|
if (ptr->is_group)
|
||||||
|
tomoyo_put_path_group(ptr->group);
|
||||||
|
else
|
||||||
|
tomoyo_put_name(ptr->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tomoyo_compare_name_union(const struct tomoyo_path_info *name,
|
||||||
|
const struct tomoyo_name_union *ptr)
|
||||||
|
{
|
||||||
|
if (ptr->is_group)
|
||||||
|
return tomoyo_path_matches_group(name, ptr->group, 1);
|
||||||
|
return tomoyo_path_matches_pattern(name, ptr->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tomoyo_compare_name_union_pattern(const struct tomoyo_path_info
|
||||||
|
*name,
|
||||||
|
const struct tomoyo_name_union
|
||||||
|
*ptr, const bool may_use_pattern)
|
||||||
|
{
|
||||||
|
if (ptr->is_group)
|
||||||
|
return tomoyo_path_matches_group(name, ptr->group,
|
||||||
|
may_use_pattern);
|
||||||
|
if (may_use_pattern || !ptr->filename->is_patterned)
|
||||||
|
return tomoyo_path_matches_pattern(name, ptr->filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tomoyo_path2keyword - Get the name of single path operation.
|
* tomoyo_path2keyword - Get the name of single path operation.
|
||||||
*
|
*
|
||||||
|
@ -637,13 +668,9 @@ static int tomoyo_path_acl2(const struct tomoyo_domain_info *domain,
|
||||||
if (!(acl->perm_high & (perm >> 16)))
|
if (!(acl->perm_high & (perm >> 16)))
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (may_use_pattern || !acl->filename->is_patterned) {
|
if (!tomoyo_compare_name_union_pattern(filename, &acl->name,
|
||||||
if (!tomoyo_path_matches_pattern(filename,
|
may_use_pattern))
|
||||||
acl->filename))
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
error = 0;
|
error = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -817,19 +844,14 @@ static int tomoyo_update_path_acl(const u8 type, const char *filename,
|
||||||
e.perm |= tomoyo_rw_mask;
|
e.perm |= tomoyo_rw_mask;
|
||||||
if (!domain)
|
if (!domain)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
if (!tomoyo_is_correct_path(filename, 0, 0, 0))
|
if (!tomoyo_parse_name_union(filename, &e.name))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
e.filename = tomoyo_get_name(filename);
|
|
||||||
if (!e.filename)
|
|
||||||
return -ENOMEM;
|
|
||||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||||
goto out;
|
goto out;
|
||||||
list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
|
list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
|
||||||
struct tomoyo_path_acl *acl =
|
struct tomoyo_path_acl *acl =
|
||||||
container_of(ptr, struct tomoyo_path_acl, head);
|
container_of(ptr, struct tomoyo_path_acl, head);
|
||||||
if (ptr->type != TOMOYO_TYPE_PATH_ACL)
|
if (!tomoyo_is_same_path_acl(acl, &e))
|
||||||
continue;
|
|
||||||
if (acl->filename != e.filename)
|
|
||||||
continue;
|
continue;
|
||||||
if (is_delete) {
|
if (is_delete) {
|
||||||
if (perm <= 0xFFFF)
|
if (perm <= 0xFFFF)
|
||||||
|
@ -864,7 +886,7 @@ static int tomoyo_update_path_acl(const u8 type, const char *filename,
|
||||||
}
|
}
|
||||||
mutex_unlock(&tomoyo_policy_lock);
|
mutex_unlock(&tomoyo_policy_lock);
|
||||||
out:
|
out:
|
||||||
tomoyo_put_name(e.filename);
|
tomoyo_put_name_union(&e.name);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -896,22 +918,15 @@ static int tomoyo_update_path2_acl(const u8 type, const char *filename1,
|
||||||
|
|
||||||
if (!domain)
|
if (!domain)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
if (!tomoyo_is_correct_path(filename1, 0, 0, 0) ||
|
if (!tomoyo_parse_name_union(filename1, &e.name1) ||
|
||||||
!tomoyo_is_correct_path(filename2, 0, 0, 0))
|
!tomoyo_parse_name_union(filename2, &e.name2))
|
||||||
return -EINVAL;
|
|
||||||
e.filename1 = tomoyo_get_name(filename1);
|
|
||||||
e.filename2 = tomoyo_get_name(filename2);
|
|
||||||
if (!e.filename1 || !e.filename2)
|
|
||||||
goto out;
|
goto out;
|
||||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||||
goto out;
|
goto out;
|
||||||
list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
|
list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
|
||||||
struct tomoyo_path2_acl *acl =
|
struct tomoyo_path2_acl *acl =
|
||||||
container_of(ptr, struct tomoyo_path2_acl, head);
|
container_of(ptr, struct tomoyo_path2_acl, head);
|
||||||
if (ptr->type != TOMOYO_TYPE_PATH2_ACL)
|
if (!tomoyo_is_same_path2_acl(acl, &e))
|
||||||
continue;
|
|
||||||
if (acl->filename1 != e.filename1 ||
|
|
||||||
acl->filename2 != e.filename2)
|
|
||||||
continue;
|
continue;
|
||||||
if (is_delete)
|
if (is_delete)
|
||||||
acl->perm &= ~perm;
|
acl->perm &= ~perm;
|
||||||
|
@ -931,8 +946,8 @@ static int tomoyo_update_path2_acl(const u8 type, const char *filename1,
|
||||||
}
|
}
|
||||||
mutex_unlock(&tomoyo_policy_lock);
|
mutex_unlock(&tomoyo_policy_lock);
|
||||||
out:
|
out:
|
||||||
tomoyo_put_name(e.filename1);
|
tomoyo_put_name_union(&e.name1);
|
||||||
tomoyo_put_name(e.filename2);
|
tomoyo_put_name_union(&e.name2);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -985,9 +1000,9 @@ static int tomoyo_path2_acl(const struct tomoyo_domain_info *domain,
|
||||||
acl = container_of(ptr, struct tomoyo_path2_acl, head);
|
acl = container_of(ptr, struct tomoyo_path2_acl, head);
|
||||||
if (!(acl->perm & perm))
|
if (!(acl->perm & perm))
|
||||||
continue;
|
continue;
|
||||||
if (!tomoyo_path_matches_pattern(filename1, acl->filename1))
|
if (!tomoyo_compare_name_union(filename1, &acl->name1))
|
||||||
continue;
|
continue;
|
||||||
if (!tomoyo_path_matches_pattern(filename2, acl->filename2))
|
if (!tomoyo_compare_name_union(filename2, &acl->name2))
|
||||||
continue;
|
continue;
|
||||||
error = 0;
|
error = 0;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
|
||||||
enum tomoyo_gc_id {
|
enum tomoyo_gc_id {
|
||||||
|
TOMOYO_ID_PATH_GROUP,
|
||||||
|
TOMOYO_ID_PATH_GROUP_MEMBER,
|
||||||
TOMOYO_ID_DOMAIN_INITIALIZER,
|
TOMOYO_ID_DOMAIN_INITIALIZER,
|
||||||
TOMOYO_ID_DOMAIN_KEEPER,
|
TOMOYO_ID_DOMAIN_KEEPER,
|
||||||
TOMOYO_ID_ALIAS,
|
TOMOYO_ID_ALIAS,
|
||||||
|
@ -91,15 +93,15 @@ static void tomoyo_del_acl(struct tomoyo_acl_info *acl)
|
||||||
{
|
{
|
||||||
struct tomoyo_path_acl *entry
|
struct tomoyo_path_acl *entry
|
||||||
= container_of(acl, typeof(*entry), head);
|
= container_of(acl, typeof(*entry), head);
|
||||||
tomoyo_put_name(entry->filename);
|
tomoyo_put_name_union(&entry->name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TOMOYO_TYPE_PATH2_ACL:
|
case TOMOYO_TYPE_PATH2_ACL:
|
||||||
{
|
{
|
||||||
struct tomoyo_path2_acl *entry
|
struct tomoyo_path2_acl *entry
|
||||||
= container_of(acl, typeof(*entry), head);
|
= container_of(acl, typeof(*entry), head);
|
||||||
tomoyo_put_name(entry->filename1);
|
tomoyo_put_name_union(&entry->name1);
|
||||||
tomoyo_put_name(entry->filename2);
|
tomoyo_put_name_union(&entry->name2);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -149,6 +151,17 @@ static void tomoyo_del_name(const struct tomoyo_name_entry *ptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void tomoyo_del_path_group_member(struct tomoyo_path_group_member
|
||||||
|
*member)
|
||||||
|
{
|
||||||
|
tomoyo_put_name(member->member_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tomoyo_del_path_group(struct tomoyo_path_group *group)
|
||||||
|
{
|
||||||
|
tomoyo_put_name(group->group_name);
|
||||||
|
}
|
||||||
|
|
||||||
static void tomoyo_collect_entry(void)
|
static void tomoyo_collect_entry(void)
|
||||||
{
|
{
|
||||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||||
|
@ -293,6 +306,29 @@ static void tomoyo_collect_entry(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
struct tomoyo_path_group *group;
|
||||||
|
list_for_each_entry_rcu(group, &tomoyo_path_group_list, list) {
|
||||||
|
struct tomoyo_path_group_member *member;
|
||||||
|
list_for_each_entry_rcu(member, &group->member_list,
|
||||||
|
list) {
|
||||||
|
if (!member->is_deleted)
|
||||||
|
continue;
|
||||||
|
if (tomoyo_add_to_gc(TOMOYO_ID_PATH_GROUP_MEMBER,
|
||||||
|
member))
|
||||||
|
list_del_rcu(&member->list);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!list_empty(&group->member_list) ||
|
||||||
|
atomic_read(&group->users))
|
||||||
|
continue;
|
||||||
|
if (tomoyo_add_to_gc(TOMOYO_ID_PATH_GROUP, group))
|
||||||
|
list_del_rcu(&group->list);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
mutex_unlock(&tomoyo_policy_lock);
|
mutex_unlock(&tomoyo_policy_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,6 +370,12 @@ static void tomoyo_kfree_entry(void)
|
||||||
if (!tomoyo_del_domain(p->element))
|
if (!tomoyo_del_domain(p->element))
|
||||||
continue;
|
continue;
|
||||||
break;
|
break;
|
||||||
|
case TOMOYO_ID_PATH_GROUP_MEMBER:
|
||||||
|
tomoyo_del_path_group_member(p->element);
|
||||||
|
break;
|
||||||
|
case TOMOYO_ID_PATH_GROUP:
|
||||||
|
tomoyo_del_path_group(p->element);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
printk(KERN_WARNING "Unknown type\n");
|
printk(KERN_WARNING "Unknown type\n");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* security/tomoyo/path_group.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005-2009 NTT DATA CORPORATION
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include "common.h"
|
||||||
|
/* The list for "struct ccs_path_group". */
|
||||||
|
LIST_HEAD(tomoyo_path_group_list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tomoyo_get_path_group - Allocate memory for "struct tomoyo_path_group".
|
||||||
|
*
|
||||||
|
* @group_name: The name of pathname group.
|
||||||
|
*
|
||||||
|
* Returns pointer to "struct tomoyo_path_group" on success, NULL otherwise.
|
||||||
|
*/
|
||||||
|
struct tomoyo_path_group *tomoyo_get_path_group(const char *group_name)
|
||||||
|
{
|
||||||
|
struct tomoyo_path_group *entry = NULL;
|
||||||
|
struct tomoyo_path_group *group = NULL;
|
||||||
|
const struct tomoyo_path_info *saved_group_name;
|
||||||
|
int error = -ENOMEM;
|
||||||
|
if (!tomoyo_is_correct_path(group_name, 0, 0, 0) ||
|
||||||
|
!group_name[0])
|
||||||
|
return NULL;
|
||||||
|
saved_group_name = tomoyo_get_name(group_name);
|
||||||
|
if (!saved_group_name)
|
||||||
|
return NULL;
|
||||||
|
entry = kzalloc(sizeof(*entry), GFP_NOFS);
|
||||||
|
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||||
|
goto out;
|
||||||
|
list_for_each_entry_rcu(group, &tomoyo_path_group_list, list) {
|
||||||
|
if (saved_group_name != group->group_name)
|
||||||
|
continue;
|
||||||
|
atomic_inc(&group->users);
|
||||||
|
error = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (error && tomoyo_memory_ok(entry)) {
|
||||||
|
INIT_LIST_HEAD(&entry->member_list);
|
||||||
|
entry->group_name = saved_group_name;
|
||||||
|
saved_group_name = NULL;
|
||||||
|
atomic_set(&entry->users, 1);
|
||||||
|
list_add_tail_rcu(&entry->list, &tomoyo_path_group_list);
|
||||||
|
group = entry;
|
||||||
|
entry = NULL;
|
||||||
|
error = 0;
|
||||||
|
}
|
||||||
|
mutex_unlock(&tomoyo_policy_lock);
|
||||||
|
out:
|
||||||
|
tomoyo_put_name(saved_group_name);
|
||||||
|
kfree(entry);
|
||||||
|
return !error ? group : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tomoyo_write_path_group_policy - Write "struct tomoyo_path_group" list.
|
||||||
|
*
|
||||||
|
* @data: String to parse.
|
||||||
|
* @is_delete: True if it is a delete request.
|
||||||
|
*
|
||||||
|
* Returns 0 on success, nagative value otherwise.
|
||||||
|
*/
|
||||||
|
int tomoyo_write_path_group_policy(char *data, const bool is_delete)
|
||||||
|
{
|
||||||
|
struct tomoyo_path_group *group;
|
||||||
|
struct tomoyo_path_group_member *member;
|
||||||
|
struct tomoyo_path_group_member e = { };
|
||||||
|
int error = is_delete ? -ENOENT : -ENOMEM;
|
||||||
|
char *w[2];
|
||||||
|
if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[1][0])
|
||||||
|
return -EINVAL;
|
||||||
|
group = tomoyo_get_path_group(w[0]);
|
||||||
|
if (!group)
|
||||||
|
return -ENOMEM;
|
||||||
|
e.member_name = tomoyo_get_name(w[1]);
|
||||||
|
if (!e.member_name)
|
||||||
|
goto out;
|
||||||
|
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||||
|
goto out;
|
||||||
|
list_for_each_entry_rcu(member, &group->member_list, list) {
|
||||||
|
if (member->member_name != e.member_name)
|
||||||
|
continue;
|
||||||
|
member->is_deleted = is_delete;
|
||||||
|
error = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!is_delete && error) {
|
||||||
|
struct tomoyo_path_group_member *entry =
|
||||||
|
tomoyo_commit_ok(&e, sizeof(e));
|
||||||
|
if (entry) {
|
||||||
|
list_add_tail_rcu(&entry->list, &group->member_list);
|
||||||
|
error = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&tomoyo_policy_lock);
|
||||||
|
out:
|
||||||
|
tomoyo_put_name(e.member_name);
|
||||||
|
tomoyo_put_path_group(group);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tomoyo_read_path_group_policy - Read "struct tomoyo_path_group" list.
|
||||||
|
*
|
||||||
|
* @head: Pointer to "struct tomoyo_io_buffer".
|
||||||
|
*
|
||||||
|
* Returns true on success, false otherwise.
|
||||||
|
*
|
||||||
|
* Caller holds tomoyo_read_lock().
|
||||||
|
*/
|
||||||
|
bool tomoyo_read_path_group_policy(struct tomoyo_io_buffer *head)
|
||||||
|
{
|
||||||
|
struct list_head *gpos;
|
||||||
|
struct list_head *mpos;
|
||||||
|
list_for_each_cookie(gpos, head->read_var1, &tomoyo_path_group_list) {
|
||||||
|
struct tomoyo_path_group *group;
|
||||||
|
group = list_entry(gpos, struct tomoyo_path_group, list);
|
||||||
|
list_for_each_cookie(mpos, head->read_var2,
|
||||||
|
&group->member_list) {
|
||||||
|
struct tomoyo_path_group_member *member;
|
||||||
|
member = list_entry(mpos,
|
||||||
|
struct tomoyo_path_group_member,
|
||||||
|
list);
|
||||||
|
if (member->is_deleted)
|
||||||
|
continue;
|
||||||
|
if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_PATH_GROUP
|
||||||
|
"%s %s\n",
|
||||||
|
group->group_name->name,
|
||||||
|
member->member_name->name))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tomoyo_path_matches_group - Check whether the given pathname matches members of the given pathname group.
|
||||||
|
*
|
||||||
|
* @pathname: The name of pathname.
|
||||||
|
* @group: Pointer to "struct tomoyo_path_group".
|
||||||
|
* @may_use_pattern: True if wild card is permitted.
|
||||||
|
*
|
||||||
|
* Returns true if @pathname matches pathnames in @group, false otherwise.
|
||||||
|
*
|
||||||
|
* Caller holds tomoyo_read_lock().
|
||||||
|
*/
|
||||||
|
bool tomoyo_path_matches_group(const struct tomoyo_path_info *pathname,
|
||||||
|
const struct tomoyo_path_group *group,
|
||||||
|
const bool may_use_pattern)
|
||||||
|
{
|
||||||
|
struct tomoyo_path_group_member *member;
|
||||||
|
bool matched = false;
|
||||||
|
list_for_each_entry_rcu(member, &group->member_list, list) {
|
||||||
|
if (member->is_deleted)
|
||||||
|
continue;
|
||||||
|
if (!member->member_name->is_patterned) {
|
||||||
|
if (tomoyo_pathcmp(pathname, member->member_name))
|
||||||
|
continue;
|
||||||
|
} else if (may_use_pattern) {
|
||||||
|
if (!tomoyo_path_matches_pattern(pathname,
|
||||||
|
member->member_name))
|
||||||
|
continue;
|
||||||
|
} else
|
||||||
|
continue;
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return matched;
|
||||||
|
}
|
Loading…
Reference in New Issue