blob: b1174ef164fef40c5cd1240861d5732c116edc9a [file] [log] [blame]
/*
* 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 *intg = foo;
memset(intg, 0, sizeof(*intg));
spin_lock_init(&intg->value_lock);
spin_lock_init(&intg->list_lock);
}
static int __init 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;
}
security_initcall(task_integrity_cache_init);
struct task_integrity *task_integrity_alloc(void)
{
struct task_integrity *intg;
intg = kmem_cache_alloc(task_integrity_cache, GFP_KERNEL);
if (intg) {
atomic_set(&intg->usage_count, 1);
INIT_LIST_HEAD(&intg->events.list);
}
return intg;
}
void task_integrity_free(struct task_integrity *intg)
{
if (intg) {
/* 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(intg->label);
intg->label = NULL;
intg->user_value = INTEGRITY_NONE;
intg->value = INTEGRITY_NONE;
atomic_set(&intg->usage_count, 0);
intg->reset_cause = CAUSE_UNSET;
if (intg->reset_file) {
fput(intg->reset_file);
intg->reset_file = NULL;
}
kmem_cache_free(task_integrity_cache, intg);
}
}
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 *intg,
enum task_integrity_reset_cause cause, struct file *file)
{
if (intg->reset_cause != CAUSE_UNSET)
return;
intg->reset_cause = cause;
if (file) {
get_file(file);
intg->reset_file = file;
}
}