Prompt y contexto
Dos procesos locales se coordinan a través de un Unix domain socket: un proceso privilegiado abre un archivo o crea un socket de escucha y se lo entrega a un worker con menos privilegios. Explica cómo pasar el descriptor, qué recibe el receptor y cómo manejar los riesgos de truncamiento y autorización.
Esto evalúa IPC en Linux/Unix, la distinción entre una tabla de descriptores de archivo (fd table) y una open file description, y el protocolo de datos auxiliares de sendmsg/recvmsg. unix(7) en Linux define SCM_RIGHTS para enviar o recibir un conjunto de descriptores de archivo abiertos entre procesos.
Qué evalúa el entrevistador
- Distinguir un entero de fd local al proceso de la open file description del kernel.
- Saber que
SCM_RIGHTSutiliza datos auxiliares (ancillary data) en lugar de colocar un entero en el payload ordinario. - Dimensionar correctamente
cmsghdr,CMSG_SPACEyCMSG_LEN, y verificarMSG_CTRUNC. - Cubrir permisos de ruta del socket, identidad del remitente, límites de recursos, tiempos de cierre (close timing) y limpieza en caso de fallos.
Preguntas para aclarar primero
- ¿Los procesos comparten usuario y dónde está la degradación de privilegios o el límite de confianza?
- ¿El descriptor es un archivo regular, un socket conectado, un socket de escucha, un epoll fd o un device fd?
- ¿El canal es
SOCK_STREAMoSOCK_DGRAM, y el protocolo necesita límites de mensaje (message boundaries) y confirmaciones (acknowledgements)? - ¿Puede el receptor verificar las credenciales del remitente, el tipo de recurso, las propiedades de solo lectura y la cantidad esperada de descriptores de archivo?
Una respuesta de 30 segundos
Crearía un par de Unix domain sockets, transportaría los descriptores en un mensaje de control SOL_SOCKET/SCM_RIGHTS desde sendmsg y colocaría una versión de protocolo y un ID de solicitud en el payload real. El receptor llamaría a recvmsg con un búfer de CMSG_SPACE suficientemente grande, verificaría el nivel, tipo, longitud y MSG_CTRUNC, y luego usaría el fd recibido como un nuevo entero en su propio proceso. Lo que cruza el límite es una referencia a una open file description, por lo que el receptor normalmente obtiene un número de fd diferente. El protocolo verificaría las credenciales del par (peer), limitaría las cantidades, establecería close-on-exec y cerraría los descriptores no aceptados o no utilizados en cada ruta de error.
Análisis detallado paso a paso
Distinguir números de fd de open file descriptions
Un fd es un índice entero en la tabla de descriptores de archivo de un proceso. Una open file description es un objeto del kernel que mantiene el estado abierto, como el offset del archivo y los flags de estado. SCM_RIGHTS copia una referencia a este último; el receptor normalmente obtiene un entero de fd diferente, semánticamente similar a duplicar un fd en la tabla de descriptores de otro proceso.
Usar datos auxiliares en lugar de bytes ordinarios
sendmsg y recvmsg transportan una cadena de registros cmsghdr a través de msghdr.msg_control. Establece cmsg_level en SOL_SOCKET, cmsg_type en SCM_RIGHTS y coloca un arreglo de enteros de fd en el área de datos. El payload ordinario puede transportar la versión, el propósito y un ID de confirmación, pero no puede reemplazar el mensaje de control.
Dimensionar el búfer de control correctamente
Para la cantidad real, el remitente usa CMSG_LEN(n * sizeof(int)) para cmsg_len; el receptor asigna espacio alineado de al menos CMSG_SPACE(n * sizeof(int)). Analiza con CMSG_FIRSTHDR y CMSG_NXTHDR, rechazando longitudes cortas y tipos inesperados.
Manejar truncamiento y límites de flujo (stream boundaries)
Si el búfer de control de recepción es demasiado pequeño, los datos auxiliares pueden truncarse o descartarse y se establece MSG_CTRUNC; el receptor no debe usar una lista parcial de descriptores. Linux requiere al menos un byte real con datos auxiliares en un SOCK_STREAM, y los datos auxiliares forman una barrera de recepción, por lo que debes vincular el mensaje de control a un ID de solicitud en lugar de confiar en la posición en el flujo de bytes.
Establecer límites de identidad y autorización
Los permisos de directorio y de socket son el primer límite para un socket en el sistema de archivos. El servidor también debe usar SO_PEERCRED o SCM_CREDENTIALS y confirmar el tenant, el propósito y el tipo de recurso en la capa de aplicación. Recibir un fd no otorga autoridad adicional por sí mismo; el remitente solo debe transferir una referencia autorizada.
Administrar el ciclo de vida y los límites de recursos
El remitente puede cerrar su propio fd después de enviarlo, pero el kernel mantiene una referencia en tránsito hasta que el receptor la acepte. Linux limita la operación con RLIMIT_NOFILE y SCM_MAX_FD; la página de manual actual registra SCM_MAX_FD como usualmente 253, mientras que las versiones anteriores usaban 255. Limita los descriptores por mensaje y por worker, y haz que los rechazos sean observables.
struct msghdr msg = {0};
struct iovec iov = {.iov_base = "F", .iov_len = 1};
union { char buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr align; } control;
msg.msg_iov = &iov; msg.msg_iovlen = 1;
msg.msg_control = control.buf; msg.msg_controllen = sizeof(control.buf);
struct cmsghdr *c = CMSG_FIRSTHDR(&msg);
c->cmsg_level = SOL_SOCKET; c->cmsg_type = SCM_RIGHTS;
c->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(c), &fd, sizeof(fd));
sendmsg(sock, &msg, MSG_NOSIGNAL);Ejemplo de una respuesta sólida
Haría de esto un protocolo IPC local con confirmaciones. El remitente usa sendmsg en un Unix domain socket con un mensaje de control SOL_SOCKET/SCM_RIGHTS; el payload real transporta una versión del protocolo, el propósito y un ID de solicitud. El receptor asigna espacio de control con CMSG_SPACE, verifica el tipo, la longitud y MSG_CTRUNC, verifica las credenciales del peer, la cantidad de descriptores y el tipo de recurso, y solo entonces se lo entrega al worker. El punto semántico importante es que la transferencia referencia una open file description, por lo que el entero del receptor suele ser diferente y el offset del archivo o el estado de apertura pueden compartirse. Establecería close-on-exec, limitaría los descriptores en tránsito, manejaría RLIMIT_NOFILE y SCM_MAX_FD, y cerraría y auditaría cada ruta de fallo.
Errores comunes
- Escribir el entero del fd en JSON o en un payload de bytes y asumir que el otro proceso puede usarlo directamente.
- Ignorar la alineación de
CMSG_SPACEy asignar únicamentesizeof(int)para los datos de control. - Omitir la comprobación de
MSG_CTRUNCy usar una lista de descriptores que fue truncada. - Verificar únicamente la ruta del socket Unix sin verificar las credenciales del peer ni el propósito.
- Olvidar que el receptor obtiene un nuevo número de fd o no especificar la semántica de offset y estado compartidos.
- Ignorar close-on-exec, límites de descriptores de archivo, fallos de envío y el cierre de descriptores no utilizados.
Preguntas de seguimiento y respuestas
¿El receptor obtiene el mismo descriptor de archivo?
Por lo general, no es el mismo número entero. El kernel copia una referencia a la misma open file description en la tabla de descriptores de archivo del receptor, por lo que el offset del archivo y algunos estados de apertura pueden compartirse. Si se requieren offsets independientes, vuelve a abrir o copia los datos en lugar de asumir números de fd idénticos.
¿Por qué enviar un byte real?
Los sockets de flujo Unix en Linux requieren al menos un byte real en el mismo sendmsg cuando se envían datos auxiliares; también le permite al protocolo asociar el mensaje de control con una solicitud. Los datagramas de Linux pueden omitirlo, pero el código portable aun así debería incluir un byte real.
¿Qué pasa si el búfer de control es demasiado pequeño?
Los datos auxiliares pueden truncarse o descartarse y se establece MSG_CTRUNC. Cierra los descriptores inválidos o sobrantes, devuelve un error de protocolo y registra el evento; nunca trates una lista parcial como una autorización completa.
¿Cómo evitas que un proceso privilegiado entregue el recurso equivocado?
Usa permisos de socket y credenciales de peer para restringir la conexión, luego vincula el ID de solicitud, el tenant, el propósito y el tipo de recurso en la capa de aplicación. El remitente selecciona únicamente de una lista de permitidos (allowlist); el receptor comprueba las propiedades de solo lectura, el estado de la ruta o del socket, y audita cada autorización y cierre.