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_modprobe
以 root
身份执行用户态进程,通过 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 ... alias net-pf-28 mpls_routeralias net-pf-26 llc2alias net-pf-15 af_keyalias net-pf-4 ipxalias net-pf-5 appletalkalias net-pf-9 x25alias net-pf-6 netromalias net-pf-11 rosealias net-pf-3 ax25alias net-pf-29 canalias net-pf-31 bluetoothalias net-pf-33 rxrpcalias net-pf-41 kcmalias net-pf-20 atmalias 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 #define AF_UNSPEC 0 #define AF_UNIX 1 #define AF_LOCAL 1 #define AF_INET 2 #define AF_AX25 3 #define AF_IPX 4 #define AF_APPLETALK 5 #define AF_NETROM 6 #define AF_BRIDGE 7 #define AF_ATMPVC 8 #define AF_X25 9 #define AF_INET6 10 #define AF_ROSE 11 #define AF_DECnet 12 #define AF_NETBEUI 13 #define AF_SECURITY 14 #define AF_KEY 15 #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 ; if (protocol == answer->protocol) { if (protocol != IPPROTO_IP) break ; } else { 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(); if (++try_loading_module == 1 ) request_module("net-pf-%d-proto-%d-type-%d" , [5 ] PF_INET, protocol, sock->type); 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
。