checking write permissions on /dev/uinput

xkb
sezanzeb 4 years ago committed by sezanzeb
parent 362c467bae
commit 07c2d86c26

@ -56,8 +56,6 @@ if __name__ == '__main__':
update_verbosity(options.debug)
log_info()
can_read_devices()
window = Window()
def stop():

@ -23,6 +23,7 @@
import re
import os
import asyncio
import time
import subprocess

@ -24,6 +24,7 @@
import grp
import getpass
import os
from keymapper.logger import logger
from keymapper.paths import USER
@ -35,13 +36,17 @@ def can_read_devices():
is_in_input_group = USER in grp.getgrnam('input').gr_mem
is_in_plugdev_group = USER in grp.getgrnam('plugdev').gr_mem
# ubuntu. funnily, individual devices in /dev/input/ have write permitted.
can_write = os.access('/dev/uinput', os.W_OK)
def warn(group):
logger.warning(
'Some devices may not be visible without being in the '
'"%s" user group. Try `sudo usermod -a -G %s $USER` '
'"%s" user group. Try `sudo usermod -a -G %s %s` '
'and log out and back in.',
group,
group
group,
USER
)
if not is_root:
@ -49,7 +54,14 @@ def can_read_devices():
warn('plugdev')
if not is_in_input_group:
warn('input')
if not can_write:
logger.error(
'Injecting keycodes into /dev/uinput is not permitted. '
'Either use sudo or run '
'`sudo setfacl -m u:%s:rw- /dev/uinput`',
{USER}
)
ok = is_root or (is_in_input_group and is_in_plugdev_group)
ok = (is_root or (is_in_input_group and is_in_plugdev_group)) and can_write
return ok, is_root, is_in_input_group, is_in_plugdev_group
return ok, is_root, is_in_input_group, is_in_plugdev_group, can_write

@ -114,7 +114,7 @@ class Window:
# already visible (without content) to make it look more responsive.
gtk_iteration()
ok, _, is_input, is_plugdev = can_read_devices()
ok, _, is_input, is_plugdev, can_write = can_read_devices()
if not ok:
missing_groups = []
if not is_input:
@ -128,6 +128,11 @@ class Window:
f'You are not in the {" and ".join(missing_groups)} '
f'group{"s" if len(missing_groups) > 0 else ""}'
)
elif not can_write:
self.get('status_bar').push(
CTX_ERROR,
f'Insufficient permissions on /dev/uinput'
)
self.populate_devices()

@ -21,6 +21,7 @@
import sys
import time
import grp
import os
import unittest
import evdev
@ -36,7 +37,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from keymapper.state import custom_mapping, system_mapping
from keymapper.paths import CONFIG, get_config_path
from keymapper.paths import CONFIG, get_config_path, USER
from keymapper.config import config
from keymapper.dev.reader import keycode_reader
from keymapper.gtk.row import to_string
@ -51,8 +52,21 @@ def gtk_iteration():
Gtk.main_iteration()
# iterate a few times when Gtk.main() is called, but don't block
# there and just continue to the tests while the UI becomes
# unresponsive
Gtk.main = gtk_iteration
# doesn't do much except avoid some Gtk assertion error, whatever:
Gtk.main_quit = lambda: None
def launch(argv=None):
"""Start key-mapper-gtk with the command line argument array argv."""
if os.path.exists(tmp):
shutil.rmtree(tmp)
custom_mapping.empty()
bin_path = os.path.join(os.getcwd(), 'bin', 'key-mapper-gtk')
if not argv:
@ -85,20 +99,7 @@ class TestIntegration(unittest.TestCase):
Try to modify the configuration only by calling functions of the window.
"""
@classmethod
def setUpClass(cls):
# iterate a few times when Gtk.main() is called, but don't block
# there and just continue to the tests while the UI becomes
# unresponsive
Gtk.main = gtk_iteration
# doesn't do much except avoid some Gtk assertion error, whatever:
Gtk.main_quit = lambda: None
def setUp(self):
if os.path.exists(tmp):
shutil.rmtree(tmp)
custom_mapping.empty()
self.window = launch()
def tearDown(self):
@ -502,5 +503,76 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(len(write_history), len_before)
original_access = os.access
original_getgrnam = grp.getgrnam
class TestPermissions(unittest.TestCase):
def tearDown(self):
os.access = original_access
os.getgrnam = original_getgrnam
self.window.on_close()
self.window.window.destroy()
gtk_iteration()
shutil.rmtree('/tmp/key-mapper-test')
def test_check_groups_missing(self):
class Grnam:
def __init__(self, group):
self.gr_mem = []
grp.getgrnam = Grnam
self.window = launch()
status = self.window.get('status_bar')
labels = ''
for label in status.get_message_area():
labels += label.get_text()
self.assertIn('input', labels)
self.assertIn('plugdev', labels)
def test_check_plugdev_missing(self):
class Grnam:
def __init__(self, group):
if group == 'input':
self.gr_mem = [USER]
else:
self.gr_mem = []
grp.getgrnam = Grnam
self.window = launch()
status = self.window.get('status_bar')
labels = ''
for label in status.get_message_area():
labels += label.get_text()
self.assertNotIn('input', labels)
self.assertIn('plugdev', labels)
def test_check_write_uinput(self):
class Grnam:
def __init__(self, group):
self.gr_mem = [USER]
grp.getgrnam = Grnam
def access(path, mode):
return False
os.access = access
self.window = launch()
status = self.window.get('status_bar')
labels = ''
for label in status.get_message_area():
labels += label.get_text()
self.assertNotIn('plugdev', labels)
self.assertIn('Insufficient permissions on /dev/uinput', labels)
if __name__ == "__main__":
unittest.main()

Loading…
Cancel
Save