have to admit: until recently, udev completely intimidated me. I needed udev rules for changing device permissions or creating symlinks with special names, but usually I had to use Google to find somebody else’s rule. Sometimes a rule I found wouldn’t work because the udev syntax changed–for example, you need to use ATTR or ATTRS instead of SYSFS with recent versions. It still scares me a little bit, but at least I feel like I actually understand what’s going on with it now. I’d like to share a few tips and tricks I’ve picked up about making udev rules.
First of all, the man page for udev is extremely useful. I know man pages can be boring, but this one has just about everything you need to know, and it’s not too long.
Where udev rules are stored
There are two directories where you can find udev rules:
- /etc/udev/rules.d
- /lib/udev/rules.d
The rules in /lib/udev/rules.d are bundled rules that you probably shouldn’t be messing with. Instead, put your custom rules in /etc/udev/rules.d. You’ll notice they are named in the form “number-description.rules”. The number is used for ordering the rules, so you can pick an order that makes sense for your needs. It’s a good idea to use that same form so that you have a good idea of the order in which the rules will be parsed. This can definitely matter–I have had troubles with creating symlinks for USB serial devices if the number is too small, probably due to some bundled rule that overrides something my rule does.
If you want to override a bundled rule file that’s in /lib/udev/rules.d, you should create a file with the same name in /etc/udev/rules.d. This will cause the new file in /etc/udev/rules.d to be used instead of the file in /lib/udev/rules.d.
The format of udev rules
udev rules are basically a comma-separated list of things–conditions and assignments. If all of the conditions in a rule are true, the rule is a match and all of the assignments are performed. Similar to many programming languages, a single equal sign (=) represents an assignment, while two equal signs (==) represent a comparison. != represents a “not equals” comparison, += adds a value to a list, and := assigns a value for the final time, disallowing any higher-numbered rules from modifying whatever you assigned. Every condition or assignment is a key-value pair separated by one of the operators listed above.
Keys you can match against
This section, I believe, is the most important part to understand. If you don’t understand the intricacies of how these matches work, especially with the keys ending in S, you will probably make mistaken assumptions about how they work (I did!). You can match against these keys:
- ACTION — what happened to the device that caused udev to be invoked. Common actions checked against are “add” and “remove”.
- KERNEL — the name of the device as given by the kernel
- KERNELS — the name of the device or a parent device as given by the kernel
- SUBSYSTEM — the kernel subsystem of the device (example: tty or usb)
- SUBSYSTEMS — the subsystem of the device or a parent device
- DRIVER — the name of the device’s driver
- DRIVERS — the name of the device’s driver or the name of a parent device’s driver
- ATTR{name} — a sysfs attribute of the device
- ATTRS{name} — a sysfs attribute of the device or a parent device
- IMPORTANT NOTE: If you are using any of the keys above that match against a parent device (KERNELS, SUBSYSTEMS, DRIVERS, ATTRS) in a rule, the parent device you’re matching against must be the same. For example, you can’t search for ATTRS of two different parent devices in the same rule. You also can’t search for the ATTRS of one parent device and the SUBSYSTEMS of a different parent device in the same rule. You can work around this limitation by creating multiple rules and using GOTO, but you can’t do it all in the same rule. This is actually a good thing because it allows you to do things like make sure you’re looking at a USB serial number instead of a PCI serial number for example.
This is very much an incomplete list, but these are some of the more important ones.
How to find values to match against
The info above is great, but it doesn’t really help much unless you know which driver, subsystem, and event attribute you’re trying to match against. That’s where a really handy command comes into play:
udevadm info –attribute-walk –name=ttyUSB0
ttyUSB0 is an example device name in this case — a USB serial port. This attribute walk command will find all of the keys/attributes for a particular device and all of its parent devices. If you run this command on a USB serial port, you will notice it finds the tty device at the top of the output, which is actually fairly boring as far as matching goes. Then as you scroll down, you will see udevadm walk all the way up the tree of parent devices.. You will see it reach the actual USB device that provides the serial port, the USB hub it’s connected to, the USB host controller the hub is connected to, and then probably a PCI controller or something like that depending on your host system.
More complicated example
Here’s a rule I created for giving certain USB-serial adapters a different name. If a USB-serial adapter with the serial number “12345678” is plugged in, I want a symlink of the form “ttyBlah#” to be created to point to it, where # is an integer. So if I plug three of them in, I want them to be called ttyBlah0, ttyBlah1, and ttyBlah2. This seems straightforward, but there might be other USB-serial adapters plugged in so I can’t just copy the kernel’s device number. For example, let’s pretend five USB-serial adapters are plugged in, named ttyUSB0 through ttyUSB4. ttyUSB0, ttyUSB1, and ttyUSB4 have the serial number “12345678” and ttyUSB2 and ttyUSB3 are other things that don’t use that serial number. I want my symlinks to be called ttyBlah0, ttyBlah1, and ttyBlah2. Notice that I can’t just blindly copy the number over because I want the third one to be called ttyBlah2, not ttyBlah4. It turns out udev can do this with a little bit of help.
I know this idea of having multiple USB devices with the same serial number seems goofy, but I actually did this in the real world. I programmed several FTDI chips to have the same serial number. It’s more convenient than getting a new USB device ID and not knowing if the Linux kernel is going to support it out of the box. The main reason I did it, though, was not Linux-related: it helps prevent Windows from creating a new COM port number each time a device with a different serial number is plugged in. In certain scenarios this behavior is undesirable.
Let’s get to the fun part. Here’s my example rule:
ACTION==”add”, KERNEL=”ttyUSB[0-9]*”, SUBSYSTEMS==”usb”, ATTRS{serial}==”12345678″, PROGRAM=”/home/doug/symlink-number.sh ttyBlah”, SYMLINK+=”ttyBlah%c”
This rule makes use of several new ideas. First of all, I have to match a device with a kernel name of ttyUSB followed by zero or more digits. This should match all USB serial ports. A parent device needs to be in the USB subsystem. This same parent device also needs to have a serial number of 12345678. The reason we’re checking for a subsystem of USB is to ensure that we don’t match against a device that has a PCI serial number (for example) of 12345678 instead. It’s probably overkill since ttyUSB devices are only going to be USB devices anyway, but it’s a good example of how the “ending in S” keys work. Anyway, we’re making sure we match against a ttyUSB* device which has a USB serial number of 12345678. Those three rules combined should match my three adapters that have that serial number.
If they match, we have two assignments. The first assignment is to PROGRAM. If you assign a value to PROGRAM, the value is interpreted as a command to run. The command is executed and the program’s output to stdout can be used in various ways in udev. This script (which we will see later) will print out the next available number to use for ttyBlah. Note that I passed a single argument to my script: ttyBlah. As you’ll see, I made this script generic so it could be used for numbering various different symlinks.
The second assignment uses the += operator, which as I said earlier adds a value to a list. The SYMLINK variable is a list of names of symlinks to create that will point to the matched device. We use += in case another rule has already set up a symlink for this same port.
You’ll notice that the symlink will be named ttyBlah%c. Several of the assigned values (one of which is SYMLINK) can be given names with printf-style formatters to add extra information. %c will be replaced with the stdout output from the program that was executed from the PROGRAM assignment. Another example of the available printf-style substitutions is %b which is the name of the parent device that was matched with the “ending in S” keys.
More & Posted from: https://www.netspi.com/blog/technical-blog/network-pentesting/linux-hacking-case-studies-part-2-nfs/
Additional link : Do More with Udev