Building Kernel Keystroke Injection and IPC Security
At this point, I needed to solve two different problems:
- How does the CLI safely tell the daemon what to do?
- How does the daemon make the active application actually receive the paste?
The first problem taught me more about UNIX sockets. The second took me to /dev/uinput.
UNIX Domain Sockets
I used a local AF_UNIX socket rather than TCP localhost beacuse it is designed for IPC for processes on the same machine. The socket lives in the user’s runtime directory, under $XDG_RUNTIME_DIR.
I also use restrictive permissions (0600) so the socket is not treated as a public interface. But permissions alone are not enough, the daemon should not blindly trust every connection.
This is where I read about SO_PEERCRED. With a UNIX socket, Linux can provide credentials for the peer process. The check prevents a different Unix user from talking to my daemon through the IPC interface. It does not solve the problem of a malicious process already running as the same user.
That is a fundamental limitation of many user-space desktop utilities.
The important part is that the daemon does not trust a UID supplied by the client. The kernel provides the credentials. That is a much more trustworthy.
The paste
After selecting an item, my daemon can put that text into the X11 clipboard. But I still need to trigger the paste action in the active application.
The obvious idea was Shift + Insert or Ctrl + V. So I looked at ways to generate keyboard input. I found two main approaches.
Approach 1: XTest
XTest can synthesize keyboard and pointer input from X11. The architecture would be roughly:
clipd --> XTest --> X11 --> Application
This is very convenient, but, since this is synthetic input so the applications can distinguish or restrict injected events in different ways. I needed to bypass that.
Approach 2: /dev/uinput
Then I found the Linux uinput interface.
Instead of pretending to be an X11 program pressing a key, I can create a virtual input device.
The architecture becomes:
clipd --> /dev/uinput --> Linux input subsystem --> active applications
From the kernel’s point of view, my daemon is creating a virtual keyboard. That is a much lower-level solution. Therefore, It also means I have to deal with the kernel device interface and permissions.
I decided to go with this and mess with this aspect too.
The final system crosses multiple abstraction layers. It utilises concepts like,
- userspace process communication
- an IPC security boundary
- X11 selection ownership
- Linux device APIs
- kernel input handling
The uinput implementation
I create a virtual keyboard and emit key events.
For Shift+Insert:
KEY_LEFTSHIFT = 1
KEY_INSERT = 1
KEY_INSERT = 0
KEY_LEFTSHIFT = 0
Every state change is written as an input event.
The implementation also needs the correct event synchronization and device setup. The important idea is that I am not asking an application to paste. I am producing input that the Linux input system can deliver to applications.
Permissions HELLLL!!!
Normal users generally do not have arbitrary write access to input devices. I did not want to run the whole daemon as root. That would be too privileged for a clipboard manager.
So instead, the machine needs a udev rule or equivalent device permission setup that allows my user to access /dev/uinput.
That keeps the rest of the process unprivileged.
Next
Now that the daemon and client are working. I had to make it feel like a real Linux utility instead of a program I manually launched from a terminal.
So I started looking into systemd, packaging, permissions and trying to make the whole thing installable as a .deb package.