197 lines
4.8 KiB
C
197 lines
4.8 KiB
C
|
/*
|
||
|
* Task Integrity Verifier
|
||
|
*
|
||
|
* Copyright (C) 2016 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 <linux/file.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include "five_porting.h"
|
||
|
|
||
|
static struct kmem_cache *task_integrity_cache;
|
||
|
|
||
|
static void init_once(void *foo)
|
||
|
{
|
||
|
struct task_integrity *tint = foo;
|
||
|
|
||
|
memset(tint, 0, sizeof(*tint));
|
||
|
spin_lock_init(&tint->value_lock);
|
||
|
spin_lock_init(&tint->list_lock);
|
||
|
}
|
||
|
|
||
|
int __init five_task_integrity_cache_init(void)
|
||
|
{
|
||
|
task_integrity_cache = kmem_cache_create("task_integrity_cache",
|
||
|
sizeof(struct task_integrity), 0,
|
||
|
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_NOTRACK, init_once);
|
||
|
|
||
|
if (!task_integrity_cache)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
struct task_integrity *task_integrity_alloc(void)
|
||
|
{
|
||
|
struct task_integrity *tint;
|
||
|
|
||
|
tint = kmem_cache_alloc(task_integrity_cache, GFP_KERNEL);
|
||
|
if (tint) {
|
||
|
atomic_set(&tint->usage_count, 1);
|
||
|
INIT_LIST_HEAD(&tint->events.list);
|
||
|
}
|
||
|
|
||
|
return tint;
|
||
|
}
|
||
|
|
||
|
void task_integrity_free(struct task_integrity *tint)
|
||
|
{
|
||
|
if (tint) {
|
||
|
/* These values should be changed under "value_lock" spinlock.
|
||
|
But then lockdep prints warning because this function can be called
|
||
|
from sw-irq (from function free_task).
|
||
|
Actually deadlock never happens because this function is called
|
||
|
only if usage_count is 0 (no reference to this struct),
|
||
|
so changing these values without spinlock is safe.
|
||
|
*/
|
||
|
kfree(tint->label);
|
||
|
tint->label = NULL;
|
||
|
tint->user_value = INTEGRITY_NONE;
|
||
|
tint->value = INTEGRITY_NONE;
|
||
|
atomic_set(&tint->usage_count, 0);
|
||
|
|
||
|
tint->reset_cause = CAUSE_UNSET;
|
||
|
if (tint->reset_file) {
|
||
|
fput(tint->reset_file);
|
||
|
tint->reset_file = NULL;
|
||
|
}
|
||
|
|
||
|
kmem_cache_free(task_integrity_cache, tint);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void task_integrity_clear(struct task_integrity *tint)
|
||
|
{
|
||
|
task_integrity_set(tint, INTEGRITY_NONE);
|
||
|
spin_lock(&tint->value_lock);
|
||
|
kfree(tint->label);
|
||
|
tint->label = NULL;
|
||
|
spin_unlock(&tint->value_lock);
|
||
|
|
||
|
tint->reset_cause = CAUSE_UNSET;
|
||
|
if (tint->reset_file) {
|
||
|
fput(tint->reset_file);
|
||
|
tint->reset_file = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int copy_label(struct task_integrity *from, struct task_integrity *to)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct integrity_label *l = NULL;
|
||
|
|
||
|
if (task_integrity_read(from) && from->label)
|
||
|
l = from->label;
|
||
|
|
||
|
if (l) {
|
||
|
struct integrity_label *label;
|
||
|
|
||
|
label = kmalloc(sizeof(*label) + l->len, GFP_ATOMIC);
|
||
|
if (!label) {
|
||
|
ret = -ENOMEM;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
label->len = l->len;
|
||
|
memcpy(label->data, l->data, label->len);
|
||
|
to->label = label;
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int task_integrity_copy(struct task_integrity *from, struct task_integrity *to)
|
||
|
{
|
||
|
int rc = -EPERM;
|
||
|
enum task_integrity_value value = task_integrity_read(from);
|
||
|
|
||
|
task_integrity_set(to, value);
|
||
|
|
||
|
if (list_empty(&from->events.list))
|
||
|
to->user_value = value;
|
||
|
|
||
|
rc = copy_label(from, to);
|
||
|
|
||
|
to->reset_cause = from->reset_cause;
|
||
|
if (from->reset_file) {
|
||
|
get_file(from->reset_file);
|
||
|
to->reset_file = from->reset_file;
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
char const * const tint_reset_cause_to_string(
|
||
|
enum task_integrity_reset_cause cause)
|
||
|
{
|
||
|
static const char * const tint_cause2str[] = {
|
||
|
[CAUSE_UNSET] = "unset",
|
||
|
[CAUSE_UNKNOWN] = "unknown",
|
||
|
[CAUSE_MISMATCH_LABEL] = "mismatch-label",
|
||
|
[CAUSE_BAD_FS] = "bad-fs",
|
||
|
[CAUSE_NO_CERT] = "no-cert",
|
||
|
[CAUSE_INVALID_HASH_LENGTH] = "invalid-hash-length",
|
||
|
[CAUSE_INVALID_HEADER] = "invalid-header",
|
||
|
[CAUSE_CALC_HASH_FAILED] = "calc-hash-failed",
|
||
|
[CAUSE_INVALID_LABEL_DATA] = "invalid-label-data",
|
||
|
[CAUSE_INVALID_SIGNATURE_DATA] = "invalid-signature-data",
|
||
|
[CAUSE_INVALID_HASH] = "invalid-hash",
|
||
|
[CAUSE_INVALID_CALC_CERT_HASH] = "invalid-calc-cert-hash",
|
||
|
[CAUSE_INVALID_UPDATE_LABEL] = "invalid-update-label",
|
||
|
[CAUSE_INVALID_SIGNATURE] = "invalid-signature",
|
||
|
[CAUSE_UKNOWN_FIVE_DATA] = "unknown-five-data",
|
||
|
[CAUSE_PTRACE] = "ptrace",
|
||
|
[CAUSE_VMRW] = "vmrw",
|
||
|
[CAUSE_EXEC] = "exec",
|
||
|
[CAUSE_TAMPERED] = "tampered",
|
||
|
[CAUSE_MAX] = "incorrect-cause",
|
||
|
};
|
||
|
|
||
|
if (cause > CAUSE_MAX)
|
||
|
cause = CAUSE_MAX;
|
||
|
|
||
|
return tint_cause2str[cause];
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* task_integrity_set_reset_reason
|
||
|
*
|
||
|
* Only first call of this function per task will have effect, because first
|
||
|
* reason will be root cause.
|
||
|
*/
|
||
|
void task_integrity_set_reset_reason(struct task_integrity *tint,
|
||
|
enum task_integrity_reset_cause cause, struct file *file)
|
||
|
{
|
||
|
if (tint->reset_cause != CAUSE_UNSET)
|
||
|
return;
|
||
|
|
||
|
tint->reset_cause = cause;
|
||
|
if (file) {
|
||
|
get_file(file);
|
||
|
tint->reset_file = file;
|
||
|
}
|
||
|
}
|