Ubuntu 24.04 « Permission denied » lors de l’exécution de scripts : montages noexec et comment réparer (cas n°58)

Cet article vous a aidé ?

Il est 02:13, le déploiement est « green », et votre script refuse toujours de s’exécuter. Vous faites chmod. Vous faites sudo. Vous le regardez comme s’il vous devait de l’argent. Et le noyau répond avec le message le plus frustrant du catalogue Unix : Permission denied.

Sur Ubuntu 24.04, il ne s’agit souvent pas des permissions de fichier. C’est le système de fichiers qui dit : « Je vais stocker vos octets, mais je n’exécuterai pas vos octets. » C’est le noexec, et c’est l’un de ces réglages de sécurité sensés qui deviennent une énigme en production quand on oublie qu’ils existent.

Un modèle mental pratique : ce que « Permission denied » signifie réellement

Quand vous essayez d’exécuter ./deploy.sh, une chaîne de décisions se produit rapidement :

  • Le shell vérifie que le fichier existe et que vous avez la permission d’exécution sur celui-ci (le bit x).
  • Le noyau le charge via execve(). C’est là que les options de montage et les politiques MAC interviennent.
  • Si c’est un script, le noyau lit le shebang (#!/bin/bash) et tente d’exécuter l’interpréteur avec le script comme argument.
  • Des couches de sécurité peuvent refuser : options de montage (noexec), profils AppArmor, sandboxing systemd, restrictions du runtime de conteneur, et parfois des valeurs par défaut de durcissement « utiles ».

Le point clé : vous pouvez avoir -rwxr-xr-x et obtenir quand même Permission denied si le système de fichiers est monté avec noexec. Le mode de fichier dit « vous pouvez exécuter », mais le montage dit « personne n’exécute rien d’ici ». Le montage l’emporte.

Une heuristique utile pour un opérateur : si chmod +x n’a pas aidé, supposez que le problème n’est pas les permissions Unix. Supposez une politique.

Une citation à garder : paraphrasant Gene Kranz (discipline du contrôle de mission) : « Être dur et compétent veut dire ne pas accepter des échecs vagues — identifiez la contrainte et concevez autour. »

Blague courte #1 : Noexec, c’est comme un hôtel qui vous laisse vous enregistrer, mais qui confisque vos chaussures pour que vous ne puissiez pas partir. Sûr ? Oui. Pratique ? Pas vraiment.

Playbook de diagnostic rapide (vérifier premièrement/deuxièmement/troisièmement)

Si vous êtes en astreinte, vous ne voulez pas une leçon. Vous voulez une séquence qui réduit rapidement la recherche.

Première étape : confirmer le chemin exact en échec et le message

  • Exécutez-le directement (./script) et aussi via l’interpréteur (bash script).
  • Observez si le message change : « Permission denied » vs « Exec format error » vs « No such file or directory ». Ceux-ci correspondent à des modes d’échec différents.

Deuxième étape : vérifier les options de montage pour le répertoire

  • findmnt -no OPTIONS --target /path est la source de vérité la plus rapide.
  • Si vous voyez noexec, arrêtez d’argumenter avec chmod.

Troisième étape : vérifier la politique au niveau système si les montages semblent corrects

  • Pour les services : les options de durcissement systemd et le sandboxing peuvent imiter le comportement de noexec.
  • Pour les sessions interactives : les refus AppArmor apparaissent dans le journal et dmesg.
  • Pour les conteneurs : confirmez les options de montage de volume et si vous exécutez depuis ConfigMap/Secret/overlay.

Puis : choisir la correction la moins dangereuse

  • Préféré : déplacer l’exécutable vers un emplacement autorisant l’exécution (par ex. /usr/local/bin ou un /opt dédié).
  • Acceptable : remonter un montage spécifique avec exec si le risque est compris.
  • Dernier recours : désactiver massivement le durcissement systemd/AppArmor. Vous le regretterez ensuite.

Faits et histoire intéressants (parce que le contexte évite les pannes)

  1. noexec est par montage, pas par fichier. Vous ne pouvez pas « chmod » votre sortie d’une politique de montage.
  2. Le noyau permet toujours la lecture et le mapping. noexec bloque execve() et certaines formes d’exécution depuis un fichier monté, mais n’empêche pas de lire le fichier ou de le copier ailleurs.
  3. Durcir /tmp est une vieille sagesse. Monter /tmp avec noexec est devenu courant après des vers et des installateurs négligents qui utilisaient /tmp comme canal de distribution de logiciels.
  4. Les scripts ne sont pas spéciaux. noexec s’applique autant aux scripts qu’aux binaires ELF. Le chemin de l’interpréteur est exec’d, mais le fichier script est ouvert sous les règles d’exécution.
  5. « Permission denied » n’est pas forcément une erreur de permissions. C’est un EACCES générique qui peut signifier « politique » aussi bien que « bits de mode ».
  6. systemd a changé la donne. Les services peuvent avoir des espaces de noms de montage privés et des restrictions qui ne correspondent pas à ce que vous voyez dans votre shell.
  7. Les images d’entreprise adorent noexec. Les référentiels de configuration CIS et les baselines internes recommandent souvent nodev,nosuid,noexec pour les zones accessibles en écriture par tous.
  8. Les plateformes de conteneurs recréent le même problème. Les volumes ConfigMap/Secret Kubernetes sont typiquement montés avec des options restrictives ; exécuter du code directement depuis eux est volontairement difficile.
  9. NFS ajoute une ambiguïté de politique. noexec peut être côté client, ou l’exécution peut être bloquée par la politique d’export côté serveur et par des mécanismes comme root-squash qui embrouillent les gens.

Tâches pratiques : commandes, sorties, décisions (12+)

Voici ce que je fais réellement quand quelqu’un dit « c’est permission denied » et que je sens le gouffre temporel approcher.

Task 1: vérifier que le fichier est exécutable et qu’il ne s’agit pas d’une faute de frappe de répertoire

cr0x@server:~$ ls -l ./deploy.sh
-rwxr-xr-x 1 cr0x cr0x 1843 Dec 30 01:55 ./deploy.sh

Ce que cela signifie : le bit d’exécution est activé pour owner/group/others. Si vous obtenez encore « Permission denied », ce n’est probablement pas les bits Unix.

Décision : passez aux options de montage et aux contrôles de politique, pas à d’autres chmod.

Task 2: essayer d’exécuter via l’interpréteur (contourne l’exécution de fichier, mais pas toujours noexec)

cr0x@server:~$ ./deploy.sh
bash: ./deploy.sh: Permission denied
cr0x@server:~$ bash ./deploy.sh
./deploy.sh: line 12: /tmp/helper: Permission denied

Ce que cela signifie : le script lui-même peut être lisible, mais il tente d’exécuter autre chose depuis un emplacement noexec (/tmp/helper ici).

Décision : identifiez chaque chemin exécutable que le script utilise ; celui qui échoue se trouve souvent dans /tmp, un espace de travail monté, ou un bind mount.

Task 3: vérifier les options de montage pour le répertoire du script

cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS --target /home/cr0x
/dev/nvme0n1p3 /home ext4 rw,relatime,noexec

Ce que cela signifie : /home est monté noexec. Toute exécution directe depuis /home échouera.

Décision : soit déplacez les exécutables hors de /home, soit retirez noexec (en évaluant sérieusement votre modèle de menace).

Task 4: confirmer le code d’erreur du noyau via strace (sérum de vérité rapide)

cr0x@server:~$ strace -f -e execve ./deploy.sh
execve("./deploy.sh", ["./deploy.sh"], 0x7ffce9c4a3b0 /* 45 vars */) = -1 EACCES (Permission denied)
strace: exec: Permission denied

Ce que cela signifie : le noyau a rejeté execve() avec EACCES. C’est compatible avec noexec, AppArmor, ou une autre politique.

Décision : vérifiez les logs de montage et de politique MAC ; ne perdez pas de temps sur des hypothèses au niveau du shell.

Task 5: inspecter tous les montages qui incluent noexec (trouver les mines)

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)

Ce que cela signifie : /home et /tmp sont noexec. C’est une baseline d’entreprise fréquente.

Décision : décidez où l’exécution doit se produire : généralement /usr/local/bin, /opt, ou un répertoire d’application dédié.

Task 6: confirmer les flags de montage exacts avec findmnt (plus précis que la sortie mount)

cr0x@server:~$ findmnt -no TARGET,PROPAGATION,OPTIONS /tmp
/tmp shared rw,relatime,nodev,nosuid,noexec

Ce que cela signifie : la propagation étant shared importe en contexte de conteneurs ; les changements de montage peuvent se propager de façon inattendue.

Décision : si vous êtes en setup conteneurisé, traitez la propagation de montage comme partie du périmètre d’impact.

Task 7: test de remédiation rapide (remount temporaire) pour prouver la cause

cr0x@server:~$ sudo mount -o remount,exec /tmp
cr0x@server:~$ findmnt -no OPTIONS --target /tmp
rw,relatime,nodev,nosuid,exec

Ce que cela signifie : vous avez basculé /tmp en exécutable pour cette session de boot (jusqu’à remount/reboot ou restauration fstab).

Décision : si l’exécution fonctionne maintenant, vous avez confirmé noexec comme cause. Restaurez ensuite et implémentez un correctif permanent plus sûr (généralement pas « rendre /tmp exécutable pour toujours »).

Task 8: mettre à jour fstab pour un changement permanent (seulement si la politique le permet)

cr0x@server:~$ grep -nE '\s/tmp\s' /etc/fstab
12:/dev/nvme0n1p4 /tmp ext4 defaults,nodev,nosuid,noexec 0 2

Ce que cela signifie : fstab a codé en dur noexec pour /tmp.

Décision : si vous devez le changer, faites-le intentionnellement et documentez pourquoi. Mieux : gardez /tmp noexec et déplacez les artefacts de build.

Task 9: le correctif sûr et ennuyeux — installer les scripts dans un chemin exécutable

cr0x@server:~$ sudo install -m 0755 ./deploy.sh /usr/local/bin/deploy
cr0x@server:~$ /usr/local/bin/deploy --help
Usage: deploy [options]

Ce que cela signifie : vous avez placé le script dans un répertoire d’exécutables standard avec les permissions appropriées.

Décision : privilégiez cela plutôt que de rendre exécutables des montages insécurisés et accessibles en écriture. Cela réduit la surface d’attaque « télécharger et exécuter ».

Task 10: vérifier la correction du shebang (un mauvais chemin d’interpréteur ressemble à un comportement de permission)

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

Ce que cela signifie : l’interpréteur existe et est exécutable. Si le shebang pointait vers quelque chose d’inexistant, vous verriez souvent « No such file or directory », pas « Permission denied », mais cela vaut quand même la peine d’être vérifié.

Décision : si les scripts doivent être portables, envisagez #!/usr/bin/env bash (avec les réserves habituelles concernant PATH dans des contextes privilégiés).

Task 11: détecter les refus AppArmor (la politique peut imiter 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

Ce que cela signifie : AppArmor a refusé l’exécution. Ce n’est pas un problème de montage ; c’est de la contention.

Décision : corrigez en ajustant le profil, en changeant l’emplacement d’exécution vers un chemin autorisé, ou en utilisant une méthode d’empaquetage non confinée. Ne désactivez pas AppArmor globalement sauf si vous aimez les réunions de conformité.

Task 12: vérifier le sandboxing d’unité systemd pour une panne de service

cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp
ProtectSystem=strict
PrivateTmp=true
NoNewPrivileges=true

Ce que cela signifie : PrivateTmp=true donne au service son propre /tmp, qui peut être monté différemment de votre /tmp interactif. Le sandboxing change la vue du système de fichiers.

Décision : reproduisez dans le contexte du service (voir tâche suivante) et ajustez l’unité ou l’emplacement des fichiers en conséquence.

Task 13: lancer un shell sous le sandbox exact du service pour reproduire

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

Ce que cela signifie : dans l’espace de noms de l’unité, /tmp est noexec. Votre script peut fonctionner en interactif mais échouer en tant que service.

Décision : évitez d’exécuter depuis /tmp dans les services. Placez les binaires d’assistance sous /usr/libexec, /opt, ou empaquetez-les correctement.

Task 14: vérifier les options de montage pour un bind mount (souvent oublié)

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

Ce que cela signifie : les bind mounts peuvent avoir leurs propres flags. Quelqu’un a peut-être « utilement » remounté un bind mount noexec alors que le système de fichiers sous-jacent est exec.

Décision : vérifiez toujours le point de montage cible à partir duquel vous exécutez, pas seulement le système de fichiers de base.

Task 15: confirmer les options de montage NFS (noexec côté client est courant)

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

Ce que cela signifie : le partage NFS est monté noexec sur le client. Les scripts stockés là-bas ne pourront pas s’exécuter.

Décision : montez avec exec si la politique le permet, ou copiez les outils localement dans un répertoire autorisant l’exécution pendant le déploiement.

D’où vient noexec sur Ubuntu 24.04

Ubuntu 24.04 n’est pas soudainement devenu hostile aux scripts. Ce qui a changé, c’est que davantage d’environnements adoptent des modèles de durcissement par défaut, et que plus d’outils supposent pouvoir exécuter depuis « n’importe où ». Ces tendances se rencontrent.

1) fstab et images « baseline de sécurité »

La source la plus simple est /etc/fstab. Les entreprises construisent des images où des partitions séparées existent pour /home, /tmp, parfois /var, et elles appliquent nodev,nosuid,noexec pour réduire les abus des zones écrites par les utilisateurs.

Si vous avez grandi sur des systèmes à racine unique, cela peut sembler exotique. Si un auditeur vous a déjà crié dessus, ça ressemble à un mardi.

2) espaces de noms de montage privés systemd

Systemd peut donner aux services leur propre monde. Ce monde peut inclure :

  • PrivateTmp= (un /tmp spécifique au service)
  • ProtectSystem= et ProtectHome= (chemins en lecture seule ou masqués)
  • ReadWritePaths= et TemporaryFileSystem= (montages personnalisés)

Résultat : vous exécutez findmnt dans votre shell, voyez exec, et échouez quand même dans le service. Ce n’est pas du gaslighting ; ce sont les namespaces.

3) montages de volumes conteneur et valeurs par défaut de l’orchestrateur

Dans les conteneurs, « le système de fichiers » est un collage : couches overlay, bind mounts, tmpfs et volumes fournis par l’orchestrateur. Beaucoup sont intentionnellement restrictifs.

Le classique : les volumes ConfigMap/Secret dans Kubernetes ne sont pas un endroit pour exécuter des programmes. Ce sont des emplacements pour des configs. Si votre application télécharge un plugin dans l’un de ces montages et tente de l’exécuter, le noyau refusera.

4) politiques de stockage réseau (NFS, CIFS)

Le stockage distant introduit l’exécution comme une question de politique. Sur les clients Linux, noexec est appliqué par le noyau client sur ce montage. Par ailleurs, les serveurs peuvent appliquer des permissions et un mapping qui mènent à des refus déroutants quand un script tente d’accéder à un interpréteur ou un binaire d’assistance.

5) politiques MAC (AppArmor) et confinement (Snap)

Ubuntu privilégie AppArmor. Les snaps ajoutent des couches de confinement. Les deux peuvent produire EACCES avec « Permission denied » même lorsque les montages semblent corrects. Un processus confiné peut être autorisé à lire un fichier mais pas à l’exécuter, ou autorisé à exécuter uniquement depuis un ensemble connu de chemins.

Schémas de correction : le plus sûr d’abord, permanent si nécessaire

Schéma A : arrêter d’exécuter depuis des emplacements utilisateurs écrits et éphémères

Si votre pipeline ou installateur dépose des binaires dans /tmp, /var/tmp, un répertoire personnel, ou un workspace monté puis les exécute, vous dépendez d’un anti-pattern de sécurité. Ça marche jusqu’à ce que ça ne marche plus — puis ça vous coûte une heure de vie.

Faites plutôt :

  • Installez les exécutables sous /usr/local/bin (géré par les admins) ou /opt/<app>/bin.
  • Pour les binaires d’assistance, pensez à /usr/libexec/<app> (courant sur les distros modernes) ou gardez-les à côté du binaire principal dans /opt.
  • Gardez les données d’exécution modifiables dans /var/lib/<app> et les logs dans /var/log/<app>.

Ce schéma ne se bat pas contre le système d’exploitation. Il coopère.

Schéma B : si vous avez besoin d’un répertoire de build inscriptible, créez-en un explicitement exécutable

Certains workflows ont besoin de compiler et d’exécuter des artefacts (compilateurs, JIT, systèmes de build). Si votre politique marque /home comme noexec, il vous faut une zone de build dédiée autorisant l’exécution. Le nom devrait rendre l’intention évidente, car l’ambiguïté est la façon dont les incidents naissent.

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

Ce que cela signifie : /srv est typiquement exec-enabled sauf si vous l’avez durci aussi. Vos artefacts de build ont maintenant un endroit sans affaiblir /home ou /tmp.

Décision : dirigez les builds vers /srv/exec-work (ou équivalent) et gardez le reste du système durci.

Schéma C : remonter en exec (chirurgical, les yeux ouverts)

Parfois vous êtes coincé : un logiciel fournisseur legacy s’attend à s’exécuter depuis /home ou un partage tools monté. Si vous ne pouvez pas refactorer rapidement, vous pouvez remonter avec exec. Faites-le aussi précisément que possible.

Bien : un point de montage dédié pour les outils avec exec. Mauvais : rendre /tmp exec parce que « ça a réparé ».

cr0x@server:~$ sudo mount -o remount,exec /home
cr0x@server:~$ findmnt -no OPTIONS --target /home
rw,relatime,exec

Ce que cela signifie : la politique est assouplie. Tout dans /home devient exécutable.

Décision : si vous faites cela, compensez ailleurs (meilleure surveillance, contrôles d’installation plus stricts, ou migration vers des outils par conteneurs/VM par utilisateur).

Schéma D : utiliser empaquetage et hygiène de déploiement plutôt que l’énergie « curl | bash »

Si votre système installe en téléchargeant dans /tmp puis en exécutant immédiatement, vous allez heurter noexec et vous habituer à des pratiques risquées. Empaquetez-le, ou au moins téléchargez dans un chemin contrôlé et exécutable et vérifiez signatures/somme de contrôle.

Blague courte #2 : La seule chose pire que « ça marche sur ma machine » est « ça marche sur ma machine parce que ma machine est non sécurisée ».

Sandboxes systemd qui ressemblent à noexec

Les opérateurs accusent systemd de tout, et systemd mérite une bonne part du blâme. Mais quand il casse l’exécution de votre script, il fait généralement exactement ce que vous (ou votre distro) avez demandé : limiter ce qu’un service peut voir et faire.

Comment ce défaut se manifeste

  • Le script s’exécute bien en session SSH.
  • Il échoue comme service avec « Permission denied » en essayant d’exécuter un binaire d’assistance.
  • Les options de montage semblent correctes depuis le shell hôte.

Pourquoi cela arrive

Les unités systemd peuvent s’exécuter dans leur propre namespace de montage. Ce namespace peut monter /tmp comme un tmpfs privé, parfois avec noexec. Ou il peut utiliser TemporaryFileSystem= pour monter un arbre inscriptible avec des flags restrictifs.

Que faire

  • Reproduire dans le namespace de l’unité en utilisant systemd-run avec des propriétés similaires (voir Task 13).
  • Arrêter d’exécuter depuis /tmp. Placez les binaires d’assistance dans un répertoire stable et autorisez le service à les lire/exécuter.
  • Utiliser ReadWritePaths avec sagesse. Si un service a besoin d’un répertoire inscriptible, accordez-le de manière ciblée plutôt qu’un accès large au système de fichiers.

Exemple de correctif précis côté service

Si votre service décompresse un outil et l’exécute, redesign: que l’outil soit livré avec le paquet. Si ce n’est pas possible, au moins décompressez dans un répertoire que vous contrôlez avec des propriétés de montage connues (pas le /tmp privé du sandbox).

cr0x@server:~$ sudo systemctl edit myapp.service
# (editor opens)

Et vous ajouteriez quelque chose comme :

cr0x@server:~$ sudo systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp
PrivateTmp=true
ReadWritePaths=/var/lib/myapp

Ce que cela signifie : vous fournissez au service un répertoire d’état inscriptible tout en gardant le reste contraint. Vous n’essayez pas de rendre son /tmp privé exécutable juste pour lancer un blob aléatoire.

Décision : favorisez des emplacements d’exécution stables et des permissions d’écriture étroites. C’est la version ops de « manger des légumes ».

Conteneurs, Kubernetes et le piège « monté mais non exécutable »

Les conteneurs aiment monter des trucs. Config. Secrets. Volumes partagés. Espace scratch. Ils aiment aussi rendre ces montages restrictifs, parce qu’un pod compromis ne doit pas devenir un hobby à l’échelle du cluster.

Mode d’échec courant en conteneur

Vous construisez une image. Elle tourne bien. Puis vous montez un volume sur /app ou /app/bin dans Kubernetes. Soudain, votre script de démarrage échoue avec « Permission denied ». Vous continuez à reconstruire l’image comme si elle allait s’excuser. Elle ne le fera pas.

Tâche : inspecter les options de montage à l’intérieur d’un conteneur

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

Ce que cela signifie : le bind mount est arrivé comme noexec (ou a été remounté noexec par la politique). Exécuter des artefacts depuis là échouera.

Décision : n’exécutez pas depuis ce montage. Copiez les exécutables dans le système de fichiers du conteneur (couche image) ou montez un volume prévu pour l’exécution.

Spécifiques à Kubernetes (la partie discrète)

Les volumes ConfigMap et Secret ne sont pas votre répertoire de plugins. Si vous avez besoin d’un script depuis un ConfigMap, un schéma courant est : le copier dans un répertoire inscriptible et exécutable au démarrage, puis l’exécuter depuis là. Cela exige que le répertoire cible soit exec-enabled.

Tâche : prouver noexec sur un chemin de volume projeté typique (exemple)

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

Ce que cela signifie : les volumes de secrets sont tmpfs et typiquement noexec. C’est intentionnel.

Décision : traitez ces montages comme des données uniquement. Si vous devez exécuter un script, déplacez-le vers un emplacement approprié aux permissions contrôlées.

NFS et systèmes de fichiers distants : politiques d’exécution et surprises

NFS apporte sa propre saveur d’étrangeté. Surtout parce qu’il réalise du mapping d’identité, des permissions et du cache sur le réseau, et tout le monde suppose que c’est juste « un disque un peu plus loin ».

Deux réalités à garder distinctes

  • Flags de montage côté client (noexec) sont appliqués par le noyau client. Si le client dit noexec, rien ne s’exécute, même si le serveur « permettrait ».
  • Permissions côté serveur importent toujours pour la lecture et la traversée des répertoires. Beaucoup de « Permission denied » sur NFS sont en réalité des permissions d’accès aux répertoires (bit x sur les répertoires) ou des comportements de root-squash qui perturbent un installateur lancé en root.

Tâche : différencier « le montage dit noexec » de « permissions serveur incorrectes »

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

Ce que cela signifie : les permissions de répertoire semblent correctes, mais le montage indique noexec. C’est la contrainte bloquante.

Décision : remontez avec exec si autorisé, ou copiez les outils localement. Modifier les perms Unix ne résoudra pas le problème.

Tâche : remonter le partage NFS en exec (si la politique le permet)

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

Ce que cela signifie : le client autorise maintenant l’exécution depuis ce montage.

Décision : si c’est un partage « outils », envisagez de le rendre en lecture seule et exec-enabled, et gardez les partages inscriptibles en noexec. Mélanger écriture+exécution, c’est inviter les malwares.

Erreurs courantes : symptôme → cause racine → correctif

  • Symptôme : bash: ./script.sh: Permission denied même si ls -l montre exécutable.
    Cause racine : le système de fichiers est monté noexec (souvent /home ou un workspace).
    Correctif : déplacez le script vers /usr/local/bin ou remontez le système de fichiers spécifique avec exec.
  • Symptôme : le script fonctionne en interactif mais échoue en service systemd avec « Permission denied » en exécutant un helper dans /tmp.
    Cause racine : le service a PrivateTmp ou autre sandboxing avec un tmpfs noexec.
    Correctif : n’exécutez plus d’helpers depuis /tmp ; livrez les helpers dans /opt ou /usr/libexec.
  • Symptôme : bash: ./script: /bin/bash^M: bad interpreter: No such file or directory (ou similaire).
    Cause racine : fins de ligne CRLF ; pas noexec, mais cela ressemble à « impossible d’exécuter ».
    Correctif : convertir en LF : sed -i 's/\r$//' script (puis retester).
  • Symptôme : Permission denied seulement quand exécuté via un outil installé par Snap.
    Cause racine : le confinement AppArmor refuse l’exécution depuis ce chemin.
    Correctif : exécuter depuis des chemins autorisés, ajuster le confinement, ou installer une version non-snap si la politique le permet.
  • Symptôme : un pod Kubernetes échoue à exécuter un script stocké dans un ConfigMap.
    Cause racine : le volume ConfigMap est monté noexec (et souvent en lecture seule).
    Correctif : copier vers un répertoire inscriptible et exec-enabled au démarrage, ou intégrer le script dans l’image.
  • Symptôme : « Permission denied » en exécutant depuis un partage d’outils NFS ; sur disque local ça marche.
    Cause racine : le client a monté NFS avec noexec.
    Correctif : remontez avec exec ou synchronisez/copiez les outils localement pendant le déploiement.
  • Symptôme : un seul répertoire spécifique échoue à exécuter ; le système de fichiers parent est exec-enabled.
    Cause racine : un bind mount remounté avec noexec (commun dans conteneurs et chroots).
    Correctif : findmnt --target le chemin exact et remontez ce bind avec exec.
  • Symptôme : vous exécutez un script, il échoue avec « Permission denied » sur un fichier qu’il tente d’exécuter depuis /var/tmp.
    Cause racine : /var/tmp est aussi durci avec noexec dans certaines baselines.
    Correctif : modifiez le script pour placer les exécutables dans un répertoire autorisant l’exécution ; ne supposez pas que /var/tmp diffère de /tmp.

Trois mini-récits d’entreprise tirés du terrain

1) L’incident causé par une mauvaise hypothèse : « chmod règle Permission denied »

Une entreprise de taille moyenne a déployé Ubuntu 24.04 sur une flotte de runners de build. La sécurité avait silencieusement mis à jour l’image de base : partition séparée pour /home, montée noexec. La raison était valable — des développeurs téléchargeaient des outils dans leur répertoire home et les exécutaient sans vérifier leur provenance.

Puis le pipeline CI a échoué. Pas un job, tous. Le message d’erreur était l’habituel haussement d’épaules : « Permission denied » en exécutant un script de build. Un ingénieur a supposé un checkout foiré : propriétaire incorrect, bit d’exécution manquant, peut-être Git. Ils ont ajouté chmod -R +x dans le pipeline. Ça n’a rien résolu. Ça a par contre créé un nouveau problème : des fichiers non exécutables se sont retrouvés avec le bit d’exécution, ce qui a rendu les vérifications d’intégrité bruyantes et confuses.

L’incident a escaladé parce que les gens poursuivaient la mauvaise couche. Quelqu’un a essayé sudo. Quelqu’un d’autre a blâmé SELinux (sur Ubuntu, dans une salle pleine de gens, ce qui est une forme spéciale d’optimisme). Un troisième a proposé « désactivez le durcissement ». Cette personne a été poliment invitée à prendre un café et ne rien toucher.

Le vrai correctif a pris cinq minutes une fois la bonne question posée : « Que dit findmnt pour l’espace de travail ? » Il a dit noexec. Le pipeline a copié la toolchain dans un répertoire exec-enabled dédié sous /opt/ci-tools durant la provision des runners, et les scripts ont été exécutés depuis là. Home est resté noexec. La sécurité était satisfaite. La CI était satisfaite. La seule victime fut la croyance que chmod est un solvant universel.

2) L’optimisation qui s’est retournée contre eux : exécuter depuis /tmp pour « gagner du temps »

Une équipe plateforme interne a cherché à gratter quelques secondes sur le démarrage d’un service Java qui utilisait un helper natif (compression et checksum). Le helper vivait sur un montage NFS partagé. Parfois la latence NFS montait pendant les sauvegardes, rendant les déploiements instables. Quelqu’un a proposé une optimisation : copier le helper dans /tmp au démarrage et l’exécuter depuis là. « Le disque local est plus rapide », ont-ils dit. Vrai, dans un sens restreint.

En staging ça a marché. En production, ça a échoué instantanément. Les nœuds plus récents avaient /tmp monté avec nodev,nosuid,noexec. Le binaire pouvait être copié, mais pas exécuté. Le service est entré en crash loop. L’astreinte a vu « Permission denied » et a supposé un problème de propriétaire de fichier. Ils ont changé la propriété. Ils ont changé les modes. Ils ont redéployé. La boucle a continué, imperturbable.

Le correctif était simple : ne pas exécuter depuis /tmp. L’équipe a créé /var/lib/myapp/bin possédé par l’utilisateur du service et l’a installé sur un système de fichiers autorisant l’exécution. Ils ont aussi arrêté la copie au runtime et inclus le helper dans l’artefact de build. La dépendance NFS a disparu, et avec elle les bricolages de démarrage.

Conclusion postmortem : les optimisations qui modifient les chemins d’exécution sont des changements de sécurité, que vous le vouliez ou non. Vous ne pouvez pas optimiser autour d’une frontière de politique. La frontière gagnera, sans effort.

3) La pratique ennuyeuse mais correcte qui a sauvé la mise : « vérifier les options de montage durant build et boot »

Une organisation régulée gérait une flotte mixte : certains hôtes généralistes, d’autres durcis selon une baseline. Ils avaient déjà subi des incidents « permission denied mystérieux », alors ils ont ajouté une vérification banale à la validation d’hôte : pendant la provision et les checks journaliers, ils enregistraient les options de montage pour des chemins clés (/tmp, /home, répertoires d’app, workspaces). La sortie était envoyée dans leur système de logs, pas parce que c’était excitant, mais parce que c’était searchable à 03:00.

Une nuit, un déploiement a échoué sur un sous-ensemble de nœuds. Le service a tenté d’exécuter un helper de migration depuis /var/lib/myapp/tmp. Sur ces nœuds, /var/lib était une partition séparée montée noexec à cause d’un rôle baseline mal appliqué. Le code était bon. Le paquet était bon. La politique de fichiers système était incohérente.

Au lieu de deviner, l’astreinte a extrait le snapshot des montages connu pour un nœud sain et l’a comparé avec un nœud en échec. La différence est apparue immédiatement. L’équipe a rollbacké le rôle baseline sur les nœuds affectés, puis corrigé la logique du rôle pour que seules les partitions prévues reçoivent noexec.

Rien d’héroïque. Personne n’a « débogué Linux » en direct sur un système critique. Ils avaient des données ennuyeuses qui répondaient à la question. Voilà à quoi ressemble la fiabilité qui a mûri.

Checklists / plan pas à pas

Checklist : trier un seul script en échec sur Ubuntu 24.04

  1. Exécutez ls -l sur le script ; confirmez qu’il est exécutable et possède le propriétaire attendu.
  2. Exécutez-le directement et via interpréteur ; notez si le chemin en échec est le script lui-même ou un helper appelé.
  3. Exécutez findmnt -no OPTIONS --target <path> pour le script et tout binaire helper.
  4. Si noexec est présent : arrêtez. Décidez de déplacer l’exécutable ou de changer la politique de montage.
  5. Si pas de noexec : vérifiez les refus AppArmor dans journalctl -k.
  6. Si échec seulement en service : reproduisez dans le contexte d’unité avec systemd-run et inspectez PrivateTmp/TemporaryFileSystem.
  7. Si échec seulement en conteneur : inspectez les options de montage à l’intérieur du conteneur et vérifiez quel volume masque le chemin.

Checklist : choisir une correction qui ne vous embarrassera pas plus tard

  1. Préféré : placer les exécutables dans un emplacement administré et autorisant l’exécution (/usr/local/bin, /opt).
  2. Deuxième meilleur : créer un workspace dédié exécutable et le documenter.
  3. Risqué : remonter des montages utilisateurs larges avec exec.
  4. Généralement mauvais : rendre /tmp exec en permanence pour satisfaire un outil de build/déploiement.
  5. Exigence opérationnelle : si vous changez une politique de montage, assurez-vous qu’elle survive au reboot (fstab/unités systemd) et soit cohérente sur la flotte.

Checklist : valider le correctif

  1. Relancez findmnt pour le chemin cible et confirmez que les options sont celles attendues.
  2. Exécutez le script sous le même utilisateur qu’en production (utilisateur de service), pas sous root sauf si nécessaire.
  3. Si unité systemd : redémarrez l’unité, puis vérifiez systemctl status et les logs.
  4. Si conteneur : redéployez et confirmez que le même chemin n’est pas masqué par un volume en prod.

FAQ

1) Pourquoi ai-je « Permission denied » alors que le fichier est chmod +x ?

Parce que le montage du système de fichiers peut interdire l’exécution via noexec. Le noyau rejette execve() quel que soit le bit de mode. Vérifiez avec findmnt -no OPTIONS --target /path.

2) noexec est-ce une fonctionnalité de sécurité ou juste une nuisance ?

Les deux. Cela réduit significativement les attaques de type « déposer un payload dans un répertoire inscriptible et l’exécuter ». C’est aussi une nuisance pour des patterns d’installation négligents qui considèrent les zones inscriptibles comme des staging exécutable.

3) Puis-je contourner noexec en faisant bash script.sh ?

Parfois vous pouvez lancer un script via un interpréteur parce que vous exécutez /bin/bash, pas le fichier script comme binaire. Mais de nombreux cas noexec bloquent toujours l’exécution de binaires helpers appelés par le script, et certaines politiques imposent encore des restrictions. Ne comptez pas là-dessus comme correctif.

4) sudo contourne-t-il noexec ?

Non. noexec est appliqué par le noyau sur ce montage, pour tous les utilisateurs y compris root.

5) Quel est le correctif permanent le plus sûr ?

Arrêter d’exécuter depuis des montages inscriptibles durcis. Installer scripts/binaires dans /usr/local/bin ou /opt/<app> et laisser l’état inscriptible ailleurs (/var/lib).

6) Pourquoi ça marche dans mon shell mais pas dans un service systemd ?

Parce que le service peut s’exécuter dans un namespace de montage différent avec des flags différents (PrivateTmp, TemporaryFileSystem) et des restrictions d’accès aux chemins (ProtectSystem, ProtectHome).

7) Comment savoir si c’est AppArmor et pas noexec ?

Consultez les logs du noyau : sudo journalctl -k -g apparmor. Les refus AppArmor sont explicites et incluent operation="exec" et le nom de profil. noexec ne génèrera pas d’entrée AppArmor.

8) Dois-je retirer noexec de /tmp ?

Généralement non. Si un outil exige d’exécuter depuis /tmp, considérez cela comme un défaut de conception et déplacez l’exécutable. Si vous devez le changer, faites-le de façon ciblée et avec contrôles compensatoires.

9) Et NFS ? « noexec » est-il appliqué par le serveur ?

Sur les clients Linux, noexec est une option de montage côté client appliquée par le noyau client. Le serveur peut toujours causer des problèmes via permissions Unix et mapping d’identité, mais noexec est typiquement côté client.

10) J’ai changé les options de montage, mais après reboot le problème est revenu. Pourquoi ?

Votre remount était temporaire. Le comportement persistant provient de /etc/fstab ou d’unités systemd de montage. Corrigez la source de vérité et assurez la cohérence sur les hôtes.

Conclusion : quoi changer lundi matin

Si Ubuntu 24.04 vous dit « Permission denied » en exécutant un script, croyez-le — mais ne supposez pas qu’il s’agit seulement de chmod. Votre premier suspect doit être noexec sur le montage contenant le script ou un de ses binaires helpers. Votre second suspect doit être le sandboxing systemd. Le troisième est AppArmor ou le comportement des volumes en conteneurs.

Prochaines étapes qui rapportent :

  1. Standardiser où résident les exécutables (/usr/local/bin, /opt) et arrêter d’exécuter depuis /tmp//home/workspaces.
  2. Ajouter une vérification légère qui enregistre les options de montage pour les chemins clés sur la flotte.
  3. Pour les services, auditer les réglages de durcissement systemd et s’assurer que votre logiciel ne dépend pas d’exécuter depuis des répertoires éphémères.
  4. Pour les conteneurs, traiter les configs montées comme données uniquement ; exécutez depuis l’image ou un volume exec-enabled conçu pour cela.

Le système d’exploitation vous rend service. Il le fait bruyamment, la nuit, pendant que votre pager regarde.

← Précédent
Proxmox TASK ERROR : où lire les journaux et trouver rapidement la cause racine
Suivant →
E‑mail : confusion entre S/MIME et TLS — ce qui améliore vraiment la sécurité

Laisser un commentaire