Problem with ioctl() in a simple kernel module

12,643

Solution 1

A few things:

  • You want to use "unlocked_ioctl" not "compat_ioctl".
  • The function interface for "device_ioctl" is wrong (see include/linux/fs.h), it should be:

        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    
  • The appln.c doesn't check error codes (open, ioctl).

After fixing that, the code will work fine.

Solution 2

Minimal runnable example

Tested in a fully reproducible QEMU + Buildroot environment, so might help others get their ioctl working. GitHub upstream: kernel module | shared header | userland.

The most annoying part was understanding that some low ids are hijacked: https://stackoverflow.com/questions/10071296/ioctl-is-not-called-if-cmd-2 , you have to use _IOx macros.

Kernel module:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */

#include "ioctl.h"

MODULE_LICENSE("GPL");

static struct dentry *dir;

static long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long argp)
{
    void __user *arg_user;
    union {
        int i;
        lkmc_ioctl_struct s;
    } arg_kernel;

    arg_user = (void __user *)argp;
    pr_info("cmd = %x\n", cmd);
    switch (cmd) {
        case LKMC_IOCTL_INC:
            if (copy_from_user(&arg_kernel.i, arg_user, sizeof(arg_kernel.i))) {
                return -EFAULT;
            }
            pr_info("0 arg = %d\n", arg_kernel.i);
            arg_kernel.i += 1;
            if (copy_to_user(arg_user, &arg_kernel.i, sizeof(arg_kernel.i))) {
                return -EFAULT;
            }
        break;
        case LKMC_IOCTL_INC_DEC:
            if (copy_from_user(&arg_kernel.s, arg_user, sizeof(arg_kernel.s))) {
                return -EFAULT;
            }
            pr_info("1 arg = %d %d\n", arg_kernel.s.i, arg_kernel.s.j);
            arg_kernel.s.i += 1;
            arg_kernel.s.j -= 1;
            if (copy_to_user(arg_user, &arg_kernel.s, sizeof(arg_kernel.s))) {
                return -EFAULT;
            }
        break;
        default:
            return -EINVAL;
        break;
    }
    return 0;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = unlocked_ioctl
};

static int myinit(void)
{
    dir = debugfs_create_dir("lkmc_ioctl", 0);
    /* ioctl permissions are not automatically restricted by rwx as for read / write,
     * but we could of course implement that ourselves:
     * https://stackoverflow.com/questions/29891803/user-permission-check-on-ioctl-command */
    debugfs_create_file("f", 0, dir, NULL, &fops);
    return 0;
}

static void myexit(void)
{
    debugfs_remove_recursive(dir);
}

module_init(myinit)
module_exit(myexit)

Shared header:

#ifndef IOCTL_H
#define IOCTL_H

#include <linux/ioctl.h>

typedef struct {
    int i;
    int j;
} lkmc_ioctl_struct;
#define LKMC_IOCTL_MAGIC 0x33
#define LKMC_IOCTL_INC     _IOWR(LKMC_IOCTL_MAGIC, 0, int)
#define LKMC_IOCTL_INC_DEC _IOWR(LKMC_IOCTL_MAGIC, 1, lkmc_ioctl_struct)

#endif

Userland:

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "../ioctl.h"

int main(int argc, char **argv)
{
    int fd, arg_int, ret;
    lkmc_ioctl_struct arg_struct;

    if (argc < 2) {
        puts("Usage: ./prog <ioctl-file>");
        return EXIT_FAILURE;
    }
    fd = open(argv[1], O_RDONLY);
    if (fd == -1) {
        perror("open");
        return EXIT_FAILURE;
    }
    /* 0 */
    {
        arg_int = 1;
        ret = ioctl(fd, LKMC_IOCTL_INC, &arg_int);
        if (ret == -1) {
            perror("ioctl");
            return EXIT_FAILURE;
        }
        printf("arg = %d\n", arg_int);
        printf("ret = %d\n", ret);
        printf("errno = %d\n", errno);
    }
    puts("");
    /* 1 */
    {
        arg_struct.i = 1;
        arg_struct.j = 1;
        ret = ioctl(fd, LKMC_IOCTL_INC_DEC, &arg_struct);
        if (ret == -1) {
            perror("ioctl");
            return EXIT_FAILURE;
        }
        printf("arg = %d %d\n", arg_struct.i, arg_struct.j);
        printf("ret = %d\n", ret);
        printf("errno = %d\n", errno);
    }
    close(fd);
    return EXIT_SUCCESS;
}
Share:
12,643
tolgatanriverdi
Author by

tolgatanriverdi

Updated on September 18, 2022

Comments

  • tolgatanriverdi
    tolgatanriverdi over 1 year

    I am trying to build a simple kernel module. Following are the contents of file involved in it:

    module.c:

        #include <linux/init.h>
        #include <linux/fs.h>
        #include <linux/device.h>
        #include <linux/kernel.h>
        #include "header.h"
    
    
    
    
    
    
        static int device_open(struct inode *inode, struct file *file)
        {
        printk("\n Open \n");
        return 0;
        }
    
        static int device_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long args)
        {
        switch(cmd)
        {
        case IOCTL_CMD:
        printk(KERN_ALERT "\n %s \n", (char *)args);
        break;
        }
        return 0;
        }
    
    
        static int device_release(struct inode *inode, struct file *file)
        {
        printk("\n Release \n");
        return 0;
        }
    
        static struct class *my_class;
    
        static struct file_operations fops={
        .open = device_open,
        .release = device_release,
        .compat_ioctl = device_ioctl
        };
    
        static int hello_init(void)
        {
        major_no = register_chrdev(0, DEVICE_NAME, &fops);
        printk("\n Major_no : %d", major_no);
    
        my_class = class_create(THIS_MODULE, DEVICE_NAME);
        device_create(my_class, NULL, MKDEV(major_no,0), NULL, DEVICE_NAME);
        printk("\n Device Initialized in kernel ....!!!");
        return 0;
        }
    
    
    
    
        static void hello_exit(void)
        {
        printk("\n Device is Released or closed \n");
        device_destroy(my_class,MKDEV(major_no,0));
        class_unregister(my_class);
        class_destroy(my_class);
        unregister_chrdev(major_no, DEVICE_NAME);
        printk("\n===============================================================\n");
        }
    
    
    
        module_init(hello_init);
        module_exit(hello_exit);
    
        MODULE_LICENSE("GPL");
    

    appln.c

        #include <stdio.h>
        #include <fcntl.h>
        #include <string.h>
    
        #include "header.h"
    
        int main()
        {
        int fd;
        char * msg = "yahoooo";
        fd = open(DEVICE_PATH, O_RDWR);
        ioctl(fd, IOCTL_CMD, msg);
        printf("ioctl executed\n");
        close(fd);
        return 0;
        }
    

    header.h:

        #include <linux/ioctl.h>
        #include <linux/kdev_t.h> /* for MKDEV */
    
        #define DEVICE_NAME "my_dev"
        #define DEVICE_PATH "/dev/my_dev"
        #define WRITE 0
        static int major_no;
        #define MAGIC_NO '4'
        /* 
         * Set the message of the device driver 
         */
        #define IOCTL_CMD _IOR(MAGIC_NO, 0, char *)
    

    My module loads perfectly(I can see the mesg in hello_init() function). But when i run the appln.c program, even when it makes the ioctl() call, I see no result of it. Can someone tell why is the module ignoring my ioctl call.

    Thanks,

  • tolgatanriverdi
    tolgatanriverdi almost 13 years
    Kees Cook: Thanks, One more thing what is the difference b/w unlocked_ioctl and device_ioctl. Is there any updated source of reference, for module development, that lists all such changes with kernel code.
  • LRDPRDX
    LRDPRDX about 4 years
    @gkt, I am not an expert in this field but ... unlocked_ioctl is not a concrete (defined) function. It is a name for a member of struct. device_ioctl, on the other hand, is a concrete function, i.e. a value. Your question is meaningless to me. It the same as what is the difference between int a and 6?.