Commit 5af75d8d authored by Al Viro's avatar Al Viro

audit: validate comparison operations, store them in sane form

Don't store the field->op in the messy (and very inconvenient for e.g.
audit_comparator()) form; translate to dense set of values and do full
validation of userland-submitted value while we are at it.

->audit_init_rule() and ->audit_match_rule() get new values now; in-tree
instances updated.
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 36c4f1b1
...@@ -247,6 +247,18 @@ ...@@ -247,6 +247,18 @@
#define AUDIT_GREATER_THAN_OR_EQUAL (AUDIT_GREATER_THAN|AUDIT_EQUAL) #define AUDIT_GREATER_THAN_OR_EQUAL (AUDIT_GREATER_THAN|AUDIT_EQUAL)
#define AUDIT_OPERATORS (AUDIT_EQUAL|AUDIT_NOT_EQUAL|AUDIT_BIT_MASK) #define AUDIT_OPERATORS (AUDIT_EQUAL|AUDIT_NOT_EQUAL|AUDIT_BIT_MASK)
enum {
Audit_equal,
Audit_not_equal,
Audit_bitmask,
Audit_bittest,
Audit_lt,
Audit_gt,
Audit_le,
Audit_ge,
Audit_bad
};
/* Status symbols */ /* Status symbols */
/* Mask values */ /* Mask values */
#define AUDIT_STATUS_ENABLED 0x0001 #define AUDIT_STATUS_ENABLED 0x0001
......
...@@ -618,7 +618,7 @@ int audit_make_tree(struct audit_krule *rule, char *pathname, u32 op) ...@@ -618,7 +618,7 @@ int audit_make_tree(struct audit_krule *rule, char *pathname, u32 op)
if (pathname[0] != '/' || if (pathname[0] != '/' ||
rule->listnr != AUDIT_FILTER_EXIT || rule->listnr != AUDIT_FILTER_EXIT ||
op & ~AUDIT_EQUAL || op != Audit_equal ||
rule->inode_f || rule->watch || rule->tree) rule->inode_f || rule->watch || rule->tree)
return -EINVAL; return -EINVAL;
rule->tree = alloc_tree(pathname); rule->tree = alloc_tree(pathname);
......
...@@ -252,7 +252,8 @@ static inline int audit_to_inode(struct audit_krule *krule, ...@@ -252,7 +252,8 @@ static inline int audit_to_inode(struct audit_krule *krule,
struct audit_field *f) struct audit_field *f)
{ {
if (krule->listnr != AUDIT_FILTER_EXIT || if (krule->listnr != AUDIT_FILTER_EXIT ||
krule->watch || krule->inode_f || krule->tree) krule->watch || krule->inode_f || krule->tree ||
(f->op != Audit_equal && f->op != Audit_not_equal))
return -EINVAL; return -EINVAL;
krule->inode_f = f; krule->inode_f = f;
...@@ -270,7 +271,7 @@ static int audit_to_watch(struct audit_krule *krule, char *path, int len, ...@@ -270,7 +271,7 @@ static int audit_to_watch(struct audit_krule *krule, char *path, int len,
if (path[0] != '/' || path[len-1] == '/' || if (path[0] != '/' || path[len-1] == '/' ||
krule->listnr != AUDIT_FILTER_EXIT || krule->listnr != AUDIT_FILTER_EXIT ||
op & ~AUDIT_EQUAL || op != Audit_equal ||
krule->inode_f || krule->watch || krule->tree) krule->inode_f || krule->watch || krule->tree)
return -EINVAL; return -EINVAL;
...@@ -420,12 +421,32 @@ static inline struct audit_entry *audit_to_entry_common(struct audit_rule *rule) ...@@ -420,12 +421,32 @@ static inline struct audit_entry *audit_to_entry_common(struct audit_rule *rule)
return ERR_PTR(err); return ERR_PTR(err);
} }
static u32 audit_ops[] =
{
[Audit_equal] = AUDIT_EQUAL,
[Audit_not_equal] = AUDIT_NOT_EQUAL,
[Audit_bitmask] = AUDIT_BIT_MASK,
[Audit_bittest] = AUDIT_BIT_TEST,
[Audit_lt] = AUDIT_LESS_THAN,
[Audit_gt] = AUDIT_GREATER_THAN,
[Audit_le] = AUDIT_LESS_THAN_OR_EQUAL,
[Audit_ge] = AUDIT_GREATER_THAN_OR_EQUAL,
};
static u32 audit_to_op(u32 op)
{
u32 n;
for (n = Audit_equal; n < Audit_bad && audit_ops[n] != op; n++)
;
return n;
}
/* Translate struct audit_rule to kernel's rule respresentation. /* Translate struct audit_rule to kernel's rule respresentation.
* Exists for backward compatibility with userspace. */ * Exists for backward compatibility with userspace. */
static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule) static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule)
{ {
struct audit_entry *entry; struct audit_entry *entry;
struct audit_field *ino_f;
int err = 0; int err = 0;
int i; int i;
...@@ -435,12 +456,28 @@ static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule) ...@@ -435,12 +456,28 @@ static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule)
for (i = 0; i < rule->field_count; i++) { for (i = 0; i < rule->field_count; i++) {
struct audit_field *f = &entry->rule.fields[i]; struct audit_field *f = &entry->rule.fields[i];
u32 n;
n = rule->fields[i] & (AUDIT_NEGATE|AUDIT_OPERATORS);
/* Support for legacy operators where
* AUDIT_NEGATE bit signifies != and otherwise assumes == */
if (n & AUDIT_NEGATE)
f->op = Audit_not_equal;
else if (!n)
f->op = Audit_equal;
else
f->op = audit_to_op(n);
entry->rule.vers_ops = (n & AUDIT_OPERATORS) ? 2 : 1;
f->op = rule->fields[i] & (AUDIT_NEGATE|AUDIT_OPERATORS);
f->type = rule->fields[i] & ~(AUDIT_NEGATE|AUDIT_OPERATORS); f->type = rule->fields[i] & ~(AUDIT_NEGATE|AUDIT_OPERATORS);
f->val = rule->values[i]; f->val = rule->values[i];
err = -EINVAL; err = -EINVAL;
if (f->op == Audit_bad)
goto exit_free;
switch(f->type) { switch(f->type) {
default: default:
goto exit_free; goto exit_free;
...@@ -462,11 +499,8 @@ static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule) ...@@ -462,11 +499,8 @@ static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule)
case AUDIT_EXIT: case AUDIT_EXIT:
case AUDIT_SUCCESS: case AUDIT_SUCCESS:
/* bit ops are only useful on syscall args */ /* bit ops are only useful on syscall args */
if (f->op == AUDIT_BIT_MASK || if (f->op == Audit_bitmask || f->op == Audit_bittest)
f->op == AUDIT_BIT_TEST) {
err = -EINVAL;
goto exit_free; goto exit_free;
}
break; break;
case AUDIT_ARG0: case AUDIT_ARG0:
case AUDIT_ARG1: case AUDIT_ARG1:
...@@ -475,11 +509,8 @@ static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule) ...@@ -475,11 +509,8 @@ static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule)
break; break;
/* arch is only allowed to be = or != */ /* arch is only allowed to be = or != */
case AUDIT_ARCH: case AUDIT_ARCH:
if ((f->op != AUDIT_NOT_EQUAL) && (f->op != AUDIT_EQUAL) if (f->op != Audit_not_equal && f->op != Audit_equal)
&& (f->op != AUDIT_NEGATE) && (f->op)) {
err = -EINVAL;
goto exit_free; goto exit_free;
}
entry->rule.arch_f = f; entry->rule.arch_f = f;
break; break;
case AUDIT_PERM: case AUDIT_PERM:
...@@ -496,33 +527,10 @@ static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule) ...@@ -496,33 +527,10 @@ static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule)
goto exit_free; goto exit_free;
break; break;
} }
entry->rule.vers_ops = (f->op & AUDIT_OPERATORS) ? 2 : 1;
/* Support for legacy operators where
* AUDIT_NEGATE bit signifies != and otherwise assumes == */
if (f->op & AUDIT_NEGATE)
f->op = AUDIT_NOT_EQUAL;
else if (!f->op)
f->op = AUDIT_EQUAL;
else if (f->op == AUDIT_OPERATORS) {
err = -EINVAL;
goto exit_free;
}
} }
ino_f = entry->rule.inode_f; if (entry->rule.inode_f && entry->rule.inode_f->op == Audit_not_equal)
if (ino_f) { entry->rule.inode_f = NULL;
switch(ino_f->op) {
case AUDIT_NOT_EQUAL:
entry->rule.inode_f = NULL;
case AUDIT_EQUAL:
break;
default:
err = -EINVAL;
goto exit_free;
}
}
exit_nofree: exit_nofree:
return entry; return entry;
...@@ -538,7 +546,6 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data, ...@@ -538,7 +546,6 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
{ {
int err = 0; int err = 0;
struct audit_entry *entry; struct audit_entry *entry;
struct audit_field *ino_f;
void *bufp; void *bufp;
size_t remain = datasz - sizeof(struct audit_rule_data); size_t remain = datasz - sizeof(struct audit_rule_data);
int i; int i;
...@@ -554,11 +561,11 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data, ...@@ -554,11 +561,11 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
struct audit_field *f = &entry->rule.fields[i]; struct audit_field *f = &entry->rule.fields[i];
err = -EINVAL; err = -EINVAL;
if (!(data->fieldflags[i] & AUDIT_OPERATORS) ||
data->fieldflags[i] & ~AUDIT_OPERATORS) f->op = audit_to_op(data->fieldflags[i]);
if (f->op == Audit_bad)
goto exit_free; goto exit_free;
f->op = data->fieldflags[i] & AUDIT_OPERATORS;
f->type = data->fields[i]; f->type = data->fields[i];
f->val = data->values[i]; f->val = data->values[i];
f->lsm_str = NULL; f->lsm_str = NULL;
...@@ -670,18 +677,8 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data, ...@@ -670,18 +677,8 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
} }
} }
ino_f = entry->rule.inode_f; if (entry->rule.inode_f && entry->rule.inode_f->op == Audit_not_equal)
if (ino_f) { entry->rule.inode_f = NULL;
switch(ino_f->op) {
case AUDIT_NOT_EQUAL:
entry->rule.inode_f = NULL;
case AUDIT_EQUAL:
break;
default:
err = -EINVAL;
goto exit_free;
}
}
exit_nofree: exit_nofree:
return entry; return entry;
...@@ -721,10 +718,10 @@ static struct audit_rule *audit_krule_to_rule(struct audit_krule *krule) ...@@ -721,10 +718,10 @@ static struct audit_rule *audit_krule_to_rule(struct audit_krule *krule)
rule->fields[i] = krule->fields[i].type; rule->fields[i] = krule->fields[i].type;
if (krule->vers_ops == 1) { if (krule->vers_ops == 1) {
if (krule->fields[i].op & AUDIT_NOT_EQUAL) if (krule->fields[i].op == Audit_not_equal)
rule->fields[i] |= AUDIT_NEGATE; rule->fields[i] |= AUDIT_NEGATE;
} else { } else {
rule->fields[i] |= krule->fields[i].op; rule->fields[i] |= audit_ops[krule->fields[i].op];
} }
} }
for (i = 0; i < AUDIT_BITMASK_SIZE; i++) rule->mask[i] = krule->mask[i]; for (i = 0; i < AUDIT_BITMASK_SIZE; i++) rule->mask[i] = krule->mask[i];
...@@ -752,7 +749,7 @@ static struct audit_rule_data *audit_krule_to_data(struct audit_krule *krule) ...@@ -752,7 +749,7 @@ static struct audit_rule_data *audit_krule_to_data(struct audit_krule *krule)
struct audit_field *f = &krule->fields[i]; struct audit_field *f = &krule->fields[i];
data->fields[i] = f->type; data->fields[i] = f->type;
data->fieldflags[i] = f->op; data->fieldflags[i] = audit_ops[f->op];
switch(f->type) { switch(f->type) {
case AUDIT_SUBJ_USER: case AUDIT_SUBJ_USER:
case AUDIT_SUBJ_ROLE: case AUDIT_SUBJ_ROLE:
...@@ -1626,28 +1623,29 @@ int audit_receive_filter(int type, int pid, int uid, int seq, void *data, ...@@ -1626,28 +1623,29 @@ int audit_receive_filter(int type, int pid, int uid, int seq, void *data,
return err; return err;
} }
int audit_comparator(const u32 left, const u32 op, const u32 right) int audit_comparator(u32 left, u32 op, u32 right)
{ {
switch (op) { switch (op) {
case AUDIT_EQUAL: case Audit_equal:
return (left == right); return (left == right);
case AUDIT_NOT_EQUAL: case Audit_not_equal:
return (left != right); return (left != right);
case AUDIT_LESS_THAN: case Audit_lt:
return (left < right); return (left < right);
case AUDIT_LESS_THAN_OR_EQUAL: case Audit_le:
return (left <= right); return (left <= right);
case AUDIT_GREATER_THAN: case Audit_gt:
return (left > right); return (left > right);
case AUDIT_GREATER_THAN_OR_EQUAL: case Audit_ge:
return (left >= right); return (left >= right);
case AUDIT_BIT_MASK: case Audit_bitmask:
return (left & right); return (left & right);
case AUDIT_BIT_TEST: case Audit_bittest:
return ((left & right) == right); return ((left & right) == right);
default:
BUG();
return 0;
} }
BUG();
return 0;
} }
/* Compare given dentry name with last component in given path, /* Compare given dentry name with last component in given path,
......
...@@ -2602,7 +2602,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) ...@@ -2602,7 +2602,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule)
case AUDIT_OBJ_ROLE: case AUDIT_OBJ_ROLE:
case AUDIT_OBJ_TYPE: case AUDIT_OBJ_TYPE:
/* only 'equals' and 'not equals' fit user, role, and type */ /* only 'equals' and 'not equals' fit user, role, and type */
if (op != AUDIT_EQUAL && op != AUDIT_NOT_EQUAL) if (op != Audit_equal && op != Audit_not_equal)
return -EINVAL; return -EINVAL;
break; break;
case AUDIT_SUBJ_SEN: case AUDIT_SUBJ_SEN:
...@@ -2736,10 +2736,10 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule, ...@@ -2736,10 +2736,10 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule,
case AUDIT_SUBJ_USER: case AUDIT_SUBJ_USER:
case AUDIT_OBJ_USER: case AUDIT_OBJ_USER:
switch (op) { switch (op) {
case AUDIT_EQUAL: case Audit_equal:
match = (ctxt->user == rule->au_ctxt.user); match = (ctxt->user == rule->au_ctxt.user);
break; break;
case AUDIT_NOT_EQUAL: case Audit_not_equal:
match = (ctxt->user != rule->au_ctxt.user); match = (ctxt->user != rule->au_ctxt.user);
break; break;
} }
...@@ -2747,10 +2747,10 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule, ...@@ -2747,10 +2747,10 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule,
case AUDIT_SUBJ_ROLE: case AUDIT_SUBJ_ROLE:
case AUDIT_OBJ_ROLE: case AUDIT_OBJ_ROLE:
switch (op) { switch (op) {
case AUDIT_EQUAL: case Audit_equal:
match = (ctxt->role == rule->au_ctxt.role); match = (ctxt->role == rule->au_ctxt.role);
break; break;
case AUDIT_NOT_EQUAL: case Audit_not_equal:
match = (ctxt->role != rule->au_ctxt.role); match = (ctxt->role != rule->au_ctxt.role);
break; break;
} }
...@@ -2758,10 +2758,10 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule, ...@@ -2758,10 +2758,10 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule,
case AUDIT_SUBJ_TYPE: case AUDIT_SUBJ_TYPE:
case AUDIT_OBJ_TYPE: case AUDIT_OBJ_TYPE:
switch (op) { switch (op) {
case AUDIT_EQUAL: case Audit_equal:
match = (ctxt->type == rule->au_ctxt.type); match = (ctxt->type == rule->au_ctxt.type);
break; break;
case AUDIT_NOT_EQUAL: case Audit_not_equal:
match = (ctxt->type != rule->au_ctxt.type); match = (ctxt->type != rule->au_ctxt.type);
break; break;
} }
...@@ -2774,31 +2774,31 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule, ...@@ -2774,31 +2774,31 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule,
field == AUDIT_OBJ_LEV_LOW) ? field == AUDIT_OBJ_LEV_LOW) ?
&ctxt->range.level[0] : &ctxt->range.level[1]); &ctxt->range.level[0] : &ctxt->range.level[1]);
switch (op) { switch (op) {
case AUDIT_EQUAL: case Audit_equal:
match = mls_level_eq(&rule->au_ctxt.range.level[0], match = mls_level_eq(&rule->au_ctxt.range.level[0],
level); level);
break; break;
case AUDIT_NOT_EQUAL: case Audit_not_equal:
match = !mls_level_eq(&rule->au_ctxt.range.level[0], match = !mls_level_eq(&rule->au_ctxt.range.level[0],
level); level);
break; break;
case AUDIT_LESS_THAN: case Audit_lt:
match = (mls_level_dom(&rule->au_ctxt.range.level[0], match = (mls_level_dom(&rule->au_ctxt.range.level[0],
level) && level) &&
!mls_level_eq(&rule->au_ctxt.range.level[0], !mls_level_eq(&rule->au_ctxt.range.level[0],
level)); level));
break; break;
case AUDIT_LESS_THAN_OR_EQUAL: case Audit_le:
match = mls_level_dom(&rule->au_ctxt.range.level[0], match = mls_level_dom(&rule->au_ctxt.range.level[0],
level); level);
break; break;
case AUDIT_GREATER_THAN: case Audit_gt:
match = (mls_level_dom(level, match = (mls_level_dom(level,
&rule->au_ctxt.range.level[0]) && &rule->au_ctxt.range.level[0]) &&
!mls_level_eq(level, !mls_level_eq(level,
&rule->au_ctxt.range.level[0])); &rule->au_ctxt.range.level[0]));
break; break;
case AUDIT_GREATER_THAN_OR_EQUAL: case Audit_ge:
match = mls_level_dom(level, match = mls_level_dom(level,
&rule->au_ctxt.range.level[0]); &rule->au_ctxt.range.level[0]);
break; break;
......
...@@ -2492,7 +2492,7 @@ static int smack_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) ...@@ -2492,7 +2492,7 @@ static int smack_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule)
if (field != AUDIT_SUBJ_USER && field != AUDIT_OBJ_USER) if (field != AUDIT_SUBJ_USER && field != AUDIT_OBJ_USER)
return -EINVAL; return -EINVAL;
if (op != AUDIT_EQUAL && op != AUDIT_NOT_EQUAL) if (op != Audit_equal && op != Audit_not_equal)
return -EINVAL; return -EINVAL;
*rule = smk_import(rulestr, 0); *rule = smk_import(rulestr, 0);
...@@ -2556,9 +2556,9 @@ static int smack_audit_rule_match(u32 secid, u32 field, u32 op, void *vrule, ...@@ -2556,9 +2556,9 @@ static int smack_audit_rule_match(u32 secid, u32 field, u32 op, void *vrule,
* both pointers will point to the same smack_known * both pointers will point to the same smack_known
* label. * label.
*/ */
if (op == AUDIT_EQUAL) if (op == Audit_equal)
return (rule == smack); return (rule == smack);
if (op == AUDIT_NOT_EQUAL) if (op == Audit_not_equal)
return (rule != smack); return (rule != smack);
return 0; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment