313 lines
6 KiB
C
313 lines
6 KiB
C
/*
|
|
* Huawei Hidden kernel symbols feature
|
|
*
|
|
* Copyright (c) 2017 Huawei.
|
|
*
|
|
* Authors:
|
|
* Weilai Zhou <zhouweilai@huawei.com>
|
|
*
|
|
* 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 <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/debugfs.h>
|
|
|
|
#define MAX_HIDESYMS_NUM 10
|
|
#define SYM_NAME_LEN 128
|
|
|
|
static DEFINE_MUTEX(hidesyms_lock);
|
|
LIST_HEAD(hidesyms_blacklist);
|
|
|
|
struct symbol_node {
|
|
char *sym;
|
|
struct list_head list;
|
|
};
|
|
|
|
static const char *hw_litehidesymbs_blacklist[MAX_HIDESYMS_NUM] = {
|
|
"commit_creds",
|
|
"prepare_kernel_cred"
|
|
};
|
|
|
|
static inline struct symbol_node *litehide_sym_node_alloc(void)
|
|
{
|
|
struct symbol_node *node;
|
|
|
|
node = (struct symbol_node *)kzalloc(sizeof(struct symbol_node), GFP_KERNEL);
|
|
if (!node)
|
|
return NULL;
|
|
|
|
node->sym = kzalloc(SYM_NAME_LEN, GFP_KERNEL);
|
|
if (!node->sym) {
|
|
kfree(node);
|
|
return NULL;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
static inline void litehide_sym_node_free(struct symbol_node *node)
|
|
{
|
|
if (node) {
|
|
kfree(node->sym);
|
|
kfree(node);
|
|
}
|
|
}
|
|
|
|
/*Clear hidesyms_blacklist and free all buffer*/
|
|
static void clear_blacklist(void)
|
|
{
|
|
struct symbol_node *node, *tmp;
|
|
|
|
mutex_lock(&hidesyms_lock);
|
|
list_for_each_entry_safe(node, tmp, &hidesyms_blacklist, list) {
|
|
list_del(&node->list);
|
|
|
|
kfree(node->sym);
|
|
kfree(node);
|
|
}
|
|
mutex_unlock(&hidesyms_lock);
|
|
}
|
|
|
|
/*
|
|
* search the symbol from the hidden symbols' blacklist.
|
|
* if found, return the symbol_node; if not, return NULL.
|
|
*/
|
|
static inline struct symbol_node *find_sym_from_blacklist(const char *symbol)
|
|
{
|
|
struct symbol_node *node = NULL;
|
|
|
|
if (!symbol)
|
|
return NULL;
|
|
|
|
list_for_each_entry(node, &hidesyms_blacklist, list) {
|
|
if (node->sym == NULL)
|
|
continue;
|
|
|
|
if (!strcmp(symbol, node->sym))
|
|
return node;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* check if it's a hidden symbol in the blacklist
|
|
*/
|
|
bool is_hide_symbols(const char *symbol)
|
|
{
|
|
struct symbol_node *hidesym_node;
|
|
|
|
mutex_lock(&hidesyms_lock);
|
|
|
|
hidesym_node = find_sym_from_blacklist(symbol);
|
|
if (hidesym_node) {
|
|
mutex_unlock(&hidesyms_lock);
|
|
return true;
|
|
}
|
|
|
|
mutex_unlock(&hidesyms_lock);
|
|
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL(is_hide_symbols);
|
|
|
|
#ifdef CONFIG_HUAWEI_HIDESYMS_DEBUGFS
|
|
static struct dentry *hidesym_d;
|
|
|
|
static int s_show(struct seq_file *file, void *v)
|
|
{
|
|
const struct symbol_node *p = list_entry(v, struct symbol_node, list);
|
|
if (p == NULL || p->sym == NULL)
|
|
return -EINVAL;
|
|
|
|
seq_printf(file, "%s\n", p->sym);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *s_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
mutex_lock(&hidesyms_lock);
|
|
return seq_list_start(&hidesyms_blacklist, *pos);
|
|
}
|
|
|
|
static void *s_next(struct seq_file *file, void *v, loff_t *pos)
|
|
{
|
|
return seq_list_next(v, &hidesyms_blacklist, pos);
|
|
}
|
|
|
|
static void s_stop(struct seq_file *m, void *p)
|
|
{
|
|
mutex_unlock(&hidesyms_lock);
|
|
}
|
|
|
|
static const struct seq_operations hide_syms_sops = {
|
|
.start = s_start,
|
|
.next = s_next,
|
|
.stop = s_stop,
|
|
.show = s_show,
|
|
};
|
|
|
|
static int parse_user_input(struct symbol_node *node, const char __user *ubuf,
|
|
size_t cnt)
|
|
{
|
|
char ch;
|
|
size_t read = 0, index = 0;
|
|
int ret = -1;
|
|
|
|
ret = get_user(ch, ubuf++);
|
|
if (ret)
|
|
goto out;
|
|
|
|
read++;
|
|
cnt--;
|
|
|
|
while (cnt && !isspace(ch)) {
|
|
if (index < SYM_NAME_LEN - 1)
|
|
node->sym[index++] = ch;
|
|
else {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
ret = get_user(ch, ubuf++);
|
|
if (ret)
|
|
goto out;
|
|
|
|
read++;
|
|
cnt--;
|
|
}
|
|
|
|
if (isspace(ch) || cnt == 0)
|
|
node->sym[index] = '\0';
|
|
else {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
ret = read;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int litehide_syms_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &hide_syms_sops);
|
|
}
|
|
|
|
static ssize_t litehide_syms_write(struct file *filp, const char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
struct symbol_node *node;
|
|
ssize_t write = 0;
|
|
|
|
/* Clear the hidesym_blacklist by 'echo > litehide_syms' */
|
|
if (cnt == 1 && *ubuf == '\n') {
|
|
clear_blacklist();
|
|
|
|
return cnt;
|
|
}
|
|
|
|
mutex_lock(&hidesyms_lock);
|
|
|
|
while (cnt > write) {
|
|
int count;
|
|
|
|
node = litehide_sym_node_alloc();
|
|
if (!node) {
|
|
mutex_unlock(&hidesyms_lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
count = parse_user_input(node, ubuf + write, cnt - write);
|
|
if (count > 0) {
|
|
if (!find_sym_from_blacklist(node->sym))
|
|
list_add_tail(&node->list, &hidesyms_blacklist);
|
|
|
|
*ppos += count;
|
|
write += count;
|
|
count = 0;
|
|
} else {
|
|
litehide_sym_node_free(node);
|
|
mutex_unlock(&hidesyms_lock);
|
|
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&hidesyms_lock);
|
|
|
|
return write;
|
|
}
|
|
|
|
static const struct file_operations hide_syms_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = litehide_syms_open,
|
|
.read = seq_read,
|
|
.write = litehide_syms_write,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
#endif
|
|
|
|
static __init int initialize_blacklist(void)
|
|
{
|
|
int i;
|
|
struct symbol_node *node;
|
|
|
|
for (i = 0; i < MAX_HIDESYMS_NUM; i++) {
|
|
if (!hw_litehidesymbs_blacklist[i])
|
|
continue;
|
|
|
|
node = litehide_sym_node_alloc();
|
|
if (!node)
|
|
return -ENOMEM;
|
|
|
|
if (strlen(hw_litehidesymbs_blacklist[i]) > SYM_NAME_LEN - 1) {
|
|
litehide_sym_node_free(node);
|
|
return -EINVAL;
|
|
}
|
|
(void)strncpy(node->sym, hw_litehidesymbs_blacklist[i], strlen(hw_litehidesymbs_blacklist[i]));
|
|
|
|
list_add_tail(&node->list, &hidesyms_blacklist);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init hw_litehide_symbols_init(void)
|
|
{
|
|
#ifdef CONFIG_HUAWEI_HIDESYMS_DEBUGFS
|
|
/*create sys/kernel/debug/litehide_syms for debug*/
|
|
hidesym_d = debugfs_create_file("litehide_syms", 0644, NULL, NULL, &hide_syms_fops);
|
|
if (!hidesym_d)
|
|
return -ENOMEM;
|
|
#endif
|
|
initialize_blacklist();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit hw_litehide_symbols_exit(void)
|
|
{
|
|
#ifdef CONFIG_HUAWEI_HIDESYMS_DEBUGFS
|
|
debugfs_remove(hidesym_d);
|
|
#endif
|
|
clear_blacklist();
|
|
}
|
|
|
|
module_init(hw_litehide_symbols_init);
|
|
module_exit(hw_litehide_symbols_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Huawei hide symbols module");
|