Saturday, August 23, 2014

close() called when process terminated after opening driver

Application is never allowed to crash the kernel if the driver does its job correctly. Otherwise, it is a buggy driver or trash kernel. So, application is not allowed to keep holes in kernel or resources leaked. It is job of the driver to find the resources and holes kept by application and remove it in close().

sample program for simple driver and simple application to prove this.

1) First install headers

# apt-get install build-essential linux-headers-$(uname -r)
2) The following is simple kernel driver in module sample_close.c.

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("CollegeBoy");
MODULE_DESCRIPTION("A Crash app module");

/*** define ***/
#define SAMPLE_DRIVER_NAME "sample"
#define SAMPLE_DEVICE_ID  "sample_int"

/**** prototype ****/
static int  sample_open(struct inode*, struct file*);
static int  sample_close(struct inode*, struct file*);

static struct miscdevice g_sample_device; /* device driver information */

/******************************************************************************/
/* Function   : sample_open                                                    */
/* Description: open SAMPLE driver                                             */
/******************************************************************************/
static
int sample_open(
 struct inode *inode,
 struct file  *fp
)
{

 printk(KERN_ERR "Device opened!\n");

 return 0;
}

/******************************************************************************/
/* Function   : sample_close                                                   */
/* Description: close SAMPLE driver                                            */
/******************************************************************************/
static
int sample_close(
 struct inode *inode,
 struct file  *fp
)
{
 printk(KERN_ERR "Device Closed!\n");
 return 0;
}

/* initialize file_operations */
static struct file_operations g_sample_fops = {
 .owner   = THIS_MODULE,
 .open   = sample_open,
 .release  = sample_close,
};

static int __init hello_init(void)
{
 int    ret;

 printk(KERN_ERR "Module Init!\n");

 g_sample_device.name  = SAMPLE_DRIVER_NAME;
 g_sample_device.fops  = &g_sample_fops;
 g_sample_device.minor = MISC_DYNAMIC_MINOR;

 /* register device driver */
 ret = misc_register(&g_sample_device);
 if (0 != ret) {
  printk(KERN_ERR "[SAMPLEK]ERR| misc_register failed ret[%d]\n", ret);
  return -1;
 }

 return 0;
}

static void __exit hello_cleanup(void)
{
 int ret;

 printk(KERN_ERR "Module Exit!\n");

 /* unregister device driver */
 ret = misc_deregister(&g_sample_device);
 if (0 != ret) {
  printk(KERN_ERR "[SAMPLEK]ERR| misc_deregister failed ret[%d]\n", ret);
 }
}

module_init(hello_init);
module_exit(hello_cleanup);

3) Copy into Makefile

ifeq ($(KERNELRELEASE),)  

KERNELDIR ?= /lib/modules/$(shell uname -r)/build 
PWD := $(shell pwd)  

.PHONY: build clean  

build:
 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules  

clean:
 rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c 
else  

$(info Building with KERNELRELEASE = ${KERNELRELEASE}) 
obj-m :=    sample_close.o  

endif

4) Do make

root@ubuntu:/mnt/hgfs/UShared/module# make
make -C /lib/modules/3.2.0-23-generic-pae/build  M=/mnt/hgfs/UShared/module   modules  
make[1]: Entering directory `/usr/src/linux-headers-3.2.0-23-generic-pae'
Building with KERNELRELEASE = 3.2.0-23-generic-pae
  CC [M]  /mnt/hgfs/UShared/module/sample_close.o
  Building modules, stage 2.
Building with KERNELRELEASE = 3.2.0-23-generic-pae
  MODPOST 1 modules
  CC      /mnt/hgfs/UShared/module/sample_close.mod.o
  LD [M]  /mnt/hgfs/UShared/module/sample_close.ko
make[1]: Leaving directory `/usr/src/linux-headers-3.2.0-23-generic-pae'

5) insmod 

root@ubuntu:/mnt/hgfs/UShared/module# insmod sample_close.ko 
root@ubuntu:/mnt/hgfs/UShared/module# dmesg -c
[ 3883.685179] Module Init!
6) Application to open driver and to crash itself.

#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define ICCOM_DEV "/dev/iccom"

int main(){

int fd;

 /* open ICCOM driver */
 fd = open(ICCOM_DEV, O_RDWR);
 if (fd < 0) {
  printf("[ICCOM]ERR| handle->fd[%d]\n", fd);
  return -1;
 }
 *((unsigned int *)0) = 87;

}
7) Sample application main.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define SAMPLE_DEV "/dev/sample"

int main(){

int fd;

 /* open SAMPLE driver */
 fd = open(SAMPLE_DEV, O_RDWR);
 if (fd < 0) {
  printf("[SAMPLE]ERR| handle->fd[%d]\n", fd);
  return -1;
 }
 *((unsigned int *)0) = 87;

}
8) Compile, execute, and see dmesg

root@ubuntu:/mnt/hgfs/UShared/module/app# cc main.c 
root@ubuntu:/mnt/hgfs/UShared/module/app# ./a.out 
Segmentation fault (core dumped)
root@ubuntu:/mnt/hgfs/UShared/module/app# dmesg -c
[ 4094.987003] Device opened!
[ 4094.987028] a.out[7653]: segfault at 0 ip 0804845d sp bf96a100 error 6 in a.out[8048000+1000]
[ 4095.103149] Device Closed!
See the device is closed (means, close() function of driver is called by kernel) automatically when the application that opened the driver has crashed. So, your job needs to be remember the current->tgid of the process that is opening the driver in the open() function through fp->private_data. When close function is called, get tgid from the fp->private_data release all the resources and requests raised by the tgid.