audit: fix error handling in audit_data_to_entry()

Commit 219ca39427 ("audit: use union for audit_field values since
they are mutually exclusive") combined a number of separate fields in
the audit_field struct into a single union.  Generally this worked
just fine because they are generally mutually exclusive.
Unfortunately in audit_data_to_entry() the overlap can be a problem
when a specific error case is triggered that causes the error path
code to attempt to cleanup an audit_field struct and the cleanup
involves attempting to free a stored LSM string (the lsm_str field).
Currently the code always has a non-NULL value in the
audit_field.lsm_str field as the top of the for-loop transfers a
value into audit_field.val (both .lsm_str and .val are part of the
same union); if audit_data_to_entry() fails and the audit_field
struct is specified to contain a LSM string, but the
audit_field.lsm_str has not yet been properly set, the error handling
code will attempt to free the bogus audit_field.lsm_str value that
was set with audit_field.val at the top of the for-loop.

This patch corrects this by ensuring that the audit_field.val is only
set when needed (it is cleared when the audit_field struct is
allocated with kcalloc()).  It also corrects a few other issues to
ensure that in case of error the proper error code is returned.

Cc: stable@vger.kernel.org
Fixes: 219ca39427 ("audit: use union for audit_field values since they are mutually exclusive")
Reported-by: syzbot+1f4d90ead370d72e450b@syzkaller.appspotmail.com
Signed-off-by: Paul Moore <paul@paul-moore.com>
This commit is contained in:
Paul Moore 2020-02-22 20:36:47 -05:00
Родитель cb5172d96d
Коммит 2ad3e17ebf
1 изменённых файлов: 44 добавлений и 37 удалений

Просмотреть файл

@ -456,6 +456,7 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
bufp = data->buf; bufp = data->buf;
for (i = 0; i < data->field_count; i++) { for (i = 0; i < data->field_count; i++) {
struct audit_field *f = &entry->rule.fields[i]; struct audit_field *f = &entry->rule.fields[i];
u32 f_val;
err = -EINVAL; err = -EINVAL;
@ -464,12 +465,12 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
goto exit_free; goto exit_free;
f->type = data->fields[i]; f->type = data->fields[i];
f->val = data->values[i]; f_val = data->values[i];
/* Support legacy tests for a valid loginuid */ /* Support legacy tests for a valid loginuid */
if ((f->type == AUDIT_LOGINUID) && (f->val == AUDIT_UID_UNSET)) { if ((f->type == AUDIT_LOGINUID) && (f_val == AUDIT_UID_UNSET)) {
f->type = AUDIT_LOGINUID_SET; f->type = AUDIT_LOGINUID_SET;
f->val = 0; f_val = 0;
entry->rule.pflags |= AUDIT_LOGINUID_LEGACY; entry->rule.pflags |= AUDIT_LOGINUID_LEGACY;
} }
@ -485,7 +486,7 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
case AUDIT_SUID: case AUDIT_SUID:
case AUDIT_FSUID: case AUDIT_FSUID:
case AUDIT_OBJ_UID: case AUDIT_OBJ_UID:
f->uid = make_kuid(current_user_ns(), f->val); f->uid = make_kuid(current_user_ns(), f_val);
if (!uid_valid(f->uid)) if (!uid_valid(f->uid))
goto exit_free; goto exit_free;
break; break;
@ -494,11 +495,12 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
case AUDIT_SGID: case AUDIT_SGID:
case AUDIT_FSGID: case AUDIT_FSGID:
case AUDIT_OBJ_GID: case AUDIT_OBJ_GID:
f->gid = make_kgid(current_user_ns(), f->val); f->gid = make_kgid(current_user_ns(), f_val);
if (!gid_valid(f->gid)) if (!gid_valid(f->gid))
goto exit_free; goto exit_free;
break; break;
case AUDIT_ARCH: case AUDIT_ARCH:
f->val = f_val;
entry->rule.arch_f = f; entry->rule.arch_f = f;
break; break;
case AUDIT_SUBJ_USER: case AUDIT_SUBJ_USER:
@ -511,11 +513,13 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
case AUDIT_OBJ_TYPE: case AUDIT_OBJ_TYPE:
case AUDIT_OBJ_LEV_LOW: case AUDIT_OBJ_LEV_LOW:
case AUDIT_OBJ_LEV_HIGH: case AUDIT_OBJ_LEV_HIGH:
str = audit_unpack_string(&bufp, &remain, f->val); str = audit_unpack_string(&bufp, &remain, f_val);
if (IS_ERR(str)) if (IS_ERR(str)) {
err = PTR_ERR(str);
goto exit_free; goto exit_free;
entry->rule.buflen += f->val; }
entry->rule.buflen += f_val;
f->lsm_str = str;
err = security_audit_rule_init(f->type, f->op, str, err = security_audit_rule_init(f->type, f->op, str,
(void **)&f->lsm_rule); (void **)&f->lsm_rule);
/* Keep currently invalid fields around in case they /* Keep currently invalid fields around in case they
@ -524,68 +528,71 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
pr_warn("audit rule for LSM \'%s\' is invalid\n", pr_warn("audit rule for LSM \'%s\' is invalid\n",
str); str);
err = 0; err = 0;
} } else if (err)
if (err) {
kfree(str);
goto exit_free; goto exit_free;
} else
f->lsm_str = str;
break; break;
case AUDIT_WATCH: case AUDIT_WATCH:
str = audit_unpack_string(&bufp, &remain, f->val); str = audit_unpack_string(&bufp, &remain, f_val);
if (IS_ERR(str)) if (IS_ERR(str)) {
err = PTR_ERR(str);
goto exit_free; goto exit_free;
entry->rule.buflen += f->val; }
err = audit_to_watch(&entry->rule, str, f_val, f->op);
err = audit_to_watch(&entry->rule, str, f->val, f->op);
if (err) { if (err) {
kfree(str); kfree(str);
goto exit_free; goto exit_free;
} }
entry->rule.buflen += f_val;
break; break;
case AUDIT_DIR: case AUDIT_DIR:
str = audit_unpack_string(&bufp, &remain, f->val); str = audit_unpack_string(&bufp, &remain, f_val);
if (IS_ERR(str)) if (IS_ERR(str)) {
err = PTR_ERR(str);
goto exit_free; goto exit_free;
entry->rule.buflen += f->val; }
err = audit_make_tree(&entry->rule, str, f->op); err = audit_make_tree(&entry->rule, str, f->op);
kfree(str); kfree(str);
if (err) if (err)
goto exit_free; goto exit_free;
entry->rule.buflen += f_val;
break; break;
case AUDIT_INODE: case AUDIT_INODE:
f->val = f_val;
err = audit_to_inode(&entry->rule, f); err = audit_to_inode(&entry->rule, f);
if (err) if (err)
goto exit_free; goto exit_free;
break; break;
case AUDIT_FILTERKEY: case AUDIT_FILTERKEY:
if (entry->rule.filterkey || f->val > AUDIT_MAX_KEY_LEN) if (entry->rule.filterkey || f_val > AUDIT_MAX_KEY_LEN)
goto exit_free; goto exit_free;
str = audit_unpack_string(&bufp, &remain, f->val); str = audit_unpack_string(&bufp, &remain, f_val);
if (IS_ERR(str))
goto exit_free;
entry->rule.buflen += f->val;
entry->rule.filterkey = str;
break;
case AUDIT_EXE:
if (entry->rule.exe || f->val > PATH_MAX)
goto exit_free;
str = audit_unpack_string(&bufp, &remain, f->val);
if (IS_ERR(str)) { if (IS_ERR(str)) {
err = PTR_ERR(str); err = PTR_ERR(str);
goto exit_free; goto exit_free;
} }
entry->rule.buflen += f->val; entry->rule.buflen += f_val;
entry->rule.filterkey = str;
audit_mark = audit_alloc_mark(&entry->rule, str, f->val); break;
case AUDIT_EXE:
if (entry->rule.exe || f_val > PATH_MAX)
goto exit_free;
str = audit_unpack_string(&bufp, &remain, f_val);
if (IS_ERR(str)) {
err = PTR_ERR(str);
goto exit_free;
}
audit_mark = audit_alloc_mark(&entry->rule, str, f_val);
if (IS_ERR(audit_mark)) { if (IS_ERR(audit_mark)) {
kfree(str); kfree(str);
err = PTR_ERR(audit_mark); err = PTR_ERR(audit_mark);
goto exit_free; goto exit_free;
} }
entry->rule.buflen += f_val;
entry->rule.exe = audit_mark; entry->rule.exe = audit_mark;
break; break;
default:
f->val = f_val;
break;
} }
} }