mirror of
https://github.com/troglobit/finit.git
synced 2026-10-25 12:04:36 +00:00
keventd: add udev rules engine
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>
This commit is contained in:
17 files changed
+4451
-44
No files matched your search
+1
-1
@@ -6,6 +6,6 @@ dist_docs_DATA = build.md cmdline.md commands.md conditions.md \
|
||||
distro.md example.md features.md index.md initctl.md \
|
||||
keventd.md plugins.md requirements.md runlevels.md \
|
||||
runparts.md service.md signals.md state-machine.md \
|
||||
watchdog.md
|
||||
udev-matching.md watchdog.md
|
||||
dist_docs_DATA += img/logo.png img/svc-machine.png img/svc-machine.svg \
|
||||
img/alpine-screenshot2.png
|
||||
+17
-8
@@ -20,9 +20,11 @@ kernel events and handles:
|
||||
|
||||
- **Device node creation**: creates and removes `/dev` nodes with
|
||||
correct permissions on device add/remove events
|
||||
- **Persistent symlinks**: creates `/dev/disk/by-id/`, `/dev/disk/by-path/`,
|
||||
and `/dev/input/by-id/`, `/dev/input/by-path/` symlinks for stable
|
||||
device naming
|
||||
- **udev rules**: matches events against rules read from
|
||||
`/lib/udev/rules.d/`, `/run/udev/rules.d/`, and `/etc/udev/rules.d/`,
|
||||
with a curated ruleset derived from eudev installed by default
|
||||
- **Persistent symlinks**: creates `by-id` and `by-path` symlinks under
|
||||
`/dev/disk/`, `/dev/input/`, and elsewhere, for stable device naming
|
||||
- **Firmware loading**: responds to kernel firmware requests by searching
|
||||
`/lib/firmware/` and writing firmware data to sysfs
|
||||
- **Module loading**: parses `MODALIAS` from uevents and spawns `modprobe`
|
||||
@@ -70,19 +72,25 @@ device subsystem and name:
|
||||
Persistent Symlinks
|
||||
-------------------
|
||||
|
||||
For block devices, keventd creates symlinks under `/dev/disk/`:
|
||||
Symlinks come from two places. A handful are built into keventd and are
|
||||
created for every device in the subsystem, whatever rules are loaded.
|
||||
|
||||
For block devices, under `/dev/disk/`:
|
||||
|
||||
- **by-id**: based on the device serial number and model, read from
|
||||
sysfs attributes (`/sys/.../device/vendor`, `model`, `serial`)
|
||||
- **by-path**: based on the device topology path
|
||||
|
||||
For input devices, symlinks are created under `/dev/input/`:
|
||||
For input devices, under `/dev/input/`:
|
||||
|
||||
- **by-id**: based on the device name from sysfs
|
||||
- **by-path**: based on the physical device path
|
||||
|
||||
These symlinks are tracked internally and automatically removed when the
|
||||
corresponding device is unplugged.
|
||||
The rest come from `SYMLINK+=` in the udev rules, so what you get
|
||||
depends on the ruleset installed.
|
||||
|
||||
Built-in and rule-provided symlinks alike are tracked internally and
|
||||
removed when the corresponding device is unplugged.
|
||||
|
||||
|
||||
Firmware Loading
|
||||
@@ -221,7 +229,7 @@ battery:
|
||||
Usage
|
||||
-----
|
||||
|
||||
keventd [-cdGhnpv] [-g GROUP]
|
||||
keventd [-cdGhnpv] [-g GROUP] [-r DIR]
|
||||
|
||||
Options:
|
||||
-c Run coldplug at startup
|
||||
@@ -231,6 +239,7 @@ Usage
|
||||
-h Show help text
|
||||
-n Run in foreground (no daemon)
|
||||
-p Passive mode: power supply events only
|
||||
-r DIR Extra rules directory
|
||||
-v Show version
|
||||
|
||||
In normal operation, Finit starts keventd automatically via its system
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user