6db4831e98
Android 14
388 lines
9.1 KiB
C
388 lines
9.1 KiB
C
/*
|
|
* FIVE State machine
|
|
*
|
|
* Copyright (C) 2017 Samsung Electronics, Inc.
|
|
* Egor Uleyskiy, <e.uleyskiy@samsung.com>
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/task_integrity.h>
|
|
#include "five_audit.h"
|
|
#include "five_state.h"
|
|
#include "five_hooks.h"
|
|
#include "five_cache.h"
|
|
#include "five_dsms.h"
|
|
|
|
enum task_integrity_state_cause {
|
|
STATE_CAUSE_UNKNOWN,
|
|
STATE_CAUSE_DIGSIG,
|
|
STATE_CAUSE_DMV_PROTECTED,
|
|
STATE_CAUSE_TRUSTED,
|
|
STATE_CAUSE_HMAC,
|
|
STATE_CAUSE_SYSTEM_LABEL,
|
|
STATE_CAUSE_NOCERT,
|
|
STATE_CAUSE_TAMPERED,
|
|
STATE_CAUSE_MISMATCH_LABEL,
|
|
STATE_CAUSE_FSV_PROTECTED
|
|
};
|
|
|
|
struct task_verification_result {
|
|
enum task_integrity_value new_tint;
|
|
enum task_integrity_value prev_tint;
|
|
enum task_integrity_state_cause cause;
|
|
};
|
|
|
|
static const char *task_integrity_state_str(
|
|
enum task_integrity_state_cause cause)
|
|
{
|
|
const char *str = "unknown";
|
|
|
|
switch (cause) {
|
|
case STATE_CAUSE_DIGSIG:
|
|
str = "digsig";
|
|
break;
|
|
case STATE_CAUSE_DMV_PROTECTED:
|
|
str = "dmv_protected";
|
|
break;
|
|
case STATE_CAUSE_FSV_PROTECTED:
|
|
str = "fsv_protected";
|
|
break;
|
|
case STATE_CAUSE_TRUSTED:
|
|
str = "trusted";
|
|
break;
|
|
case STATE_CAUSE_HMAC:
|
|
str = "hmac";
|
|
break;
|
|
case STATE_CAUSE_SYSTEM_LABEL:
|
|
str = "system_label";
|
|
break;
|
|
case STATE_CAUSE_NOCERT:
|
|
str = "nocert";
|
|
break;
|
|
case STATE_CAUSE_MISMATCH_LABEL:
|
|
str = "mismatch_label";
|
|
break;
|
|
case STATE_CAUSE_TAMPERED:
|
|
str = "tampered";
|
|
break;
|
|
case STATE_CAUSE_UNKNOWN:
|
|
str = "unknown";
|
|
break;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
static enum task_integrity_reset_cause state_to_reason_cause(
|
|
enum task_integrity_state_cause cause)
|
|
{
|
|
enum task_integrity_reset_cause reset_cause;
|
|
|
|
switch (cause) {
|
|
case STATE_CAUSE_UNKNOWN:
|
|
reset_cause = CAUSE_UNKNOWN;
|
|
break;
|
|
case STATE_CAUSE_TAMPERED:
|
|
reset_cause = CAUSE_TAMPERED;
|
|
break;
|
|
case STATE_CAUSE_NOCERT:
|
|
reset_cause = CAUSE_NO_CERT;
|
|
break;
|
|
case STATE_CAUSE_MISMATCH_LABEL:
|
|
reset_cause = CAUSE_MISMATCH_LABEL;
|
|
break;
|
|
default:
|
|
/* Integrity is not NONE. */
|
|
reset_cause = CAUSE_UNSET;
|
|
break;
|
|
}
|
|
|
|
return reset_cause;
|
|
}
|
|
|
|
static int is_system_label(struct integrity_label *label)
|
|
{
|
|
if (label && label->len == 0)
|
|
return 1; /* system label */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int integrity_label_cmp(struct integrity_label *l1,
|
|
struct integrity_label *l2)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int verify_or_update_label(struct task_integrity *intg,
|
|
struct integrity_iint_cache *iint)
|
|
{
|
|
struct integrity_label *l;
|
|
struct integrity_label *file_label = iint->five_label;
|
|
int rc = 0;
|
|
|
|
if (!file_label) /* digsig doesn't have label */
|
|
return 0;
|
|
|
|
if (is_system_label(file_label))
|
|
return 0;
|
|
|
|
spin_lock(&intg->value_lock);
|
|
l = intg->label;
|
|
|
|
if (l) {
|
|
if (integrity_label_cmp(file_label, l)) {
|
|
rc = -EPERM;
|
|
goto out;
|
|
}
|
|
} else {
|
|
struct integrity_label *new_label;
|
|
|
|
new_label = kmalloc(sizeof(file_label->len) + file_label->len,
|
|
GFP_ATOMIC);
|
|
if (!new_label) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
new_label->len = file_label->len;
|
|
memcpy(new_label->data, file_label->data, new_label->len);
|
|
intg->label = new_label;
|
|
}
|
|
|
|
out:
|
|
spin_unlock(&intg->value_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static bool set_first_state(struct integrity_iint_cache *iint,
|
|
struct task_integrity *integrity,
|
|
struct task_verification_result *result)
|
|
{
|
|
enum task_integrity_value tint = INTEGRITY_NONE;
|
|
enum five_file_integrity status = five_get_cache_status(iint);
|
|
bool trusted_file = iint->five_flags & FIVE_TRUSTED_FILE;
|
|
enum task_integrity_state_cause cause = STATE_CAUSE_UNKNOWN;
|
|
|
|
result->new_tint = result->prev_tint = task_integrity_read(integrity);
|
|
task_integrity_clear(integrity);
|
|
|
|
switch (status) {
|
|
case FIVE_FILE_RSA:
|
|
if (trusted_file) {
|
|
cause = STATE_CAUSE_TRUSTED;
|
|
tint = INTEGRITY_PRELOAD_ALLOW_SIGN;
|
|
} else {
|
|
cause = STATE_CAUSE_DIGSIG;
|
|
tint = INTEGRITY_PRELOAD;
|
|
}
|
|
break;
|
|
case FIVE_FILE_FSVERITY:
|
|
case FIVE_FILE_DMVERITY:
|
|
if (trusted_file) {
|
|
cause = STATE_CAUSE_TRUSTED;
|
|
tint = INTEGRITY_DMVERITY_ALLOW_SIGN;
|
|
} else {
|
|
cause = (status == FIVE_FILE_FSVERITY)
|
|
? STATE_CAUSE_FSV_PROTECTED
|
|
: STATE_CAUSE_DMV_PROTECTED;
|
|
tint = INTEGRITY_DMVERITY;
|
|
}
|
|
break;
|
|
case FIVE_FILE_HMAC:
|
|
cause = STATE_CAUSE_HMAC;
|
|
tint = INTEGRITY_MIXED;
|
|
break;
|
|
case FIVE_FILE_FAIL:
|
|
cause = STATE_CAUSE_TAMPERED;
|
|
tint = INTEGRITY_NONE;
|
|
break;
|
|
case FIVE_FILE_UNKNOWN:
|
|
cause = STATE_CAUSE_NOCERT;
|
|
tint = INTEGRITY_NONE;
|
|
break;
|
|
default:
|
|
cause = STATE_CAUSE_NOCERT;
|
|
tint = INTEGRITY_NONE;
|
|
break;
|
|
}
|
|
|
|
task_integrity_set(integrity, tint);
|
|
result->new_tint = tint;
|
|
result->cause = cause;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool set_next_state(struct integrity_iint_cache *iint,
|
|
struct task_integrity *integrity,
|
|
struct task_verification_result *result)
|
|
{
|
|
bool is_newstate = false;
|
|
enum five_file_integrity status = five_get_cache_status(iint);
|
|
bool has_digsig = (status == FIVE_FILE_RSA);
|
|
bool dmv_protected = (status == FIVE_FILE_DMVERITY);
|
|
bool fsv_protected = (status == FIVE_FILE_FSVERITY);
|
|
bool xv_protected = dmv_protected || fsv_protected;
|
|
struct integrity_label *label = iint->five_label;
|
|
enum task_integrity_state_cause cause = STATE_CAUSE_UNKNOWN;
|
|
enum task_integrity_value state_tint = INTEGRITY_NONE;
|
|
|
|
result->new_tint = result->prev_tint = task_integrity_read(integrity);
|
|
|
|
if (has_digsig)
|
|
return is_newstate;
|
|
|
|
if (status == FIVE_FILE_UNKNOWN || status == FIVE_FILE_FAIL) {
|
|
spin_lock(&integrity->value_lock);
|
|
|
|
if (status == FIVE_FILE_UNKNOWN)
|
|
cause = STATE_CAUSE_NOCERT;
|
|
else
|
|
cause = STATE_CAUSE_TAMPERED;
|
|
|
|
state_tint = INTEGRITY_NONE;
|
|
is_newstate = true;
|
|
goto out;
|
|
}
|
|
|
|
if (verify_or_update_label(integrity, iint)) {
|
|
spin_lock(&integrity->value_lock);
|
|
cause = STATE_CAUSE_MISMATCH_LABEL;
|
|
state_tint = INTEGRITY_NONE;
|
|
is_newstate = true;
|
|
goto out;
|
|
}
|
|
|
|
spin_lock(&integrity->value_lock);
|
|
switch (integrity->value) {
|
|
case INTEGRITY_PRELOAD_ALLOW_SIGN:
|
|
if (xv_protected) {
|
|
cause = fsv_protected ? STATE_CAUSE_FSV_PROTECTED
|
|
: STATE_CAUSE_DMV_PROTECTED;
|
|
state_tint = INTEGRITY_DMVERITY_ALLOW_SIGN;
|
|
} else if (is_system_label(label)) {
|
|
cause = STATE_CAUSE_SYSTEM_LABEL;
|
|
state_tint = INTEGRITY_MIXED_ALLOW_SIGN;
|
|
} else {
|
|
cause = STATE_CAUSE_HMAC;
|
|
state_tint = INTEGRITY_MIXED;
|
|
}
|
|
is_newstate = true;
|
|
break;
|
|
case INTEGRITY_PRELOAD:
|
|
if (xv_protected) {
|
|
cause = fsv_protected ? STATE_CAUSE_FSV_PROTECTED
|
|
: STATE_CAUSE_DMV_PROTECTED;
|
|
state_tint = INTEGRITY_DMVERITY;
|
|
} else {
|
|
cause = STATE_CAUSE_HMAC;
|
|
state_tint = INTEGRITY_MIXED;
|
|
}
|
|
is_newstate = true;
|
|
break;
|
|
case INTEGRITY_MIXED_ALLOW_SIGN:
|
|
if (!xv_protected && !is_system_label(label)) {
|
|
cause = STATE_CAUSE_HMAC;
|
|
state_tint = INTEGRITY_MIXED;
|
|
is_newstate = true;
|
|
}
|
|
break;
|
|
case INTEGRITY_DMVERITY:
|
|
if (!xv_protected) {
|
|
cause = STATE_CAUSE_HMAC;
|
|
state_tint = INTEGRITY_MIXED;
|
|
is_newstate = true;
|
|
}
|
|
break;
|
|
case INTEGRITY_DMVERITY_ALLOW_SIGN:
|
|
if (!xv_protected) {
|
|
if (is_system_label(label)) {
|
|
cause = STATE_CAUSE_SYSTEM_LABEL;
|
|
state_tint = INTEGRITY_MIXED_ALLOW_SIGN;
|
|
} else {
|
|
cause = STATE_CAUSE_HMAC;
|
|
state_tint = INTEGRITY_MIXED;
|
|
}
|
|
is_newstate = true;
|
|
}
|
|
break;
|
|
case INTEGRITY_MIXED:
|
|
break;
|
|
case INTEGRITY_NONE:
|
|
break;
|
|
default:
|
|
// Unknown state
|
|
cause = STATE_CAUSE_UNKNOWN;
|
|
state_tint = INTEGRITY_NONE;
|
|
is_newstate = true;
|
|
}
|
|
|
|
out:
|
|
|
|
if (is_newstate) {
|
|
__task_integrity_set(integrity, state_tint);
|
|
result->new_tint = state_tint;
|
|
result->cause = cause;
|
|
}
|
|
spin_unlock(&integrity->value_lock);
|
|
|
|
return is_newstate;
|
|
}
|
|
|
|
void five_state_proceed(struct task_integrity *integrity,
|
|
struct file_verification_result *file_result)
|
|
{
|
|
struct integrity_iint_cache *iint = file_result->iint;
|
|
enum five_hooks fn = file_result->fn;
|
|
struct task_struct *task = file_result->task;
|
|
struct file *file = file_result->file;
|
|
bool is_newstate;
|
|
struct task_verification_result task_result = {};
|
|
|
|
if (!iint)
|
|
return;
|
|
|
|
if (fn == BPRM_CHECK)
|
|
is_newstate = set_first_state(iint, integrity, &task_result);
|
|
else
|
|
is_newstate = set_next_state(iint, integrity, &task_result);
|
|
|
|
if (is_newstate) {
|
|
if (task_result.new_tint == INTEGRITY_NONE) {
|
|
task_integrity_set_reset_reason(integrity,
|
|
state_to_reason_cause(task_result.cause), file);
|
|
five_hook_integrity_reset(task, file,
|
|
state_to_reason_cause(task_result.cause));
|
|
|
|
if (fn != BPRM_CHECK) {
|
|
char comm[TASK_COMM_LEN];
|
|
char filename[NAME_MAX];
|
|
char *pathbuf = NULL;
|
|
|
|
five_dsms_reset_integrity(
|
|
get_task_comm(comm, task),
|
|
task_result.cause,
|
|
five_d_path(&file->f_path, &pathbuf,
|
|
filename));
|
|
if (pathbuf)
|
|
__putname(pathbuf);
|
|
}
|
|
}
|
|
five_audit_verbose(task, file, five_get_string_fn(fn),
|
|
task_result.prev_tint, task_result.new_tint,
|
|
task_integrity_state_str(task_result.cause),
|
|
file_result->five_result);
|
|
}
|
|
}
|
|
|