Cuando «está ejecutándose» no basta
Un proceso aparece en una lista y alguien dice que el servicio está arriba. Minutos después desaparece, vuelve a aparecer con otro identificador o responde a una petición aunque su proceso original ya terminó. En otro host, el binario es correcto pero arranca antes de que exista el directorio que necesita, conserva privilegios de la cuenta que lo lanzó o se inicia dos veces desde mecanismos distintos. La misma palabra, servicio, está ocultando varias preguntas.
Este capítulo estudia esas preguntas en un sistema operativo, con Linux y las interfaces POSIX como referencia principal. Un proceso es una instancia con estado de ejecución, memoria, credenciales y descriptores; un programa es el código y los datos que pueden dar origen a una instancia. Un daemon es un proceso diseñado para prestar una función sin depender de una terminal interactiva. Un servicio es una función gestionada, no necesariamente un proceso único: puede tener varios procesos, sockets activados bajo demanda, workers y recursos externos. Un supervisor crea, ordena, observa y detiene procesos o unidades, pero no hereda automáticamente la corrección de lo que ejecutan.
La tesis es concreta: la disponibilidad de un proceso en la tabla no prueba que el servicio cumpla su contrato, y la existencia de una unidad de arranque no prueba que una propiedad de seguridad se mantenga. Para reconstruir un estado profesional hay que separar creación, ejecución, identidad, dependencia, preparación, observabilidad, terminación y recuperación. Cada transición deja evidencia diferente y tiene límites distintos.
El proceso que ejecuta no es el servicio que se administra
Un proceso comienza como una instancia creada por otro proceso. En Unix, fork() crea una copia lógica del proceso llamador y devuelve resultados diferentes al padre y al hijo; execve() reemplaza la imagen del proceso por un programa nuevo. La combinación explica un patrón frecuente: un supervisor conserva su propio proceso y crea un hijo que termina ejecutando el binario del servicio. El PID del hijo identifica una instancia concreta, no una identidad permanente del servicio. POSIX, fork() Linux man-pages, execve(2)
La diferencia se vuelve visible al reiniciar. Si un proceso termina y el supervisor crea otro, el PID cambia aunque la unidad lógica sea la misma. Un cliente que guarda el PID puede confundirse, mientras una herramienta que consulta la unidad y su estado puede reconocer la nueva instancia. La evidencia debe registrar al menos PID, PPID, ejecutable, argumentos, credenciales, directorio de trabajo, descriptores relevantes y momento de observación. El nombre del proceso en una lista no basta: varios binarios pueden usar el mismo nombre visible, y el mismo programa puede prestar funciones diferentes según sus argumentos y entorno.
El kernel conserva la relación padre-hijo hasta que el hijo termina y el padre recoge su estado con una operación de espera. Un hijo cuyo padre muere puede ser adoptado por otro proceso del sistema; esa reasignación permite continuar la ejecución, pero no convierte al proceso en un servicio correctamente supervisado. Un proceso huérfano puede perder el canal por el que debía recibir una orden de detención, un límite, una renovación de credenciales o información de salud. «Sigue ejecutándose» describe una observación; no describe quién responde por su ciclo de vida.
Un daemon tradicional suele desacoplarse de una terminal: puede crear una sesión, cambiar su directorio de trabajo, cerrar descriptores heredados y escribir registros en un canal elegido. Es una convención de proceso, no un mecanismo de autorización. En un sistema moderno, dejar que el daemon se doble-desacople puede ocultar el proceso real al supervisor. La configuración debe declarar quién mantiene el proceso en primer plano, quién captura sus salidas y quién recibe su estado. El daemon no debe inventarse una segunda autoridad de arranque sólo porque históricamente se ejecutaba desde un script.
Caso: dos procesos que parecen uno
Supongamos un servicio sintético ledger-api iniciado por una unidad del supervisor. La unidad lanza un proceso principal, que a su vez crea workers. Un ps muestra varios nombres iguales. Si uno de los workers termina, el supervisor puede no reiniciar nada: el proceso principal sigue vivo y la unidad se considera activa. Si el proceso principal termina, el supervisor puede recrear toda la instancia. La pregunta de disponibilidad exige saber qué proceso acepta solicitudes, qué proceso gestiona workers y qué condición marca «listo».
Una investigación no debe llamar incidente a la desaparición de un worker sin contexto. Primero separa observación —el PID dejó de existir—, inferencia —pudo fallar el worker—, impacto posible —algunas solicitudes pueden perder capacidad— y decisión —revisar límites y política de reinicio—. La misma cadena evita atribuir a una intrusión un reinicio provocado por una actualización o por agotamiento de memoria.
Alcance: modelo POSIX/Linux; reinicio crea otra instancia y no prueba causa corregida.
¿Qué conserva el supervisor y qué conserva el proceso?
Un supervisor aporta una autoridad externa al proceso. Puede imponer usuario y grupos, directorio raíz o de trabajo, límites de recursos, variables de entorno, descriptores, capacidades, namespaces y política de reinicio. La configuración de una unidad puede declarar que un servicio requiere otro, que debe comenzar después de un montaje o que queda habilitado para un objetivo de arranque. La unidad es una descripción de gestión; el proceso sigue teniendo sus propias credenciales, memoria y decisiones internas.
En systemd, systemd.service distingue servicios de tipo simple, forking, notify y otros modos de seguimiento. El tipo cambia qué evento usa el gestor para considerar iniciada una unidad; no transforma un proceso defectuoso en uno preparado. Type=notify, por ejemplo, permite que un servicio informe una transición de preparación mediante el protocolo esperado, pero una notificación de «listo» sólo es significativa si el programa la emite después de inicializar aquello que su contrato requiere. systemd.service, sección Type=
La política Restart= expresa qué hacer tras determinadas terminaciones. Reiniciar ayuda a recuperar una instancia, pero puede ocultar un fallo persistente, generar una tormenta de procesos o volver a ejecutar una operación no idempotente. La unidad debe combinarse con límites de frecuencia, una causa observable y un procedimiento de diagnóstico. Reinicio automático es una estrategia de recuperación; no es evidencia de disponibilidad sostenida ni de integridad del estado.
La identidad merece un análisis separado. Un supervisor puede iniciar el proceso con un usuario dedicado y grupos mínimos, pero el binario puede conservar capacidades, abrir descriptores privilegiados heredados o delegar en otro proceso que sí tiene autoridad. El orden importa: el programa puede necesitar privilegios para abrir un socket o leer una clave y luego reducirlos, mientras que una unidad puede hacer que el kernel aplique la identidad antes de ejecutar. User=, Group=, SupplementaryGroups= y las opciones de aislamiento de systemd son mecanismos de configuración; su eficacia requiere observar el proceso creado y las rutas que puede alcanzar. systemd.exec, opciones de identidad
La relación entre proceso y supervisor tampoco es un canal de confianza unilateral. El supervisor recibe una orden de inicio o detención; el proceso puede ignorar una señal, crear descendientes, mantener sockets o dejar datos incompletos. Una detención limpia exige conocer señales, tiempo de gracia, proceso que debe salir y recursos que deben cerrarse. Matar sólo el PID que figura en una pantalla puede dejar workers vivos o permitir que otro actor active una instancia duplicada.
De fork y exec a un proceso en segundo plano
El ciclo de vida práctico comienza con una decisión de creación. El supervisor puede ejecutar directamente el binario, crear un hijo con fork(), recibir una conexión y hacer socket activation, o encargar el trabajo a un gestor de tareas. En todos los casos hay una transición entre una especificación y una instancia. Las precondiciones —montajes, directorios, credenciales, sockets, secretos y configuración— deben estar disponibles antes de que el proceso alcance la fase que los usa.
Tras execve(), el proceso obtiene la imagen del programa, conserva ciertos atributos heredados y empieza su inicialización. Los descriptores que no se marcan para cierre al ejecutar pueden continuar abiertos; las variables de entorno y el directorio de trabajo pueden transportar supuestos inesperados. El binario puede cargar plugins o leer configuración desde una ruta que el supervisor no conoce. Una revisión debe distinguir lo declarado en la unidad, lo heredado del supervisor y lo que el programa decide durante su arranque.
La entrada en segundo plano no equivale a estar listo. Un proceso puede existir, escuchar un puerto y todavía no haber cargado sus datos, aplicado su política o conectado con una dependencia. Por ello, un estado operacional útil separa al menos created, starting, ready, degraded, stopping y failed. El supervisor puede conocer sólo una parte de esos estados; la aplicación puede emitir una señal de preparación sin exponer qué comprobó. La interfaz de salud debe documentar qué propiedad prueba y qué fallos no detecta.
Un socket abierto tampoco prueba autorización correcta. El proceso pudo abrirlo como root y reducir privilegios después, o pudo aceptar conexiones pero aplicar una política de acceso incorrecta. En el análisis, «puerto escuchando» es una observación de red local; «petición autorizada» requiere rastrear identidad, decisión y datos que cruzan la frontera. El capítulo 20 desarrollará la trazabilidad; aquí basta no usar el estado LISTEN como sustituto del contrato del servicio.
Arranque: composición y orden, no una lista de comandos
El arranque del sistema es una composición de unidades, montajes, dispositivos, sockets, servicios y objetivos. En systemd hay que razonar sobre dos ejes separados. Requires= y Wants= introducen relaciones de requisito/activación con distinta fuerza; After= y Before= ordenan los trabajos cuando ambas unidades participan en la transacción. Una relación no incorpora automáticamente la otra: Requires=b.service sin After=b.service permite que los trabajos se inicien en paralelo, mientras After=b.service por sí solo no solicita que b.service sea activado. Las consecuencias de un fallo o una parada dependen además de la relación y de otras directivas; ninguna de ellas demuestra readiness de la aplicación. systemd.unit, “Requires=”, “Wants=” y “Before=/After=”
Una dependencia temporal tampoco garantiza disponibilidad real. After=network-online.target ordena respecto de un objetivo cuya implementación depende del sistema de red; no demuestra que el nombre remoto, la ruta o la aplicación estén utilizables. De forma análoga, iniciar después de un montaje no garantiza que el contenido esperado tenga la versión correcta o los permisos correctos. El claim debe nombrar la precondición relevante, no la etiqueta aproximada que se usó para ordenar.
Alcance: semántica documentada de systemd; la operación funcional se prueba aparte.
El arranque también compone autoridades. El proceso inicial puede comenzar servicios con una identidad de sistema; un servicio puede abrir un socket antes de cambiar de usuario; una unidad puede delegar a un contenedor o a otro supervisor; una tarea puede obtener secretos mediante un agente. Cada salto introduce una frontera. La pregunta profesional es «¿qué principal puede hacer qué operación en qué momento?», no sólo «¿qué servicio arrancó?». Las unidades de un host tampoco describen necesariamente el estado de un proceso dentro de un contenedor, namespace o gestor externo.
Caso: la dependencia que existe demasiado pronto
Un report-worker necesita una ruta /var/lib/report y un servicio queue-gateway. La unidad declara After=queue-gateway.service pero no expresa que la ruta deba estar montada ni que el gateway esté listo para aceptar el protocolo de trabajo. El worker arranca, crea un directorio vacío si la ruta no está presente y marca su proceso como activo. Más tarde, el montaje aparece y el gateway acepta conexiones, pero el worker sigue usando el estado inicial.
La conclusión correcta no es que el arranque sea inseguro en abstracto. Es que el diseño carece de una precondición verificable para el recurso y de un criterio de preparación para la cola. La evidencia que discrimina incluye la unidad efectiva, el grafo de dependencias, el montaje visible al proceso, el contenido observado antes y después y la señal de readiness. Una corrección puede consistir en declarar el montaje, impedir que el programa cree un estado vacío y hacer que la aplicación valide una versión esperada; la elección depende de la propiedad que se quiera preservar.
Detener, reiniciar y recuperar no son sinónimos
La terminación de un proceso tiene una causa y una ruta. Puede recibir una señal, devolver un código, sufrir una excepción, ser terminado por el kernel o ser detenido por límites de recursos. El padre puede recolectar el estado mediante wait(); si no lo hace, queda un zombie que conserva información mínima aunque ya no ejecute código. Un zombie no presta servicio, pero su presencia revela una relación de gestión incompleta. Linux man-pages, wait(2)
Detener una unidad debería especificar qué proceso recibe la orden, cuánto tiempo tiene y qué se verifica al final. Un proceso puede crear hijos fuera del grupo esperado, desactivar un canal, completar una escritura pendiente o perder datos al ser terminado. La política de cgroup o de grupo de procesos puede ayudar a delimitar el conjunto, pero sigue siendo necesario comprobar que no quedó un socket, un worker o una tarea externa atendiendo la función.
Reiniciar modifica el estado, pero no necesariamente repara la causa. Si el proceso falla por una configuración inválida, reiniciarlo repite el fallo. Si falla por corrupción de un archivo, reiniciar puede agravarla. Si falla por un límite de memoria, reiniciar puede recuperar temporalmente la capacidad y ocultar la tendencia. Un supervisor debe registrar causa, número de intentos, tiempo entre intentos y estado de dependencias. La métrica «está activo» necesita un horizonte: activo ahora, activo durante la ventana definida y capaz de cumplir una operación concreta son claims distintos.
La recuperación también tiene que respetar identidad y secreto. Un proceso nuevo puede no heredar el contexto de la instancia anterior; un socket activado puede permanecer abierto entre reinicios; un archivo temporal puede contener estado parcial; una clave en memoria no debe reaparecer en un volcado. Por eso el retest debe observar el proceso antes y después, la operación funcional y las propiedades de autorización, no sólo comprobar que el PID cambió.
Arranque como frontera de seguridad
El arranque concentra autoridad porque decide qué código se ejecuta antes de que estén disponibles muchas defensas posteriores. Un servicio de actualización, agente de administración o recolector de secretos puede recibir una identidad poderosa y una ruta de ejecución temprana. Esta observación no convierte todo servicio de arranque en una vulnerabilidad. Obliga a modelar quién puede modificar la unidad, el binario, sus dependencias, la configuración y los recursos que el proceso abrirá.
El control de permisos sobre un archivo de unidad no cubre automáticamente el ejecutable ni los directorios padres. Un proceso puede cargar configuración desde una ubicación escribible por otra cuenta o seguir un enlace simbólico en una ruta que cambió entre validación y uso. Las reglas del capítulo 17 sobre nombres, metadatos y permisos se aplican aquí: la unidad es un objeto más dentro de una cadena de resolución. La evidencia debe incluir la ruta efectiva, propietario, permisos, ACL, montaje y momento; un systemctl status resumido es insuficiente para una conclusión amplia.
La separación de privilegios reduce la autoridad necesaria, pero puede fallar por composición. Un servicio con usuario dedicado puede escribir en un directorio compartido, llamar a un helper privilegiado o usar un socket que otro principal controla. Un proceso que abandona root conserva descriptores abiertos si no se gestionan, y una capability puede conceder una operación específica sin que el UID visible explique toda la autoridad. La pregunta no es «¿corre como root?», sino «¿qué operaciones puede realizar por cada interfaz y qué principal controla esa interfaz?».
Cómo leer evidencia de servicios
Para evaluar un servicio, registre la identidad de la observación: host, versión del sistema, unidad o mecanismo de lanzamiento, momento y herramienta. Después separe cuatro capas:
- Especificación: archivo de unidad, argumentos previstos, dependencias, política de reinicio y límites.
- Instancia: PID, PPID, árbol de procesos, UID/GID, grupos, capabilities, namespaces, descriptores y ejecutable real.
- Contrato: puerto o socket, operación de salud, estado listo, errores y comportamiento ante detención.
- Historia: arranques, terminaciones, cambios de configuración y recursos que sobrevivieron.
Una unidad puede declarar User=svc-report, mientras /proc/<pid>/status muestra la identidad efectiva de la instancia. Ambas observaciones responden preguntas distintas: la primera es intención de configuración; la segunda es estado materializado. La discrepancia requiere una hipótesis, no una acusación. Puede haber un helper legítimo, una unidad generada, un contenedor o una configuración no recargada.
La procedencia también importa en el arranque. Un archivo generado por un paquete, una unidad local, un enlace habilitado y un comando manual pueden producir estados parecidos. Antes de decidir, determine qué autoridad escribió cada artefacto y cuál mecanismo lo activó. No llame evidencia de ejecución a un archivo de configuración que nunca se cargó, ni evidencia de intención a un argumento que pudo ser inyectado por el entorno.
Alcance: cada fuente responde una pregunta concreta y no sustituye corroboración.
Errores profesionales frecuentes
«Está en ejecución, por tanto el servicio funciona». Confunde existencia con readiness y contrato. Pruebe una operación sintética autorizada y registre dependencia, identidad y respuesta.
«El proceso se reinicia, por tanto es resiliente». Reinicios repetidos pueden indicar fallo permanente o pérdida de estado. Defina ventana, degradación y causa.
«El nombre del servicio identifica su autoridad». La autoridad proviene de credenciales, capabilities, descriptores, sockets y helpers, no del nombre visible.
«Cerrar el proceso principal detuvo la función». Workers, sockets activados o procesos externos pueden continuar. Compruebe el árbol y el punto de entrada.
«Después del arranque la dependencia está lista». Orden temporal no equivale a readiness. Declare la condición que el consumidor necesita y cómo se prueba.
«Un daemon autónomo es más seguro». Desacoplarlo puede eliminar límites, registro y control del supervisor. El diseño debe justificar qué autoridad se conserva y cómo se detiene.
«El archivo de unidad es la configuración efectiva». Generadores, drop-ins, entornos, contenedores y versiones pueden cambiarla. Compare configuración cargada con proceso observado.
Un método acotado de diagnóstico
Ante un servicio que «no está», «se cae» o «arrancó mal», siga una cadena reversible:
- Fije el claim: función, host, versión, ventana de tiempo y operación que debe ser posible.
- Identifique el mecanismo de lanzamiento: supervisor, socket, cron, sesión, contenedor o comando manual.
- Obtenga la configuración efectiva y sus fuentes, incluidas dependencias, identidad, límites y política de reinicio.
- Observe la instancia: proceso principal, descendientes, credenciales, descriptores, namespaces, rutas y sockets.
- Compare
starting,ready,degradedyfailedcon una operación de control positiva y, cuando sea seguro, una negativa. - Reconstruya terminaciones, reinicios y cambios de configuración con relojes y procedencia declarados.
- Exponga la conclusión y sus límites: qué se observó, qué se infiere, qué impacto es posible y qué requiere otra fuente.
El procedimiento no autoriza tráfico destructivo ni cambios en producción. Si el proceso puede afectar datos, la prueba debe usar un entorno sintético o una operación permitida. La observabilidad es parte del argumento: sin árbol de procesos, autoridad efectiva y momento, una explicación puede ser compatible con demasiadas causas.
Síntesis
Un programa es código; un proceso es una instancia; un daemon es una forma de ejecución; un servicio es una función gestionada; y un supervisor es una autoridad de ciclo de vida. La distinción permite explicar por qué un PID cambia, por qué un worker puede sobrevivir al proceso visible y por qué una unidad activa no implica que el contrato se cumpla.
El arranque no es una lista lineal. Es una composición de precondiciones, dependencias, identidades, sockets, montajes y estados de preparación. After= puede ordenar sin garantizar readiness; Restart= puede recuperar una instancia sin reparar su causa; User= puede limitar una ejecución sin describir todos sus descriptores y delegaciones. En cada caso, el mecanismo tiene cobertura y límites.
El uso profesional consiste en formular claims verificables: «en este host, durante esta ventana, la unidad X creó el proceso Y con estas credenciales; la operación Z quedó disponible después de comprobar estas dependencias; al detenerla no quedaron puntos de entrada activos». Esa frase es menos amplia que «el servicio es seguro y siempre disponible», pero permite probar, refutar, corregir y repetir. El capítulo siguiente añadirá relojes, identificadores y logging para reconstruir con mayor precisión lo que ocurrió.
Comprobación de comprensión
- Distingue programa, proceso, daemon, servicio y supervisor usando una instancia de trabajo sintética.
- ¿Por qué un PID no es una identidad estable del servicio? Relaciona
fork,exec, terminación y reinicio. - Compara
After=con una condición de readiness. ¿Qué evidencia autoriza cada una? - Un proceso aparece como activo, pero no atiende operaciones. Formula dos hipótesis y una prueba discriminante.
- ¿Qué puede quedar ejecutándose después de detener el proceso principal y cómo lo comprobarías sin atribuir intención?
- Explica por qué
Restart=puede mejorar recuperación y, a la vez, ocultar una causa persistente. - ¿Qué diferencia hay entre la identidad declarada por un supervisor y la autoridad efectiva observada en un proceso?
- Redacta un claim limitado sobre un servicio iniciado durante el arranque, incluyendo dependencias, identidad, operación, evidencia y límites.
Problema de transferencia
Un host inicia sync-agent automáticamente. El estado del supervisor dice active, el puerto local escucha y la unidad declara After=network-online.target. Sin embargo, algunos trabajos no se procesan después de un reinicio y un usuario con permisos sobre un directorio compartido puede cambiar la configuración que el agente carga.
Construye un diagnóstico que separe observaciones, inferencias, impactos posibles y decisiones. Incluye: proceso principal y workers, identidad efectiva y capacidades, configuración cargada, montaje y permisos del directorio, criterio de readiness, causa de los reinicios, dependencia realmente necesaria y prueba de una operación sintética. Indica qué no podrías concluir a partir del estado active y qué evidencia adicional exigirías antes de afirmar una vulnerabilidad o un incidente.
Fuentes principales
- The Open Group. POSIX.1-2017,
fork(), process creation and environment. - Linux man-pages.
fork(2),execve(2),wait(2),daemon(7),setsid(2). - freedesktop.org.
systemd.service,systemd.unit,systemd.exec,BOOTUP.