Operating Systems 2018F: Tutorial 5

From Soma-notes

In this tutorial you will be building and installing kernel modules. You will need root access to install kernel modules.

It is highly recommended that you use a comp3000 openstack instance for the exercises below for two reasons. First, you may have difficulties compiling kernel modules on other systems. Second, these operations are potentially dangerous and mistakes could destroy all data on the Linux system. Consider yourself warned!

This is the first tutorial where you are seeing kernel code. By the end of tutorial you should be able to run all of the code here and be able to make trivial modifications. Lectures later this week will go through this code in detail. Learning as much as you can about the code now will help you prepare for the upcoming lectures. In particular, try to figure out what is different about this code.

Openstack

See Lecture 8 for a walkthrough of setting up an openstack instance. Note that you can do all of the following in Windows or MacOS. On Windows, you can use PuTTY as your SSH client (MacOS includes the ssh command). Key steps:

  • Login at openstack.scs.carleton.ca (you need to be connected to the Carleton network, either directly or via VPN).
  • If you can't log in, change your SCS password (this updates your openstack credentials).
  • Create an instance by going to Images, selecting one of the public images, and then launching an instance based on it. Be sure to give it a name (your username dash a number, e.g. anilsomayaji-1) and enable ssh & ping access under "Access control". We suggest using the image "ubuntu_18.04_x64_openstack_lightapps_desktop_2018-09-28", but any other ubuntu 18.04 image should work (although you may need to install extra packages, see below).
  • After your instance has started, assign your instance a globally routable visible IP address (rather than a private 192.168.X.X address).
  • Connect to your instance via ssh or x2go. Remember the default username is "student" and the password is "student". Please change your password! Use the passwd command or GUI tools for managing users.
  • (Please don't try to create any snapshots, you can't - it will just get queued up and won't succeed.)

If you cannot build a module you may have installed a version of ubuntu that is too minimal. But you can fix it by installing the right packages. Do the following:

 sudo apt update
 sudo apt dist-upgrade
 sudo apt install build-essential
 sudo apt clean

When upgrading you may get a message when upgrading grub about which boot device to use. Select /dev/vda or /dev/sda - don’t select the devices that end with a number.

If your build failed before doing this and again after, delete the downloaded code and unpack the zip file again. (The build process generates hidden files which can mess up later builds.)

Note: You may encounter errors when trying to update the virtualbox VM. That is because that VM's disk is too small. You shouldn't encounter such issues on openstack.

Tasks

A simple kernel module

  1. Download the source for this simple module, unpack, and build it by typing "make". Use wget to download the zip file.
  2. Install the module using "sudo insmod simple.ko". The hello message is recorded in the kernel logs. How do you view the kernel logs?
  3. Check to see that the module has been loaded. How do you do this?
  4. Remove the module from the kernel. What did you do?

A character device kernel module

  1. Download the source for ones, a kernel module implementing a character device that ouputs an unbounded string of "1"'s. Build, compile, and run it as before.
  2. What kernel messages does the module generate? Does it create any new files (other than /dev/ones)? If so, where?
  3. What happens when you "cat" the device /dev/ones? How can you limit the output?
  4. How can you modify your module to generate a kernel "Oops" as reported in the kernel logs or outright crash the kernel?

Listing processes from a module

To answer these questions on newgetpid.c, you'll need to refer to the Linux kernel source, particularly kernel/sys.c. (These are links to a cross-referenced version of the Linux kernel source, a key resource for understanding and developing kernel code.)

  1. Download the source newgetpid.c. Build and run it as before.
  2. What type is "current"? How can you figure this out?
  3. Modify newgetpid.c so that it creates a device file /dev/describe rather than /dev/newgetpid.
  4. Make /dev/describe output the calling process's parent ID (ppid), user ID (uid), group ID (gid), effective user ID (euid), and effective group ID (egid).
  5. (Advanced) Modify /dev/describe so that if you write a process ID to it, it will output the information on the provided process. To make this work, you'll need to:
    • Add a write method by adding a write operation to the file operations struct. Write operations have the same prototype as read operations, except the buffer is marked constant (because it shouldn't be modified).
    • Convert the written text to an integer and store in a global variable (to the module).
    • Find the right task struct. See the implementation of the kill system call, and how it looks up the pid struct and then gets the right task struct using that pid struct.
    • After returning info on the selected process, further calls should return info on the current process. You can do this by setting the global process ID to 0 and checking this value, using the current task if it is zero.


Code

Simple module

simple.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

static int __init simple_init(void)
{
        printk ("Hello kernel world!\n");
        return 0;
}

static void __exit simple_exit(void)
{
        printk ("Goodbye kernel world.\n");
        return;
}

module_init(simple_init);
module_exit(simple_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Somayaji <soma@scs.carleton.ca>");
MODULE_DESCRIPTION("A simple module");


Makefile

obj-m := simple.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules


Ones module

ones.c

/* Code derived from:
  https://appusajeev.wordpress.com/2011/06/18/writing-a-linux-character-device-driver/
  and
  http://pete.akeo.ie/2011/08/writing-linux-device-driver-for-kernels.html
*/

#include <linux/module.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>

#define dbg(format, arg...) do { if (debug) pr_info(CLASS_NAME ": %s: " format, __FUNCTION__, ## arg); } while (0)
#define err(format, arg...) pr_err(CLASS_NAME ": " format, ## arg)
#define info(format, arg...) pr_info(CLASS_NAME ": " format, ## arg)
#define warn(format, arg...) pr_warn(CLASS_NAME ": " format, ## arg)


#define DEVICE_NAME "ones"
#define CLASS_NAME "comp3000"

static struct class* ones_class = NULL;
static struct device* ones_device = NULL;
static int ones_major;

static int ones_open(struct inode *the_inode, struct file *f)
{
        return 0;
}

static ssize_t ones_read(struct file *f, char *buf, size_t len, loff_t *offset)
{
        size_t i;

        for (i = 0; i < len; i++) {
                put_user('1', buf++);
        }

        return i;
}

static int ones_release(struct inode *the_inode, struct file *f)
{
        printk(KERN_ALERT "Ones device closed\n");
        return 0;
}


static struct file_operations ones_fops = {
        .open = ones_open,
        .read = ones_read,
        .release = ones_release,
};


static char *ones_devnode(struct device *dev, umode_t *mode)
{
        if (mode)
	        *mode = 0444;
        return NULL;
}

static int __init ones_init(void)
{
        int retval;
  
        ones_major = register_chrdev(0, DEVICE_NAME, &ones_fops);
        if (ones_major < 0) {
                err("failed to register device: error %d\n", ones_major);
                retval = ones_major;
                goto failed_chrdevreg;
        }
 
        ones_class = class_create(THIS_MODULE, CLASS_NAME);
        if (IS_ERR(ones_class)) {
                err("failed to register device class '%s'\n", CLASS_NAME);
                retval = PTR_ERR(ones_class);
                goto failed_classreg;
        }
 
	ones_class->devnode = ones_devnode;

        ones_device = device_create(ones_class, NULL, MKDEV(ones_major, 0),
                                    NULL, DEVICE_NAME);

        if (IS_ERR(ones_device)) {
                err("failed to create device '%s'\n", DEVICE_NAME);
                retval = PTR_ERR(ones_device);
                goto failed_devreg;
        }
        
        info("Ones device registered using major %d.\n", ones_major);
        
        return 0;
        
 failed_devreg:
        class_unregister(ones_class);
        class_destroy(ones_class);
 failed_classreg:
        unregister_chrdev(ones_major, DEVICE_NAME);
 failed_chrdevreg:
        return -1;
}

static void __exit ones_exit(void)
{
        device_destroy(ones_class, MKDEV(ones_major, 0));
        class_unregister(ones_class);
        class_destroy(ones_class);
        unregister_chrdev(ones_major, "ones");
        info("Unloading Ones module.\n");
        return;
}

module_init(ones_init);
module_exit(ones_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Somayaji <soma@scs.carleton.ca>");
MODULE_DESCRIPTION("A write ones character device module");


Makefile

obj-m := ones.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

newgetpid module

newgetpid.c

/* Code derived from:
  https://appusajeev.wordpress.com/2011/06/18/writing-a-linux-character-device-driver/
  and
  http://pete.akeo.ie/2011/08/writing-linux-device-driver-for-kernels.html
*/

#include <linux/module.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <asm/uaccess.h>

#define dbg(format, arg...) do { if (debug) pr_info(CLASS_NAME ": %s: " format, __FUNCTION__, ## arg); } while (0)
#define err(format, arg...) pr_err(CLASS_NAME ": " format, ## arg)
#define info(format, arg...) pr_info(CLASS_NAME ": " format, ## arg)
#define warn(format, arg...) pr_warn(CLASS_NAME ": " format, ## arg)


#define DEVICE_NAME "newgetpid"
#define CLASS_NAME "comp3000"

static struct class* newgetpid_class = NULL;
static struct device* newgetpid_device = NULL;
static int newgetpid_major;

static int newgetpid_open(struct inode *the_inode, struct file *f)
{
        return 0;
}

static ssize_t newgetpid_read(struct file *f, char *buf, size_t len, loff_t *offset)
{
        size_t i, msglen;
        pid_t thepid;

        char message[100];
        
        if (*offset > 0) {
                return 0;
        }
        
        thepid = task_tgid_vnr(current);

        snprintf(message, 100, "Your PID is %d!\n", thepid);
        
        msglen = strlen(message);

        if (len < msglen) {
                msglen = len;
        }

        for (i = 0; i < msglen; i++) {
                put_user(message[i], buf++);
        }

        *offset = i;

        return i;
}

static int newgetpid_release(struct inode *the_inode, struct file *f)
{
        printk(KERN_ALERT "Newgetpid device closed\n");
        return 0;
}


static struct file_operations newgetpid_fops = {
        .open = newgetpid_open,
        .read = newgetpid_read,
        .release = newgetpid_release,
};


static char *newgetpid_devnode(struct device *dev, umode_t *mode)
{
        if (mode)
	        *mode = 0444;
        return NULL;
}

static int __init newgetpid_init(void)
{
        int retval;
  
        newgetpid_major = register_chrdev(0, DEVICE_NAME, &newgetpid_fops);
        if (newgetpid_major < 0) {
                err("failed to register device: error %d\n", newgetpid_major);
                retval = newgetpid_major;
                goto failed_chrdevreg;
        }
 
        newgetpid_class = class_create(THIS_MODULE, CLASS_NAME);
        if (IS_ERR(newgetpid_class)) {
                err("failed to register device class '%s'\n", CLASS_NAME);
                retval = PTR_ERR(newgetpid_class);
                goto failed_classreg;
        }
 
	newgetpid_class->devnode = newgetpid_devnode;

        newgetpid_device = device_create(newgetpid_class, NULL, MKDEV(newgetpid_major, 0),
                                    NULL, DEVICE_NAME);

        if (IS_ERR(newgetpid_device)) {
                err("failed to create device '%s'\n", DEVICE_NAME);
                retval = PTR_ERR(newgetpid_device);
                goto failed_devreg;
        }
        
        info("Newgetpid device registered using major %d.\n", newgetpid_major);
        
        return 0;
        
 failed_devreg:
        class_unregister(newgetpid_class);
        class_destroy(newgetpid_class);
 failed_classreg:
        unregister_chrdev(newgetpid_major, DEVICE_NAME);
 failed_chrdevreg:
        return -1;
}

static void __exit newgetpid_exit(void)
{
        device_destroy(newgetpid_class, MKDEV(newgetpid_major, 0));
        class_unregister(newgetpid_class);
        class_destroy(newgetpid_class);
        unregister_chrdev(newgetpid_major, "newgetpid");
        info("Unloading Newgetpid module.\n");
        return;
}

module_init(newgetpid_init);
module_exit(newgetpid_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Somayaji <soma@scs.carleton.ca>");
MODULE_DESCRIPTION("A write newgetpid character device module");

Makefile

obj-m := newgetpid.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules