In Part 1 I created a simple World module, now it's time to configure it and there are 3 main ways to do it:
- at build time
- at load time
- at run time
1. Build time
Here is the simple world.c module that we used in previous post.
#include <linux/init.h>
#include <linux/module.h>
static char *whom = "World";
module_param(whom, charp, S_IRUGO|S_IWUSR);
void world_print(char* greet)
{
printk(KERN_INFO "%s, %s\n", greet, whom);
}
EXPORT_SYMBOL(world_print);
static int __init world_init(void)
{
printk(KERN_INFO "Init, %s\n", whom);
return 0;
}
module_init(world_init);
static void __exit world_exit(void)
{
printk(KERN_INFO "Exit, %s\n", whom);
}
module_exit(world_exit);
MODULE_AUTHOR("Iulian Costan <kernel@costan.ro>");
MODULE_DESCRIPTION("World module that exports a symbol");
MODULE_LICENSE("Dual BSD/GPL");
Nothing fancy here, we just build our little module.
echo 'Building modules...'
make
Building modules... make -C /lib/modules/`uname -r`/build M=$PWD make[1]: Entering directory '/usr/lib/modules/5.8.2-arch1-1/build' LARD [M] /home/icostan/Projects/blog/content/post/world.ko make[1]: Leaving directory '/usr/lib/modules/5.8.2-arch1-1/build'
We can easily see the compiled param value by inspecting with strings
strings world.ko | grep World
World description=World module that exports a symbol
2. Load time
Load time configuration can be split in 3 categories as well:
-
kernel command line at system boot - where kernel module parameters are passed with the following syntax module_name.parameter_name=parameter_value and it can done either:
- temporarily/interactively - in boot loader's boot selection menu, you just need to press a key to access the menu: e - for Grub, systemd-boot, Tab - Syslinux, + - rEFInd
-
permanently - by modifying boot loader's configuration file
initrd=\initramfs-linux.img root=/dev/sda1 world.whom=Mondo
-
configuration file - /etc/modprobe.d/world.conf
options world whom=Mondo
-
command line - when module is installed using either insmod or modprobe
echo 'Installing module...' sudo insmod ./world.ko whom=Mondo
And check the kernel log:
journalctl -k | grep Mondo
Aug 25 14:25:38 drakarys kernel: Init, Mondo
3. Run time
At runtime we can do it the easy way or the hard way, it depends on one's needs.
-
sysfs - manual, in command line but this only works if S_IWUSR flag is present
sudo echo Sysfs > /sys/module/world/parameters/whom cat /sys/module/world/parameters/whom
Sysfs
-
ioctl - programmatic, using a syscall by the same name
First of all let's define all constants in world.h file:
#define WORLD_MAJOR 6 #define WORLD_MINOR 0 #define WORLD_NAME "world" #define WORLD_WHOM "World" #define WORLD_SIZE 8 #define WORLD_IOC_MAGIC 'w' #define WORLD_IOCRESET _IO(WORLD_IOC_MAGIC, 0) #define WORLD_IOCSWHOM _IOW(WORLD_IOC_MAGIC, 1, char) #define WORLD_IOCGWHOM _IOR(WORLD_IOC_MAGIC, 2, char)
And the new world.c with ioctl capabilities:
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/slab.h> #include "world.h" static char *whom = NULL; module_param(whom, charp, S_IRUGO|S_IWUSR); long world_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int retval = 0; switch (cmd) { case WORLD_IOCRESET: strncpy(whom, WORLD_WHOM, WORLD_SIZE); break; case WORLD_IOCSWHOM: /* TODO: we will try to exploit this possible vulnerability in another post */ retval = copy_from_user(whom, (char __user *)arg, WORLD_SIZE); break; case WORLD_IOCGWHOM: retval = copy_to_user((char __user *)arg, whom, WORLD_SIZE); break; default: return -ENOTTY; } return retval; } void world_print(char* greet) { printk(KERN_INFO "%s, %s\n", greet, whom); } EXPORT_SYMBOL(world_print); struct file_operations world_fops = { .owner = THIS_MODULE, .unlocked_ioctl = world_ioctl, }; static int __init world_init(void) { int err; err = register_chrdev(WORLD_MAJOR, WORLD_NAME, &world_fops); if(!whom) { whom = kmalloc(WORLD_SIZE, GFP_KERNEL); strncpy(whom, WORLD_WHOM, WORLD_SIZE); } printk(KERN_INFO "Init, %s\n", whom); return err; } module_init(world_init); static void __exit world_exit(void) { printk(KERN_INFO "Exit, %s\n", whom); if(whom) kfree(whom); unregister_chrdev(WORLD_MAJOR, WORLD_NAME); } module_exit(world_exit); MODULE_AUTHOR("Iulian Costan <blog@cosan.ro>"); MODULE_DESCRIPTION("World module that exports a symbol"); MODULE_LICENSE("Dual BSD/GPL");
Building the new module with ioctl capabilities:
make
make -C /lib/modules/`uname -r`/build M=$PWD make[1]: Entering directory '/usr/lib/modules/5.8.5-arch1-1/build' make[1]: Leaving directory '/usr/lib/modules/5.8.5-arch1-1/build'
Install module with ioctl capabilities:
sudo insmod ./world.ko
Check module installation:
journalctl -k | grep Init
Aug 31 18:48:46 drakarys kernel: Init, World
Check device registration:
grep -C 1 world /proc/devices
5 /dev/ptmx 6 world 7 vcs
Create node in /dev:
#!/usr/bin/env sh device=world echo 'Creating /dev/${device}0 node...' mknod /dev/${device}0 c 6 0 echo 'Change node permissions...' chgrp users /dev/${device}* chmod 664 /dev/${device}*
Verify node creation:
ls -l /dev/world*
crw-rw-r-- 1 root users 6, 0 Aug 31 18:49 /dev/world0
Now, let's create a simple ioctl.c tool to configure the module from user-space:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include "world.h" int main(int argc, char **argv) { int retval = 0; int fd = open("/dev/world0", O_WRONLY); if (argc > 1) { retval = ioctl(fd, WORLD_IOCSWHOM, argv[1]); fprintf(stdout, "IOCTL write: %s\n", argv[1]); } else { char* result = malloc(WORLD_SIZE); retval = ioctl(fd, WORLD_IOCGWHOM, result); fprintf(stdout, "IOCTL read: %s\n", result); free(result); } if (retval < 0) { fprintf(stderr, "IOCTL: %s, %d\n", strerror(errno), retval); exit(1); } else { fprintf(stdout, "IOCTL: %s\n", strerror(errno)); exit(0); } }
Build ioctl.c tool:
cc ioctl.c
Read and write parameter via ioctl:
./a.out ./a.out IOCTL
IOCTL read: World IOCTL: Success IOCTL write: IOCTL IOCTL: Success
Check module parameter:
cat /sys/module/world/parameters/whom
IOCTL
Remove module:
sudo rmmod world
Check module removal, it should display the value set via ioctl tool:
journalctl -k | grep IOCTL
Aug 31 18:55:01 drakarys kernel: Exit, IOCTL