I'm running a Linux 3.2 kernel with the following ioctl
prototype:
long ioctl(struct file *f, unsigned int cmd, unsigned long arg);
I noticed that arg
is always unsigned long regardless of the actual data type passed to ioctl
from the respective userspace function. Examples for ioctl
typically show the following implementation (source):
typedef struct
{
int status, dignity, ego;
} query_arg_t;
#define QUERY_GET_VARIABLES _IOR('q', 1, query_arg_t *)
static long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
query_arg_t q;
switch (cmd)
{
case QUERY_GET_VARIABLES:
q.status = status;
q.dignity = dignity;
q.ego = ego;
if (copy_to_user((query_arg_t *)arg, &q, sizeof(query_arg_t)))
{
return -EACCES;
}
break;
default:
return -EINVAL;
}
return 0;
}
Notice that a query_arg_t * type is expected, so a cast is applied: (query_arg_t *)arg
Doesn't this break the strict aliasing rule though, since arg
is of type unsigned long while the cast is query_arg_t *?
Pointers are integral types. It's entirely fine to store a pointer in any integral type that is large enough to contain it. For example, the following is perfectly valid C:
double f()
{
double a = 10.5;
uintptr_t p = (uintptr_t)(&a);
double * q = (double *)p;
return *q;
}
By contrast, the following is a clear aliasing violation:
short buf[100] = {};
double x = *(double*)(buf + 13);
The point is that it doesn't matter how you store your pointer values. What matters is that you must only treat those pointers as pointers to an object that are actually pointers to an object of the correct type.
In the first example, p
does really store the pointer to a double, although it is not itself a double *
. In the second example, buf + 13
is simply not a pointer to a double, so dereferencing it as such is type punning, and an aliasing violation.
(Pointers and casts are one of the reasons that C is not a safe language: The correctness of an operation can depend on the value of a variable, rather than just its type.)