mirror of
https://github.com/troglobit/finit.git
synced 2026-10-13 10:14:47 +00:00
Transform keventd from a power-supply monitor + basic hotplug handler into
a full udev-compatible device manager.
Rules engine (rules.c):
- Full .rules file parser covering all udev key types: ACTION, KERNEL,
SUBSYSTEM, DEVPATH, ENV, ATTR, SYSCTL, TAG, RESULT, PROGRAM, TEST,
parent-chain KERNELS/SUBSYSTEMS/ATTRS/DRIVERS, and more
- Pattern matching: plain string, fnmatch glob, and pipe-separated alternatives
- Operators: ==, !=, =, +=, -=, :=
- Assignments: NAME=, MODE=, OWNER=, GROUP=, SYMLINK+=, ENV{k}=, TAG+=, RUN+=
- IMPORT{program|file|builtin|parent|cmdline|db}=
- PROGRAM= with stdout capture for subsequent RESULT== matching
- GOTO=/LABEL= flow control
- Loads *.rules from /lib/udev/rules.d, /run/udev/rules.d, /etc/udev/rules.d
and an optional extra directory (-r DIR); reloads on SIGHUP
Builtin framework (builtin.c):
- kmod: load module by MODALIAS or explicit alias
- hwdb: match device against *.hwdb text files in udev hwdb dirs; builds
correct lookup key per subsystem — evdev:input:b*v*p*e* for input,
usb:v*p* for USB, raw modalias for PCI/platform
- path_id: build stable ID_PATH / ID_PATH_TAG from sysfs topology (PCI, USB,
ATA, NVMe, platform, ACPI, virtio)
- usb_id: read idVendor/idProduct/bcdDevice/serial from sysfs; look up
ID_VENDOR_FROM_DATABASE and ID_MODEL_FROM_DATABASE from usb.ids
(hwdata package) when available; silent fallback when absent
- input_id: classify input devices (keyboard, mouse, joystick, touchscreen,
touchpad) from evdev capability bitmasks in sysfs
- net_id: generate predictable names — MAC-based enx<mac> and PCI-slot-based
enp<bus>s<dev>[f<func>]
- blkid: probe filesystem type, UUID, and label via libblkid; sets ID_FS_*
and ID_PART_TABLE_* properties
Network interface renaming (uevent.c):
- netdev_add() renames interfaces via SIOCSIFNAME when a NAME= rule matched,
then sets the Finit dev/ condition on the final name; and any setup using
persistent interface naming via udev rules
Device node and symlink improvements (uevent.c):
- NAME=, MODE=, OWNER=, GROUP= overrides from matched rules applied at
mknod/chown time, falling back to the built-in permission table
- SYMLINK+= links from rules applied alongside built-in by-id/by-path links
Device property database (udevdb.c):
- Persist per-device E:/S:/I: records to /run/udev/data/ on ADD/CHANGE,
delete on REMOVE; IMPORT{db}= restores saved properties into event env
Build:
- libblkid (util-linux) is now required for keventd
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
124 lines
6.0 KiB
Markdown
124 lines
6.0 KiB
Markdown
# How udev rules work
|
|
|
|
## Summary of the complete fix
|
|
|
|
- hardware.c: Write authorized_default=1 on the bus path before nftw
|
|
runs (just 2 lines added). This exploits the fact that apath already
|
|
contains path/authorized_default at that point (set by the snprintf on
|
|
line 71). With authorized_default=1 set on the root hub, any device
|
|
that appears behind an intermediate hub during hub enumeration gets
|
|
auto-authorized by the kernel.
|
|
- 70-usb-authorize.rules: Belt-and-suspenders for the async case. If hub
|
|
port probing is slow enough that children appear after nftw has
|
|
already returned, udev sees the add event with authorized=0, walks up
|
|
the parent chain with ATTRS{authorized_default}=="1", and authorizes
|
|
the device. Locked buses (authorized_default=0) are correctly skipped.
|
|
|
|
```
|
|
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
|
|
ATTR{authorized}=="0", \
|
|
ATTRS{authorized_default}=="1", \
|
|
RUN+="/bin/sh -c 'echo 1 > /sys%p/authorized'"
|
|
```
|
|
|
|
This keeps authorized_default=2 (deny by default) fully intact — the
|
|
security window stays closed. The udev rule only fires when confd has
|
|
explicitly marked a bus as unlocked.
|
|
|
|
## Comment from Undersigned
|
|
|
|
Very interesting, I did not know you could do such complex operations
|
|
with udev rules. Could you explain a bit more in detail how the matching
|
|
works and how we can guarantee locked buses/ports are not accidentally unlocked?
|
|
|
|
> Sure. Let's walk through it step by step.
|
|
|
|
## How udev ATTRS{} matching works
|
|
|
|
A udev rule matches against a device and its entire parent chain in the
|
|
sysfs device tree. When a device like 2-1.4 (the keyboard) is added, its
|
|
sysfs hierarchy looks like:
|
|
|
|
```
|
|
/sys/devices/.../0000:01:00.0/usb2/ ← root hub (usbN)
|
|
usb2/2-1/ ← VIA Labs hub
|
|
usb2/2-1/2-1.4/ ← keyboard ← event fires here
|
|
```
|
|
|
|
- ATTR{} (no trailing S) — matches attributes on the device itself only
|
|
- ATTRS{} (with S) — matches attributes on the device or any of its
|
|
parents, walking up the tree until it finds a match or runs out of
|
|
parents
|
|
|
|
So ATTRS{authorized_default}=="1" on a keyboard event at 2-1.4 walks:
|
|
|
|
1. 2-1.4/authorized_default — doesn't exist (not a hub/controller)
|
|
2. 2-1/authorized_default — doesn't exist (downstream hubs don't have
|
|
this attribute, only root hubs do)
|
|
3. usb2/authorized_default — exists, check its value
|
|
|
|
This is exactly the right level: authorized_default only lives on root
|
|
hubs (usbN), which is precisely where confd writes.
|
|
|
|
## Why locked buses can't be accidentally unlocked
|
|
|
|
The invariant is: confd owns `authorized_default`. It sets it to 0
|
|
(locked) or 1 (unlocked). The udev rule only reads it, never writes it.
|
|
|
|
Walk through the locked case:
|
|
|
|
```
|
|
confd sets usb2/authorized_default = 0 ← locked
|
|
```
|
|
|
|
A device plugs into usb2. udev fires:
|
|
|
|
1. ATTR{authorized}=="0" — matches (device is unauthorized)
|
|
2. ATTRS{authorized_default}=="1" — walks up to usb2, finds 0 → no match
|
|
3. Rule doesn't fire. Device stays denied. ✓
|
|
|
|
For a device behind an intermediate hub on a locked bus:
|
|
|
|
```
|
|
usb2/authorized_default = 0 ← bus locked
|
|
usb2/2-1/ ← hub (somehow authorized)
|
|
usb2/2-1/2-1.4/ ← keyboard
|
|
```
|
|
|
|
Even if the hub itself were authorized somehow, the keyboard's parent
|
|
chain still reaches `usb2/authorized_default = 0` before finding any
|
|
`authorized_default = 1`. The rule doesn't fire. ✓
|
|
|
|
The only remaining question: what if two buses exist and one is locked?
|
|
|
|
Say usb1 is unlocked and usb2 is locked. A device on usb2 fires a udev
|
|
event. ATTRS walks up: `usb2/authorized_default = 0` — stops there (once
|
|
it finds the attribute it stops). It does not continue up to PCIe or
|
|
platform buses looking for usb1's attribute. Each USB device's parent
|
|
chain only goes through its own root hub.
|
|
|
|
This is because authorized_default is found at usb2 — udev stops as soon
|
|
as it matches the attribute name, regardless of value. If the value
|
|
doesn't match the condition, the whole rule fails. It doesn't continue
|
|
looking at other buses.
|
|
|
|
## Summary of the guarantee
|
|
|
|
```
|
|
┌───────────────────────┬─────────────────────────┬─────────────────────────┐
|
|
│ Scenario │ usb2/authorized_default │ Rule fires? │
|
|
├───────────────────────┼─────────────────────────┼─────────────────────────┤
|
|
│ Bus unlocked by confd │ 1 │ Yes → device authorized │
|
|
├───────────────────────┼─────────────────────────┼─────────────────────────┤
|
|
│ Bus locked by confd │ 0 │ No → device denied │
|
|
├───────────────────────┼─────────────────────────┼─────────────────────────┤
|
|
│ Bus at kernel default │ 2 │ No → device denied │
|
|
├───────────────────────┼─────────────────────────┼─────────────────────────┤
|
|
│ Bus doesn't exist yet │ — │ No → no match │
|
|
└───────────────────────┴─────────────────────────┴─────────────────────────┘
|
|
```
|
|
|
|
The rule is purely reactive: it can only authorize devices on buses that
|
|
confd has already explicitly declared unlocked. It has no ability to
|
|
override a lock.
|