udev


udev -- Linux dynamic device management

udev supplies the system software with device events, manages permissions of device nodes and may create additional symlinks in the /dev directory, or renames network interfaces. The kernel usually just assigns unpredictable device names based on the order of discovery. Meaningful symlinks or network device names provide a way to reliably identify devices based on their properties or current configuration.
The udev daemon udevd(8) receives device uevents directly from the kernel whenever a device is added or removed from the system, or it changes its state. When udev receives a device event, it matches its configured set of rules against various device attributes to identify the device. Rules that match, may provide additional device information to be stored in the udev database, or information to be used to create meaningful symlink names.
All device information udev processes, is stored in the udev database and sent out to possible event subscribers. Access to all stored data and the event sources are provided by the library libudev.
udev 是Linux kernel 2.6系列的设备管理器。它主要的功能是管理/dev目录底下的设备节点。它同时也是用来接替devfs及hotplug的功能,这意味着它要在添加/删除硬件时处理/dev目录以及所有用户空间的行为,包括在加载firmware时。
udev的最新版本依赖于升级后的Linux kernel 2.6.13的uevent接口的最新版本。使用新版本udev的系统不能在2.6.13以下版本启动,除非使用noudev参数来禁用udev并使用传统的/dev来进行设备读取。
udev是一个通用的内核设备管理器,它以守护进程的方式运行于Linux系统,并监听在新设备初始化或设备从系统中移除时内核(通过netlink socket)发出的uevent。系统提供了一套规则用于匹配可发现的设备事件和属性的导出值。匹配规则可能命名并创建设备节点,并运行配置程序来对设备进行设置。udev规则可以匹配像内核子系统、内核设备名称、设备的物理等属性,或设备序列号的属性。规则也可以请求外部程序提供信息来命名设备,或指定一个永远一样的自定义名称来命名设备,而不管设备什么时候被系统发现。其功能特点如下:
1.dynamic replacement for /dev。作为devfs的替代者,传统的devfs不能动态分配major和minor的值,而major和minor非常有限,很快就会用完了。udev能够像DHCP动态分配IP地址一样去动态分配major和minor。
2.device naming。提供设备命名持久化的机制。传统设备命名方式不具直观性,像/dev/hda1这样的名字肯定没有boot_disk这样的名字直观。udev能够像DNS解析域名一样去给设备指定一个有意义的名称。
3.API to access info about current system devices 。提供了一组易用的API去操作sysfs,避免重复实现同样的代码。
udev系统可以分为三个部分:
* namedev函数库,处理设备的命名。
* libsysfs函数库,进行设备信息的读取(080版本后废弃)
* 守护进程udevd,处于用户空间,用于创建虚拟/dev
系统获取内核通过netlink socket发出的信息。早期的版本使用hotplug,并在/etc/hotplug.d/default添加一个链接到自身来达到目的。
在传统的Linux系统中,/dev目录下的设备节点为一系列静态存在的文件,而udev则动态提供了在系统中实际存在的设备节点。虽然devfs提供了类似功能,udev的支持者也给出了很多udev实现比devfs好的理由:
*udev支持设备的固定命名,而并不依赖于设备插入系统的顺序,默认的udev设置提供了存储设备的固定命名。任何硬盘都根据其唯一的文件系统id、磁盘名称及硬件连接的物理位置来进行识别。
*udev完全在用户空间执行,而不是像devfs在内核空间一样执行。结果就是udev将命名策略从内核中移走,并可以在节点创建前用任意程序在设备属性中为设备命名。
而用户空间的程序与设备通信的方法,主要有以下几种方式:
1.通过ioperm获取操作IO端口的权限,然后用inb/inw/inl/outb/outw/outl等函数,避开设备驱动程序,直接去操作IO端口。
2.用ioctl函数去操作/dev目录下对应的设备,这是设备驱动程序提供的接口。像键盘、鼠标和触摸屏等输入设备一般都是这样做的。
3.用write/read/mmap去操作/dev目录下对应的设备,这也是设备驱动程序提供的接口。像framebuffer等都是这样做的。
上面的方法在大多数情况下,都可以正常工作,但是对于热插拨(hotplug)的设备,比如像U盘,就有点困难了,因为你不知道:什么时候设备插上了,什么时候设备拔掉了。这就是所谓的hotplug问题了。
处理hotplug传统的方法是,在内核中执行一个称为hotplug的程序,相关参数通过环境变量传递过来,再由hotplug通知其它关注hotplug事件的应用程序。这样做会有效率上的问题,新的方法是采用NETLINK实现的,这是一种特殊类型的socket,专门用于内核空间与用户空间的异步通信。
udev的命令格式
---------------------------------------------------
BUS 总线 KERNEL 内核名如sd* ID 设备id 如总线id PLACE
SYSFS{filename}
PROGRAM 调用外部程序 RESULT 匹配program返回的结果 NAME
SYMLINK 连接规则
---------------------------------------------------
udev服务管理着设备的基本功能,例如增加了一块硬盘、拔掉了一个 USB 设备等等。下面来小结一下其工作结构及其工作流程。
1、udev 的工作方法
udev 整体架构可大致分为三个部分:内核层、用户空间守护进程、规则引擎与工具集。
简单来说,当设备有变动时,内核层最先发现, 之后通过 uevent 机制(基于 netlink 套接字)向用户空间发送设备事件(如设备插入、移除、状态变化),设备信息通过 sysfs 虚拟文件系统暴露(路径如 /sys/block/sda)。
用户空间守护进程监听内核的 uevent 事件,之后根据规则文件(.rules)中的规则处理事件。
下面是一个规则文件的案例
# 匹配特定 USB 设备,为其分配固定名称和权限
SUBSYSTEM=="block", ACTION=="add", ATTRS{idVendor}=="0781", ATTRS{idProduct}=="5580", \
SYMLINK+="my_secure_usb", GROUP="users", MODE="0660"
匹配条件:设备为块设备(SUBSYSTEM=="block"),动作为插入(ACTION=="add"),且匹配厂商/产品 ID。
执行动作:创建符号链接 /dev/my_secure_usb,设置设备组为 users,权限为 0660。
sudo 后门以后不再对二进制文件依赖的共享库 (xxx.so) 文件替换劫持做权限维持做单独说明,主要是探讨通过配置文件的方式进行权限维持。执行动作也为运行一些指令创造了条件,为系统安全埋下了伏笔。
2、udev 规则文件
1. 规则文件存储位置
udev 的规则文件按照规范一般是 xxxx.rules 文件,udev 的规则文件存在于系统的以下三个目录中:
ls -al /lib/udev/rules.d/
ls -al /etc/udev/rules.d/
ls -al /run/udev/rules.d/
可以看到,这里并没有目录,都是文件,而且文件后缀都是固定的,稍后我们了解配置文件编写规则后,再测试目录以及各种文件名称的有效性
2. 规则文件的加载顺序
不同目录的规则优先级,尽管文件名决定加载顺序,但目录路径的优先级更高:
/etc/udev/rules.d/ 的规则优先级最高(即使文件名数字小,也会覆盖其他目录的规则)
/run/udev/rules.d/ 的规则次之
/lib/udev/rules.d/ 的规则优先级最低
优先级总结:
/etc/udev/rules.d/ -> /run/udev/rules.d/ -> /lib/udev/rules.d/
3. 规则编写规范
archlinuxcn-Udev
archlinux-udev.7
writing_udev_rules
udev 存在的意义是在设备出现变化时对应进行处理,所以整体语法应该是如何标识一个设备或一批设备,也就是匹配、如何标识出变化、如何标识出要做的动作,也就是赋值。
规则语法
每条规则由逗号分隔的 键-操作符-值 表达式组成:
匹配条件:使用 == 或 != 运算符(如 SUBSYSTEM=="usb")。
赋值操作:使用 =, +=, -=, := 运算符(如 SYMLINK+="my_device")。
注释
规则使用 # 作为注释
匹配键
1) 基础设备属性
匹配键 描述
ACTION 设备事件类型:add(添加)、remove(移除)、change(状态变更)等。
KERNEL 内核分配的设备名称(如 sda、eth0)。
SUBSYSTEM 设备所属子系统(如 block(块设备)、usb、net(网络设备))。
DRIVER 设备绑定的驱动名称(如 usb-storage)。
DEVPATH 设备在 /sys 中的路径(如 /devices/pci0000:00/0000:00:1a.0/usb1)。
2) 设备属性(sysfs 属性)
匹配键 描述
ATTR{filename} 设备的 sysfs 属性文件值(如 ATTR{size}=="4096")。
ATTRS{filename} 向上级设备查找 sysfs 属性(用于跨层级匹配,如 parent 设备的属性)。
3) 环境变量
匹配键 描述
ENV{key} 设备或全局环境变量(如 ENV{ID_MODEL}=="MyDisk")。
4) 设备标签与特征
匹配键 描述
TAG 设备标签(如 TAG=="uaccess",用于用户空间访问权限)。
TEST 检查文件或目录是否存在(如 TEST=="/dev/my_device")。
PROGRAM 执行外部程序,若返回值为 0 则匹配(需结合 RESULT 使用)。
5) 设备关系与拓扑
匹配键 描述
PARENT 匹配父设备的属性(如 PARENT{SUBSYSTEM}=="usb")。
KERNELS 向上级设备匹配内核名称(类似 ATTRS)。
SUBSYSTEMS 向上级设备匹配子系统(类似 ATTRS)。
DRIVERS 向上级设备匹配驱动名称(类似 ATTRS)。
6) 高级匹配
匹配键 描述
IMPORT{type} 导入外部数据(如 IMPORT{program}="/sbin/blkid -o udev -p $tempnode")。
NAME 设备节点名称(仅用于网络设备,如 NAME="eth0")。
MODE/OWNER/GROUP 直接匹配设备的权限或所有权(不推荐,通常用于赋值而非匹配)。
匹配运算符
匹配运算符(Matching Operators)用于定义设备属性的匹配条件
1) 基础匹配运算符
运算符 描述
== 等于
:严格匹配属性值。示例:SUBSYSTEM=="usb"(匹配 USB 子系统设备)。
!= 不等于
:排除特定属性值。示例:KERNEL!="sda*"(排除内核名以 sda 开头的设备)。
2) 字符串匹配运算符
运算符 描述
=~ 正则表达式匹配
:使用正则表达式匹配属性值。示例:KERNEL=~"^sd[a-z][0-9]"(匹配形如 sda1、sdb2 的设备名)。
!~ 正则表达式不匹配
:排除符合正则表达式的属性值。示例:KERNEL!~"^loop"(排除内核名以 loop 开头的设备)。
3) 特殊匹配操作
运算符 描述
$= 字符串包含
:检查属性值是否包含子字符串(部分实现支持,非官方标准)。示例:ATTR{name}$="video"(匹配 name 属性包含 video 的设备)。
4) 逻辑组合符
运算符 描述
, 逻辑与
:多个条件需同时满足。示例:SUBSYSTEM=="usb", ATTR{idVendor}=="0781"(USB 子系统且厂商 ID 为 0781)。
注意事项
大小写敏感:匹配操作默认区分大小写。
转义字符:在正则表达式中需转义特殊字符(如 \* 匹配字面量 *)。
兼容性:=~ 和 !~ 依赖于 udev 版本,需验证支持性
赋值键
1) 基础设备操作
赋值键 描述
NAME 命名设备节点(仅适用于网络设备,如网卡)。
SYMLINK 创建设备符号链接(可创建多个链接,如 SYMLINK+="my_device")。
MODE 设置设备节点的权限(如 MODE="0660")。
OWNER 设置设备节点的所有者(如 OWNER="root")。
GROUP 设置设备节点的所属组(如 GROUP="users")。
2) 设备标签与元数据
赋值键 描述
TAG 为设备添加标签(如 TAG+="uaccess",用于用户空间访问权限)。
ENV{key} 设置设备的环境变量(如 ENV{DISK_TYPE}="ssd")。
3) 脚本与程序执行
赋值键 描述
RUN{type} 执行外部程序或脚本(type 支持 program 或 builtin)。
IMPORT{type} 导入外部数据(如 IMPORT{program}="/sbin/blkid -o udev -p $tempnode")。
PROGRAM 执行外部程序并捕获输出(通常与 RESULT 配合使用)。
4) 高级控制
赋值键 描述
OPTIONS 设置特殊选项,如:
- OPTIONS+="last_rule"(跳过后续规则)。
- OPTIONS+="watch"(监视设备状态变化)。
SECLABEL 设置设备的安全标签(如 SELinux 上下文)。
ATTR{filename} 直接修改设备的 sysfs 属性(需谨慎使用)。
5) 网络设备专用
赋值键 描述
NAME 重命名网络接口(如 NAME="eth0")。
MAC 设置网络接口的 MAC 地址(需驱动支持)。
赋值运算符
赋值运算符(Assignment Operators) 用于定义如何修改设备属性或执行操作
1) 基础赋值运算符
运算符 描述
= 直接赋值
:覆盖属性的原有值。示例:MODE="0660"(设置权限为 0660,忽略默认值)。
+= 追加赋值
:在原有值基础上添加新值(适用于多值属性,如 SYMLINK)。示例:SYMLINK+="my_device"(在默认符号链接基础上添加 my_device)。
:= 最终赋值
:设置不可被后续规则覆盖的值。示例:GROUP:="users"(后续规则无法修改 GROUP 的值)。
2) 特殊运算符
运算符 描述
-= 移除值
:从多值属性中删除指定值(适用于 SYMLINK 或 TAG)。示例:TAG-="uaccess"(移除 uaccess 标签)。
== 匹配条件
:仅用于匹配键(非赋值操作)。示例:SUBSYSTEM=="usb"(匹配 USB 子系统设备)。
注意事项
= 与 +=:
使用 = 会覆盖原有值(如 SYMLINK="my_link" 将删除默认符号链接)。
使用 += 更安全,通常用于扩展而非替换。
:= 的强制力:
通过 := 赋值的属性不可被后续规则修改,即使后续规则使用 = 或 +=。
慎用 -=:
移除系统默认值可能导致意外行为(如权限错误)。
取值
udev 规则中的值都是被双引号包裹的,这其中也涉及到转义、大小写、正则、变量等,但其实我不需要特别关心,可以在遇到的时候使用 deepseek 等进行解释
常用键的值示例
键 值示例 说明
SUBSYSTEM "usb", "block" 设备子系统类型
ACTION "add", "remove" 设备事件类型
ATTR{size} "4096", "0" 设备的 sysfs 属性值
ENV{ID_VENDOR} "SanDisk" 设备环境变量
MODE "0660", "0644" 八进制权限值
SYMLINK "my_device", "backup_disk" 符号链接名称
RUN "/usr/bin/mount.sh" 脚本或程序的绝对路径
4. udev 规则案例
vbox虚拟机下的Devuan4下获取的第二块硬盘设备(sdb)信息:
fdisk -l #可见Disk identifier
udevadm info -a -p $(udevadm info -q path -n /dev/sdb)
内容非常长,它表示了整个设备系统按照层级的属性关系,SUBSYSTEM 表示当前设备的层级。该设备是 block 时就是 SUBSYSTEM="scsi";设备是 USB 时就是 SUBSYSTEM="usb"。
当前设备的层级解析
找到属于待查设备的属性信息
ATTRS{idProduct}==""
ATTRS{idVendor}==""
ATTRS{manufactureer}==""
ATTRS{product}==""
获取到父级的 idProduct 和 idVendor (因为是 ATTRS),Usb设备可以通过 lsusb 进行验证:
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 004: ID 04f2:b217 Chicony Electronics Co., Ltd Lenovo Integrated Camera (0.3MP)
Bus 001 Device 003: ID 046d:081b Logitech, Inc. Webcam C310
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
查看Logitech, Inc. Webcam C310的相关信息:
lsusb -v -d 046d:081b
会有很长的输出。。。
当开启环境变量“LSBLK_DEBUG=all”时,运行lsblk也会有本地块设备的信息。
编写 udev 案例
插入优盘后希望创建一个符号连接 nop_driver
首先标识一下这个设备
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545"
注意,idVentor 和 idProduct 是来自父级的,这点在原始的输出中可以看到
之后我们描述一下插入的动作
ACTION=="add"
接下来再标识一下要做的赋值,创建 /tmp/flag 文件
RUN+="/bin/touch /tmp/flag"
合起来就是
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag"
将内容复制到 /etc/udev/rules.d/99-nop-driver.rules 中
测试案例有效性
首先查看是否存在这个'/tmp/flag'文件
不存在,接下来插入优盘。可以看到,文件成功创建,该规则在未显式地手动重新加载配置文件的情况下就已经可以生效了
测试规则文件夹加载顺序
通过在三个目录中创建相同文件名称的规则文件,对比一下实际的执行顺序
/lib/udev/rules.d/99-nop-driver.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag4lib"
/etc/udev/rules.d/99-nop-driver.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag4etc"
/run/udev/rules.d/99-nop-driver.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag4run"
首先直接插入优盘进行测试
ls /tmp/
/tmp/flag4etc
在默认情况下,优先级最高的竟然是 /etc/udev/rules.d/ 目录下的
删除 /etc/udev/rules.d/ 目录下的配置文件,看看下一个被加载的是哪一个
/tmp/flag4run
看来接下来是 /run/udev/rules.d/ 目录下的,删除该文件,再次测试
/tmp/flag4lib
需要注意的是,现在现代 Linux 发行版(如 Ubuntu/Debian/Fedora 等)中/lib 通常是指向 /usr/lib 的符号链
/lib -> usr/lib
所以 /lib/udev/rules.d/99-nop-driver.rules 与 /usr/lib/udev/rules.d/99-nop-driver.rules 为同一文件
所以在 Debiab 系中 udev 规则文件夹同名规则文件加载顺序为
/etc/udev/rules.d/ -> /run/udev/rules.d/ -> /lib/udev/rules.d/
测试文件名称
在 archlinux 的官方文档上说,规则文件需要使用 '.rules' 后缀,无后缀规则文件是无效的
测试是否可以在新建目录
如果我们在该目录新建一个目录,之后在其中加入规则文件,规则文件会生效吗?
测试发现也是不行的,看来对于配置文件的要求还是比较固定。
3、udev 持久化探索
上面的测试中,规则文件肯定可以被用来做后门,所以稍后着重探讨,排除替换可执行文件、替换共享库这类持久化以外,尝试从剩下的三个配置文件探索持久化的可能
1. hwdb
hwdb 是硬件属性数据库,将设备的硬件属性(如 USB 厂商/产品 ID、PCI 设备 ID 等)映射到自定义属性(如触摸板手势配置、键盘重复速率等)
默认 Debian 中 /etc/udev/hwdb.d 是空的,在 /lib/udev/hwdb.d 中是有内容的。
攻击者如果修改硬件属性数据库,可能会导致系统挂载硬件设备时错误识别等,想实现持久化的话,可能需要配合其他程序错误识别会导致一些额外bug。所以一般的攻击者是不会通过硬件属性数据库进行持久化的。
2. iocost.conf
iocost.conf 是 udev 用于定义和管理设备 I/O 成本的配置文件。通过在这个文件中指定各类设备的 I/O 成本,系统能够在设备接入时自动调整 I/O 调度策略,以优化性能和资源分配。
根据目前的文档来看,只能用来调节I/O 成本,无法创建文件,也无法执行外部程序,甚至官方文档也仅仅介绍了 TargetSolution 这一个参数,我尝试了直接在其中写 shell 命令,并没有成功执行
3. udev.conf
udev.conf 是 udev 守护进程的主配置文件,用于设置 udev 的全局运行参数(/etc/udev/udev.conf),而不是用来定义具体的设备匹配或行为规则
下面是可以设置的选项:
udev_log=日志级别。有效值可以是数值形式的 syslog 优先级,或它们的文本描述:
err
info
debug
children_max=一个整数,表示并行执行的最大事件数。 如果未指定或指定为 0,最大并行数将基于系统资源自动确定。 此选项与命令行中的 --children-max= 相同
exec_delay=一个整数。为每个 RUN{program} 参数延迟执行指定的秒数。 此选项在调试因加载不可用内核模块导致的冷插拔启动崩溃时可能会很有用。 此选项与命令行中的 --exec-delay= 相同。
event_timeout=一个整数,表示等待事件完成的秒数。 超过这个时间后,事件将被终止。默认值为 180 秒。 此选项与命令行中的 --event-timeout= 相同。
resolve_names=指定 systemd-udevd 解析用户和组名的时机。
当设置为 early(默认)时,会在解析规则时进行名称解析;
设置为 late 时,将对每个事件单独进行名称解析;
设置为 never 时,则永不解析名称,此时所有设备都将归 root 所有。 此选项与命令行中的 --resolve-names= 相同。
timeout_signal=指定当工作超时时 systemd-udevd 发送给工作线程的信号。 注意:无论是工作线程还是派生进程,都会使用此信号来终止。 默认信号为 SIGKILL。
从配置项来看,也就 timeout_signal 似乎还有点对外部造成影响的可能,但是用来做权限维持较为困难
4. 规则文件
根据之前的案例,我们了解到 RUN 这个键可以执行系统命令,接下来探究一下这些键还能做哪些有利于权限维持的操作,有哪些限制
RUN
执行外部程序或脚本(type 支持 program 或 builtin)
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag"
PROGRAM
执行外部程序并捕获输出(通常与 RESULT 配合使用)
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", PROGRAM="/usr/bin/env bash -c '/bin/touch /tmp/flag-program && echo FLAG=touched'"
IMPORT
导入外部数据(如 IMPORT{program}="/sbin/blkid -o udev -p $tempnode")
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", IMPORT{program}="/usr/bin/env bash -c '/bin/touch /tmp/flag-import && echo FLAG=touched'"
ENV
ENV 设置的环境变量只是局部环境变量,在 udev 事件处理期间生效,不会共享到整个操作系统的全局环境中,所以只能做辅助。
4、FAQ
问:什么是udev?
答:udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。
问:udev支持什么内核?
答:udev只支持linux-2.6内核,因为udev严重依赖于sysfs文件系统提供的信息,而sysfs文件系统只在linux-2.6内核中才有。
问:udev是一个内核程序还是用户程序?
答:udev是一个用户程序(user-mode daemon)。
问:udev和devfs有什么差别?
答:udev能够实现所有devfs实现的功能。但udev运行在用户模式中,而devfs运行在内核中。据称:devfs具有一些不太容易解决的先天缺陷。
问:udev的配置文件放在哪里?
答:udev是一个用户模式程序。它的配置文件是/etc/udev/udev.conf。这个文件一般缺省有这样几项:
udev_root="/dev" ; udev产生的设备文件的根目录是/dev。
udev_db="/dev/.udevdb" ; 通过udev产生的设备文件形成的数据库。
udev_rules="/etc/udev/rules.d" ;用于指导udev工作的规则所在目录。
udev_log="err" ;当出现错误时,用syslog记录错误信息。
问:udev的工作过程是怎样的?
答:由于没有研究过udev的源程序,不敢贸然就说udev的工作过程。只是通过一些网上的资料和udev的说明文档,大致猜测它的工作过程可能是这样的。
1. 当内核检测到在系统中出现了新设备后,内核会在sysfs文件系统中为该新设备生成一项新的记录,一般sysfs文件系统会被mount 到 /sys目录中。新记录是以一个或多个文件或目录的方式来表示。每个文件都包含有特定的信息。
2.udev在系统中是以守护进程的方式udevd在运行,它通过某种途径检测到新设备的出现,通过查找设备对应的sysfs中的记录得到设备的一些信息。
3.udev会根据/etc/udev/udev.conf文件中的udev_rules指定的目录,逐个检查该目录下的文件,这个目录下的文件都是针对某类或某个设备应该施行什么措施的规则文件。udev读取文件是按照文件名的ASCII字母顺序来读取的,如果udev一旦找到了与新加入的设备匹配的规则,udev就会根据规则定义的措施对新设备进行配置。同时不再读后续的规则文件。
问:udev的规则文件的语法是怎样的?
答:udev的规则文件以行为单位,以"#"开头的行代表注释行。其余的每一行代表一个规则。每个规则分成一个或多个“匹配”和“赋值”部分。“匹配”部分用“匹配“专用的关键字来表示,相应的“赋值”部分用“赋值”专用的关键字来表示。“匹配”关键字包括:ACTION,KERNEL,BUS, SYSFS等等,“赋值”关键字包括:NAME,SYMLINK,OWNER等等。具体详细的描述可以阅读udev的man文档。
下面举例来说明一下,有这样一条规则:
SUBSYSTEM=="net", ACTION=="add", SYSFS{address}=="00:0d:87:f6:59:f3", IMPORT="/sbin/rename_netiface %k eth0"
这个规则中的“匹配”部分有三项,分别是SUBSYSTEM,ACTION和SYSFS。而"赋值"部分有一项,是IMPORT。这个规则就是说,当系统中出现的新硬件属于net子系统范畴,系统对该硬件采取的动作是加入这个硬件,且这个硬件在SYSFS文件系统中的“address”信息等于“00: 0d..."时,对这个硬件在udev层次施行的动作是调用外部程序/sbin/rename_netiface,传递的参数有两个,一个是“%k”,代表内核对该新设备定义的名称。另一个是”eth0“。
从上面这个例子中可以看出,udev的规则的写法比较灵活的,尤其在“匹配”部分中,可以通过诸如”*“, ”?“,[a-c],[1-9]等shell通配符来灵活匹配多个匹配项。具体的语法可以参考udev的man文档。
问:udev怎样做到不管设备连接的顺序而维持一个统一的设备名?
答:实际上,udev是通过对内核产生的设备名增加别名的方式来达到上述目的的。前面说过,udev是用户模式程序,不会更改内核的行为。因此内核依然会我行我素地产生设备名如sda,sdb等。但udev可以根据设备的其他信息如总线(bus),生产商(vendor)等不同来区分不同的设备,并产生设备文件。udev只要为这个设备文件取一个固定的文件名就可以解决这个问题。在后续对设备的操作中,只要引用新的设备名就可以了。但为了保证最大限度的兼容,一般来说,新设备名总是作为一个对内核自动产生的设备名的符号链接(link)来使用的。
例如:内核产生了sda设备名,而根据信息,这个设备对应于是我的内置硬盘,那我就可以制定udev规则,让udev除了产生/dev/sda设备文件外,另外创建一个符号链接叫/dev/internalHD。这样在我fstab文件中,就可以用/dev/internalHD来代替原来的 /dev/sda了。下次由于某些原因,这个硬盘在内核中变成了sdb设备名了,那也不用着急,udev还会自动产生/dev/internalHD这个链接,并指向正确的/dev/sdb设备。所有其他的文件像fstab等都不用修改。
问:怎样才能找到这些设备信息,并把他们放到udev的规则文件中来匹配呢?
答:这个问题比较难,网上资料不多,我只找到一篇文章来介绍如何写udev的规则。他的基本方法是通过udevinfo这个实用程序来找到那些可以作为规则文件里的匹配项的项目。有这样两种情况可以使用这个工具:
第一种情况是,当你把设备插入系统后,系统为设备产生了设备名(如/dev/sda)。那样的话,你先用udevinfo -q path -n /dev/sda,命令会产生一个该设备名对应的在sysfs下的路径,如/block/sda。然后,你再用udevinfo -a -p /sys/block/sda,这个命令会显示一堆信息,信息分成很多块。这些信息实际来自于操作系统维护的sysfs链表,不同的块对应不同的路径。你就可以用这些信息来作为udev规则文件中的匹配项。但需要注意的是,同一个规则只能使用同一块中显示的信息,不能跨块书写规则。
第二种情况是,不知道系统产生的设备名,那就只有到/sys目录下去逐个目录查找了,反复用udevinfo -a -p /sys/path...这个命令看信息,如果对应的信息是这个设备的,那就恭喜你,否则就再换个目录。当然,在这种情况下,成功的可能性比较小。
参考来源:
http://zh.wikipedia.org/zh/Udev
http://www.linuxsky.org/doc/admin/200710/139.html

udev supplies the system software with device events, manages permissions of device nodes and may create additional symlinks in the /dev directory, or renames network interfaces. The kernel usually just assigns unpredictable device names based on the order of discovery. Meaningful symlinks or network device names provide a way to reliably identify devices based on their properties or current configuration.
The udev daemon udevd(8) receives device uevents directly from the kernel whenever a device is added or removed from the system, or it changes its state. When udev receives a device event, it matches its configured set of rules against various device attributes to identify the device. Rules that match, may provide additional device information to be stored in the udev database, or information to be used to create meaningful symlink names.
All device information udev processes, is stored in the udev database and sent out to possible event subscribers. Access to all stored data and the event sources are provided by the library libudev.
udev 是Linux kernel 2.6系列的设备管理器。它主要的功能是管理/dev目录底下的设备节点。它同时也是用来接替devfs及hotplug的功能,这意味着它要在添加/删除硬件时处理/dev目录以及所有用户空间的行为,包括在加载firmware时。
udev的最新版本依赖于升级后的Linux kernel 2.6.13的uevent接口的最新版本。使用新版本udev的系统不能在2.6.13以下版本启动,除非使用noudev参数来禁用udev并使用传统的/dev来进行设备读取。
udev是一个通用的内核设备管理器,它以守护进程的方式运行于Linux系统,并监听在新设备初始化或设备从系统中移除时内核(通过netlink socket)发出的uevent。系统提供了一套规则用于匹配可发现的设备事件和属性的导出值。匹配规则可能命名并创建设备节点,并运行配置程序来对设备进行设置。udev规则可以匹配像内核子系统、内核设备名称、设备的物理等属性,或设备序列号的属性。规则也可以请求外部程序提供信息来命名设备,或指定一个永远一样的自定义名称来命名设备,而不管设备什么时候被系统发现。其功能特点如下:
1.dynamic replacement for /dev。作为devfs的替代者,传统的devfs不能动态分配major和minor的值,而major和minor非常有限,很快就会用完了。udev能够像DHCP动态分配IP地址一样去动态分配major和minor。
2.device naming。提供设备命名持久化的机制。传统设备命名方式不具直观性,像/dev/hda1这样的名字肯定没有boot_disk这样的名字直观。udev能够像DNS解析域名一样去给设备指定一个有意义的名称。
3.API to access info about current system devices 。提供了一组易用的API去操作sysfs,避免重复实现同样的代码。
udev系统可以分为三个部分:
* namedev函数库,处理设备的命名。
* libsysfs函数库,进行设备信息的读取(080版本后废弃)
* 守护进程udevd,处于用户空间,用于创建虚拟/dev
系统获取内核通过netlink socket发出的信息。早期的版本使用hotplug,并在/etc/hotplug.d/default添加一个链接到自身来达到目的。
在传统的Linux系统中,/dev目录下的设备节点为一系列静态存在的文件,而udev则动态提供了在系统中实际存在的设备节点。虽然devfs提供了类似功能,udev的支持者也给出了很多udev实现比devfs好的理由:
*udev支持设备的固定命名,而并不依赖于设备插入系统的顺序,默认的udev设置提供了存储设备的固定命名。任何硬盘都根据其唯一的文件系统id、磁盘名称及硬件连接的物理位置来进行识别。
*udev完全在用户空间执行,而不是像devfs在内核空间一样执行。结果就是udev将命名策略从内核中移走,并可以在节点创建前用任意程序在设备属性中为设备命名。
而用户空间的程序与设备通信的方法,主要有以下几种方式:
1.通过ioperm获取操作IO端口的权限,然后用inb/inw/inl/outb/outw/outl等函数,避开设备驱动程序,直接去操作IO端口。
2.用ioctl函数去操作/dev目录下对应的设备,这是设备驱动程序提供的接口。像键盘、鼠标和触摸屏等输入设备一般都是这样做的。
3.用write/read/mmap去操作/dev目录下对应的设备,这也是设备驱动程序提供的接口。像framebuffer等都是这样做的。
上面的方法在大多数情况下,都可以正常工作,但是对于热插拨(hotplug)的设备,比如像U盘,就有点困难了,因为你不知道:什么时候设备插上了,什么时候设备拔掉了。这就是所谓的hotplug问题了。
处理hotplug传统的方法是,在内核中执行一个称为hotplug的程序,相关参数通过环境变量传递过来,再由hotplug通知其它关注hotplug事件的应用程序。这样做会有效率上的问题,新的方法是采用NETLINK实现的,这是一种特殊类型的socket,专门用于内核空间与用户空间的异步通信。
udev的命令格式
---------------------------------------------------
BUS 总线 KERNEL 内核名如sd* ID 设备id 如总线id PLACE
SYSFS{filename}
PROGRAM 调用外部程序 RESULT 匹配program返回的结果 NAME
SYMLINK 连接规则
---------------------------------------------------
udev服务管理着设备的基本功能,例如增加了一块硬盘、拔掉了一个 USB 设备等等。下面来小结一下其工作结构及其工作流程。
1、udev 的工作方法
udev 整体架构可大致分为三个部分:内核层、用户空间守护进程、规则引擎与工具集。
简单来说,当设备有变动时,内核层最先发现, 之后通过 uevent 机制(基于 netlink 套接字)向用户空间发送设备事件(如设备插入、移除、状态变化),设备信息通过 sysfs 虚拟文件系统暴露(路径如 /sys/block/sda)。
用户空间守护进程监听内核的 uevent 事件,之后根据规则文件(.rules)中的规则处理事件。
下面是一个规则文件的案例
# 匹配特定 USB 设备,为其分配固定名称和权限
SUBSYSTEM=="block", ACTION=="add", ATTRS{idVendor}=="0781", ATTRS{idProduct}=="5580", \
SYMLINK+="my_secure_usb", GROUP="users", MODE="0660"
匹配条件:设备为块设备(SUBSYSTEM=="block"),动作为插入(ACTION=="add"),且匹配厂商/产品 ID。
执行动作:创建符号链接 /dev/my_secure_usb,设置设备组为 users,权限为 0660。
sudo 后门以后不再对二进制文件依赖的共享库 (xxx.so) 文件替换劫持做权限维持做单独说明,主要是探讨通过配置文件的方式进行权限维持。执行动作也为运行一些指令创造了条件,为系统安全埋下了伏笔。
2、udev 规则文件
1. 规则文件存储位置
udev 的规则文件按照规范一般是 xxxx.rules 文件,udev 的规则文件存在于系统的以下三个目录中:
ls -al /lib/udev/rules.d/
ls -al /etc/udev/rules.d/
ls -al /run/udev/rules.d/
可以看到,这里并没有目录,都是文件,而且文件后缀都是固定的,稍后我们了解配置文件编写规则后,再测试目录以及各种文件名称的有效性
2. 规则文件的加载顺序
不同目录的规则优先级,尽管文件名决定加载顺序,但目录路径的优先级更高:
/etc/udev/rules.d/ 的规则优先级最高(即使文件名数字小,也会覆盖其他目录的规则)
/run/udev/rules.d/ 的规则次之
/lib/udev/rules.d/ 的规则优先级最低
优先级总结:
/etc/udev/rules.d/ -> /run/udev/rules.d/ -> /lib/udev/rules.d/
3. 规则编写规范
archlinuxcn-Udev
archlinux-udev.7
writing_udev_rules
udev 存在的意义是在设备出现变化时对应进行处理,所以整体语法应该是如何标识一个设备或一批设备,也就是匹配、如何标识出变化、如何标识出要做的动作,也就是赋值。
规则语法
每条规则由逗号分隔的 键-操作符-值 表达式组成:
匹配条件:使用 == 或 != 运算符(如 SUBSYSTEM=="usb")。
赋值操作:使用 =, +=, -=, := 运算符(如 SYMLINK+="my_device")。
注释
规则使用 # 作为注释
匹配键
1) 基础设备属性
匹配键 描述
ACTION 设备事件类型:add(添加)、remove(移除)、change(状态变更)等。
KERNEL 内核分配的设备名称(如 sda、eth0)。
SUBSYSTEM 设备所属子系统(如 block(块设备)、usb、net(网络设备))。
DRIVER 设备绑定的驱动名称(如 usb-storage)。
DEVPATH 设备在 /sys 中的路径(如 /devices/pci0000:00/0000:00:1a.0/usb1)。
2) 设备属性(sysfs 属性)
匹配键 描述
ATTR{filename} 设备的 sysfs 属性文件值(如 ATTR{size}=="4096")。
ATTRS{filename} 向上级设备查找 sysfs 属性(用于跨层级匹配,如 parent 设备的属性)。
3) 环境变量
匹配键 描述
ENV{key} 设备或全局环境变量(如 ENV{ID_MODEL}=="MyDisk")。
4) 设备标签与特征
匹配键 描述
TAG 设备标签(如 TAG=="uaccess",用于用户空间访问权限)。
TEST 检查文件或目录是否存在(如 TEST=="/dev/my_device")。
PROGRAM 执行外部程序,若返回值为 0 则匹配(需结合 RESULT 使用)。
5) 设备关系与拓扑
匹配键 描述
PARENT 匹配父设备的属性(如 PARENT{SUBSYSTEM}=="usb")。
KERNELS 向上级设备匹配内核名称(类似 ATTRS)。
SUBSYSTEMS 向上级设备匹配子系统(类似 ATTRS)。
DRIVERS 向上级设备匹配驱动名称(类似 ATTRS)。
6) 高级匹配
匹配键 描述
IMPORT{type} 导入外部数据(如 IMPORT{program}="/sbin/blkid -o udev -p $tempnode")。
NAME 设备节点名称(仅用于网络设备,如 NAME="eth0")。
MODE/OWNER/GROUP 直接匹配设备的权限或所有权(不推荐,通常用于赋值而非匹配)。
匹配运算符
匹配运算符(Matching Operators)用于定义设备属性的匹配条件
1) 基础匹配运算符
运算符 描述
== 等于
:严格匹配属性值。示例:SUBSYSTEM=="usb"(匹配 USB 子系统设备)。
!= 不等于
:排除特定属性值。示例:KERNEL!="sda*"(排除内核名以 sda 开头的设备)。
2) 字符串匹配运算符
运算符 描述
=~ 正则表达式匹配
:使用正则表达式匹配属性值。示例:KERNEL=~"^sd[a-z][0-9]"(匹配形如 sda1、sdb2 的设备名)。
!~ 正则表达式不匹配
:排除符合正则表达式的属性值。示例:KERNEL!~"^loop"(排除内核名以 loop 开头的设备)。
3) 特殊匹配操作
运算符 描述
$= 字符串包含
:检查属性值是否包含子字符串(部分实现支持,非官方标准)。示例:ATTR{name}$="video"(匹配 name 属性包含 video 的设备)。
4) 逻辑组合符
运算符 描述
, 逻辑与
:多个条件需同时满足。示例:SUBSYSTEM=="usb", ATTR{idVendor}=="0781"(USB 子系统且厂商 ID 为 0781)。
注意事项
大小写敏感:匹配操作默认区分大小写。
转义字符:在正则表达式中需转义特殊字符(如 \* 匹配字面量 *)。
兼容性:=~ 和 !~ 依赖于 udev 版本,需验证支持性
赋值键
1) 基础设备操作
赋值键 描述
NAME 命名设备节点(仅适用于网络设备,如网卡)。
SYMLINK 创建设备符号链接(可创建多个链接,如 SYMLINK+="my_device")。
MODE 设置设备节点的权限(如 MODE="0660")。
OWNER 设置设备节点的所有者(如 OWNER="root")。
GROUP 设置设备节点的所属组(如 GROUP="users")。
2) 设备标签与元数据
赋值键 描述
TAG 为设备添加标签(如 TAG+="uaccess",用于用户空间访问权限)。
ENV{key} 设置设备的环境变量(如 ENV{DISK_TYPE}="ssd")。
3) 脚本与程序执行
赋值键 描述
RUN{type} 执行外部程序或脚本(type 支持 program 或 builtin)。
IMPORT{type} 导入外部数据(如 IMPORT{program}="/sbin/blkid -o udev -p $tempnode")。
PROGRAM 执行外部程序并捕获输出(通常与 RESULT 配合使用)。
4) 高级控制
赋值键 描述
OPTIONS 设置特殊选项,如:
- OPTIONS+="last_rule"(跳过后续规则)。
- OPTIONS+="watch"(监视设备状态变化)。
SECLABEL 设置设备的安全标签(如 SELinux 上下文)。
ATTR{filename} 直接修改设备的 sysfs 属性(需谨慎使用)。
5) 网络设备专用
赋值键 描述
NAME 重命名网络接口(如 NAME="eth0")。
MAC 设置网络接口的 MAC 地址(需驱动支持)。
赋值运算符
赋值运算符(Assignment Operators) 用于定义如何修改设备属性或执行操作
1) 基础赋值运算符
运算符 描述
= 直接赋值
:覆盖属性的原有值。示例:MODE="0660"(设置权限为 0660,忽略默认值)。
+= 追加赋值
:在原有值基础上添加新值(适用于多值属性,如 SYMLINK)。示例:SYMLINK+="my_device"(在默认符号链接基础上添加 my_device)。
:= 最终赋值
:设置不可被后续规则覆盖的值。示例:GROUP:="users"(后续规则无法修改 GROUP 的值)。
2) 特殊运算符
运算符 描述
-= 移除值
:从多值属性中删除指定值(适用于 SYMLINK 或 TAG)。示例:TAG-="uaccess"(移除 uaccess 标签)。
== 匹配条件
:仅用于匹配键(非赋值操作)。示例:SUBSYSTEM=="usb"(匹配 USB 子系统设备)。
注意事项
= 与 +=:
使用 = 会覆盖原有值(如 SYMLINK="my_link" 将删除默认符号链接)。
使用 += 更安全,通常用于扩展而非替换。
:= 的强制力:
通过 := 赋值的属性不可被后续规则修改,即使后续规则使用 = 或 +=。
慎用 -=:
移除系统默认值可能导致意外行为(如权限错误)。
取值
udev 规则中的值都是被双引号包裹的,这其中也涉及到转义、大小写、正则、变量等,但其实我不需要特别关心,可以在遇到的时候使用 deepseek 等进行解释
常用键的值示例
键 值示例 说明
SUBSYSTEM "usb", "block" 设备子系统类型
ACTION "add", "remove" 设备事件类型
ATTR{size} "4096", "0" 设备的 sysfs 属性值
ENV{ID_VENDOR} "SanDisk" 设备环境变量
MODE "0660", "0644" 八进制权限值
SYMLINK "my_device", "backup_disk" 符号链接名称
RUN "/usr/bin/mount.sh" 脚本或程序的绝对路径
4. udev 规则案例
vbox虚拟机下的Devuan4下获取的第二块硬盘设备(sdb)信息:
fdisk -l #可见Disk identifier
udevadm info -a -p $(udevadm info -q path -n /dev/sdb)
内容非常长,它表示了整个设备系统按照层级的属性关系,SUBSYSTEM 表示当前设备的层级。该设备是 block 时就是 SUBSYSTEM="scsi";设备是 USB 时就是 SUBSYSTEM="usb"。
当前设备的层级解析
层级路径 | 子系统 | 设备角色 | 关键属性示例 |
pci0000:00 | - | PCI 总线根节点 | 无 |
0000:00:1d.6 | pci | USB 主控制器硬件 | vendor="0x1033" |
usb4 | usb | 宿主 USB 3.0 控制器 | product="xHCI Host Controller" |
4-1 | usb | 物理优盘的 USB 设备控制器 | idVendor="0930", idProduct="6545" |
4-1:1.0 | usb | USB 设备接口(存储协议接口) | bInterfaceClass="08" (存储设备) |
host8 | scsi | 虚拟 SCSI 主机(USB 存储驱动模拟) | model="TransMemory-Ex" |
target8:0:0 | scsi | SCSI 目标设备(虚拟磁盘) | vendor="TOSHIBA" |
8:0:0:0 | scsi | SCSI 逻辑单元(LUN 0) | rev="PMAP" |
block/sdb | block | 块设备节点(最终暴露的磁盘) | size="62029824" (磁盘容量) |
找到属于待查设备的属性信息
ATTRS{idProduct}==""
ATTRS{idVendor}==""
ATTRS{manufactureer}==""
ATTRS{product}==""
获取到父级的 idProduct 和 idVendor (因为是 ATTRS),Usb设备可以通过 lsusb 进行验证:
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 004: ID 04f2:b217 Chicony Electronics Co., Ltd Lenovo Integrated Camera (0.3MP)
Bus 001 Device 003: ID 046d:081b Logitech, Inc. Webcam C310
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
查看Logitech, Inc. Webcam C310的相关信息:
lsusb -v -d 046d:081b
会有很长的输出。。。
当开启环境变量“LSBLK_DEBUG=all”时,运行lsblk也会有本地块设备的信息。
编写 udev 案例
插入优盘后希望创建一个符号连接 nop_driver
首先标识一下这个设备
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545"
注意,idVentor 和 idProduct 是来自父级的,这点在原始的输出中可以看到
之后我们描述一下插入的动作
ACTION=="add"
接下来再标识一下要做的赋值,创建 /tmp/flag 文件
RUN+="/bin/touch /tmp/flag"
合起来就是
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag"
将内容复制到 /etc/udev/rules.d/99-nop-driver.rules 中
测试案例有效性
首先查看是否存在这个'/tmp/flag'文件
不存在,接下来插入优盘。可以看到,文件成功创建,该规则在未显式地手动重新加载配置文件的情况下就已经可以生效了
测试规则文件夹加载顺序
通过在三个目录中创建相同文件名称的规则文件,对比一下实际的执行顺序
/lib/udev/rules.d/99-nop-driver.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag4lib"
/etc/udev/rules.d/99-nop-driver.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag4etc"
/run/udev/rules.d/99-nop-driver.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag4run"
首先直接插入优盘进行测试
ls /tmp/
/tmp/flag4etc
在默认情况下,优先级最高的竟然是 /etc/udev/rules.d/ 目录下的
删除 /etc/udev/rules.d/ 目录下的配置文件,看看下一个被加载的是哪一个
/tmp/flag4run
看来接下来是 /run/udev/rules.d/ 目录下的,删除该文件,再次测试
/tmp/flag4lib
需要注意的是,现在现代 Linux 发行版(如 Ubuntu/Debian/Fedora 等)中/lib 通常是指向 /usr/lib 的符号链
/lib -> usr/lib
所以 /lib/udev/rules.d/99-nop-driver.rules 与 /usr/lib/udev/rules.d/99-nop-driver.rules 为同一文件
所以在 Debiab 系中 udev 规则文件夹同名规则文件加载顺序为
/etc/udev/rules.d/ -> /run/udev/rules.d/ -> /lib/udev/rules.d/
测试文件名称
在 archlinux 的官方文档上说,规则文件需要使用 '.rules' 后缀,无后缀规则文件是无效的
测试是否可以在新建目录
如果我们在该目录新建一个目录,之后在其中加入规则文件,规则文件会生效吗?
测试发现也是不行的,看来对于配置文件的要求还是比较固定。
3、udev 持久化探索
上面的测试中,规则文件肯定可以被用来做后门,所以稍后着重探讨,排除替换可执行文件、替换共享库这类持久化以外,尝试从剩下的三个配置文件探索持久化的可能
1. hwdb
hwdb 是硬件属性数据库,将设备的硬件属性(如 USB 厂商/产品 ID、PCI 设备 ID 等)映射到自定义属性(如触摸板手势配置、键盘重复速率等)
默认 Debian 中 /etc/udev/hwdb.d 是空的,在 /lib/udev/hwdb.d 中是有内容的。
攻击者如果修改硬件属性数据库,可能会导致系统挂载硬件设备时错误识别等,想实现持久化的话,可能需要配合其他程序错误识别会导致一些额外bug。所以一般的攻击者是不会通过硬件属性数据库进行持久化的。
2. iocost.conf
iocost.conf 是 udev 用于定义和管理设备 I/O 成本的配置文件。通过在这个文件中指定各类设备的 I/O 成本,系统能够在设备接入时自动调整 I/O 调度策略,以优化性能和资源分配。
根据目前的文档来看,只能用来调节I/O 成本,无法创建文件,也无法执行外部程序,甚至官方文档也仅仅介绍了 TargetSolution 这一个参数,我尝试了直接在其中写 shell 命令,并没有成功执行
3. udev.conf
udev.conf 是 udev 守护进程的主配置文件,用于设置 udev 的全局运行参数(/etc/udev/udev.conf),而不是用来定义具体的设备匹配或行为规则
下面是可以设置的选项:
udev_log=日志级别。有效值可以是数值形式的 syslog 优先级,或它们的文本描述:
err
info
debug
children_max=一个整数,表示并行执行的最大事件数。 如果未指定或指定为 0,最大并行数将基于系统资源自动确定。 此选项与命令行中的 --children-max= 相同
exec_delay=一个整数。为每个 RUN{program} 参数延迟执行指定的秒数。 此选项在调试因加载不可用内核模块导致的冷插拔启动崩溃时可能会很有用。 此选项与命令行中的 --exec-delay= 相同。
event_timeout=一个整数,表示等待事件完成的秒数。 超过这个时间后,事件将被终止。默认值为 180 秒。 此选项与命令行中的 --event-timeout= 相同。
resolve_names=指定 systemd-udevd 解析用户和组名的时机。
当设置为 early(默认)时,会在解析规则时进行名称解析;
设置为 late 时,将对每个事件单独进行名称解析;
设置为 never 时,则永不解析名称,此时所有设备都将归 root 所有。 此选项与命令行中的 --resolve-names= 相同。
timeout_signal=指定当工作超时时 systemd-udevd 发送给工作线程的信号。 注意:无论是工作线程还是派生进程,都会使用此信号来终止。 默认信号为 SIGKILL。
从配置项来看,也就 timeout_signal 似乎还有点对外部造成影响的可能,但是用来做权限维持较为困难
4. 规则文件
根据之前的案例,我们了解到 RUN 这个键可以执行系统命令,接下来探究一下这些键还能做哪些有利于权限维持的操作,有哪些限制
RUN
执行外部程序或脚本(type 支持 program 或 builtin)
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", RUN+="/bin/touch /tmp/flag"
PROGRAM
执行外部程序并捕获输出(通常与 RESULT 配合使用)
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", PROGRAM="/usr/bin/env bash -c '/bin/touch /tmp/flag-program && echo FLAG=touched'"
IMPORT
导入外部数据(如 IMPORT{program}="/sbin/blkid -o udev -p $tempnode")
SUBSYSTEM=="usb", ATTRS{idVendor}=="0930", ATTRS{idProduct}=="6545", ACTION=="add", IMPORT{program}="/usr/bin/env bash -c '/bin/touch /tmp/flag-import && echo FLAG=touched'"
ENV
ENV 设置的环境变量只是局部环境变量,在 udev 事件处理期间生效,不会共享到整个操作系统的全局环境中,所以只能做辅助。
4、FAQ
问:什么是udev?
答:udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。
问:udev支持什么内核?
答:udev只支持linux-2.6内核,因为udev严重依赖于sysfs文件系统提供的信息,而sysfs文件系统只在linux-2.6内核中才有。
问:udev是一个内核程序还是用户程序?
答:udev是一个用户程序(user-mode daemon)。
问:udev和devfs有什么差别?
答:udev能够实现所有devfs实现的功能。但udev运行在用户模式中,而devfs运行在内核中。据称:devfs具有一些不太容易解决的先天缺陷。
问:udev的配置文件放在哪里?
答:udev是一个用户模式程序。它的配置文件是/etc/udev/udev.conf。这个文件一般缺省有这样几项:
udev_root="/dev" ; udev产生的设备文件的根目录是/dev。
udev_db="/dev/.udevdb" ; 通过udev产生的设备文件形成的数据库。
udev_rules="/etc/udev/rules.d" ;用于指导udev工作的规则所在目录。
udev_log="err" ;当出现错误时,用syslog记录错误信息。
问:udev的工作过程是怎样的?
答:由于没有研究过udev的源程序,不敢贸然就说udev的工作过程。只是通过一些网上的资料和udev的说明文档,大致猜测它的工作过程可能是这样的。
1. 当内核检测到在系统中出现了新设备后,内核会在sysfs文件系统中为该新设备生成一项新的记录,一般sysfs文件系统会被mount 到 /sys目录中。新记录是以一个或多个文件或目录的方式来表示。每个文件都包含有特定的信息。
2.udev在系统中是以守护进程的方式udevd在运行,它通过某种途径检测到新设备的出现,通过查找设备对应的sysfs中的记录得到设备的一些信息。
3.udev会根据/etc/udev/udev.conf文件中的udev_rules指定的目录,逐个检查该目录下的文件,这个目录下的文件都是针对某类或某个设备应该施行什么措施的规则文件。udev读取文件是按照文件名的ASCII字母顺序来读取的,如果udev一旦找到了与新加入的设备匹配的规则,udev就会根据规则定义的措施对新设备进行配置。同时不再读后续的规则文件。
问:udev的规则文件的语法是怎样的?
答:udev的规则文件以行为单位,以"#"开头的行代表注释行。其余的每一行代表一个规则。每个规则分成一个或多个“匹配”和“赋值”部分。“匹配”部分用“匹配“专用的关键字来表示,相应的“赋值”部分用“赋值”专用的关键字来表示。“匹配”关键字包括:ACTION,KERNEL,BUS, SYSFS等等,“赋值”关键字包括:NAME,SYMLINK,OWNER等等。具体详细的描述可以阅读udev的man文档。
下面举例来说明一下,有这样一条规则:
SUBSYSTEM=="net", ACTION=="add", SYSFS{address}=="00:0d:87:f6:59:f3", IMPORT="/sbin/rename_netiface %k eth0"
这个规则中的“匹配”部分有三项,分别是SUBSYSTEM,ACTION和SYSFS。而"赋值"部分有一项,是IMPORT。这个规则就是说,当系统中出现的新硬件属于net子系统范畴,系统对该硬件采取的动作是加入这个硬件,且这个硬件在SYSFS文件系统中的“address”信息等于“00: 0d..."时,对这个硬件在udev层次施行的动作是调用外部程序/sbin/rename_netiface,传递的参数有两个,一个是“%k”,代表内核对该新设备定义的名称。另一个是”eth0“。
从上面这个例子中可以看出,udev的规则的写法比较灵活的,尤其在“匹配”部分中,可以通过诸如”*“, ”?“,[a-c],[1-9]等shell通配符来灵活匹配多个匹配项。具体的语法可以参考udev的man文档。
问:udev怎样做到不管设备连接的顺序而维持一个统一的设备名?
答:实际上,udev是通过对内核产生的设备名增加别名的方式来达到上述目的的。前面说过,udev是用户模式程序,不会更改内核的行为。因此内核依然会我行我素地产生设备名如sda,sdb等。但udev可以根据设备的其他信息如总线(bus),生产商(vendor)等不同来区分不同的设备,并产生设备文件。udev只要为这个设备文件取一个固定的文件名就可以解决这个问题。在后续对设备的操作中,只要引用新的设备名就可以了。但为了保证最大限度的兼容,一般来说,新设备名总是作为一个对内核自动产生的设备名的符号链接(link)来使用的。
例如:内核产生了sda设备名,而根据信息,这个设备对应于是我的内置硬盘,那我就可以制定udev规则,让udev除了产生/dev/sda设备文件外,另外创建一个符号链接叫/dev/internalHD。这样在我fstab文件中,就可以用/dev/internalHD来代替原来的 /dev/sda了。下次由于某些原因,这个硬盘在内核中变成了sdb设备名了,那也不用着急,udev还会自动产生/dev/internalHD这个链接,并指向正确的/dev/sdb设备。所有其他的文件像fstab等都不用修改。
问:怎样才能找到这些设备信息,并把他们放到udev的规则文件中来匹配呢?
答:这个问题比较难,网上资料不多,我只找到一篇文章来介绍如何写udev的规则。他的基本方法是通过udevinfo这个实用程序来找到那些可以作为规则文件里的匹配项的项目。有这样两种情况可以使用这个工具:
第一种情况是,当你把设备插入系统后,系统为设备产生了设备名(如/dev/sda)。那样的话,你先用udevinfo -q path -n /dev/sda,命令会产生一个该设备名对应的在sysfs下的路径,如/block/sda。然后,你再用udevinfo -a -p /sys/block/sda,这个命令会显示一堆信息,信息分成很多块。这些信息实际来自于操作系统维护的sysfs链表,不同的块对应不同的路径。你就可以用这些信息来作为udev规则文件中的匹配项。但需要注意的是,同一个规则只能使用同一块中显示的信息,不能跨块书写规则。
第二种情况是,不知道系统产生的设备名,那就只有到/sys目录下去逐个目录查找了,反复用udevinfo -a -p /sys/path...这个命令看信息,如果对应的信息是这个设备的,那就恭喜你,否则就再换个目录。当然,在这种情况下,成功的可能性比较小。
参考来源:
http://zh.wikipedia.org/zh/Udev
http://www.linuxsky.org/doc/admin/200710/139.html
该文章最后由 Administrator 于 2025-04-01 11:06:51 更新,目前是第 2 版。