La frontera usuario↔kernel
copy_to_user y copy_from_user: cómo el kernel mueve datos a través de la frontera de forma segura, y por qué jamás debe confiar en un puntero del espacio de usuario.
Cuando un programa hace una syscall con datos (leer, escribir, ioctl), esos datos viven en el espacio de usuario, y el kernel debe traerlos o enviarlos cruzando la frontera. Hacerlo mal no es un bug cualquiera: es un agujero de seguridad que compromete todo el sistema. Este es uno de los temas más críticos del kernel.
- Por qué no puedes dereferenciar punteros de usuario.
- copy_from_user y copy_to_user.
- get_user y put_user.
- La regla de oro: nunca confíes en el usuario.
Por qué no puedes dereferenciar un puntero de usuario
Cuando tu código de kernel recibe un char *buf que viene de un programa, ese puntero apunta al espacio de usuario. Dereferenciarlo directamente es peligroso por tres razones:
- La dirección podría ser inválida o maliciosa (el programa podría pasar un puntero a memoria del kernel para engañarte).
- La página podría no estar presente (paginada a disco), causando un fallo en un contexto donde no puede resolverse.
- Sería una vulnerabilidad: un programa sin privilegios podría leer o escribir memoria del kernel.
Por eso el kernel prohíbe dereferenciar punteros de usuario directamente. Los marca __user (nivel 9.2) y sparse te avisa si lo intentas.
copy_from_user y copy_to_user
Las funciones seguras que cruzan la frontera, comprobando cada acceso:
/* traer datos DEL usuario al kernel */
char kbuf[128];
if (copy_from_user(kbuf, user_ptr, len)) /* devuelve nº de bytes NO copiados */
return -EFAULT; /* si no es 0, la dirección era mala */
/* enviar datos DEL kernel al usuario */
if (copy_to_user(user_ptr, kbuf, len))
return -EFAULT;
Devuelven el número de bytes que no pudieron copiarse: 0 significa éxito. Cualquier otra cosa, devuelves -EFAULT. Para valores simples (un int), get_user/put_user son atajos.
Esta es una de las mentalidades más importantes de toda la programación de kernel, y por extensión, de la seguridad informática. Todo dato que cruza desde el espacio de usuario es hostil hasta que se demuestre lo contrario. Un programa podría pasarte un puntero a memoria del kernel esperando que lo copies a un sitio que él pueda leer (fuga de datos), o un tamaño gigante para desbordar tu buffer, o valores diseñados para romper tu lógica. El kernel es el guardián con privilegio total; si baja la guardia una sola vez, un programa sin privilegios escala a control total del sistema. Por eso: (1) nunca dereferencies punteros de usuario directamente — usa copy_*_user, que valida la dirección; (2) valida cada tamaño y cada valor antes de usarlo (¿cabe en mi buffer? ¿está en rango?); (3) asume malicia, no descuido. Innumerables vulnerabilidades del kernel (CVEs) han sido exactamente esto: un punto de la frontera donde se confió en el usuario. Interiorizar esta desconfianza sistemática no es paranoia: es el oficio.
- Escribe una función que reciba un
__userbuffer y lo copie al kernel concopy_from_user, devolviendo-EFAULTsi falla. - Escribe la inversa con
copy_to_user. - Añade validación de
len: rechaza tamaños que no quepan en tu buffer. - Investiga un CVE real del kernel causado por confiar en un puntero o tamaño de usuario.