476 lines
13 KiB
C
476 lines
13 KiB
C
/*
|
|
* Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <asm/barrier.h>
|
|
#include <linux/compiler.h>
|
|
#include <linux/const.h>
|
|
#ifdef CONFIG_SECURITY_DSMS
|
|
#include <linux/dsms.h>
|
|
#endif
|
|
#include <linux/file.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/types.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/random.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/stddef.h>
|
|
#ifdef CONFIG_SECURITY_DSMS
|
|
#include <linux/string.h>
|
|
#endif
|
|
#include <linux/syscalls.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/version.h>
|
|
#include <linux/vmalloc.h>
|
|
#include "include/defex_caches.h"
|
|
#include "include/defex_catch_list.h"
|
|
#include "include/defex_config.h"
|
|
#include "include/defex_internal.h"
|
|
#include "include/defex_rules.h"
|
|
|
|
#ifdef CONFIG_SECURITY_DSMS
|
|
|
|
# define PED_VIOLATION "DFX1"
|
|
# define SAFEPLACE_VIOLATION "DFX2"
|
|
# define INTEGRITY_VIOLATION "DFX3"
|
|
# define MESSAGE_BUFFER_SIZE 200
|
|
# define STORED_CREDS_SIZE 100
|
|
|
|
static void defex_report_violation(const char *violation, uint64_t counter,
|
|
int syscall, struct task_struct *p, struct file *f, uid_t stored_uid, uid_t stored_fsuid, uid_t stored_egid)
|
|
{
|
|
int usermode_result;
|
|
char message[MESSAGE_BUFFER_SIZE + 1];
|
|
|
|
const uid_t uid = uid_get_value(p->cred->uid);
|
|
const uid_t euid = uid_get_value(p->cred->euid);
|
|
const uid_t fsuid = uid_get_value(p->cred->fsuid);
|
|
const uid_t egid = uid_get_value(p->cred->egid);
|
|
const char *process_name = p->comm;
|
|
const char *program_path = defex_get_filename(p);
|
|
const pid_t pid = p->pid, tgid = p->tgid;
|
|
|
|
char *file_path = NULL, *buff = NULL;
|
|
const struct path *dpath = NULL;
|
|
|
|
char stored_creds[STORED_CREDS_SIZE + 1];
|
|
|
|
if (f != NULL) {
|
|
buff = kzalloc(PATH_MAX, GFP_KERNEL);
|
|
if (buff != NULL) {
|
|
dpath = &(f->f_path);
|
|
file_path = d_path(dpath, buff, PATH_MAX);
|
|
}
|
|
}
|
|
else {
|
|
snprintf(stored_creds, sizeof(stored_creds), "[euid=%ld fsuid=%ld egid=%ld]", (long)stored_uid, (long)stored_fsuid, (long)stored_egid);
|
|
stored_creds[sizeof(stored_creds) - 1] = 0;
|
|
}
|
|
|
|
snprintf(message, sizeof(message), "sc=%d tsk=%s (%s) pid=%u tgid=%u uid=%ld euid=%ld fsuid=%ld egid=%ld%s%s",
|
|
syscall, process_name, (program_path ? program_path : ""), pid, tgid, (long)uid, (long)euid, (long)fsuid, (long)egid,
|
|
(file_path ? " file=" : " stored "), (file_path ? file_path : stored_creds));
|
|
message[sizeof(message) - 1] = 0;
|
|
|
|
#ifdef DEFEX_DEBUG_ENABLE
|
|
printk(KERN_ERR "DEFEX Violation : feature=%s value=%ld, detail=[%s]",
|
|
violation, (long)counter, message);
|
|
#endif /* DEFEX_DEBUG_ENABLE */
|
|
usermode_result = dsms_send_message(violation, message, counter);
|
|
#ifdef DEFEX_DEBUG_ENABLE
|
|
printk(KERN_ERR "DEFEX Result : %d\n", usermode_result);
|
|
#endif /* DEFEX_DEBUG_ENABLE */
|
|
|
|
kfree(program_path);
|
|
kfree(buff);
|
|
}
|
|
#endif /* CONFIG_SECURITY_DSMS */
|
|
|
|
#ifdef DEFEX_SAFEPLACE_ENABLE
|
|
static long kill_process(struct task_struct *p)
|
|
{
|
|
read_lock(&tasklist_lock);
|
|
force_sig(SIGKILL, p);
|
|
read_unlock(&tasklist_lock);
|
|
return 0;
|
|
}
|
|
#endif /* DEFEX_SAFEPLACE_ENABLE */
|
|
|
|
#ifdef DEFEX_PED_ENABLE
|
|
static long kill_process_group(struct task_struct *p, int tgid, int pid)
|
|
{
|
|
read_lock(&tasklist_lock);
|
|
for_each_process(p) {
|
|
if (p->tgid == tgid)
|
|
send_sig(SIGKILL, p, 0);
|
|
}
|
|
send_sig(SIGKILL, current, 0);
|
|
read_unlock(&tasklist_lock);
|
|
return 0;
|
|
}
|
|
#endif /* DEFEX_PED_ENABLE */
|
|
|
|
struct file *defex_get_source_file(struct task_struct *p)
|
|
{
|
|
struct file *file_addr = NULL;
|
|
|
|
#ifdef DEFEX_CACHES_ENABLE
|
|
file_addr = defex_file_cache_find(p->pid);
|
|
|
|
if (file_addr == NULL) {
|
|
file_addr = get_mm_exe_file(p->mm);
|
|
defex_file_cache_add(p->pid, file_addr);
|
|
} else {
|
|
down_read(&p->mm->mmap_sem);
|
|
if (file_addr != p->mm->exe_file) {
|
|
file_addr = p->mm->exe_file;
|
|
if (!file_addr) {
|
|
up_read(&p->mm->mmap_sem);
|
|
return NULL;
|
|
}
|
|
defex_file_cache_update(file_addr);
|
|
get_file(file_addr);
|
|
}
|
|
up_read(&p->mm->mmap_sem);
|
|
}
|
|
#else
|
|
file_addr = get_mm_exe_file(p->mm);
|
|
#endif /* DEFEX_CACHES_ENABLE */
|
|
return file_addr;
|
|
}
|
|
|
|
char *defex_get_filename(struct task_struct *p)
|
|
{
|
|
struct file *exe_file = NULL;
|
|
const struct path *dpath = NULL;
|
|
char *path = NULL, *buff = NULL;
|
|
char *filename = NULL;
|
|
|
|
exe_file = defex_get_source_file(p);
|
|
if (!exe_file)
|
|
goto out_filename;
|
|
|
|
dpath = &exe_file->f_path;
|
|
|
|
buff = kzalloc(PATH_MAX, GFP_ATOMIC);
|
|
if (buff)
|
|
path = d_path(dpath, buff, PATH_MAX);
|
|
|
|
#ifndef DEFEX_CACHES_ENABLE
|
|
fput(exe_file);
|
|
#endif /* DEFEX_CACHES_ENABLE */
|
|
|
|
out_filename:
|
|
if (IS_ERR(path) || !path)
|
|
filename = kstrdup("<unknown filename>", GFP_ATOMIC);
|
|
else
|
|
filename = kstrdup(path, GFP_ATOMIC);
|
|
|
|
kfree(buff);
|
|
return filename;
|
|
}
|
|
|
|
#ifdef DEFEX_PED_ENABLE
|
|
static int task_defex_is_secured(struct task_struct *p)
|
|
{
|
|
struct file *exe_file = NULL;
|
|
const struct path *dpath = NULL;
|
|
int is_secured = 1;
|
|
|
|
exe_file = defex_get_source_file(p);
|
|
if (!exe_file)
|
|
goto skip_secured;
|
|
|
|
dpath = &exe_file->f_path;
|
|
if (!dpath->dentry || !dpath->dentry->d_inode)
|
|
goto out_secured;
|
|
|
|
is_secured = !rules_lookup(dpath, feature_ped_exception, exe_file);
|
|
|
|
out_secured:
|
|
#ifndef DEFEX_CACHES_ENABLE
|
|
fput(exe_file);
|
|
#endif /* DEFEX_CACHES_ENABLE */
|
|
skip_secured:
|
|
return is_secured;
|
|
}
|
|
#endif /* DEFEX_PED_ENABLE */
|
|
|
|
#ifdef DEFEX_PED_ENABLE
|
|
static int at_same_group(unsigned int uid1, unsigned int uid2)
|
|
{
|
|
static const unsigned int lod_base = 0x61A8;
|
|
|
|
/* allow the weaken privilege */
|
|
if (uid1 >= 10000 && uid2 < 10000) return 1;
|
|
/* allow traverse in the same class */
|
|
if ((uid1 / 1000) == (uid2 / 1000)) return 1;
|
|
/* allow LoD process */
|
|
return ((uid1 >> 16) == lod_base) && ((uid2 >> 16) == lod_base);
|
|
}
|
|
|
|
static int at_same_group_gid(unsigned int gid1, unsigned int gid2)
|
|
{
|
|
static const unsigned int lod_base = 0x61A8, inet = 3003;
|
|
|
|
/* allow the weaken privilege */
|
|
if (gid1 >= 10000 && gid2 < 10000) return 1;
|
|
/* allow traverse in the same class */
|
|
if ((gid1 / 1000) == (gid2 / 1000)) return 1;
|
|
/* allow LoD process */
|
|
return (((gid1 >> 16) == lod_base) || (gid1 == inet)) && ((gid2 >> 16) == lod_base);
|
|
}
|
|
|
|
/* Cred. violation feature decision function */
|
|
#ifndef CONFIG_SECURITY_DSMS
|
|
static int task_defex_check_creds(struct task_struct *p)
|
|
#else
|
|
static int task_defex_check_creds(struct task_struct *p, int syscall)
|
|
#endif /* CONFIG_SECURITY_DSMS */
|
|
{
|
|
char *path = NULL;
|
|
int check_deeper, case_num;
|
|
unsigned int cur_uid, cur_euid, cur_fsuid, cur_egid;
|
|
unsigned int uid, fsuid, egid;
|
|
unsigned int g_uid, g_fsuid, g_egid;
|
|
static const unsigned int dead_uid = 0xDEADBEAF;
|
|
|
|
if (!is_task_creds_ready() || !p->cred)
|
|
goto out;
|
|
|
|
get_task_creds(p->pid, &uid, &fsuid, &egid);
|
|
if (p->tgid != p->pid) {
|
|
get_task_creds(p->tgid, &g_uid, &g_fsuid, &g_egid);
|
|
} else {
|
|
g_uid = uid;
|
|
g_fsuid = fsuid;
|
|
g_egid = egid;
|
|
}
|
|
|
|
cur_uid = uid_get_value(p->cred->uid);
|
|
cur_euid = uid_get_value(p->cred->euid);
|
|
cur_fsuid = uid_get_value(p->cred->fsuid);
|
|
cur_egid = uid_get_value(p->cred->egid);
|
|
|
|
if (!uid) {
|
|
if (CHECK_ROOT_CREDS(p))
|
|
set_task_creds(p->pid, 1, 1, 1);
|
|
else
|
|
set_task_creds(p->pid, cur_euid, cur_fsuid, cur_egid);
|
|
} else if (uid == 1) {
|
|
if (!CHECK_ROOT_CREDS(p))
|
|
set_task_creds(p->pid, cur_euid, cur_fsuid, cur_egid);
|
|
} else if (uid == dead_uid || g_uid == dead_uid) {
|
|
path = defex_get_filename(p);
|
|
pr_crit("defex[5]: process wasn't killed [task=%s, filename=%s, uid=%d]\n", p->comm, path, cur_uid);
|
|
pr_crit("defex[5]: uid=%d euid=%d fsuid=%d egid=%d\n",
|
|
cur_uid, cur_euid, cur_fsuid, cur_egid);
|
|
goto exit;
|
|
} else {
|
|
check_deeper = 0;
|
|
if ((cur_uid != uid) || (cur_euid != uid) || !((cur_fsuid == fsuid) || (cur_fsuid == uid)) || (cur_egid != egid)) {
|
|
check_deeper = 1;
|
|
set_task_creds(p->pid, cur_euid, cur_fsuid, cur_egid);
|
|
}
|
|
if (check_deeper && (!at_same_group(cur_uid, uid) ||
|
|
!at_same_group(cur_euid, uid) ||
|
|
!at_same_group_gid(cur_egid, egid) ||
|
|
!at_same_group(cur_fsuid, fsuid)) &&
|
|
task_defex_is_secured(p)) {
|
|
set_task_creds(p->pid, dead_uid, dead_uid, dead_uid);
|
|
if (p->tgid != p->pid)
|
|
set_task_creds(p->tgid, dead_uid, dead_uid, dead_uid);
|
|
case_num = 1;
|
|
goto show_violation;
|
|
}
|
|
|
|
if (p->tgid != p->pid) {
|
|
if ((g_uid > 1) && (!at_same_group(cur_uid, g_uid) ||
|
|
!at_same_group(cur_euid, g_uid) ||
|
|
!at_same_group_gid(cur_egid, g_egid)) &&
|
|
task_defex_is_secured(p)) {
|
|
set_task_creds(p->tgid, dead_uid, dead_uid, dead_uid);
|
|
if (p->tgid != p->pid)
|
|
set_task_creds(p->pid, dead_uid, dead_uid, dead_uid);
|
|
case_num = 2;
|
|
goto show_violation;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((p->tgid != p->pid) && CHECK_ROOT_CREDS(p) && !CHECK_ROOT_CREDS(p->real_parent)) {
|
|
if ((g_uid > 1) && task_defex_is_secured(p)) {
|
|
set_task_creds(p->tgid, dead_uid, dead_uid, dead_uid);
|
|
if (p->tgid != p->pid)
|
|
set_task_creds(p->pid, dead_uid, dead_uid, dead_uid);
|
|
case_num = 3;
|
|
goto show_violation;
|
|
}
|
|
}
|
|
|
|
if (CHECK_ROOT_CREDS(p) && !CHECK_ROOT_CREDS(p->real_parent) &&
|
|
task_defex_is_secured(p)) {
|
|
set_task_creds(p->pid, dead_uid, dead_uid, dead_uid);
|
|
if (p->tgid != p->pid)
|
|
set_task_creds(p->tgid, dead_uid, dead_uid, dead_uid);
|
|
case_num = 4;
|
|
goto show_violation;
|
|
}
|
|
|
|
out:
|
|
return DEFEX_ALLOW;
|
|
|
|
show_violation:
|
|
path = defex_get_filename(p);
|
|
pr_crit("defex[%d]: credential violation [task=%s, filename=%s, uid=%d]\n",
|
|
case_num, p->comm, (path ? path : ""), cur_uid);
|
|
pr_crit("defex[%d]: stored [euid=%d fsuid=%d egid=%d] uid=%d euid=%d fsuid=%d egid=%d\n",
|
|
case_num, uid, fsuid, egid, cur_uid, cur_euid, cur_fsuid, cur_egid);
|
|
|
|
#ifdef CONFIG_SECURITY_DSMS
|
|
defex_report_violation(PED_VIOLATION, 0, syscall, p, NULL, uid, fsuid, egid);
|
|
#endif /* CONFIG_SECURITY_DSMS */
|
|
|
|
exit:
|
|
kfree(path);
|
|
return -DEFEX_DENY;
|
|
}
|
|
#endif /* DEFEX_PED_ENABLE */
|
|
|
|
#ifdef DEFEX_SAFEPLACE_ENABLE
|
|
/* Safeplace feature decision function */
|
|
static int task_defex_safeplace(struct task_struct *p, struct file *f)
|
|
{
|
|
static const char def[] = "";
|
|
int ret = DEFEX_ALLOW, is_violation = 0;
|
|
char *proc_file, *new_file = (char *)def, *buff;
|
|
const struct path *dpath = NULL;
|
|
|
|
if (!CHECK_ROOT_CREDS(p))
|
|
goto out;
|
|
|
|
if (IS_ERR(f))
|
|
goto out;
|
|
|
|
dpath = &f->f_path;
|
|
if (!dpath->dentry || !dpath->dentry->d_inode)
|
|
goto out;
|
|
|
|
is_violation = rules_lookup(dpath, feature_safeplace_path, f);
|
|
#ifdef DEFEX_INTEGRITY_ENABLE
|
|
if (is_violation != DEFEX_INTEGRITY_FAIL)
|
|
#endif /* DEFEX_INTEGRITY_ENABLE */
|
|
is_violation = !is_violation;
|
|
|
|
if (is_violation) {
|
|
ret = -DEFEX_DENY;
|
|
proc_file = defex_get_filename(p);
|
|
buff = kzalloc(PATH_MAX, GFP_ATOMIC);
|
|
if (buff)
|
|
new_file = d_path(dpath, buff, PATH_MAX);
|
|
|
|
#ifdef DEFEX_INTEGRITY_ENABLE
|
|
if (is_violation == DEFEX_INTEGRITY_FAIL) {
|
|
pr_crit("defex: integrity violation [task=%s (%s), child=%s, uid=%d]\n",
|
|
p->comm, (proc_file ? proc_file : ""), new_file, uid_get_value(p->cred->uid));
|
|
#ifdef CONFIG_SECURITY_DSMS
|
|
defex_report_violation(INTEGRITY_VIOLATION, 0, __DEFEX_execve, p, f, 0, 0, 0);
|
|
#endif /* CONFIG_SECURITY_DSMS */
|
|
|
|
/* Temporary make permissive mode for tereble
|
|
* system image is changed as google's and defex might not work
|
|
*/
|
|
ret = DEFEX_ALLOW;
|
|
}
|
|
else
|
|
#endif /* DEFEX_INTEGRITY_ENABLE */
|
|
{
|
|
pr_crit("defex: safeplace violation [task=%s (%s), child=%s, uid=%d]\n",
|
|
p->comm, (proc_file ? proc_file : ""), new_file, uid_get_value(p->cred->uid));
|
|
#ifdef CONFIG_SECURITY_DSMS
|
|
defex_report_violation(SAFEPLACE_VIOLATION, 0, __DEFEX_execve, p, f, 0, 0, 0);
|
|
#endif /* CONFIG_SECURITY_DSMS */
|
|
}
|
|
|
|
kfree(proc_file);
|
|
kfree(buff);
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
#endif /* DEFEX_SAFEPLACE_ENABLE */
|
|
|
|
/* Main decision function */
|
|
int task_defex_enforce(struct task_struct *p, struct file *f, int syscall)
|
|
{
|
|
int ret = DEFEX_ALLOW;
|
|
int feature_flag;
|
|
const struct local_syscall_struct *item;
|
|
|
|
if (!p || p->pid == 1 || !p->mm)
|
|
return ret;
|
|
|
|
if (syscall < 0) {
|
|
item = get_local_syscall(-syscall);
|
|
if (!item)
|
|
return ret;
|
|
syscall = item->local_syscall;
|
|
}
|
|
|
|
feature_flag = defex_get_features();
|
|
|
|
#ifdef DEFEX_PED_ENABLE
|
|
/* Credential escalation feature */
|
|
if (feature_flag & FEATURE_CHECK_CREDS) {
|
|
#ifndef CONFIG_SECURITY_DSMS
|
|
ret = task_defex_check_creds(p);
|
|
#else
|
|
ret = task_defex_check_creds(p, syscall);
|
|
#endif /* CONFIG_SECURITY_DSMS */
|
|
if (ret) {
|
|
if (!(feature_flag & FEATURE_CHECK_CREDS_SOFT)) {
|
|
kill_process_group(p, p->tgid, p->pid);
|
|
return -DEFEX_DENY;
|
|
}
|
|
}
|
|
}
|
|
#endif /* DEFEX_PED_ENABLE */
|
|
|
|
#ifdef DEFEX_SAFEPLACE_ENABLE
|
|
/* Safeplace feature */
|
|
if (feature_flag & FEATURE_SAFEPLACE) {
|
|
if (syscall == __DEFEX_execve) {
|
|
ret = task_defex_safeplace(p, f);
|
|
if (ret == -DEFEX_DENY) {
|
|
if (!(feature_flag & FEATURE_SAFEPLACE_SOFT)) {
|
|
kill_process(p);
|
|
return -DEFEX_DENY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* DEFEX_SAFEPLACE_ENABLE */
|
|
|
|
return DEFEX_ALLOW;
|
|
}
|
|
|
|
int task_defex_zero_creds(struct task_struct *tsk)
|
|
{
|
|
if (is_task_creds_ready())
|
|
delete_task_creds(tsk->pid);
|
|
|
|
#ifdef DEFEX_CACHES_ENABLE
|
|
defex_file_cache_delete(tsk->pid);
|
|
#endif /* DEFEX_CACHES_ENABLE */
|
|
|
|
return 0;
|
|
}
|
|
|