blob: 76f4028c45463b3b3ffa3de01ee553e952f5a70a [file] [log] [blame]
/*
* ICD Driver
*
* Copyright (C) 2018 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/module.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/version.h>
#include "oemflag.h"
#include "icd_protect_list.h"
#include "five_hooks.h"
#include "icd.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
#include <linux/sched/mm.h>
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
#include <linux/string_helpers.h>
#endif
static void icd_hook_integrity_reset(struct task_struct *task,
struct file *file, enum task_integrity_reset_cause cause);
static struct five_hook_list five_ops[] = {
FIVE_HOOK_INIT(integrity_reset2, icd_hook_integrity_reset),
};
__visible_for_testing bool contains_str(const char * const array[], const char *str)
{
const char * const *p;
for (p = &array[0]; *p != NULL; ++p) {
if (strncmp(str, *p, PATH_MAX) == 0)
return true;
}
return false;
}
#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 4, 0)
static void fix_dpath(const struct path *path, char *pathbuf, char *pathname)
{
/* `d_path' appends " (deleted)" string if a file is unlinked. Below
* code removes it.
* `d_path' fills the buffer from the end of it therefore we can easily
* calculate the length of the pathname.
*/
const long pathname_size = pathbuf + PATH_MAX - pathname;
static const char str_deleted[] = " (deleted)";
const int deleted_size = sizeof(str_deleted);
if (pathname_size > deleted_size && d_unlinked(path->dentry)) {
char *start_deleted = pathbuf + PATH_MAX - deleted_size;
if (!strncmp(str_deleted, start_deleted, deleted_size))
*start_deleted = '\0';
}
}
#else
static const char *get_first_argument_from_cmdline(struct task_struct *task)
{
char *buffer, *quoted;
int i, res, pos = 0;
buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buffer)
return NULL;
res = get_cmdline(task, buffer, PAGE_SIZE - 1);
buffer[res] = '\0';
/* Collapse trailing NULLs, leave res pointing to last non-NULL. */
while (--res >= 0 && buffer[res] == '\0')
;
/* Find first argument */
for (i = 0; i <= res; i++) {
if (buffer[i] == '\0') {
pos = i;
break;
}
}
if (pos == 0) {
if(buffer)
kfree(buffer);
return NULL;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
/* Make sure result is printable. */
quoted = kstrdup_quotable(buffer+pos+1, GFP_KERNEL);
#else
quoted = kmalloc(PAGE_SIZE, GFP_KERNEL);
strncpy(quoted, buffer+pos+1, strlen(buffer+pos+1));
#endif
kfree(buffer);
return (const char *)quoted;
}
#endif
static bool cmdline_check(struct task_struct *task, const char *str)
{
const char *first_arg = NULL;
bool ret = false;
#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 4, 0)
first_arg = kstrdup_quotable_cmdline(task, GFP_KERNEL);
#else
first_arg = get_first_argument_from_cmdline(task);
#endif
if (first_arg != NULL) {
pr_debug("IOF drv: first_arg: %s\n", first_arg);
#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 4, 0)
if (strnstr(first_arg, str, strlen(first_arg)) != NULL)
#else
if (!strncmp(first_arg, str, strlen(str)))
#endif
ret = true;
}
kfree(first_arg);
return ret;
}
enum oemflag_id affected_oemflag_id(struct task_struct *task, const char *path)
{
if (contains_str(tz_drm_list, path)
|| cmdline_check(task, "--TZ_DRM"))
return OEMFLAG_TZ_DRM;
if (contains_str(fido_list, path)
|| cmdline_check(task, "--FIDO"))
return OEMFLAG_FIDO;
if (contains_str(cc_list, path)
|| cmdline_check(task, "--CC"))
return OEMFLAG_CC;
if (contains_str(etc_list, path)
|| cmdline_check(task, "--ETC"))
return OEMFLAG_ETC;
return OEMFLAG_NONE;
}
static const char *get_exec_path(struct task_struct *task, char **pathbuf)
{
struct mm_struct *mm;
struct file *exe_file;
struct path *exe_path;
char *pathname = NULL;
mm = get_task_mm(task);
if (!mm)
return ERR_PTR(-ENOENT);
exe_file = get_mm_exe_file(mm);
mmput(mm);
if (!exe_file)
return ERR_PTR(-ENOENT);
exe_path = &exe_file->f_path;
*pathbuf = __getname();
if (*pathbuf) {
#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 4, 0)
pathname = d_path(exe_path, *pathbuf, PATH_MAX);
#else
pathname = d_absolute_path(exe_path, *pathbuf, PATH_MAX);
#endif
if (IS_ERR(pathname)) {
__putname(*pathbuf);
*pathbuf = NULL;
pathname = NULL;
}
#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 4, 0)
fix_dpath(exe_path, *pathbuf, pathname);
#endif
}
if (!pathname || !*pathbuf) {
pr_err("IOF drv: Can't obtain absolute path: %p %p\n",
pathname, *pathbuf);
}
fput(exe_file);
return pathname ?: (const char *)exe_path->dentry->d_name.name;
}
static const char *icd_d_path(const struct path *path, char **pathbuf, char *namebuf)
{
char *pathname = NULL;
*pathbuf = __getname();
if (*pathbuf) {
#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 4, 0)
pathname = d_path(path, *pathbuf, PATH_MAX);
#else
pathname = d_absolute_path(path, *pathbuf, PATH_MAX);
#endif
if (IS_ERR(pathname)) {
__putname(*pathbuf);
*pathbuf = NULL;
pathname = NULL;
}
#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 4, 0)
fix_dpath(path, *pathbuf, pathname);
#endif
}
if (!pathname) {
strlcpy(namebuf, path->dentry->d_name.name, NAME_MAX);
pathname = namebuf;
}
return pathname;
}
static void icd_hook_integrity_reset(struct task_struct *task,
struct file *file, enum task_integrity_reset_cause cause)
{
const char *execpath = NULL;
char *pathbuf = NULL;
enum oemflag_id oemid;
pr_debug("IOF drv: observer t=%pi\n", task);
execpath = get_exec_path(task, &pathbuf);
if (IS_ERR_OR_NULL(execpath)) {
if (execpath) {
pr_err("IOF drv: get_exec_path err: %ld\n",
PTR_ERR(execpath));
}
goto out;
}
oemid = affected_oemflag_id(task, execpath);
if (oemid != OEMFLAG_NONE) {
int ret;
const char *pathname = NULL;
char *pathbuf = NULL;
char filename[NAME_MAX];
pr_info("IOF drv: %s: %u\n", execpath, oemid);
if (file) {
pathname = icd_d_path(&file->f_path, &pathbuf, filename);
pr_info("IOF drv: file=%s cause=%d\n", pathname, (int) cause);
if (pathbuf)
__putname(pathbuf);
}
ret = oem_flags_set(oemid);
if (ret)
pr_err("oem_flags_set err: %d\n", ret);
}
out:
if (pathbuf)
__putname(pathbuf);
}
static int __init icd_driver_init(void)
{
int ret = 0;
five_add_hooks(five_ops, ARRAY_SIZE(five_ops));
return ret;
}
static void __exit icd_driver_exit(void)
{
pr_info("Exit ICDriver\n");
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Samsung ICD Driver");
MODULE_VERSION("1.00");
module_init(icd_driver_init);
module_exit(icd_driver_exit);