Book Image

Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization

By : Kaiwan N. Billimoria
Book Image

Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization

By: Kaiwan N. Billimoria

Overview of this book

Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization is an ideal companion guide to the Linux Kernel Programming book. This book provides a comprehensive introduction for those new to Linux device driver development and will have you up and running with writing misc class character device driver code (on the 5.4 LTS Linux kernel) in next to no time. You'll begin by learning how to write a simple and complete misc class character driver before interfacing your driver with user-mode processes via procfs, sysfs, debugfs, netlink sockets, and ioctl. You'll then find out how to work with hardware I/O memory. The book covers working with hardware interrupts in depth and helps you understand interrupt request (IRQ) allocation, threaded IRQ handlers, tasklets, and softirqs. You'll also explore the practical usage of useful kernel mechanisms, setting up delays, timers, kernel threads, and workqueues. Finally, you'll discover how to deal with the complexity of kernel synchronization with locking technologies (mutexes, spinlocks, and atomic/refcount operators), including more advanced topics such as cache effects, a primer on lock-free techniques, deadlock avoidance (with lockdep), and kernel lock debugging techniques. By the end of this Linux kernel book, you'll have learned the fundamentals of writing Linux character device driver code for real-world projects and products.
Table of Contents (11 chapters)
Section 1: Character Device Driver Basics
User-Kernel Communication Pathways
Handling Hardware Interrupts
Working with Kernel Timers, Threads, and Workqueues
Section 2: Delving Deeper

User space test app modifications

We slightly modify the user space application – our process context, in effect. This particular version of the user-mode test app differs from the earlier one in one regard: we now have a macro called HACKIT. If it's defined (it is by default), this process will deliberately write only zeroes into the user space buffer and send that to our bad driver's write method. If the driver has the DANGER_GETROOT_BUG macro defined (it is by default), then it will write the zeroes into the process's UID member, thus making the user-mode process obtain root privileges!

In the traditional Unix/Linux paradigm, if the Real User ID (RUID) and/or Effective User ID (EUID) (they're within the task structure, in struct cred) are set to the special value zero (0), it implies that the process has superuser (root) powers. Nowadays, the POSIX Capabilities model is considered a superior way to work with privileges, as it allows assigning fine-grained permissions – capabilities – on a thread, as opposed to giving a process or thread complete control over the system as root.

Here's a quick diff of the user space test app from the previous version, allowing you to see the changes made to the code (again, we curtail the output to only what's most relevant):

// in ch1/bad_miscdrv
$ diff -u ../miscdrv/rdwr_test.c rdwr_test_hackit.c
[ ... ]
+#define HACKIT
[ ... ]
+#ifndef HACKIT
+ strncpy(buf, argv[3], num);
+ printf("%s: attempting to get root ...\n", argv[0]);
+ /*
+ * Write only 0's ... our 'bad' driver will write this into
+ * this process's current->cred->uid member, thus making us
+ * root !
+ */
+ memset(buf, 0, num);
- } else { // test writing ..
n = write(fd, buf, num);
[ ... ]
+ printf("%s: wrote %zd bytes to %s\n", argv[0], n, argv[2]);
+#ifdef HACKIT
+ if (getuid() == 0) {
+ printf(" !Pwned! uid==%d\n", getuid());
+ /* the hacker's holy grail: spawn a root shell */
+ execl("/bin/sh", "sh", (char *)NULL);
+ }
[ ... ]

This does imply that the (so-called) secret never gets written; that's okay. Now, let's look at the modifications made to the driver.