我正在编写一个 netfilter 内核模块,用于删除来自我的计算机的特定 DNS 请求。为此,我需要从 DNS 数据包问题部分 (rfc1035) 中提取域名。此部分内的
QNAME
字段的格式为长度字节序列,后跟该字节数,以 0
字节终止符结尾,例如12cppreference3com0
。
但是我的机器是小端字节序,所以我得到了
0moc3ecnereferppc21
的相反字节顺序。 我如何解析这个?我无法通过空终止符检测结尾,并且我不知道整个 DNS 数据包字段的长度来反转其中的字节。
在
insmod
之后(在用户进行任何交互之前),我尝试访问网站并获得内核日志Blocked DNS request for 0
。我获得 0
哈希值的唯一方法是立即在 0
中遇到 QNAME
。奇怪的是,再次运行后,我的操作系统完全崩溃了。
更新:如果我不这样做,我仍然会崩溃
- '\0'
。但崩溃发生得晚一些。当我加载模块然后卸载它时,我无法再次加载它 insmod
:
insmod: ERROR: could not insert module dns_filter_module.ko: Device or resource busy
内核日志表明存在一些 NULL 指针取消引用。我也得到了
watchdog bug: soft lockup CPU stuck
。
代码:
附注我知道我会遇到哈希冲突,并且我可能会遇到多个问题部分,我不明白为什么我的系统会从这个特定的非常简单的程序中崩溃
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
#define DEVICE_NAME "dns_blocker_device"
#define BLOCKED_URLS_SIZE 120
#define DNS_PACKET_SECTION_SIZE 2
#define DNS_HEADER_SECTIONS_COUNT 6
#define UDP_HEADER_SIZE 8
static int blocked_url_hashes[BLOCKED_URLS_SIZE] = {0};
static struct nf_hook_ops nfho = {0};
static int major_number;
static struct class* class_blocker = NULL;
static struct device* device_blocker = NULL;
static atomic_t is_device_open = {0};
static int hash_func(unsigned char *start, int len)
{
int hash = 0;
int i = 0;
for (i = 0; i < len; ++i)
{
hash += start[i];
}
return hash;
}
// get domain hash as the sum of values of every its character
static int extract_qname_hash(unsigned char *start)
{
int len = *start - '0';
int hash = 0;
while (len != 0)
{
++start;
hash += hash_func(start, len);
start += len;
len = *start - '0';
}
return hash;
}
static int get_domain_name_hash(unsigned char *header)
{
unsigned char *curr = header;
// move past headers, to QUESTION section
curr += DNS_HEADER_SECTIONS_COUNT * DNS_PACKET_SECTION_SIZE;
// return QNAME hash
return extract_qname_hash(curr);
}
static bool is_url_blocked(int url_hash)
{
int i = 0;
for (i = 0; i < BLOCKED_URLS_SIZE; ++i)
{
if (url_hash == blocked_url_hashes[i])
{
return true;
}
}
return false;
}
static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct iphdr *ip_header;
struct udphdr *udp_header;
unsigned char *data_start;
int url_hash = -1;
if (skb == NULL)
{
return NF_ACCEPT;
}
ip_header = ip_hdr(skb);
if (ip_header->protocol == IPPROTO_UDP)
{
udp_header = udp_hdr(skb);
if (ntohs(udp_header->dest) == 53)
{
data_start = (unsigned char *)udp_header + UDP_HEADER_SIZE;
url_hash = get_domain_name_hash(data_start);
if (is_url_blocked(url_hash))
{
printk(KERN_INFO "Blocked DNS request for %d\n", url_hash);
return NF_DROP;
}
}
}
return NF_ACCEPT;
}
static int device_open(struct inode *inode, struct file *file)
{
if (atomic_cmpxchg(&is_device_open, 0, 1)) return -EBUSY;
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
atomic_set(&is_device_open, 0);
return 0;
}
// copy int array of domain hashes from user
static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset)
{
if (len >= BLOCKED_URLS_SIZE)
{
printk(KERN_ALERT "Max URLs reached\n");
return -EINVAL;
}
if (copy_from_user(blocked_url_hashes, buffer, len) != 0)
{
printk(KERN_ALERT "Failed to copy URL from user space\n");
return -EFAULT;
}
return len;
}
static struct file_operations fops =
{
.open = device_open,
.write = device_write,
.release = device_release
};
static int __init init_blocker_module(void)
{
int ret = 0;
nfho.hook = hook_func;
nfho.hooknum = NF_INET_PRE_ROUTING;
nfho.pf = PF_INET;
nfho.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &nfho);
major_number = register_chrdev(0, DEVICE_NAME, &fops);
if (major_number < 0)
{
printk(KERN_ALERT "Failed to register a major number\n");
ret = major_number;
goto device_register_fail;
}
class_blocker = class_create(THIS_MODULE, "blocker_class");
if (IS_ERR(class_blocker))
{
printk(KERN_ALERT "Failed to create device class\n");
ret = PTR_ERR(class_blocker);
goto class_create_fail;
}
device_blocker = device_create(class_blocker, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME);
if (IS_ERR(device_blocker))
{
printk(KERN_ALERT "Failed to create the device\n");
ret = PTR_ERR(device_blocker);
goto device_create_fail;
}
return ret;
device_create_fail:
class_destroy(class_blocker);
class_create_fail:
unregister_chrdev(major_number, DEVICE_NAME);
device_register_fail:
nf_unregister_net_hook(&init_net, &nfho);
return ret;
}
static void __exit exit_blocker_module(void)
{
unregister_chrdev(major_number, DEVICE_NAME);
class_destroy(class_blocker);
device_destroy(class_blocker, MKDEV(major_number, 0));
nf_unregister_net_hook(&init_net, &nfho);
printk(KERN_INFO "Blocker module removed\n");
}
module_init(init_blocker_module);
module_exit(exit_blocker_module);
此代码有 3 个错误:
len = *start - '0';
应该是 len = *start;
因为长度表示为字节,而不是 char
数字if (len >= BLOCKED_URLS_SIZE)
应该是
if (len > BLOCKED_URLS_SIZE * sizeof(int))
因为
blocked_url_hashes
是一个大小为 BLOCKED_URLS_SIZE * sizeof(int)
字节的数组。
NULL
被销毁后调用
device_destroy(class_blocker, MKDEV(major_number, 0));
时,退出函数会导致
class_blocker
指针取消引用。 另外,应在设备被销毁后调用
unregister_chrdev(major_number, DEVICE_NAME);
,因为它会销毁与设备关联的
major_number
。
static void __exit exit_blocker_module(void)
{
device_destroy(class_blocker, MKDEV(major_number, 0));
class_destroy(class_blocker);
unregister_chrdev(major_number, DEVICE_NAME);
nf_unregister_net_hook(&init_net, &nfho);
printk(KERN_INFO "Blocker module removed\n");
}