Linux内核笔记

560 阅读4分钟

Sparse

Sparse是内核代码静态分析工具, 能够帮助我们找出代码中的隐患.www.cnblogs.com/wang_yb/p/3…

宏名称宏定义检查点
__bitwiseattribute((bitwise))确保变量是相同的位方式(比如 bit-endian, little-endian等)
__userattribute((noderef, address_space(1)))指针地址必须在用户地址空间
__kernelattribute((noderef, address_space(0)))指针地址必须在内核地址空间
__iomemattribute((noderef, address_space(2)))指针地址必须在设备地址空间
__safeattribute((safe))变量可以为空
__forceattribute((force))变量可以进行强制转换
__nocastattribute((nocast))参数类型与实际参数类型必须一致
__acquires(x)attribute((context(x, 0, 1)))参数x 在执行前引用计数必须是0,执行后,引用计数必须为1
__releases(x)attribute((context(x, 1, 0)))与 __acquires(x) 相反
__acquire(x)context(x, 1)参数x 的引用计数 + 1
__release(x)context(x, -1)与 __acquire(x) 相反
__cond_lock(x,c)((c) ? ({ __acquire(x); 1; }) : 0)参数c 不为0时,引用计数 + 1, 并返回1
__rcuattribute((noderef, address_space(4)))读-复制-更新是一种算法,无需锁定结构即可同时访问数据结构的读取器

常用宏

1. SYSCALL_DEFINE(X)

Linux 内核中用于定义系统调用的宏,其中(X)可以是1~6,下是 SYSCALL_DEFINE6 宏的基本结构:

cCopy code
SYSCALL_DEFINE6(name, type1, name1, type2, name2, type3, name3,
                type4, name4, type5, name5, type6, name6)
{
    // 在这里实现系统调用的功能
}

其中:

  • name 是系统调用函数的名称。
  • type1, type2, ..., type6 是系统调用函数的参数类型。
  • name1, name2, ..., name6 是系统调用函数的参数名称。

使用 SYSCALL_DEFINE6 宏可以使得定义系统调用函数更加简洁和清晰,同时确保了系统调用函数的正确性和安全性

2. likely、unlikely

在 Linux 内核中,likely 是一个宏,用于提示编译器某个条件通常为真。它用于在编写代码时,帮助编译器优化分支预测,提高程序的执行效率。

likely 宏并不改变程序的逻辑行为,它只是一种提示,告诉编译器某个条件通常为真。

likely 宏类似,unlikely 宏也只是一种提示,告诉编译器某个条件通常为假,而不会改变程序的逻辑行为。

3. NF_HOOK_COND

NF_HOOK_COND 是 Linux 内核中用于注册和执行网络数据包处理钩子函数的一个宏。这个宏允许内核模块注册一个网络数据包处理钩子函数,并在网络数据包经过协议栈的特定点时执行该函数。

以下是 NF_HOOK_COND 宏的基本语法:

unsigned int NF_HOOK_COND(unsigned int pf, unsigned int hook, struct net_device *in,
                          struct sk_buff *skb, struct net_device *out,
                          int (*okfn)(struct net *, struct sock *, struct sk_buff *),
                          int thresh, int (*cond)(const struct nf_hook_ops *, struct sk_buff *,
                                                 struct net_device *, struct net_device *,
                                                 int (*okfn)(struct net *, struct sock *, struct sk_buff *)));

其中参数含义如下:

  • pf: 协议族(Protocol Family),如 PF_INETPF_INET6 等。
  • hook: 钩子函数类型,如 NF_INET_PRE_ROUTINGNF_INET_LOCAL_IN 等,表示数据包处理的不同阶段。
  • in: 指向输入网络设备的指针。
  • skb: 指向待处理的数据包的 sk_buff 结构体指针。
  • out: 指向输出网络设备的指针。
  • okfn: 一个函数指针,指向一个函数,用于表示数据包处理完成的回调函数。
  • thresh: 钩子函数阈值,用于指定钩子函数执行的优先级。
  • cond: 一个函数指针,指向一个函数,用于定义钩子函数执行的条件。

NF_HOOK_COND 宏将钩子函数注册到指定的钩子点,当满足指定的条件时,将执行该钩子函数。


内核方法

1. array_index_nospec

array_index_nospec x86/uaccess: Use pointer masking to limit uaccess speculation

2018年的Spectre变种1(bounds-check bypass),借助边界检查时cpu会投机访问(speculate access),实现攻击。之前x86在 copy_from_user中使用LFENCE减缓这种攻击。但是LFENCE有点重了。

来自Redhat的Josh把LFENCE替换为 array_index_nospec,后者原本的用途是数组访问越界时,把index被钳位为0,从而避免攻击者访问原本不允许访问的超过数组范围的地址。由于64位内核中,用户空间地址为低地址(以一串0开头),内核为高地址(以一串1开头)。如果给 array_index_nospec传入0和用户地址的最大范围( user_addr_max()),当攻击者试图从用户空间传入内核地址(即大于 user_addr_max()的地址时,该地址会被 array_index_nospec设为0。从而避免了攻击者从用户空间“偷到”内核的数据。

2. atomic_read

对原子类型的变量v进行原子读操作,得到其值。

3. container_of

通过结构体成员变量地址获取这个结构体的地址

未完待续...