Son las 02:13, el despliegue está “verde” y tu script sigue sin ejecutarse. Le das chmod. Le das sudo. Lo miras como si te debiera dinero. Y el kernel responde con el mensaje más irritantemente vago del catálogo Unix: Permission denied.
En Ubuntu 24.04 esto a menudo no tiene nada que ver con permisos de archivo. Es el sistema de archivos diciendo: “Guardo tus bytes, pero no ejecutaré tus bytes.” Eso es noexec, y es uno de esos valores por defecto de seguridad sensatos que se convierten en un misterio de producción cuando te olvidas de que existen.
Un modelo mental práctico: qué significa realmente “Permission denied”
Cuando intentas ejecutar ./deploy.sh, ocurre una cadena de decisiones rápidamente:
- El shell verifica que el archivo exista y que tengas permiso de ejecución sobre él (el bit
x). - El kernel lo carga vía
execve(). Aquí es donde las opciones de montaje y las políticas MAC muerden. - Si es un script, el kernel lee el shebang (
#!/bin/bash) e intenta ejecutar el intérprete con el script como argumento. - Capas de seguridad pueden vetar: flags de montaje (
noexec), perfiles de AppArmor, sandboxing de systemd, restricciones del runtime de contenedores y, a veces, valores por defecto “útiles” de endurecimiento.
El punto clave: puedes tener -rwxr-xr-x y aun así recibir Permission denied si el sistema de archivos está montado con noexec. El modo de archivo dice “puedes ejecutar”, pero el montaje dice “nadie ejecuta nada desde aquí.” Gana el montaje.
Una heurística útil para operadores: si chmod +x no ayudó, asume que el problema no son permisos Unix. Asume política.
Una cita para recordar: parafraseando la disciplina de misión control de Gene Kranz: “Ser duro y competentes significa no aceptar fallos vagos: identifica la restricción y diseña alrededor de ella.”
Broma corta #1: Noexec es como un hotel que te deja registrarte, pero te confisca los zapatos para que no puedas salir. ¿Seguro? Sí. ¿Conveniente? No tanto.
Guía rápida de diagnóstico (comprueba primero/segundo/tercero)
Si estás de guardia, no quieres una lección. Quieres una secuencia que reduzca la búsqueda rápido.
Primero: confirma la ruta exacta que falla y el error
- Ejecuta directamente (
./script) y también vía intérprete (bash script). - Observa si el error cambia: “Permission denied” vs “Exec format error” vs “No such file or directory”. Esos mapean a distintos modos de fallo.
Segundo: comprueba las opciones de montaje para el directorio
findmnt -no OPTIONS --target /pathes la fuente de verdad más rápida.- Si ves
noexec, deja de discutir con chmod.
Tercero: revisa la política a nivel sistema si los montajes parecen correctos
- Para servicios: las opciones de hardening y sandboxing de systemd pueden emular comportamiento noexec.
- Para sesiones interactivas: las denegaciones de AppArmor aparecen en el journal y en
dmesg. - Para contenedores: confirma las opciones de montaje del volumen y si estás ejecutando desde ConfigMap/Secret/overlay.
Luego: elige la solución menos peligrosa
- Preferible: mueve el ejecutable a una ubicación con ejecución habilitada (por ejemplo,
/usr/local/bino un/optdedicado). - Aceptable: remontar un montaje específico con
execsi el riesgo se entiende. - Último recurso: deshabilitar ampliamente el hardening de systemd/AppArmor. Lo lamentarás después.
Hechos e historia interesantes (porque el contexto evita incidencias)
- noexec es por montaje, no por archivo. No puedes “arreglarlo con chmod” a nivel de archivo.
- El kernel aún permite leer y mapear. noexec bloquea
execve()y algunas formas de ejecución basada en archivo, pero no impide leer el archivo o copiarlo a otro lugar. - Endurecer /tmp es un viejo consejo. Montar
/tmpconnoexecse volvió común tras gusanos e instaladores descuidados que trataban/tmpcomo canal de distribución de software. - Los scripts no son especiales. noexec aplica tanto a scripts como a binarios ELF. Se ejecuta el intérprete, pero el archivo script se abre bajo reglas de exec.
- “Permission denied” no es necesariamente un error de permisos. Es un
EACCESgenérico que puede significar “política” tan fácilmente como “bits de modo”. - systemd cambió el juego. Los servicios pueden tener namespaces de montaje privados y restricciones que no coinciden con lo que ves en tu shell.
- Las imágenes empresariales adoran noexec. Los benchmarks CIS y las bases de seguridad internas frecuentemente recomiendan
nodev,nosuid,noexecpara áreas escribibles por el mundo. - Las plataformas de contenedores recrearon el mismo problema. Los volúmenes ConfigMap/Secret de Kubernetes suelen montarse con opciones restrictivas; ejecutar código directamente desde ellos es intencionalmente incómodo.
- NFS añade ambigüedad de políticas. “noexec” puede estar en el montaje cliente, o la ejecución puede estar bloqueada por la política de exportación del servidor y patrones de root-squash que confunden a la gente.
Tareas prácticas: comandos, salidas, decisiones (12+)
Estas son las cosas que realmente hago cuando alguien dice “está permission denied” y huelo la pérdida de tiempo inminente.
Task 1: verifica que el archivo sea ejecutable y que no sea un error de directorio
cr0x@server:~$ ls -l ./deploy.sh
-rwxr-xr-x 1 cr0x cr0x 1843 Dec 30 01:55 ./deploy.sh
Qué significa: el bit de ejecución está establecido para owner/group/others. Si aún recibes “Permission denied”, probablemente no sean los bits de modo Unix.
Decisión: pasa a comprobar opciones de montaje y políticas, no más chmod.
Task 2: intenta ejecutar vía el intérprete (evita exec de archivo, pero no siempre noexec)
cr0x@server:~$ ./deploy.sh
bash: ./deploy.sh: Permission denied
cr0x@server:~$ bash ./deploy.sh
./deploy.sh: line 12: /tmp/helper: Permission denied
Qué significa: el script puede ser legible, pero intenta ejecutar algo más desde una ubicación noexec (/tmp/helper aquí).
Decisión: identifica cada ruta ejecutable que use el script; la que falla suele estar en /tmp, un workspace montado o un bind mount.
Task 3: comprueba las opciones de montaje para el directorio del script
cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS --target /home/cr0x
/dev/nvme0n1p3 /home ext4 rw,relatime,noexec
Qué significa: /home está montado con noexec. Cualquier ejecución directa desde /home fallará.
Decisión: mueve los ejecutables fuera de /home o quita noexec (con una evaluación seria del modelo de amenaza).
Task 4: confirma el código de error del kernel vía strace (suero de la verdad rápido)
cr0x@server:~$ strace -f -e execve ./deploy.sh
execve("./deploy.sh", ["./deploy.sh"], 0x7ffce9c4a3b0 /* 45 vars */) = -1 EACCES (Permission denied)
strace: exec: Permission denied
Qué significa: el kernel rechazó execve() con EACCES. Esto es consistente con noexec, AppArmor u otra política.
Decisión: revisa montajes y logs MAC; no pierdas tiempo en hipótesis a nivel de shell.
Task 5: inspecciona todos los montajes que incluyen noexec (encuentra las minas)
cr0x@server:~$ mount | grep -w noexec
/dev/nvme0n1p3 on /home type ext4 (rw,relatime,noexec)
/dev/nvme0n1p4 on /tmp type ext4 (rw,relatime,nodev,nosuid,noexec)
Qué significa: tanto /home como /tmp son noexec. Eso es una línea base común en entornos empresariales.
Decisión: decide dónde se supone que debe ejecutarse: normalmente /usr/local/bin, /opt o un directorio de aplicación dedicado.
Task 6: confirma los flags de montaje exactos con findmnt (más preciso que mount)
cr0x@server:~$ findmnt -no TARGET,PROPAGATION,OPTIONS /tmp
/tmp shared rw,relatime,nodev,nosuid,noexec
Qué significa: la propagación siendo shared importa en contextos de contenedores; los cambios de montaje pueden propagarse inesperadamente.
Decisión: si estás en un entorno containerizado, trata la propagación de montaje como parte del radio de blast.
Task 7: prueba de remediación rápida (remount temporal) para probar la causa raíz
cr0x@server:~$ sudo mount -o remount,exec /tmp
cr0x@server:~$ findmnt -no OPTIONS --target /tmp
rw,relatime,nodev,nosuid,exec
Qué significa: has cambiado /tmp a ejecutable para esta sesión de arranque (hasta que se remonte/reinicie o fstab lo restaure).
Decisión: si la ejecución ahora funciona, has confirmado noexec como causa. Reviértelo y aplica una solución permanente más segura (por lo general no “hacer /tmp exec para siempre”).
Task 8: actualiza fstab para un cambio permanente (solo cuando la política lo permita)
cr0x@server:~$ grep -nE '\s/tmp\s' /etc/fstab
12:/dev/nvme0n1p4 /tmp ext4 defaults,nodev,nosuid,noexec 0 2
Qué significa: fstab codifica noexec para /tmp.
Decisión: si debes cambiarlo, hazlo intencionalmente y documenta por qué. Mejor: deja /tmp noexec y reubica los artefactos de compilación.
Task 9: la solución aburrida y segura—instala scripts en una ruta ejecutable
cr0x@server:~$ sudo install -m 0755 ./deploy.sh /usr/local/bin/deploy
cr0x@server:~$ /usr/local/bin/deploy --help
Usage: deploy [options]
Qué significa: colocaste el script en un directorio estándar ejecutable con permisos adecuados.
Decisión: prefiere esto en vez de hacer ejecutables los montajes escribibles por usuarios. Reduce la superficie de ataque “descargar y ejecutar”.
Task 10: comprueba el shebang (ruta de intérprete errónea parece un problema de permisos)
cr0x@server:~$ head -n 1 ./deploy.sh
#!/bin/bash
cr0x@server:~$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1265648 Apr 10 2024 /bin/bash
Qué significa: el intérprete existe y es ejecutable. Si el shebang apuntara a algo inexistente, a menudo verías “No such file or directory”, no “Permission denied”, pero vale la pena verificar.
Decisión: si los scripts deben ser portables, considera #!/usr/bin/env bash (con las habituales precauciones sobre PATH en contextos privilegiados).
Task 11: detecta denegaciones de AppArmor (la política puede imitar noexec)
cr0x@server:~$ sudo journalctl -k -g apparmor --since "10 min ago"
Dec 30 02:05:41 server kernel: audit: type=1400 apparmor="DENIED" operation="exec" profile="snap.mytool.mytool" name="/home/cr0x/deploy.sh" pid=22109 comm="mytool" requested_mask="x" denied_mask="x" fsuid=1000 ouid=1000
Qué significa: AppArmor denegó la ejecución. Esto no es un problema de montaje; es confinamiento.
Decisión: corrige ajustando el perfil, cambiando la ubicación de ejecución a una ruta permitida o usando un método de empaquetado no confinado. No desactives AppArmor globalmente a menos que disfrutes reuniones de cumplimiento.
Task 12: comprueba el sandboxing del unit de systemd por una falla de servicio
cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp
ProtectSystem=strict
PrivateTmp=true
NoNewPrivileges=true
Qué significa: PrivateTmp=true da al servicio su propio /tmp, que puede montarse de forma distinta a tu /tmp interactivo. El sandboxing cambia la vista del sistema de archivos.
Decisión: reproduce dentro del contexto del servicio (ver siguiente tarea) y ajusta la unidad o la ubicación de los archivos en consecuencia.
Task 13: ejecuta un shell bajo el mismo sandbox del servicio para reproducir
cr0x@server:~$ sudo systemd-run --unit debug-myapp --pty --property=PrivateTmp=true /bin/bash
Running as unit: debug-myapp.service
Press ^] three times within 1s to disconnect TTY.
root@server:/# findmnt -no OPTIONS --target /tmp
rw,nosuid,nodev,noexec,relatime
root@server:/# exit
exit
Qué significa: dentro del namespace de la unidad, /tmp es noexec. Tu script puede funcionar interactivamente pero fallar como servicio.
Decisión: evita ejecutar desde /tmp en servicios. Coloca binarios auxiliares bajo /usr/libexec, /opt o empaquétalos correctamente.
Task 14: verifica opciones de montaje para un bind mount (a menudo pasado por alto)
cr0x@server:~$ sudo mount --bind /srv/build /mnt/build
cr0x@server:~$ findmnt -no SOURCE,TARGET,OPTIONS /mnt/build
/srv/build /mnt/build rw,relatime
cr0x@server:~$ sudo mount -o remount,bind,noexec /mnt/build
cr0x@server:~$ findmnt -no OPTIONS /mnt/build
rw,relatime,bind,noexec
Qué significa: los bind mounts pueden tener sus propios flags. Alguien puede haber remonteado “útilmente” un bind mount con noexec mientras el sistema de archivos subyacente es exec.
Decisión: siempre comprueba el punto de montaje objetivo desde el que ejecutas, no solo el sistema de archivos de respaldo.
Task 15: confirma opciones de montaje NFS (noexec en el cliente es común)
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS --target /mnt/tools
nfs01:/exports/tools nfs4 rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noexec
Qué significa: el share NFS está montado noexec en el cliente. Los scripts almacenados allí no se ejecutarán.
Decisión: monta con exec (si la política lo permite) o copia las herramientas a un directorio local con ejecución habilitada durante el despliegue.
De dónde viene noexec en Ubuntu 24.04
Ubuntu 24.04 no se volvió hostil de repente a los scripts. Lo que cambió es que más entornos predeterminan patrones de endurecimiento, y más herramientas asumen que pueden ejecutar desde “donde sea”. Esas tendencias chocan.
1) fstab e imágenes con “baseline de seguridad”
La fuente más simple es /etc/fstab. Las empresas preparan imágenes donde existen particiones separadas para /home, /tmp, a veces /var, y aplican nodev,nosuid,noexec para reducir el abuso de ubicaciones escribibles por usuarios.
Si creciste en sistemas con un único filesystem raíz, esto parece exótico. Si alguna vez te gritó un auditor, parece un martes.
2) namespaces privados de montaje de systemd
Systemd puede dar a los servicios su propio mundo. Ese mundo puede incluir:
PrivateTmp=(un/tmpespecífico para el servicio)ProtectSystem=yProtectHome=(rutas solo lectura u ocultas)ReadWritePaths=yTemporaryFileSystem=(montajes personalizados)
Resultado: ejecutas findmnt en tu shell, ves exec y aun así falla en el servicio. Eso no es gaslighting; son namespaces.
3) montajes de volúmenes en contenedores y valores por defecto del orquestador
En contenedores, “el sistema de archivos” es un collage: capas overlay, bind mounts, tmpfs y volúmenes proporcionados por el orquestador. Muchos de estos son intencionalmente restrictivos.
El clásico: los volúmenes ConfigMap/Secret en Kubernetes no son un lugar para ejecutar programas. Son para poner configuración. Si tu app descarga un plugin dentro de uno de esos montajes e intenta ejecutarlo, el kernel lo rechazará.
4) políticas de almacenamiento en red (NFS, CIFS)
El almacenamiento remoto introduce la ejecución como una cuestión de política. En clientes Linux, noexec lo aplica el kernel cliente en ese montaje. Por separado, los servidores pueden imponer permisos y mapeos que llevan a denegaciones confusas cuando un script intenta acceder a un intérprete o binario auxiliar.
5) políticas MAC (AppArmor) y confinamiento (Snap)
Ubuntu favorece AppArmor. Los snaps añaden capas de confinamiento. Ambos pueden producir EACCES con “Permission denied” incluso cuando los montajes están bien. Un proceso confinado puede tener permitido leer un archivo pero no ejecutarlo, o ejecutar solo desde un conjunto conocido de rutas.
Patrones de solución: lo más seguro primero, permanente cuando haga falta
Patrón A: deja de ejecutar desde ubicaciones efímeras y escribibles por usuarios
Si tu pipeline o instalador deja binarios en /tmp, /var/tmp, un directorio home o un workspace montado y luego los ejecuta, estás confiando en un anti-patrón de seguridad. Funciona hasta que no funciona—y entonces te quema una hora de tu vida.
Haz esto en su lugar:
- Instala ejecutables bajo
/usr/local/bin(gestión admin) o/opt/<app>/bin. - Para binarios auxiliares, considera
/usr/libexec/<app>(común en distros modernas) o mantenlos junto al binario principal en/opt. - Mantén datos de ejecución escribibles en
/var/lib/<app>y logs en/var/log/<app>.
Este patrón no pelea con el SO. Coopera con él.
Patrón B: si necesitas un directorio de compilación escribible, crea uno explícitamente exec-enabled
Algunos flujos de trabajo necesitan legítimamente compilar y ejecutar artefactos (compiladores, JITs, sistemas de build). Si tu política marca /home como noexec, necesitas un área de compilación dedicada con ejecución habilitada. El nombre debería dejar clara la intención, porque la ambigüedad es cómo nacen los incidentes.
cr0x@server:~$ sudo mkdir -p /srv/exec-work
cr0x@server:~$ sudo chown cr0x:cr0x /srv/exec-work
cr0x@server:~$ findmnt -no OPTIONS --target /srv
rw,relatime
Qué significa: /srv suele estar con ejecución habilitada a menos que también lo hayas endurecido. Ahora tus artefactos de build tienen un lugar sin debilitar /home o /tmp.
Decisión: dirige las compilaciones a /srv/exec-work (o equivalente) y mantén el resto del sistema endurecido.
Patrón C: remontar con exec (quirúrgico, con los ojos abiertos)
A veces estás atrapado: software legado de un proveedor espera ejecutar desde /home o un share de herramientas montado. Si no puedes refactorizar rápido, puedes remontar con exec. Hazlo lo más estrecho posible.
Bueno: un punto de montaje dedicado para herramientas con exec. Malo: hacer /tmp exec porque “lo arregló”.
cr0x@server:~$ sudo mount -o remount,exec /home
cr0x@server:~$ findmnt -no OPTIONS --target /home
rw,relatime,exec
Qué significa: la política se relaja. Todo en /home pasa a ser ejecutable.
Decisión: si haces esto, compensa en otros lados (mejor monitoreo, controles estrictos de instalación de software, o migrar a herramientas por usuario en contenedores/VMs).
Patrón D: usa empaquetado y buenas prácticas de despliegue en lugar de “curl | bash”
Si tu sistema instala descargando a /tmp y ejecutando inmediatamente, vas a tropezar con noexec y además estás entrenando a la gente en hábitos arriesgados. Empaqueta, o al menos descarga a una ruta controlada ejecutable y verifica firmas/checksums.
Broma corta #2: Lo único peor que “funciona en mi máquina” es “funciona en mi máquina porque mi máquina es insegura”.
Sandboxes de systemd que parecen noexec
Los operadores culpan a systemd de todo, y systemd se gana una buena parte de esa culpa. Pero cuando rompe la ejecución de tu script, normalmente está haciendo exactamente lo que pediste (o lo que tu distro pidió): limitar lo que un servicio puede ver y hacer.
Cómodo modo de fallar
- El script se ejecuta bien en la sesión SSH.
- Falla como servicio con “Permission denied” al ejecutar un binario auxiliar.
- Las opciones de montaje parecen correctas desde el shell del host.
Por qué ocurre
Las unidades systemd pueden ejecutarse en su propio namespace de montaje. Ese namespace puede montar /tmp como un tmpfs privado, a veces con noexec. O puede usar TemporaryFileSystem= para montar un árbol escribible con flags restrictivos.
Qué hacer
- Reproduce en el namespace de la unidad usando
systemd-runcon propiedades similares (ver Task 13). - Deja de ejecutar desde /tmp. Coloca binarios auxiliares en un directorio estable y permite que el servicio los lea/ejecute.
- Usa ReadWritePaths con prudencia. Si un servicio necesita un directorio escribible, asígnaselo de forma precisa en lugar de dar acceso amplio al sistema de archivos.
Un ejemplo preciso de corrección del lado del servicio
Si tu servicio desempaqueta una herramienta y la ejecuta, rediseña para que la herramienta se entregue como parte del paquete. Si no puedes, al menos desempaqueta en un directorio que controles con propiedades de montaje conocidas (no /tmp dentro de un sandbox).
cr0x@server:~$ sudo systemctl edit myapp.service
# (editor opens)
Y añadirías algo como:
cr0x@server:~$ sudo systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp
PrivateTmp=true
ReadWritePaths=/var/lib/myapp
Qué significa: le das al servicio un directorio de estado escribible mientras mantienes el resto restringido. No intentas hacer su /tmp privado ejecutable solo para correr un blob aleatorio.
Decisión: favorece ubicaciones de ejecución estables y permisos de escritura estrechos. Es la versión operativa de comer verduras.
Contenedores, Kubernetes y la trampa “montado pero no ejecutable”
Los contenedores adoran montar cosas. Configuración. Secretos. Volúmenes compartidos. Espacio temporal. También les gusta hacer esos montajes restrictivos, porque un pod comprometido no debería convertirse en un pasatiempo para todo el clúster.
Modo de fallo común en contenedores
Construyes una imagen. Funciona. Luego montas un volumen sobre /app o /app/bin en Kubernetes. De pronto tu script de arranque falla con “Permission denied.” Sigues reconstruyendo la imagen como si se fuera a disculpar. No lo hará.
Tarea: inspeccionar opciones de montaje dentro de un contenedor
cr0x@server:~$ sudo docker run --rm -it -v /tmp:/mnt/tmp ubuntu:24.04 bash -lc 'findmnt -no TARGET,OPTIONS /mnt/tmp'
/mnt/tmp rw,nosuid,nodev,noexec,relatime
Qué significa: el bind mount llegó como noexec (o fue remonteado noexec por la política). Ejecutar artefactos desde allí fallará.
Decisión: no ejecutes desde ese montaje. Copia los ejecutables al sistema de archivos de la imagen (capa de imagen) o monta un volumen diseñado para ejecución.
Específicos de Kubernetes (la parte silenciosa)
Los volúmenes ConfigMap y Secret no son tu directorio de plugins. Si necesitas un script desde un ConfigMap, un patrón común es: cópialo a un directorio escribible y exec-enabled al inicio, y luego ejecútalo desde allí. Eso aún requiere que el directorio objetivo permita ejecución.
Tarea: demostrar noexec en una ruta típica de volúmenes proyectados (ejemplo)
cr0x@server:~$ findmnt -R /var/lib/kubelet/pods 2>/dev/null | head -n 5
TARGET SOURCE FSTYPE OPTIONS
/var/lib/kubelet/pods /dev/sda1 ext4 rw,relatime
/var/lib/kubelet/pods/abcd.../volumes/kubernetes.io~secret/default-token tmpfs tmpfs rw,nosuid,nodev,noexec,relatime,size=4096k
Qué significa: los volúmenes de secretos son tmpfs y típicamente noexec. Eso es por diseño.
Decisión: trata estos montajes como solo datos. Si debes ejecutar un script, muévelo a una ubicación adecuada con permisos controlados para ejecución.
NFS y sistemas remotos: políticas de ejecución y sorpresas
NFS trae su propio sabor de rareza. Principalmente porque hace mapeo de identidad, permisos y caching sobre la red, y todos asumen que es simplemente “un disco pero más lejos”.
Dos realidades para mantener separadas
- Flags de montaje en el cliente (
noexec) son aplicadas por el kernel cliente. Si el cliente dice noexec, nada se ejecuta, aunque el servidor “lo permitiera”. - Permisos en el servidor siguen importando para leer y atravesar directorios. Muchas “Permission denied” en NFS son en realidad permisos de ejecución en directorios (bit
x) o root-squash rompiendo un instalador que corre como root.
Tarea: diferencia “el montaje dice noexec” de “los permisos del servidor están mal”
cr0x@server:~$ findmnt -no OPTIONS --target /mnt/tools
rw,relatime,vers=4.2,hard,timeo=600,retrans=2,noexec
cr0x@server:~$ ls -ld /mnt/tools
drwxr-xr-x 10 root root 4096 Dec 29 18:21 /mnt/tools
Qué significa: los permisos de directorio parecen correctos, pero el montaje indica noexec. Esa es la restricción bloqueante.
Decisión: remonta con exec si está permitido, o copia las herramientas localmente. Arreglar permisos Unix no ayudará.
Tarea: remonta el share NFS con exec (si la política lo permite)
cr0x@server:~$ sudo mount -o remount,exec /mnt/tools
cr0x@server:~$ findmnt -no OPTIONS --target /mnt/tools
rw,relatime,vers=4.2,hard,timeo=600,retrans=2,exec
Qué significa: el cliente ahora permite ejecución desde ese montaje.
Decisión: si es un “share de herramientas”, considera hacerlo de solo lectura y con exec habilitado, y mantén los shares escribibles como noexec. Mezclar write+exec es donde el malware lo pasa bien.
Errores comunes: síntoma → causa raíz → solución
-
Síntoma:
bash: ./script.sh: Permission deniedincluso aunquels -lmuestre ejecutable.
Causa raíz: el sistema de archivos está montadonoexec(a menudo/homeo un workspace).
Solución: mueve el script a/usr/local/bino remonta el sistema específico conexec. -
Síntoma: el script se ejecuta interactivamente pero falla en el servicio systemd con “Permission denied” al ejecutar un auxiliar en
/tmp.
Causa raíz: el servicio tienePrivateTmpu otro sandbox con un tmpfs noexec.
Solución: deja de ejecutar auxiliares desde/tmp; empaqueta auxiliares en/opto/usr/libexec. -
Síntoma:
bash: ./script: /bin/bash^M: bad interpreter: No such file or directory(o similar).
Causa raíz: finales de línea CRLF; no es noexec, pero se presenta como “no se puede ejecutar”.
Solución: convierte a LF:sed -i 's/\r$//' script(y vuelve a probar). -
Síntoma:
Permission deniedsolo cuando se ejecuta vía herramienta instalada por Snap.
Causa raíz: el confinamiento de AppArmor deniega la ejecución desde esa ruta.
Solución: ejecuta desde rutas permitidas, ajusta el confinamiento o instala una versión no-snap cuando la política lo permita. -
Síntoma: el pod de Kubernetes falla al ejecutar un script almacenado en un ConfigMap.
Causa raíz: el volumen ConfigMap está montadonoexec(y a menudo read-only).
Solución: copia al inicio a un directorio ejecutable y escribible, o incorpora el script en la imagen. -
Síntoma: “Permission denied” al ejecutar desde un share NFS; en disco local funciona.
Causa raíz: el cliente montó NFS connoexec.
Solución: remonta conexeco sincroniza/copia herramientas localmente durante despliegues. -
Síntoma: Solo un directorio específico falla al ejecutar; el filesystem padre permite exec.
Causa raíz: bind mount remonteado connoexec(común en contenedores y chroots).
Solución:findmnt --targetla ruta exacta y remonta ese bind conexec. -
Síntoma: Ejecutas un script, falla con “Permission denied” sobre un archivo que intenta correr desde
/var/tmp.
Causa raíz:/var/tmptambién está endurecido connoexecen algunas bases.
Solución: cambia el script para colocar ejecutables en un directorio ejecutable; no asumas que/var/tmpes distinto de/tmp.
Tres mini-historias corporativas desde el campo
1) El incidente causado por una suposición errónea: “chmod arregla Permission denied”
Una compañía mediana desplegó Ubuntu 24.04 en una flota de runners de build. Seguridad había actualizado silenciosamente la imagen base: partición separada para /home, montada con noexec. La razón era sólida: los desarrolladores descargaban herramientas en directorios home y las ejecutaban con procedencia dudosa.
Entonces falló la CI. No un job, todos. El mensaje era el típico encogimiento de hombros: “Permission denied” al ejecutar un script de build. Un ingeniero asumió que era un checkout mal hecho: propietario equivocado, bit de ejecución faltante, tal vez Git lo estropeó. Añadieron chmod -R +x en el pipeline. No ayudó. Sí creó un nuevo problema: archivos no ejecutables ahora tenían bits de ejecución, lo que volvió ruidosas las comprobaciones de integridad posteriores.
El incidente escaló porque la gente seguía persiguiendo la capa equivocada. Alguien intentó ejecutar con sudo. Otro culpó a SELinux (en Ubuntu, en una sala llena de gente, que es una clase especial de optimismo). Un tercero sugirió “simplemente desactiven el hardening de seguridad.” A esa persona se le pidió sutilmente que fuera por un café y no tocara nada.
La solución real tomó cinco minutos una vez que se hizo la pregunta correcta: “¿Qué dice findmnt para el workspace?” Dijo noexec. El pipeline copió la toolchain a un directorio ejecutable dedicado bajo /opt/ci-tools durante la provisión del runner, y los scripts se ejecutaron desde allí. Home siguió siendo noexec. Seguridad feliz. CI feliz. La única víctima fue la creencia colectiva de que chmod es un solvente universal.
2) La optimización que salió mal: ejecutar desde /tmp para “ir más rápido”
Un equipo de plataforma interno intentó ahorrar segundos en el despliegue de un servicio Java que usaba un binario nativo auxiliar (compresión y checksum). El auxiliar vivía en un montaje NFS compartido. A veces la latencia NFS subía durante backups, haciendo deploys frágiles. Alguien propuso una optimización: copiar el auxiliar a /tmp al inicio y ejecutarlo desde allí. “El disco local es más rápido”, dijeron. Cierto, en un sentido estrecho.
En staging funcionó. En producción falló al instante. Nodos más nuevos e endurecidos montaban /tmp con nodev,nosuid,noexec. El binario podía copiarse, pero no ejecutarse. El servicio entró en crash loop. On-call vio “Permission denied” y asumió problemas de propiedad. Cambiaron propietarios. Cambiaron modos. Re-deployaron. El crash loop siguió, sonriente.
La solución también fue simple: no ejecutar desde /tmp. El equipo creó /var/lib/myapp/bin propiedad del usuario del servicio y se aseguró de que estuviera en un filesystem con exec habilitado. También dejaron de copiar en tiempo de ejecución y empezaron a incluir el auxiliar en el artefacto de build. La dependencia de NFS desapareció, y también las tretas de arranque.
Lección postmortem: optimizaciones que cambian rutas de ejecución son cambios de seguridad, quieras o no. No puedes optimizar en torno a una frontera de política. La frontera de política ganará, y ni siquiera se esforzará.
3) La práctica aburrida pero correcta que salvó el día: “verificar opciones de montaje durante build y arranque”
Una organización regulada tenía una flota mixta: algunos hosts eran de propósito general, otros endurecidos según una baseline. Ya les había pasado antes lo de “misterioso permission denied”, así que añadieron una comprobación mundana a la validación del host: durante la provisión y chequeos diarios registraban opciones de montaje para rutas clave (/tmp, /home, directorios de apps, workspaces de build). La salida se enviaba a su sistema de logging, no porque fuera emocionante, sino porque era buscable a las 03:00.
Una noche un despliegue falló en un subconjunto de nodos. El servicio intentó ejecutar un helper de migración desde /var/lib/myapp/tmp. En esos nodos, /var/lib era una partición separada montada noexec debido a una baseline mal aplicada. El código estaba bien. El paquete estaba bien. La política de filesystem era inconsistente.
En lugar de adivinar, on-call sacó la última instantánea de montajes conocida de un nodo bueno y la comparó con uno que fallaba. La diferencia saltó inmediatamente. El equipo revirtió la role de baseline en los nodos afectados y luego corrigió la lógica de roles para que solo las particiones previstas recibieran noexec.
No pasó nada heroico. Nadie “depuró Linux” en vivo sobre un sistema crítico. Simplemente tenían datos aburridos que respondieron la pregunta. Esto es lo que parece la fiabilidad cuando madura.
Listas de verificación / plan paso a paso
Checklist: diagnosticar un script que falla en Ubuntu 24.04
- Ejecuta
ls -lsobre el script; confirma que es ejecutable y tiene la propiedad esperada. - Ejecuta directamente y vía intérprete; anota si la ruta que falla es el script en sí o un auxiliar que llama.
- Ejecuta
findmnt -no OPTIONS --target <path>para el script y cualquier binario auxiliar. - Si aparece
noexec: detente. Decide si mover el ejecutable o cambiar la política de montaje. - Si no hay
noexec: revisa denegaciones de AppArmor enjournalctl -k. - Si falla solo en un servicio: reproduce en el contexto de la unidad usando
systemd-rune inspeccionaPrivateTmp/TemporaryFileSystem. - Si falla solo en contenedores: inspecciona opciones de montaje dentro del contenedor y verifica qué volumen está cubriendo la ruta.
Checklist: elegir una solución que no te avergüence después
- Preferido: coloca ejecutables en una ubicación admin-controlada con ejecución (
/usr/local/bin,/opt). - Siguiente mejor: crea un workspace dedicado con ejecución habilitada y documentado.
- Arriesgado: remonta montajes amplios escribibles por usuarios con
exec. - Normalmente incorrecto: hacer
/tmpexec permanentemente para satisfacer una herramienta de build/despliegue. - Requisito operativo: si cambias la política de montaje, asegúrate de que sobreviva al reinicio (fstab/systemd mount units) y sea consistente en la flota.
Checklist: validar la solución
- Vuelve a ejecutar
findmntpara la ruta objetivo y confirma que las opciones son las que crees. - Ejecuta el script con el mismo usuario que en producción (usuario del servicio), no como root salvo que sea necesario.
- Si es un servicio systemd: reinicia la unidad y luego revisa
systemctl statusy logs. - Si es un contenedor: redeploy y confirma que la misma ruta no esté siendo tapada por un volumen en producción.
FAQ
1) ¿Por qué obtengo “Permission denied” cuando el archivo tiene chmod +x?
Porque el montaje del sistema de archivos puede prohibir la ejecución mediante noexec. El kernel rechaza execve() independientemente de los bits de modo. Verifícalo con findmnt -no OPTIONS --target /path.
2) ¿Es noexec una característica de seguridad o solo una molestia?
Ambas cosas. Reduce significativamente ataques del tipo “dejar un payload en un directorio escribible y ejecutarlo”. También es una molestia para patrones de instalación descuidados que tratan áreas escribibles como staging ejecutable.
3) ¿Puedo evitar noexec ejecutando bash script.sh?
A veces puedes ejecutar un script pasando por el intérprete porque ejecutas /bin/bash, no el archivo script como binario. Pero muchos escenarios noexec aún bloquean la ejecución de binarios auxiliares que el script llama, y algunas políticas aún imponen restricciones. No confíes en esto como “solución”.
4) ¿sudo evita noexec?
No. noexec lo aplica el kernel en ese montaje para todos los usuarios, incluido root.
5) ¿Cuál es la solución permanente más segura?
Deja de ejecutar desde montajes endurecidos y escribibles. Instala scripts/binarios en /usr/local/bin o /opt/<app> y mantén el estado de ejecución en otro lugar (/var/lib).
6) ¿Por qué funciona en mi shell pero falla en un servicio systemd?
Porque el servicio puede ejecutarse en un namespace de montaje diferente con flags distintos (PrivateTmp, TemporaryFileSystem) y restricciones de acceso a rutas (ProtectSystem, ProtectHome).
7) ¿Cómo sé si es AppArmor y no noexec?
Revisa los logs del kernel: sudo journalctl -k -g apparmor. Las denegaciones de AppArmor son explícitas e incluyen operation="exec" y el nombre del perfil. noexec no generará una entrada de AppArmor.
8) ¿Debo quitar noexec de /tmp?
Normalmente no. Si una herramienta requiere ejecutar desde /tmp, trátalo como un fallo de diseño y reubica el ejecutable. Si debes cambiarlo, hazlo de forma estrecha y con controles compensatorios.
9) ¿Y NFS? ¿se aplica “noexec” en el servidor?
En clientes Linux, noexec es una opción de montaje del cliente aplicada por el kernel cliente. El servidor aún puede causar problemas de permiso mediante permisos Unix y mapeo de identidad, pero noexec típicamente es del lado cliente.
10) Cambié opciones de montaje, pero después del reinicio el problema volvió. ¿Por qué?
Tu remount fue temporal. El comportamiento persistente viene de /etc/fstab o de unidades de montaje systemd. Corrige la fuente de la verdad y asegúrate de que sea consistente en los hosts.
Conclusión: qué cambiar el lunes por la mañana
Si Ubuntu 24.04 te dice “Permission denied” al ejecutar un script, créelo—pero no asumas que es por chmod. Tu primer sospechoso debería ser noexec en el montaje que contiene el script o alguno de sus binarios auxiliares. Tu segundo sospechoso debería ser el sandboxing de systemd. El tercero es AppArmor o el comportamiento de volúmenes en contenedores.
Siguientes pasos que pagan la renta:
- Estandariza dónde viven los ejecutables (
/usr/local/bin,/opt) y deja de ejecutar desde/tmp//home/workspaces. - Añade una comprobación ligera que registre opciones de montaje para rutas clave en toda la flota.
- Para servicios, audita ajustes de hardening de systemd y asegúrate de que tu software no dependa de ejecutar desde directorios efímeros.
- Para contenedores, trata la configuración montada como solo datos; ejecuta desde la imagen o desde un volumen diseñado para ejecución.
El sistema operativo te está haciendo un favor. Solo lo hace en voz alta, de noche, mientras tu pager vigila.