Educational Project - This project is developed for learning purposes as part of the 42 school curriculum. It demonstrates Linux kernel module development, device drivers, and input subsystem handling.
LK2 is a Linux kernel module that implements a keyboard event logger. It captures all keyboard events through the Linux input subsystem and exposes them via a misc device at /dev/keylogs.
The patterns exercised here (misc device registration, hooking into the input subsystem, and locking correctly across interrupt/atomic and process context) are the same building blocks used in real peripheral drivers such as GPIO, UART, or I2C on embedded Linux targets.
This project is strictly for educational purposes to understand:
- Linux kernel module development
- Device driver architecture
- Input subsystem and event handling
- Misc device implementation
- Ring buffer data structures in kernel space
- Proper kernel memory management and locking
- Captures all keyboard press and release events
- Logs timestamp (HH:MM:SS), key name, key code, and state
- Stores events in a lock-protected dynamic buffer (unlimited, starts at 1024)
- Exposes logs via
/dev/keylogsmisc device - Prints a summary of typed characters on module unload
- Linux kernel >= 4.0 (developed with 6.18.5)
- GCC compiler
- Make
- QEMU (for VM testing)
.
├── Makefile # Main build system
├── module/
│ ├── Kbuild # Kernel build configuration
│ ├── Makefile # Out-of-tree build helper
│ ├── lk2.h # Shared header and data structures
│ ├── lk2_main.c # Module init/exit, misc device ops
│ ├── lk2_input.c # Input subsystem handler
│ ├── lk2_ring.c # Ring buffer implementation
│ └── lk2_format.c # Key name mapping and formatting
├── scripts/ # Helper scripts for rootfs and QEMU
└── disks/ # Generated disk images
make devmake modulemake module-installmake dev-module- Load the module:
insmod /lib/modules/extra/lk2.ko- Read the keylogs:
cat /dev/keylogs- Unload the module (prints summary to kernel log):
rmmod lk2You can also build and test the module directly on your current Linux system without QEMU.
Install kernel headers:
Debian/Ubuntu:
sudo apt install -y linux-headers-$(uname -r)RHEL-family:
sudo dnf install -y kernel-devel-$(uname -r)Build and load:
cd module/
make
sudo insmod lk2.ko
cat /dev/keylogs
sudo rmmod lk2The summary is also written to /tmp/keylogs_final.log on unload.
Each entry follows the format:
HH:MM:SS KeyName(keycode) Pressed/Released
Example output:
14:32:15 H(35) Pressed
14:32:15 H(35) Released
14:32:16 E(18) Pressed
14:32:16 E(18) Released
14:32:16 L(38) Pressed
14:32:16 L(38) Released
14:32:17 L(38) Pressed
14:32:17 L(38) Released
14:32:17 O(24) Pressed
14:32:17 O(24) Released
| Target | Description |
|---|---|
dev |
Build kernel (if needed) + rootfs (if needed) + run QEMU |
run |
Launch QEMU with existing bzImage + rootfs |
module |
Build the kernel module |
module-install |
Inject the .ko into the rootfs image |
dev-module |
Module + install + run (fast iteration) |
rootfs |
Create rootfs if missing |
rootfs-reinstall |
Recreate rootfs (destructive) |
clean |
Clean kernel build artifacts |
fclean |
Full clean (kernel + rootfs + archive) |
- lk2_main.c: Module initialization, misc device registration, and file operations
- lk2_input.c: Registers an input handler to capture keyboard events from all keyboard devices
- lk2_ring.c: Thread-safe ring buffer with spinlock protection
- lk2_format.c: Keycode to key name mapping (US QWERTY) and log line formatting
struct lk2_entry {
struct lk2_time t; // Timestamp (HH:MM:SS)
u16 keycode; // Linux input key code
u8 state; // Pressed or Released
char ascii; // ASCII value (if printable)
};- Misc device instead of a full char device. Registering a
struct miscdevicewith a dynamic minor avoids the boilerplate ofcdev/major number management, which is unnecessary for a single, simple read-only log device. - Dynamic ring buffer instead of a fixed-size one. Starting at
LK2_RING_INIT_CAPentries and growing on demand means no keyboard events are silently dropped once a fixed cap is hit, at the cost of unbounded memory growth (acceptable for an educational tool, not for production). - Spinlock instead of a mutex in
lk2_ring.c.lk2_event()(the input handler callback) can run in interrupt/atomic context, where sleeping locks such as mutexes are not allowed. A spinlock withspin_lock_irqsave/spin_unlock_irqrestoreis the correct primitive here since it never sleeps and also disables local interrupts while held. - Allocation happens outside the spinlock during buffer growth.
kvmalloc_array()can sleep (GFP_KERNEL), which is illegal while holding a spinlock.lk2_ring_push()therefore releases the lock before allocating the larger buffer, then reacquires it and re-checks the capacity (double-checked locking) before committing the new buffer, so the allocation never happens in atomic context. - Snapshot on
open()instead of holding the lock duringread().keylogs_open()copies the ring into a private buffer once, sokeylogs_read()can safely callsimple_read_from_buffer()(which performscopy_to_user()and can fault/sleep) without ever holding the spinlock across a user-space copy.
This software is created solely for educational purposes to learn about Linux kernel development and device drivers. It should only be used in controlled environments (virtual machines) for learning. Unauthorized use of keylogging software is illegal and unethical.