0,0 → 1,526 |
/*- |
* Copyright 2005, Anatoli Klassen <anatoli@aksoft.net> |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* are met: |
* 1. Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* 2. Redistributions in binary form must reproduce the above copyright |
* notice, this list of conditions and the following disclaimer in the |
* documentation and/or other materials provided with the distribution. |
* |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
* SUCH DAMAGE. |
* |
* $FreeBSD$ |
*/ |
|
/* |
* "allow uid 1,2,4-5" - any of specified users in main machine or any jail |
* "allow gid 123 jail 1-10" - a group in any of specified jail |
* "allow nojail" - any user on main machine |
* "deny not jail 3" - any user in jail 3 |
* |
* rules are applied in given order, first match wins; |
* if some specification is omit it means "any", last rule is always "deny" |
* |
* #sysctl security.mac.settime.rules="rule1; rule2; ruleN" |
* |
* rules := *([:rule:] *([;|\n] *[:rule:] *)*)? |
* rule := (allow|deny) +((not)? +(uid|gid) +[:digits-range:])? |
* ((not)? +(nojail|(jail +[:digits-range:]))? |
* digits-range := (\d+|(\d+-\d+))(,(\d+|(\d+-\d+)))* |
* tab and underscore can be used instead of space |
* |
*/ |
|
#include <sys/param.h> |
#include <sys/conf.h> |
#include <sys/jail.h> |
#include <sys/kernel.h> |
#include <sys/libkern.h> |
#include <sys/mac.h> |
#include <sys/malloc.h> |
#include <sys/mount.h> |
#include <sys/mutex.h> |
#include <sys/queue.h> |
#include <sys/sysctl.h> |
#include <sys/mac_policy.h> |
|
SYSCTL_DECL(_security_mac); |
|
SYSCTL_NODE(_security_mac, OID_AUTO, settime, CTLFLAG_RW, 0, |
"mac_settime policy controls"); |
|
static int mac_settime_enabled = 1; |
SYSCTL_INT(_security_mac_settime, OID_AUTO, enabled, CTLFLAG_RW, |
&mac_settime_enabled, 0, "Enforce settime policy"); |
TUNABLE_INT("security.mac.settime.enabled", &mac_settime_enabled); |
|
static MALLOC_DEFINE(M_SETTIME, "settime rule", "Rules for mac_settime"); |
|
#define MAC_RULE_STRING_LEN 10240 |
|
#define RULE_NONE 0 |
#define RULE_UID 1 |
#define RULE_GID 2 |
#define RULE_NOJAIL 1 |
#define RULE_JAIL 2 |
|
struct rule { |
int allow; /* 0 == "deny", 1 == "allow" */ |
int id_not; /* 0 == "", 1 == "not" */ |
int id_type; /* 0 == "" - no check, RULE_UID == "uid", RULE_GID == "gid" */ |
int id_count; /* number of id ranges */ |
int *id; /* uid or gid ranges */ |
int jail_not; /* 0 == "", 1 == "not" */ |
int jail_type; /* 0 == "" - no check, RULE_NOJAIL == "nojail", |
RULE_JAIL == "jail" */ |
int jailid_count; /* number of jail id ranges */ |
int *jailid; /* jail id ranges */ |
|
TAILQ_ENTRY(rule) r_entries; |
}; |
|
#define ALLOW_STRING "allow" |
#define DENY_STRING "deny" |
#define NOT_STRING "not" |
#define GID_STRING "gid" |
#define UID_STRING "uid" |
#define NOJAIL_STRING "nojail" |
#define JAIL_STRING "jail" |
|
static struct mtx rule_mtx; |
static TAILQ_HEAD(rulehead, rule) rule_head; |
static char rule_string[MAC_RULE_STRING_LEN]; |
|
static void |
delete_rules(struct rulehead *head) |
{ |
struct rule *rule; |
|
while ((rule = TAILQ_FIRST(head)) != NULL) { |
TAILQ_REMOVE(head, rule, r_entries); |
free(rule->id, M_SETTIME); |
free(rule->jailid, M_SETTIME); |
free(rule, M_SETTIME); |
} |
} |
|
static void |
destroy(struct mac_policy_conf *mpc) |
{ |
|
mtx_destroy(&rule_mtx); |
delete_rules(&rule_head); |
} |
|
static void |
init(struct mac_policy_conf *mpc) |
{ |
|
mtx_init(&rule_mtx, "rule_mtx", NULL, MTX_DEF); |
TAILQ_INIT(&rule_head); |
} |
|
static int |
parse_range_step(char *token, int *count, int **array) |
{ |
long n1, n2; |
int error; |
int end; |
|
error = 0; |
end = 0; |
*count = 0; |
while (!end && !error) { |
/* not start with digit - to avoid +/- recognition */ |
if (*token < '0' || *token > '9') { |
error = EINVAL; |
break; |
} |
|
n1 = strtol(token, &token, 10); |
switch (*token) { |
case '\0': /* end of string */ |
n2 = n1; |
end = 1; |
break; |
case ',': /* single value */ |
token++; |
n2 = n1; |
break; |
case '-': /* interval */ |
token++; |
/* not start with digit */ |
if (*token < '0' || *token > '9') |
error = EINVAL; |
else { |
n2 = strtol(token, &token, 10); |
switch (*token) { |
case '\0': /* end of string */ |
end = 1; |
break; |
case ',': /* continue */ |
token++; |
break; |
default: |
error = EINVAL; |
} |
} |
break; |
default: |
error = EINVAL; |
} |
|
if (!error) { |
if (array != NULL) { |
(*array)[*count * 2] = (int)n1; |
(*array)[*count * 2 + 1] = (int)n2; |
} |
(*count)++; |
} |
} |
|
return (error); |
} |
|
static int |
parse_range(char *token, int *count, int **array) |
{ |
int error; |
|
/* parse first time to determine number of intervals */ |
error = parse_range_step(token, count, NULL); |
if (error) { |
*array = NULL; |
return (error); |
} |
|
*array = malloc(*count * 2 * sizeof(int), M_SETTIME, M_WAITOK); |
|
/* parse once again - to fill the array */ |
error = parse_range_step(token, count, array); |
|
if (error) { |
free(*array, M_SETTIME); |
*array = NULL; |
} |
|
return (error); |
} |
|
static int |
parse_rule(char *s, struct rule **rule) |
{ |
enum parse_state { |
STATE_BEFORE_ACTION, |
STATE_AFTER_ACTION, |
STATE_AFTER_NOT1, |
STATE_AFTER_IDTYPE, |
STATE_AFTER_ID, |
STATE_AFTER_NOT2, |
STATE_AFTER_JAIL, |
STATE_END |
}; |
|
int error; |
int not; |
char *token; |
struct rule *r; |
enum parse_state state; |
|
r = malloc(sizeof(*r), M_SETTIME, M_ZERO | M_WAITOK); |
r->id = NULL; /* some arch where (pointer)0 != (binary)0 ? */ |
r->jailid = NULL; |
|
error = 0; |
not = 0; |
state = STATE_BEFORE_ACTION; |
|
while (!error && (token = strsep(&s, " \t_")) != NULL) { |
if (strlen(token) == 0) |
continue; |
|
switch(state) { |
case STATE_BEFORE_ACTION: |
not = 0; |
if (strcmp(token, ALLOW_STRING) == 0) { |
r->allow = 1; |
state = STATE_AFTER_ACTION; |
} else if (strcmp(token, DENY_STRING) == 0) { |
r->allow = 0; |
state = STATE_AFTER_ACTION; |
} else |
error = EINVAL; |
break; |
|
case STATE_AFTER_ACTION: |
if (strcmp(token, NOT_STRING) == 0) { |
state = STATE_AFTER_NOT1; |
not = 1; |
break; |
} |
/* FALLTHROUGH */ |
|
case STATE_AFTER_NOT1: |
if (strcmp(token, UID_STRING) == 0) { |
r->id_type = RULE_UID; |
r->id_not = not; |
state = STATE_AFTER_IDTYPE; |
break; |
} else if (strcmp(token, GID_STRING) == 0) { |
r->id_type = RULE_GID; |
r->id_not = not; |
state = STATE_AFTER_IDTYPE; |
break; |
} |
/* FALLTHROUGH */ |
|
/* attention: order of cases doesn't match order of tokens */ |
case STATE_AFTER_ID: |
if (strcmp(token, NOT_STRING) == 0) { |
if (STATE_AFTER_NOT1 == state) |
error = EINVAL; /* double 'not' */ |
else { |
state = STATE_AFTER_NOT2; |
not = 1; |
} |
break; |
} |
/* FALLTHROUGH */ |
|
case STATE_AFTER_NOT2: |
if (strcmp(token, NOJAIL_STRING) == 0) { |
r->jail_type = RULE_NOJAIL; |
r->jail_not = not; |
state = STATE_END; |
} else if(strcmp(token, JAIL_STRING) == 0) { |
r->jail_type = RULE_JAIL; |
r->jail_not = not; |
state = STATE_AFTER_JAIL; |
} else |
error = EINVAL; |
break; |
|
case STATE_AFTER_IDTYPE: |
error = parse_range(token, &(r->id_count), &(r->id)); |
state = STATE_AFTER_ID; |
break; |
|
case STATE_AFTER_JAIL: |
error = parse_range(token, &(r->jailid_count), &(r->jailid)); |
state = STATE_END; |
break; |
|
case STATE_END: |
error = EINVAL; /* something after end of rule */ |
break; |
} |
} |
|
/* check for unexpected end of rule */ |
if (STATE_BEFORE_ACTION != state && STATE_AFTER_ACTION != state && |
STATE_END != state && STATE_AFTER_ID != state) |
error = EINVAL; |
|
if (error || STATE_BEFORE_ACTION == state) { /* error or spaces only */ |
free(r, M_SETTIME); |
*rule = NULL; |
} |
else |
*rule = r; |
|
return (error); |
} |
|
/* this will destroy the given string */ |
static int |
parse_rules(char *s, struct rulehead *head) |
{ |
int error; |
char *token; |
struct rule *r; |
|
error = 0; |
while (!error && (token = strsep(&s, ";\n")) != NULL) { |
if (strlen(token) == 0) |
continue; |
|
error = parse_rule(token, &r); |
if (error) |
break; |
|
if (r != NULL) |
TAILQ_INSERT_TAIL(head, r, r_entries); |
} |
|
if (error) |
delete_rules(head); |
|
return (error); |
} |
|
/* |
* Note: due to races, there is not a single serializable order |
* between parallel calls to the sysctl. |
*/ |
static int |
sysctl_rules(SYSCTL_HANDLER_ARGS) |
{ |
char *string, *copy_string, *new_string; |
struct rulehead head, save_head; |
int error; |
|
new_string = NULL; |
if (req->newptr == NULL) { |
new_string = malloc(MAC_RULE_STRING_LEN, M_SETTIME, |
M_WAITOK | M_ZERO); |
strcpy(new_string, rule_string); |
string = new_string; |
} else |
string = rule_string; |
|
error = sysctl_handle_string(oidp, string, MAC_RULE_STRING_LEN, req); |
if (error) |
goto out; |
|
if (req->newptr != NULL) { |
copy_string = strdup(string, M_SETTIME); |
TAILQ_INIT(&head); |
error = parse_rules(copy_string, &head); |
free(copy_string, M_SETTIME); |
if (error) |
goto out; |
|
TAILQ_INIT(&save_head); |
mtx_lock(&rule_mtx); |
TAILQ_CONCAT(&save_head, &rule_head, r_entries); |
TAILQ_CONCAT(&rule_head, &head, r_entries); |
strcpy(rule_string, string); |
mtx_unlock(&rule_mtx); |
delete_rules(&save_head); |
} |
out: |
if (new_string != NULL) |
free(new_string, M_SETTIME); |
return (error); |
} |
|
SYSCTL_PROC(_security_mac_settime, OID_AUTO, rules, |
CTLTYPE_STRING|CTLFLAG_RW, 0, 0, sysctl_rules, "A", "Rules"); |
|
static int |
range_match(int value, int ranges_count, int *ranges, int not) |
{ |
int i, match; |
|
match = 0; |
for (i = 0; i < ranges_count; i++) |
if (ranges[i * 2] <= value && value <= ranges[i * 2 + 1]) { |
match = 1; |
break; |
} |
|
if (not) |
match = !match; |
|
return (match); |
} |
|
static int |
rule_match(struct ucred *cred, struct rule *r) |
{ |
int match_id, match_jail; |
|
switch (r->id_type) { |
case RULE_NONE: |
match_id = 1; |
break; |
case RULE_UID: |
match_id = range_match(cred->cr_uid, r->id_count, r->id, r->id_not); |
break; |
case RULE_GID: |
/* FIXME implement for other groups */ |
match_id = range_match(cred->cr_gid, r->id_count, r->id, r->id_not); |
break; |
default: |
printf("Unknown rule id type: %d\n", r->id_type); |
match_id = 0; |
} |
|
switch(r->jail_type) { |
case RULE_NONE: |
match_jail = 1; |
break; |
case RULE_NOJAIL: |
match_jail = !jailed(cred); |
break; |
case RULE_JAIL: |
if (cred->cr_prison == NULL) |
match_jail = 0; |
else |
match_jail = range_match(cred->cr_prison->pr_id, |
r->jailid_count, r->jailid, r->jail_not); |
break; |
default: |
printf("Unknown rule jail type: %d\n", r->jail_type); |
match_jail = 0; |
} |
|
return (match_id && match_jail); |
} |
|
static int |
rules_check(struct ucred *cred) |
{ |
struct rule *rule; |
int error; |
|
error = EPERM; |
|
mtx_lock(&rule_mtx); |
for (rule = TAILQ_FIRST(&rule_head); |
rule != NULL; |
rule = TAILQ_NEXT(rule, r_entries)) { |
if (rule_match(cred, rule)) { |
if (rule->allow) |
error = 0; |
break; |
} |
} |
mtx_unlock(&rule_mtx); |
|
return (error); |
} |
|
static int |
check_system_settime(struct ucred *cred) |
{ |
if (mac_settime_enabled == 0) |
return (0); |
|
return (rules_check(cred)); |
} |
|
static struct mac_policy_ops mac_settime_ops = |
{ |
.mpo_destroy = destroy, |
.mpo_init = init, |
.mpo_check_system_settime = check_system_settime, |
}; |
|
MAC_POLICY_SET(&mac_settime_ops, mac_settime, |
"MAC/settime", MPC_LOADTIME_FLAG_UNLOADOK, NULL); |
|