Planet Signove

November 24, 2015

Raul Herbster

IOCTL Android

IOCTL is a very useful system call: it is simple and multiplexes the different commands to the appropriate kernel space function. In this post, I want to describe how you can implement a module with IOCTL support for Android. There are a lot of good articles about it (links below), and I just describe the differences regarding to the Android platform.

So, let's create a very simple misc device and let's play with it, doing some reads and writes. Initially, let's define a very simple kernel module (most of the code was taken from here).

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>


#define MY_MACIG 'G'
#define READ_IOCTL _IOR(MY_MACIG, 0, int)
#define WRITE_IOCTL _IOW(MY_MACIG, 1, int)

static int used;
static char msg[200];

static ssize_t device_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset)
{
return simple_read_from_buffer(buffer, length, offset, msg, 200);
}

static ssize_t device_write(struct file *filp, const char __user *buff, size_t len, loff_t *off)
{
if (len > 199)
return -EINVAL;
copy_from_user(msg, buff, len);
msg[len] = '\0';
return len;
}

static int device_open(struct inode *inode, struct file *file)
{
used++;
return 0;
}

static int device_release(struct inode *inode, struct file *file)
{
used--;
return 0;
}

char buf[200];
int device_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
int len = 200;
switch(cmd) {
case READ_IOCTL:
copy_to_user((char *)arg, buf, 200);
break;

case WRITE_IOCTL:
copy_from_user(buf, (char *)arg, len);
break;

default:
return -ENOTTY;
}
return len;
}

static struct file_operations fops = {
.owner = THIS_MODULE,
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release,
.unlocked_ioctl = device_ioctl
};

static struct miscdevice my_miscdev = {
.name = "my_device",
.mode = S_IRWXUGO,
.fops = &fops,
};

static int __init cdevexample_module_init(void)
{
int ret = misc_register(&my_miscdev);
if (ret < 0) {
printk ("Registering the character device failed\n");
return ret;
}
printk("create node with mknod /dev/my_device\n");
return 0;
}

static void __exit cdevexample_module_exit(void)
{
misc_deregister(&my_miscdev);
}

module_init(cdevexample_module_init);
module_exit(cdevexample_module_exit);
MODULE_LICENSE("GPL");

Compile it and insert into Android kernel.

shell@flounder:/ $ su
shell@flounder:/ $ insmod my_module.ko

Now, we can check the new device in /dev:

shell@flounder:/ $ su
shell@flounder:/ $ ls -l /dev
...
crw-rw---- root     mtp       10,  23 2015-11-23 18:04 mtp_usb
crw------- root     root      10,   0 2015-11-23 18:11 my_device
crw------- root     root      10,  36 2015-11-23 18:04 network_latency
crw------- root     root      10,  35 2015-11-23 18:04 network_throughput
crw-rw-rw- root     root       1,   3 2015-11-23 18:04 null

...

See that the permissions are limited. Don't forget to set it to:

shell@flounder:/ $ chmod 666 /dev/my_device
shell@flounder:/ $ ls -l /dev
...
crw-rw---- root     mtp       10,  23 2015-11-23 18:04 mtp_usb
crw-rw-rw- root     root      10,   0 2015-11-23 18:11 my_device
crw------- root     root      10,  36 2015-11-23 18:04 network_latency
crw------- root     root      10,  35 2015-11-23 18:04 network_throughput
crw-rw-rw- root     root       1,   3 2015-11-23 18:04 null

...

Now, let's try to do some operations with our device driver:

shell@flounder:/ $ echo "Hello world" > /dev/my_device
shell@flounder:/ $ cat /dev/my_device

You will see the following error on the logcat:

avc: denied { read write } for name="my_device" dev="tmpfs" scontext=u:r:system_app:s0 tcontext

This means that SELinux (yes, Android makes heavy usage of it) also controls the access to device drivers and you cannot read/write from/to your new drive. You have two options: i) disable SELinux in Android (you need to change some kernel options and rebuild it) or ii) add some new rules into SELinux. Let's do the last to learn a bit more :-)

So, we change the following files and give access (read, write, getattr, ioctl, open and create) to our new device /dev/my_device. If you need to restrict the access, you can adapt the policies according to your needs. For more information about SELinux and Android, take a look in this doc (specially the section "Implementation").

external/sepolicy/device.te
type fscklogs, dev_type;
type full_device, dev_type;
type my_device, dev_type;


external/sepolicy/file_contexts
/dev/rproc_user        u:object_r:rpmsg_device:s0
/dev/my_device         u:object_r:my_device:s0
/dev/snd(/.*)?         u:object_r:audio_device:s0


external/sepolicy/app.te
allow appdomain usb_device:chr_file { read write getattr ioctl };
allow appdomain usbaccessory_device:chr_file { read write getattr };
allow appdomain my_device:chr_file { read write getattr ioctl open create };


Now, let's build the Android framework again and flash the device. Everything should work fine.

shell@flounder:/ $ echo "Hello world" > /dev/my_device
shell@flounder:/ $ cat /dev/my_device
Hello world

That's it!! You can also check the following links

  • http://www.all-things-android.com/content/understanding-se-android-policy-files
  • https://source.android.com/security/selinux/

by Unknown (noreply@blogger.com) at November 24, 2015 12:16 AM