سؤال

I want to print all the parameter values passed to linux system calls. In case of ioctl(), for example, I have following prototype and print statement.

asmlinkage long our_sys_ioctl(unsigned int fd ,  unsigned int cmd , unsigned long arg)
{
    printk ("fd=%u, cmd=%u and arg=%lu \n ", fd, cmd, arg);
    return original_call_ioctl(fd , cmd , arg);
}

I understand, fd is file descriptor of driver files, cmd defines driver , ioctl number, type of operation and size of parameter. But I am confused about arg parameter either it is a pointer to memory or just an immediate value what most of the documentations call it.

By using this arg parameter, how can I get the memory content if it is passed as unsigned long arg as given above, instead of a pointer?

هل كانت مفيدة؟

المحلول

The arg parameter to the ioctl is opaque at the generic vfs level. How to interpret it is up to the driver or filesystem that actually handles it. So it may be a pointer to userspace memory, or it could be an index, a flag, whatever. It might even be unused and conventionally passed in a 0.

For example, look at the implementation of the TCSBRKP ioctl in drivers/tty/tty_io.c:

long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
//...
       case TCSBRKP:   /* support for POSIX tcsendbreak() */
            return send_break(tty, arg ? arg*100 : 250);

You can look at the ioctl_list(2) man page to see the parameters that various ioctls take; all of the entries on that list that have int or other non-pointer parameters are other examples.

So you could do something like

    void __user *argp = (void __user *) arg;

and then use copy_from_user() or get_user() to read the memory that arg points to, but that may fail if the parameter isn't a pointer. And at the generic ioctl syscall, you might not really want to have a huge table of every possible ioctl.

نصائح أخرى

This document, also linked in this answer, should clarify the point more in dept. An interesting excerpt (with bold text added by me) could be the following:

In user space, the ioctl system call has the following prototype:

int ioctl(int fd, unsigned long cmd, ...);

The prototype stands out in the list of Unix system calls because of the dots, which usually mark the function as having a variable number of arguments. In a real system, however, a system call can't actually have a variable number of arguments. System calls must have a well-defined prototype, because user programs can access them only through hardware "gates." Therefore, the dots in the prototype represent not a variable number of arguments but a single optional argument, traditionally identified as char *argp. The dots are simply there to prevent type checking during compilation. The actual nature of the third argument depends on the specific control command being issued (the second argument). Some commands take no arguments, some take an integer value, and some take a pointer to other data. Using a pointer is the way to pass arbitrary data to the ioctl call; the device is then able to exchange any amount of data with user space.

The unstructured nature of the ioctl call has caused it to fall out of favor among kernel developers. Each ioctl command is, essentially, a separate, usually undocumented system call, and there is no way to audit these calls in any sort of comprehensive manner. It is also difficult to make the unstructured ioctl arguments work identically on all systems; for example, consider 64-bit systems with a user-space process running in 32-bit mode. As a result, there is strong pressure to implement miscellaneous control operations by just about any other means. Possible alternatives include embedding commands into the data stream (we will discuss this approach later in this chapter) or using virtual filesystems, either sysfs or driver-specific filesystems. (We will look at sysfs in Chapter 14.) However, the fact remains that ioctl is often the easiest and most straightforward choice for true device operations.

This means there is no any way to understand how to interpret an ioctl argument as an external observer, without an insightful knowledge of a device driver conventions/internals. An ioctl argument is untyped from userspace perspective, and somehow loosely typed in kernel space, since it's dealt with as an unsigned long just to reserve space for it. It's a 'pure' number or any sequence of bits that fits into an unsigned long integer space, could be used as a (very short) string, a (small) char array, a (small) struct - but being careful of endianness and architecture-specific sizes - could represent an opcode for a device's onboard chip, or even be dealt with as a float by type puning!

Also, this means it's very easy to mess things up, by passing inconsistent data to the driver (not just wrong data, but wrong data of the wrong type!), eventually causing undefined behaviour of the device, or corruption of userspace memory (e.g. by passing a pointer to a struct of the wrong size in a read ioctl).

A few more lines:

[...] The cmd argument is passed from the user unchanged, and the optional arg argument is passed in the form of an unsigned long, regardless of whether it was given by the user as an integer or a pointer. If the invoking program doesn't pass a third argument, the arg value received by the driver operation is undefined. Because type checking is disabled on the extra argument, the compiler can't warn you if an invalid argument is passed to ioctl, and any associated bug would be difficult to spot.

Anyway, if you wanted to try a 'blind' audit of a device driver ioctl calls, without looking into header and source files, one could try and deal with arg as a pointer first, with copy_from_user(), on failure chances are that's an immediate value (or an error occurred), then one could try to log its value to give it a look and try to interprete it (but why reversing an ioctl instead of studying driver code?); on success, whitout knowledge, different sizes of memory could be read and logged for a decoding attempt (again, pointlessly as far as sources are available, as they should).

An operation of more interest is surely decoding an ioctl code (cmd), since it can point to the right direction to find the driver(s) its numeric value is tied to - there should be only one, if the convention for ioctl definition is applied, anyway different drivers are allowed to use the same magic character, thus a regular expression to grep all kernel source files for a #define containing a 'r' or 'D' or the like could pick out a number of files to inspect for ioctl definitions, than matching against the function number should sort out a few or all wrong ones, and looking for correct argument size would complete the search.

Regards.

Keep in mind that the prototype for ioctl looks like this:

int ioctl(int fildes, unsigned long request, ...);

You only know for sure what the first two parameters are. According to this article:

Additional arguments are optional and could vary from the ioctl implementation on one device to the implementation on another. As far as I can tell, a third argument always is present, and I have yet to find more than a third. This third argument usually seems to be a pointer to a structure. This allows the passing of an arbitrary amount of data in both directions, the data being defined by the structure to which the pointer refers, simply by passing the pointer.

...but even assuming there will only ever be a third parameter, you still don't know if it's a literal value or a pointer to a structure (absent an explicit mapping of requests to expected parameters).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top