I'm writing a pseudo-chorded keyboard remapper here using evdev and uinput. I don't want to give my regular user the permission to read and write to /dev/input/event* and /dev/uinput so I have created a new user (hopr) with the sole purpose of running the program. While trying to make it work, I noticed some very peculiar behaviour with uinput that I'm hoping someone can explain.
To make sure this is about uinput and nothing else, I wrote a simple test script using python-evdev:
from evdev import UInput, ecodes
KEY_RELEASE = 0L
KEY_PRESS = 1L
ui = UInput()
ui.write(ecodes.EV_KEY, ecodes.KEY_A, KEY_PRESS)
ui.write(ecodes.EV_KEY, ecodes.KEY_A, KEY_RELEASE)
ui.syn()
ui.close()
I also created a new user called hopr and a new group called uinput and added hopr to the group uinput and input (to read events). The plan is to change the group of /dev/uinput to uinput and only have rw permissio for the group. The groups for the two users are:
user: user adm cdrom sudo dip plugdev lpadmin sambashare
hopr: hopr input uinput
To start, I just tried running the script without changing the group and with no extra rules in /etc/udev/rules.d. By default, the permissions on /dev/uinput in XUbuntu 16.04 are rw for user root and group root.
crw-rw----+ 1 root root 10, 223 mar 23 09:36 /dev/uinput
user> python test.py # OK
hopr> python test.py # evdev.uinput.UInputError: "/dev/uinput" cannot be opened for writing
I was a bit surprised to see my regular user being able to run the script but the new user couldn't. Next, I added rw permission for everyone using chmod:
crw-rw-rw-+ 1 root root 10, 223 mar 23 09:36 /dev/uinput
user> python test.py # OK
hopr> python test.py # OK
OK, that was as expected. Next, I removed rw for everyone again and changed the group to uinput using chown:
crw-rw----+ 1 root uinput 10, 223 mar 23 09:36 /dev/uinput
user> python test.py # OK
hopr> python test.py # evdev.uinput.UInputError: "/dev/uinput" cannot be opened for writing
Again, that was unexpected but it gets worse. Next, I added rw permissions for everyone again but kept group uinput
crw-rw-rw-+ 1 root uinput 10, 223 mar 23 09:36 /dev/uinput
user> python test.py # OK
hopr> python test.py # evdev.uinput.UInputError: "/dev/uinput" cannot be opened for writing
Now I'm really confused but changing the group back to root using chown makes it work again
crw-rw-rw-+ 1 root root 10, 223 mar 23 09:36 /dev/uinput
user> python test.py # OK
hopr> python test.py # OK
Clearly there are things I don't understand here so I also tried setting it up properly with a rule in /dev/udev/rules.d:
KERNEL=="uinput*", GROUP="uinput", MODE="0660"
To my great surprise, everything works (almost) as expected now! The restricted hopr user can run the program without rw permissions for everyone. The only question is why my regular user can also run it.
crw-rw----+ 1 root uinput 10, 223 mar 23 09:58 /dev/uinput
user> python test.py # OK
hopr> python test.py # OK
So, my questions are:
- Why can my default user use /dev/uinput despite the file permissions saying I should not be able to? How do I turn it off?
- Why can't I just chown the group? Why do I have to use rules in /etc/udev/rules.d?
- What is actually going on with chmod/chown, rules and uinput? Why can't the restricted hopr user use uinput despite rw for everyone when the group is chowned to uinput but it CAN when the group is root?
- Is using a rule in /etc/udev/rules.d the proper way to set this up? Can I expect such a setup to work on all/most linux version? I noticed Ubuntu 15.04 has different default permissions for /dev/uinput.