Straightening out ioctl() size confusion
The Linux kernel takes things a little further by encoding some useful information in the command codes. Along with driver-specific "magic" and command numbers, the ioctl() command code includes the direction of data movement (if any) between kernel and user space and the size of the data to be moved. The kernel itself does not do anything with those values, but their presence does enable a driver to perform some checks. If, for example, the size of a structure used as an ioctl() argument changes, the driver can use the size field in the command code to determine whether the application is using the older version or not. Some kernel code actually does check the sizes to be sure that things match up.
The command codes are created using some macros in <asm/ioctl.h>. A driver defining codes would use one of these macros:
_IOR(type, number, size) _IOW(type, number, size) _IORW(type, number, size)
The macro used specifies whether the ioctl() operation reads or writes kernel-space data (or both); type is the driver's "magic" code, and number is the command-specific code. The confusion comes in with the argument called size; it is supposed to be the type of the data to be passed between kernel and user space. So, for example, the "get tape position" code is defined as:
#define MTIOCPOS _IOR('m', 3, struct mtpos)
The problem is that a number of hackers saw the size argument and assumed that they were expected to pass the size of the expected data transfer. The result was a number of definitions like:
#define CIOC_KERNEL_VERSION _IOWR('c', 10, sizeof (int))
As a result, the actual size value, as encoded within the command, was the size of the size value, or, on most architectures, four bytes. Since most code never looks at that size value, things worked, but the values defined were not as intended. Another problem that occasionally came up was that some code used very large size values, overflowing the space allotted in the command word, thus corrupting the rest of the command code. Once again, things worked, but not quite in the way people expected.
One of the themes of 2.6 development has been the addition of type checking anywhere that the compiler can be coerced into doing it. So the obvious thing to do was to add checking to the generation of command codes; Arnd Bergmann submitted a patch which does exactly that. It adds a bit of preprocessor magic in the form of this macro:
#define _IOC_TYPECHECK(t) \ ((sizeof(t) == sizeof(t[1]) && \ sizeof(t) < (1 << _IOC_SIZEBITS)) ? \ sizeof(t) : __invalid_size_argument_for_IOC)
The first test ensures that an actual type (as opposed to a simple size) has been passed in; the second makes sure it is not too large.
All that remains is the inconvenient fact that the old, erroneous codes
have found their way into a number of application programs. Changing those
codes would break those applications, and that's something the kernel hackers
try never to do. So, for these cases, a new set of macros (with names like
_IOW_BAD() has been introduced, and the erroneous uses have been
moved over to the new macros. The command codes remain unchanged, but the
mistake is noted so that it is not replicated when somebody copies the code
in question.
Posted Sep 11, 2003 18:24 UTC (Thu)
by Ross (guest, #4065)
[Link] (1 responses)
Did you mean to say "no" there? It seems like there wouldn't be a problem
Posted Sep 11, 2003 21:58 UTC (Thu)
by chad.netzer (subscriber, #4257)
[Link]
Posted Sep 19, 2003 14:16 UTC (Fri)
by h.j.thomassen (guest, #15232)
[Link]
In the AT&T-style the CMD-code only needs a "number" component to control the "switch" statement that every ioctl-driver-routine effectively is, and All relevant information re. the data involved in an ioctl should be conveyed between user and driver via the "arg" parameter exclusively, and not via CMD-components. Driver writers using the size-component in the CMD to draw conclusions actually pile a mistake of their own on top of a mistake in the design of the overall framework. I understand the reasons for trying not to break existing driver code, but now the decision has been made to go for an incompatible change it is a missed chance that the cleanup has not been more thorough. The introduction of e.g. the _IOC_TYPECHECK(t) is still a continuation of the historical mistake and therefore most probably not a final end to all discussions. The size and direction should be dropped alltogether, and the type should be expanded into some better driver-signature-id for the stacking case. Hendrik-Jan Thomassen <hjt@ATComputing.nl> (wrote my first UNIX driver in 1975...)
Posted Jan 22, 2013 19:15 UTC (Tue)
by awardak (guest, #88954)
[Link] (1 responses)
Posted Jan 24, 2013 19:57 UTC (Thu)
by jimparis (guest, #38647)
[Link]
"If no two drivers implement the same command codes..."Straightening out ioctl() size confusion
if all the command codes were unique... if one was sent to the wrong
driver it would not match any known ioctl command.
Read further. It is a double negative.Straightening out ioctl() size confusion
It is a historical mistake that the four-bytefield ioctl-CMD constant (type, number, size, direction) has found its way into the Linux kernel. It comes from the BSD-design, as contrasted to the AT&T design which uses a two-byte CMD-mechanism (type and number only). Berkeley introduced it because they wanted the "arg" argument, if it was of a pointer type, to be a pointer into kernel address space. In a BSD kernel a higher kernel part does the data transfer between user address space and (malloc'ed) kernel space v.v. and hence this higher part, which has no knowledge about what it is transporting at all. must be told the size and direction of the transfers. Think of this higher kernel part as being near the system call dispatcher (for data from user to kernel) and near the point where syscall return values are handed back (for data from driver to user). The benefit of this approach is that driver code can use the "arg" pointer in memcpy, strcpy or even cast it to a structure pointer. Linux adopted the AT&T style, where the "arg" argument serves as a pointer into user space and requires the driver to use the helper-routines copy_from_user and copy_to_user to do the transfer. The Linux-combination of BSD-style-CMD and AT&T-style-arg is the root of the evil.Straightening out ioctl() size confusion
it needs a "type" component to make sure that in a stack of drivers the right driver picks out the ioctl-call (or decides to pass it on downward in the stack; and yes, there has been a SysV-case where two drivers using the same type-character were put in a single stack and thus caused a soundcard to hiss when actually some mouse control was intended).
Straightening out ioctl() size confusion
sizeof(t) == sizeof(t[1])
Straightening out ioctl() size confusion