0

I want to try out how to use i/o-controls of a loadable kernel module, here a character device. The question is: How to check if the ioctl call on userspace side has an argument or not. I found that on ioctl an argument is optional.

Inside the userspace function:

// set a parameter - this is a proper call
if( ioctl(fd, IOCTL_SET_PARAM1, 5)<0 )
{
  fprintf(stderr,"Error while ioctl: %s\n", strerror(errno));
}

// call a setter without argument - this should cause an error
if( ioctl(fd, IOCTL_SET_PARAM1)<0 )
{
  fprintf(stderr,"Error while ioctl: %s\n", strerror(errno));
}

The corresponding kernel module handler:

long
fops_unlocked_ioctl (struct file    *p_file,
                     unsigned int    cmd,
                     unsigned long   arg)
{
  switch(cmd)
  {
  case IOCTL_SET_PARAM1:
    printk(KERN_INFO "IOCTL called with IOCTL_SET_PARAM1\n");
    if( /* how to check for the argument here? */ )
    {
      printk(KERN_WARNING "Missing argument\n");
      return -EINVAL;
    }
    param1 = (unsigned short) arg;
    printk(KERN_INFO "param1 set to %d\n",param1);
    break;
  default:
    printk(KERN_WARNING "IOCTL called with wrong request code.\n");
    return -EINVAL;
  }
  return 0;
}

Regards, Alex

Alex44
  • 3,597
  • 7
  • 39
  • 56
  • 1
    The user finds and read the manual for the device that they're seeking to `ioctl()`. If the user can't find the manual, the user finds the source code. If the user can't find the manual or source code, the user who has any sense doesn't do anything until they've found some more or less definitive source of information about what the `ioctl()` they're planning to use does, and the argument(s) it takes. There is nothing in the C calling convention that makes it easy for the called code to determine how many arguments it was called with. You have to assume that the user got it right. – Jonathan Leffler Mar 10 '16 at 23:39
  • The only thing you can do is verify that `arg` is a valid number. For example, if `arg` is supposed to be `0` or `1`, then any other value indicates that the user doesn't know what they're doing. However, the user could get lucky (or unlucky depending on your point of view), and `arg` may be a valid value even if the user didn't pass a value. – user3386109 Mar 11 '16 at 00:03

1 Answers1

0

Jonathan Leffler is fully right then he say, that the user should know what to do and what not.

By the way. I worked out an solution which replaces the argument by an array with the count and the originally argument. On userspace side nothing change for the user. On kernelspace side there are two macros to get the originally argument and the number of arguments.

Here comes the code:

expdev.h

#ifndef EXPDEV_H_INCLUDED
#define EXPDEV_H_INCLUDED

#ifndef __KERNEL__
  #include <stdint.h>
#endif // __KERNEL__

#include "pp_narg.h"

enum {
  IOCTL_SET_PARAM1,
};

/* ioctl - wrapper *************************************************** */

#ifndef __KERNEL__
  long int argW[2]={0};

  long int PP_IOCTL_WRAPARG( long int narg, long int arg )
  {
    argW[0] = narg;
    argW[1] = arg;
    return (long int)(argW);
  }

  #define PP_IOCTL_NARG(n,fd,cmd,arg,...) \
    ( (n==3) ? \
      ( ioctl(fd,cmd,(long int)(PP_IOCTL_WRAPARG(1,(long int)arg))) ) : \
      ( ioctl(fd,cmd,(long int)(PP_IOCTL_WRAPARG(0,(long int)arg))) ) )

  #define ioctl(...) \
    (PP_IOCTL_NARG(PP_NARG(__VA_ARGS__),         \
                   __VA_ARGS__, (long int)(0),(long int)(0)))

#else // __KERNEL__

  #define IOCTL_ARGC(argW) (((long int*)argW)[0])
  #define IOCTL_ARG(argW)  (((long int*)argW)[1])

#endif // __KERNEL__

#endif // EXPDEV_H_INCLUDED

For counting the arguments I follow this post.

pp_narg.h

/*
 Source: https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s
 */

#ifndef PP_NARG_INCLUDED
#define PP_NARG_INCLUDED

#define PP_NARG(...) \
         PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0

#endif // PP_NARG_INCLUDED

Now a call inside a userspace function (same as usual):

#include "expdev.h"

// set a parameter
if( ioctl(fd, IOCTL_SET_PARAM1, 5)<0 )
{
  fprintf(stderr,"Error while ioctl: %s\n", strerror(errno));
}

// call a setter without argument - this should cause an error
if( ioctl(fd, IOCTL_SET_PARAM1)<0 )
{
  fprintf(stderr,"Error while ioctl: %s\n", strerror(errno));
}

and the kernelspace side (mind IOCTL_ARGC(arg) and IOCTL_ARG(arg)):

#include "expdev.h"

long
fops_unlocked_ioctl (struct file    *p_file,
                     unsigned int    cmd,
                     unsigned long   arg)
{
  switch(cmd)
  {
  case IOCTL_SET_PARAM1:
    printk(KERN_INFO "IOCTL called with IOCTL_SET_PARAM1\n");
    if( IOCTL_ARGC(arg)!=1 )
    {
      printk(KERN_ERR "Missing argument.\n");
      return -EINVAL;
    }
    param1 = (unsigned short) IOCTL_ARG(arg);
    printk(KERN_INFO "param1 set to %d\n",param1);
    break;
  default:
    printk(KERN_WARNING "IOCTL called with wrong request code.\n");
    return -EINVAL;
  }
  return 0;
}

This leads to the following output on the kern.log:

kernel: [18577.042438] IOCTL called with IOCTL_SET_PARAM1
kernel: [18577.042439] param1 set to 5
kernel: [18577.042442] IOCTL called with IOCTL_SET_PARAM1
kernel: [18577.042443] Missing argument.

and on userspace side:

Error while ioctl: Invalid argument

NOTE:

The macros redefine ioctl. This means using ioctl on different devices inside the same source will cause errors on the device which doesn't support this macro (for example if you use a standard tty).

Community
  • 1
  • 1
Alex44
  • 3,597
  • 7
  • 39
  • 56