Hi Mauro,

On Mon, Mar 26, 2018 at 02:28:34PM -0300, Mauro Carvalho Chehab wrote:
> Em Mon, 26 Mar 2018 16:23:24 +0300
> Sakari Ailus <sakari.ai...@linux.intel.com> escreveu:
> 
> > Maintain a list of supported IOCTL argument sizes and allow only those in
> > the list.
> > 
> > As an additional bonus, IOCTL handlers will be able to check whether the
> > caller actually set (using the argument size) the field vs. assigning it
> > to zero. Separate macro can be provided for that.
> > 
> > This will be easier for applications as well since there is no longer the
> > problem of setting the reserved fields zero, or at least it is a lesser
> > problem.
> > 
> > Signed-off-by: Sakari Ailus <sakari.ai...@linux.intel.com>
> > Acked-by: Hans Verkuil <hans.verk...@cisco.com>
> > ---
> > Hi folks,
> > 
> > I've essentially addressed Mauro's comments on v2.
> > 
> > The code is only compile tested so far but the changes from the last
> > tested version are not that big. There's still some uncertainty though.
> 
> You should test it... I guess there is a bug on this version :-)
> (see below)
> 
> > 
> > since v2:
> > 
> > - Rework is_valid_ioctl based on the comments
> > 
> >     - Improved comments,
> >     
> >     - Rename cmd as user_cmd, as this comes from the user
> >     
> >     - Check whether there are alternative argument sizes before any
> >       checks on IOCTL command if there is no exact match
> >       
> >     - Use IOCSIZE_MASK macro instead of creating our own
> > 
> > - Add documentation for macros declaring IOCTLs
> > 
> > 
> >  drivers/media/media-device.c | 98 
> > +++++++++++++++++++++++++++++++++++++++++---
> >  1 file changed, 92 insertions(+), 6 deletions(-)
> > 
> > diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c
> > index 35e81f7..279d740 100644
> > --- a/drivers/media/media-device.c
> > +++ b/drivers/media/media-device.c
> > @@ -387,22 +387,65 @@ static long copy_arg_to_user(void __user *uarg, void 
> > *karg, unsigned int cmd)
> >  /* Do acquire the graph mutex */
> >  #define MEDIA_IOC_FL_GRAPH_MUTEX   BIT(0)
> >  
> > -#define MEDIA_IOC_ARG(__cmd, func, fl, from_user, to_user)         \
> > +/**
> > + * MEDIA_IOC_SZ_ARG - Declare a Media device IOCTL with alternative size 
> > and
> > + *               to_user/from_user callbacks
> > + *
> > + * @__cmd: The IOCTL command suffix (without "MEDIA_IOC_")
> > + * @func:  The handler function
> > + * @fl:            Flags from @enum media_ioc_flags
> > + * @alt_sz:        A 0-terminated list of alternative argument struct 
> > sizes.
> > + * @from_user:     Function to copy argument struct from the user to the 
> > kernel
> > + * @to_user:       Function to copy argument struct to the user from the 
> > kernel
> > + */
> > +#define MEDIA_IOC_SZ_ARG(__cmd, func, fl, alt_sz, from_user, to_user)      
> > \
> >     [_IOC_NR(MEDIA_IOC_##__cmd)] = {                                \
> >             .cmd = MEDIA_IOC_##__cmd,                               \
> >             .fn = (long (*)(struct media_device *, void *))func,    \
> >             .flags = fl,                                            \
> > +           .alt_arg_sizes = alt_sz,                                \
> >             .arg_from_user = from_user,                             \
> >             .arg_to_user = to_user,                                 \
> >     }
> >  
> > -#define MEDIA_IOC(__cmd, func, fl)                                 \
> > -   MEDIA_IOC_ARG(__cmd, func, fl, copy_arg_from_user, copy_arg_to_user)
> > +/**
> > + * MEDIA_IOC_ARG - Declare a Media device IOCTL with to_user/from_user 
> > callbacks
> > + *
> > + * Just as MEDIA_IOC_SZ_ARG but without the alternative size list.
> > + */
> 
> Nitpick: either use:
>       /*
>        *...
>        */
> 
> or add the arguments to the macro there, as /** ... */ expects
> the arguments. Same for other comments below.

I think a regular comment would do. It's only used below.

> 
> > +#define MEDIA_IOC_ARG(__cmd, func, fl, from_user, to_user)         \
> > +   MEDIA_IOC_SZ_ARG(__cmd, func, fl, NULL, from_user, to_user)
> > +
> > +/**
> > + * MEDIA_IOC_ARG - Declare a Media device IOCTL with alternative argument 
> > struct
> > + *            sizes
> > + *
> > + * Just as MEDIA_IOC_SZ_ARG but without the callbacks to copy the data 
> > from the
> > + * user space and back to user space.
> > + */
> > +#define MEDIA_IOC_SZ(__cmd, func, fl, alt_sz)                      \
> > +   MEDIA_IOC_SZ_ARG(__cmd, func, fl, alt_sz,               \
> > +                    copy_arg_from_user, copy_arg_to_user)
> > +
> > +/**
> > + * MEDIA_IOC_ARG - Declare a Media device IOCTL
> > + *
> > + * Just as MEDIA_IOC_SZ_ARG but without the alternative size list or the
> > + * callbacks to copy the data from the user space and back to user space.
> > + */
> > +#define MEDIA_IOC(__cmd, func, fl)                         \
> > +   MEDIA_IOC_ARG(__cmd, func, fl,                          \
> > +                 copy_arg_from_user, copy_arg_to_user)
> >  
> >  /* the table is indexed by _IOC_NR(cmd) */
> >  struct media_ioctl_info {
> >     unsigned int cmd;
> >     unsigned short flags;
> > +   /*
> > +    * Sizes of the alternative arguments. If there are none, this
> > +    * pointer is NULL.
> > +    */
> > +   const unsigned short *alt_arg_sizes;
> >     long (*fn)(struct media_device *dev, void *arg);
> >     long (*arg_from_user)(void *karg, void __user *uarg, unsigned int cmd);
> >     long (*arg_to_user)(void __user *uarg, void *karg, unsigned int cmd);
> > @@ -416,6 +459,46 @@ static const struct media_ioctl_info ioctl_info[] = {
> >     MEDIA_IOC(G_TOPOLOGY, media_device_get_topology, 
> > MEDIA_IOC_FL_GRAPH_MUTEX),
> >  };
> >  
> > +static inline long is_valid_ioctl(unsigned int user_cmd)
> > +{
> > +   const struct media_ioctl_info *info = ioctl_info;
> > +   const unsigned short *alt_arg_sizes;
> > +
> > +   if (_IOC_NR(user_cmd) >= ARRAY_SIZE(ioctl_info))
> > +           return -ENOIOCTLCMD;
> > +
> > +   info += _IOC_NR(user_cmd);
> > +
> > +   if (user_cmd == info->cmd)
> > +           return 0;
> > +
> > +   /*
> > +    * There was no exact match between the user-passed IOCTL command and
> > +    * the definition. Are there earlier revisions of the argument struct
> > +    * available?
> > +    */
> > +   if (!info->alt_arg_sizes)
> > +           return -ENOIOCTLCMD;
> > +
> > +   /*
> > +    * Variable size IOCTL argument support allows using either the latest
> > +    * revision of the IOCTL argument struct or an earlier version. Check
> > +    * that the size-independent portions of the IOCTL command match and
> > +    * that the size matches with one of the alternative sizes that
> > +    * represent earlier revisions of the argument struct.
> > +    */
> > +   if ((user_cmd & ~IOCSIZE_MASK) != (info->cmd & ~IOCSIZE_MASK)
> > +       || _IOC_SIZE(user_cmd) < _IOC_SIZE(info->cmd))
> > +           return -ENOIOCTLCMD;
> 
> I guess it should be, instead:
> 
>           || _IOC_SIZE(user_cmd) > _IOC_SIZE(info->cmd))
> 
> The hole idea is that the struct sizes used by ioctls can monotonically
> increase as newer fields become needed, but never decrease.

Oops, indeed. I'll send a new version...

> 
> Assuming that, _IOC_SIZE(MEDIA_IOC_foo) will give the size of the
> latest version of an ioctl supported by a given Kernel version,
> while alt_arg_sizes will list smaller sizes from previous
> Kernel versions that will also be accepted, in order to make it
> backward-compatible with apps compiled against older Kernel headers.
> 
> However, if an application is compiled with a kernel newer than
> the current one, it should fail, as an older Kernel doesn't know
> how to handle the newer fields. So, it should be up to the userspace
> app to add backward-compatible code if it needs to support older
> Kernels.
> 
> (perhaps it should be worth adding a comment like the above
> somewhere).

Good point. I wonder if it'd be better to just handle this in the kernel
and allow larges arguments as well. This would effectively be the same as
we have right now, with a very large number of reserved fields.

We could not assume an application knowingly set a field that is present in
a struct of which an older revision exists: all it takes is to compile the
application in an environment which has the new definitions. Unless... we
put the version to the struct name. But I don't like that, it makes IOCTL
calls (and the documentation) quite ugly.

That'd also suggest the list of alternative sizes isn't very useful: even
when we have reserved fields, we don't have any way of knowing whether an
application intentionally set a field to zero or just left out initialising
that particular field.

I wonder what Hans thinks...

> 
> > +
> > +   for (alt_arg_sizes = info->alt_arg_sizes; *alt_arg_sizes;
> > +        alt_arg_sizes++)
> > +           if (_IOC_SIZE(user_cmd) == *alt_arg_sizes)
> > +                   return 0;
> > +
> > +   return -ENOIOCTLCMD;
> > +}
> > +
> >  static long media_device_ioctl(struct file *filp, unsigned int cmd,
> >                            unsigned long __arg)
> >  {
> > @@ -426,9 +509,9 @@ static long media_device_ioctl(struct file *filp, 
> > unsigned int cmd,
> >     char __karg[256], *karg = __karg;
> >     long ret;
> >  
> > -   if (_IOC_NR(cmd) >= ARRAY_SIZE(ioctl_info)
> > -       || ioctl_info[_IOC_NR(cmd)].cmd != cmd)
> > -           return -ENOIOCTLCMD;
> > +   ret = is_valid_ioctl(cmd);
> > +   if (ret)
> > +           return ret;
> >  
> >     info = &ioctl_info[_IOC_NR(cmd)];
> >  
> > @@ -444,6 +527,9 @@ static long media_device_ioctl(struct file *filp, 
> > unsigned int cmd,
> >                     goto out_free;
> >     }
> >  
> > +   /* Set the rest of the argument struct to zero */
> > +   memset(karg + _IOC_SIZE(cmd), 0, _IOC_SIZE(info->cmd) - _IOC_SIZE(cmd));
> > +
> >     if (info->flags & MEDIA_IOC_FL_GRAPH_MUTEX)
> >             mutex_lock(&dev->graph_mutex);
> >  
> 
> Regards,
> Mauro

-- 
Sakari Ailus
sakari.ai...@linux.intel.com

Reply via email to