Sunichi's Blog

sunichi@DUBHE | Linux & Pwn & Fuzz

0%

Linux 内核模块自动加载机制

Linux 系统中,当用户态需要特定模块功能时,内核在特定代码处会自动加载所需的模块。

原文链接:

Linux kernel module autoloading - Michael S (duasynt.com)

根据发行版系统的配置,一些未使用(或系统启动时未加载)的模块可以被非特权用户加载。从攻击者角度来看,模块自动加载机制显然非常有用,特别是当这些模块被发现漏洞时。

下面以一个网络模块为例,介绍 Linux 内核的模块自动加载机制。 DCCP 功能在大多数发行版编译时的选项是 m,并可在用户态使用该协议。当 DCCP socket 创建时:

1
int sock_fd = socket(AF_INET, SOCK_DCCP, IPPROTO_DCCP);

进入下列代码路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)
{
...
if (family < 0 || family >= NPROTO)
return -EAFNOSUPPORT;
if (type < 0 || type >= SOCK_MAX)
return -EINVAL;
...
#ifdef CONFIG_MODULES
...
if (rcu_access_pointer(net_families[family]) == NULL)
request_module("net-pf-%d", family); [1]
#endif
...

pf = rcu_dereference(net_families[family]);
err = -EAFNOSUPPORT;
if (!pf)
goto out_release;
...
err = pf->create(net, sock, protocol, kern); [2]
if (err < 0)
goto out_module_put;

family 在这是 AF_INET,如果整个协议族都没有加载,内核就会在 [1] 处尝试自动加载模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int __request_module(bool wait, const char *fmt, ...)
{
va_list args;
char module_name[MODULE_NAME_LEN];
int ret;

...
if (!modprobe_path[0])
return 0;

va_start(args, fmt);
ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);
va_end(args);
if (ret >= MODULE_NAME_LEN)
return -ENAMETOOLONG;

ret = security_kernel_module_request(module_name);
if (ret)
return ret;

...

trace_module_request(module_name, wait, _RET_IP_);

ret = call_modprobe(module_name, wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC); [3]

atomic_inc(&kmod_concurrent_max);
wake_up(&kmod_wq);

return ret;
}

在 [3] 处,内核执行 call_modproberoot 身份执行用户态进程,通过 modprobe 加载内核模块。modprobe 会解析模块名并加载特定模块,在 /lib/modules/$(uname -r)/modules.alias 中存有模块别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# grep net-pf- /lib/modules/`uname -r`/modules.alias
...
alias net-pf-28 mpls_router
alias net-pf-26 llc2
alias net-pf-15 af_key
alias net-pf-4 ipx
alias net-pf-5 appletalk
alias net-pf-9 x25
alias net-pf-6 netrom
alias net-pf-11 rose
alias net-pf-3 ax25
alias net-pf-29 can
alias net-pf-31 bluetooth
alias net-pf-33 rxrpc
alias net-pf-41 kcm
alias net-pf-20 atm
alias net-pf-8 atm
...

所有内核支持的协议在 include/linux/socket.h 中列出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Supported address families. */
#define AF_UNSPEC 0
#define AF_UNIX 1 /* Unix domain sockets */
#define AF_LOCAL 1 /* POSIX name for AF_UNIX */
#define AF_INET 2 /* Internet IP Protocol */
#define AF_AX25 3 /* Amateur Radio AX.25 */
#define AF_IPX 4 /* Novell IPX */
#define AF_APPLETALK 5 /* AppleTalk DDP */
#define AF_NETROM 6 /* Amateur Radio NET/ROM */
#define AF_BRIDGE 7 /* Multiprotocol bridge */
#define AF_ATMPVC 8 /* ATM PVCs */
#define AF_X25 9 /* Reserved for X.25 project */
#define AF_INET6 10 /* IP version 6 */
#define AF_ROSE 11 /* Amateur Radio X.25 PLP */
#define AF_DECnet 12 /* Reserved for DECnet project */
#define AF_NETBEUI 13 /* Reserved for 802.2LLC project*/
#define AF_SECURITY 14 /* Security callback pseudo AF */
#define AF_KEY 15 /* PF_KEY key management API */
#define AF_NETLINK 16
...

当网络协议族需要加载,__sock_create() 函数在 [2] 处调用对应协议的 create() 函数,如果传入的是 AF_INET,那么 __sock_create() 函数将会调用 inet_create() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
static int inet_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
...
lookup_protocol:
err = -ESOCKTNOSUPPORT;
rcu_read_lock();
list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

err = 0;
/* Check the non-wild match. */
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP)
break;
} else {
/* Check for the two wild cases. */
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
if (IPPROTO_IP == answer->protocol)
break;
}
err = -EPROTONOSUPPORT;
}

if (unlikely(err)) {
if (try_loading_module < 2) {
rcu_read_unlock();
/*
* Be more specific, e.g. net-pf-2-proto-132-type-1
* (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
*/
if (++try_loading_module == 1)
request_module("net-pf-%d-proto-%d-type-%d", [5]
PF_INET, protocol, sock->type);
/*
* Fall back to generic, e.g. net-pf-2-proto-132
* (net-pf-PF_INET-proto-IPPROTO_SCTP)
*/
else
request_module("net-pf-%d-proto-%d", [6]
PF_INET, protocol);
goto lookup_protocol;
} else
goto out_rcu_unlock;
}
...

inet_create() 函数当中将会遍历与它相匹配的协议,在 [5] 和 [6] 处尝试自动加载相关的模块。

根据发行版的不同,一些协议会被放入黑名单当中,不被允许为非特权用户自动加载,特权用户可以手动加载这些黑名单当中的模块:

1
for p in `grep -v "^#" /etc/modprobe.d/blacklist-rare-network.conf | cut -d" " -f2`; do grep "$p " /lib/modules/`uname -r`/modules.alias | cut -d" " -f3; done

同时,一些协议会检查 capability,例如需要 init user/network namespace。例如 AF_PACKET 协议族需要 CAP_SYS_RAW,但在一些发行版中,非特权用户可以通过 namespace 获取 CAP_SYS_RAW