Fichier binaire d'un daemon writable ssi le damon est stoppé

Le
Francois Lafont
Bonjour Í  tous,

Attention, je préfère prévenir, je connais très mal le système BSD alors
désolé par avance si je passe Í  cÍ´té de choses basiques.

Voici le résumé de ma demande : j'ai un serveur FreeBSD 12.2-RC3 (plus
précisément c'est un serveur TrueNAS) sur lequel j'ai installé manuellement
un daemon correspondant Í  un fichier binaire qu'on appellera "b". Je suis
root sur la machine et, si j'en crois la sortie de « ls -l "b" », en tant que
root le fichier m'est parfaitement accessible en écriture. Pourtant, si je fais
un simple

if [ -w "$b" ]; then echo WRITABLE; else echo NOT-WRITABLE; fi

cela m'affichera :

1. NOT-WRITABLE si le daemon est en cours d'exécution.
2. WRITABLE si le daemon est stoppé.

Pourtant, dans les deux cas (que le daemon soit stoppé ou non), les permissions
Unix du fichier "b" restent totalement inchangées. De plus, dans les deux cas,
je suis parfaitement en mesure de faire un touch sur le fichier. Quel mécanisme
système de l'OS entraÍ®ne cette différence entre les deux cas (daemon stoppé ou
démarré) ?




Maintenant, voici les détails. Le fichier s'appelle

~# b=/usr/local/capsdrv/filer/bin/capsdrv

Voici les permissions :

~# ls -ld /usr /usr/local /usr/local/capsdrv /usr/local/capsdrv/filer /usr/local/capsdrv/filer/bin "$b"
drwxr-xr-x 13 root wheel 13 Dec 2 11:27 /usr
drwxr-xr-x 17 root wheel 18 Dec 2 14:05 /usr/local
drwxr-x 3 root wheel 3 Dec 2 14:05 /usr/local/capsdrv
drwxr-xr-x 5 root wheel 6 Jan 11 15:14 /usr/local/capsdrv/filer
drwxr-xr-x 2 root wheel 6 Jan 11 16:46 /usr/local/capsdrv/filer/bin
-rwxrw- 1 root wheel 11057232 Jan 11 20:11 /usr/local/capsdrv/filer/bin/capsdrv

Voici le fichier qui m'a permis de faire de ce binaire un daemon :

~# cat /usr/local/etc/rc.d/capsdrv_filer
#!/bin/sh

# This file is managed by Ansible, don't edit it.
#
# https://redbyte.eu/en/blog/supervised-freebsd-init-script-for-go-deamon/

# PROVIDE: capsdrv_filer
# REQUIRE: networking
# KEYWORD:

. /etc/rc.subr
. /usr/local/capsdrv/filer/etc/setenv.sh

name="capsdrv_filer"
rcvar="capsdrv_filer_enable"
goprogram_user="capsrdv"
goprogram_command="/usr/local/capsdrv/filer/bin/capsdrv -path /usr/local/capsdrv/filer/etc"
pidfile="/var/run/${name}.pid"
command="/usr/sbin/daemon"
command_args="-S -P ${pidfile} -r -f ${goprogram_command}"

load_rc_config $name
: ${goprogram_enable:=no}

run_rc_command "$1"


Le daemon est en train de tourner actuellement :

~# service capsdrv_filer status
capsdrv_filer is running as pid 16351.

Les deux tests suivants m'indiquent que le fichier n'est pas writable :

~# if [ -w "$b" ]; then echo WRITABLE; else echo NOT-WRITABLE; fi
NOT-WRITABLE

~# echo "import os; x = os.access('$b', os.W_OK); print(x)" | python
False

~# whoami # Et pourtant je suis root.
root

DéjÍ , Í  ce stade, je suis surpris car les tests me disent que le fichier
n'est pas writable alors que je suis root et que les permissions Unix semblent
me donner un accès en écriture Í  ce fichier. Mais ce qui suit me surprend
encore plus, si je stoppe le daemon, alors les choses changent ;

~# service capsdrv_filer stop
Stopping capsdrv_filer.
Waiting for PIDS: 16351.

~# ls -ld /usr /usr/local /usr/local/capsdrv /usr/local/capsdrv/filer /usr/local/capsdrv/filer/bin "$b"
drwxr-xr-x 13 root wheel 13 Dec 2 11:27 /usr
drwxr-xr-x 17 root wheel 18 Dec 2 14:05 /usr/local
drwxr-x 3 root wheel 3 Dec 2 14:05 /usr/local/capsdrv
drwxr-xr-x 5 root wheel 6 Jan 11 15:14 /usr/local/capsdrv/filer
drwxr-xr-x 2 root wheel 6 Jan 11 16:46 /usr/local/capsdrv/filer/bin
-rwxrw- 1 root wheel 11057232 Jan 11 20:11 /usr/local/capsdrv/filer/bin/capsdrv

Absolument rien n'a changé au niveau des permissions Unix et pourtant :

~# if [ -w "$b" ]; then echo WRITABLE; else echo NOT-WRITABLE; fi
WRITABLE
~# echo "import os; x = os.access('$b', os.W_OK); print(x)" | python
True

Force est de constater qu'il y a un mécanisme système qui protège le
fichier en écriture lorsque le daemon est en cours d'exécution mais ce
mécanisme ne correspond pas aux permissions Unix classiques que je connais.
Mais quel est ce mécanisme ?

Merci d'avance pour votre aide.

--
François Lafont
  • Partager ce contenu :
Vos réponses
Trier par : date / pertinence
Christian Weisgerber
Le #26564757
On 2021-01-11, Francois Lafont
if [ -w "$b" ]; then echo WRITABLE; else echo NOT-WRITABLE; fi
1. NOT-WRITABLE si le daemon est en cours d'exécution.
2. WRITABLE si le daemon est stoppé.

La commande test(1), alias « [ », invoque l'appel système access(2),
qui retourne l'erreur ETXTBSY si le fichier exécutable est en état
d'utilisation.
C'est Í  cause de la mémoire virtuelle paginée. Seulement les morceaux
nécessaires d'un fichier exécutable sont transferés en mémoire vive
lorsque c'est exigé (« on-demand paging »). Évidemment, ça finirait
mal si on surécrivait le fichier et après chargeait un autre morceau.
C'est comme ça depuis l'introduction de la mémoire virtuelle paginée
au monde Unix, il y a environ une quarantaine d'années.
--
Christian "naddy" Weisgerber
Francois Lafont
Le #26564762
Bonsoir,
On 1/11/21 10:55 PM, Christian Weisgerber wrote:
La commande test(1), alias « [ », invoque l'appel système access(2),
qui retourne l'erreur ETXTBSY si le fichier exécutable est en état
d'utilisation.
C'est Í  cause de la mémoire virtuelle paginée. Seulement les morceaux
nécessaires d'un fichier exécutable sont transferés en mémoire vive
lorsque c'est exigé (« on-demand paging »). Évidemment, ça finirait
mal si on surécrivait le fichier et après chargeait un autre morceau.
C'est comme ça depuis l'introduction de la mémoire virtuelle paginée
au monde Unix, il y a environ une quarantaine d'années.


Merci beaucoup pour ta réponse Christian. Et ben... j'ai appris quelque
chose.
Ok, manifestement ce n'est pas récent mais pour moi qui vient du monde
Linux, c'est la première fois que je rencontre ça. Par exemple sur une
Ubuntu 18.04, un noyau Linux 4.15 donc, je n'ai pas le même comportement.
Sur cette Ubuntu, j'ai un daemon Apache2 qui tourne via le binaire
/usr/sbin/apache2. Comme tu peux voir ci-dessous, je n'ai pas le même
comportement qu'avec FreeBSD :
-------------------------------------------------------------
~# whoami
root
~# b=/usr/sbin/apache2
~# namei -mox "$b"
f: /usr/sbin/apache2
Drwxr-xr-x root root /
drwxr-xr-x root root usr
drwxr-xr-x root root sbin
-rwxr-xr-x root root apache2
~# echo "import os; x = os.access('$b', os.W_OK); print(x)" | python
True
~# [ -w "$b" ] && echo WRITABLE
WRITABLE
-------------------------------------------------------------
Du coup, le comportement que j'ai décrit dans mon premier message n'est-il
pas spécifique Í  (Free)BSD ? Ou alors, inversement, le comportement ci-dessus
n'est-il pas spécifique Í  Linux ? Pourquoi les deux OS se comportent-ils
différemment sur ce point ?
--
François Lafont
Francois Lafont
Le #26564763
On 1/12/21 2:02 AM, Francois Lafont wrote:
-------------------------------------------------------------
~# whoami
root
~# b=/usr/sbin/apache2
~# namei -mox "$b"
f: /usr/sbin/apache2
 Drwxr-xr-x root root /
 drwxr-xr-x root root usr
 drwxr-xr-x root root sbin
 -rwxr-xr-x root root apache2
~# echo "import os; x = os.access('$b', os.W_OK); print(x)" | python
True
~# [ -w "$b" ] && echo WRITABLE
WRITABLE
-------------------------------------------------------------

Je précise que, lorsque je lis la page man access(2) de ma Ubuntu, j'ai
bien ETXTBSY considéré comme une erreur. Du coup, sur Linux, le comportement
ne me paraÍ®t effectivement pas en cohérence avec ce que je lis dans la
page man.
--
François Lafont
Christian Weisgerber
Le #26564819
On 2021-01-12, Francois Lafont
Sur cette Ubuntu, j'ai un daemon Apache2 qui tourne via le binaire
/usr/sbin/apache2. Comme tu peux voir ci-dessous, je n'ai pas le même
comportement qu'avec FreeBSD :
~# b=/usr/sbin/apache2
~# [ -w "$b" ] && echo WRITABLE
WRITABLE

Si tu essaies de surécrire le binaire, ça va quand même échouer
avec une erreur ETXTBSY.
La différence, c'est le comportement d'access(2). POSIX permet les
deux comportements.
--
Christian "naddy" Weisgerber
Francois Lafont
Le #26564852
Bonsoir,
On 1/12/21 3:54 PM, Christian Weisgerber wrote:
Sur cette Ubuntu, j'ai un daemon Apache2 qui tourne via le binaire
/usr/sbin/apache2. Comme tu peux voir ci-dessous, je n'ai pas le même
comportement qu'avec FreeBSD :

~# b=/usr/sbin/apache2
~# [ -w "$b" ] && echo WRITABLE
WRITABLE

Si tu essaies de surécrire le binaire, ça va quand même échouer
avec une erreur ETXTBSY.

Ce n'est pas ce que je constate sur ma Ubuntu 18.04 (une petite VM de test) :
------------------------------------------------------------------------
~# whoami
root
# Mon service Apache2 en cours d'exécution.
~# service apache2 status
* apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
Drop-In: /lib/systemd/system/apache2.service.d
`-apache2-systemd.conf
Active: active (running) since Tue 2021-01-12 19:22:04 CET; 11s ago
Main PID: 948 (apache2)
Tasks: 55 (limit: 2375)
CGroup: /system.slice/apache2.service
|-948 /usr/sbin/apache2 -k start
|-951 /usr/sbin/apache2 -k start
`-952 /usr/sbin/apache2 -k start
Jan 12 19:22:04 bionic-vbox systemd[1]: Starting The Apache HTTP Server...
Jan 12 19:22:04 bionic-vbox systemd[1]: Started The Apache HTTP Server.
~# ls -l /usr/sbin/apache2
-rwxr-xr-x 1 root root 671392 Aug 12 23:33 /usr/sbin/apache2
~# sha1sum /usr/sbin/apache2
013eebcb901aa2c3e1ea7057e08811563fc866c7 /usr/sbin/apache2
# C'est bête évidemment mais c'est juste pour modifier (n'importe comment)
# le fichier.
~# vim /usr/sbin/apache2
# Et j'ai bien modifié le fichier sans erreur.
# Autant dire qu'en modifiant le fichier avec vim, je l'ai complètement
# corrompu mais j'ai quand même pu le modifier, l'opération n'a pas échoué.
~# sha1sum /usr/sbin/apache2
6adbc993c5de98abe63c930629f200646c9ae978 /usr/sbin/apache2
------------------------------------------------------------------------
Au passage, après la modification, le service Apache2 fonctionne toujours
sur la VM. Par contre, si je fais un restart du service, évidemment ça plante
car le binaire est corrompu.
Du coup, je suis toujours perplexe quant Í  cette différence de comportement
entre FreeBSD et Linux.
--
François Lafont
Christian Weisgerber
Le #26564866
On 2021-01-12, Francois Lafont
Si tu essaies de surécrire le binaire, ça va quand même échouer
avec une erreur ETXTBSY.

Ce n'est pas ce que je constate sur ma Ubuntu 18.04 (une petite VM de test) :
# C'est bête évidemment mais c'est juste pour modifier (n'importe comment)
# le fichier.
~# vim /usr/sbin/apache2

Essaie quelque chose de simple.
# echo fdfdjjdf > /usr/sbin/apache2
Je ne sais pas ce que vim fait. Peut-être il invoque rename(2) ou
unlink(2) et crée un nouveau fichier après.
# Et j'ai bien modifié le fichier sans erreur.

$ ssh machine.de.linux
$ cp /bin/bash .
$ ./bash
$ echo sfjffjk >bash
bash: bash: Text file busy
--
Christian "naddy" Weisgerber
Francois Lafont
Le #26564872
Re,
On 1/12/21 9:32 PM, Christian Weisgerber wrote:
Ce n'est pas ce que je constate sur ma Ubuntu 18.04 (une petite VM de test) :
# C'est bête évidemment mais c'est juste pour modifier (n'importe comment)
# le fichier.
~# vim /usr/sbin/apache2

Essaie quelque chose de simple.
# echo fdfdjjdf > /usr/sbin/apache2
Je ne sais pas ce que vim fait. Peut-être il invoque rename(2) ou
unlink(2) et crée un nouveau fichier après.

Bien vu, merci beaucoup. En effet, j'aurais dÍ» le tester. Je ne vais pas
copier/coller les commandes Í  nouveau mais avec un « ls -li » sur le binaire
pour afficher le numéro d'inode avant et après le vim, on peut constater qu'il
change. Donc tu as raison, vim doit faire un rename ou un unlink. Et si je
regarde les fichiers ouverts par les processus apache2 avec la commande lsof
après modification avec vim, on voit bien le fichier /usr/sbin/apache2 ouvert
mais en deleted. Le fichier n'est donc plus référencé dans l'aborescence du
filesystem mais existe toujours et n'a pas été modifié.
Et enfin, avec un echo comme tu le suggères, j'ai bien comme toi :
~# echo AAAAAAAAA > /usr/sbin/apache2
-bash: /usr/sbin/apache2: Text file busy
Et le echo fonctionne si le service apache2 est stoppé donc.
Bon, ben c'est cool, j'ai appris un truc « système » même si c'est un truc vieux
de 40 ans :). Du coup, comme tu le disais, dans la question qui me préoccupe dans
ce fil, la seule différence est bien au niveau de access() :
1. sur un Linux (en tout cas sur ma Ubuntu 18.04) access() ne renvoie pas d'erreur
sur un ETXTBSY (même si la page man access(2) de ma Ubuntu ne le mentionne nulle
part et mentionne même clairement le contraire).
2. sur une FreeBSD, access() renvoie bien une erreur sur un ETXTBSY.
Par facile de trouver une doc de référence lÍ -dessus. La seule chose que j'ai
trouvée (mais j'ai peut-être mal cherché) c'est cette page
https://pubs.opengroup.org/onlinepubs/9699919799/ o͹ il y a juste ͠ la fin la
mention elliptique suivante :
-----------------------------------------------------
Issue 6
The following new requirements on POSIX implementations derive from alignment
with the Single UNIX Specification:
* The [ELOOP] mandatory error condition is added.
* A second [ENAMETOOLONG] is added as an optional error condition.
* The [ETXTBSY] optional error condition is added. <= LÍ  !
-----------------------------------------------------
J'ai encore une dernière petite interrogation si possible : la vérité ultime
c'est donc que le binaire n'est pas modifiable quand il est en cours d'exécution
(sur un Unix), mais alors pourquoi diable sur Linux les développeurs ont-ils
voulu implémenter une fonction access() qui ne dit pas la vérité (elle dit que
c'est modifiable alors que c'est faux) ? J'avoue que la logique m'échappe un peu.
--
François Lafont
Christian Weisgerber
Le #26564956
On 2021-01-13, Francois Lafont
1. sur un Linux (en tout cas sur ma Ubuntu 18.04) access() ne renvoie pas d'erreur
sur un ETXTBSY (même si la page man access(2) de ma Ubuntu ne le mentionne nulle
part et mentionne même clairement le contraire).

La page dit:
| access() and faccessat() may fail if:
...
| ETXTBSY
| Write access was requested to an executable which is being
| executed.
https://man7.org/linux/man-pages/man2/access.2.html
C'est « may », donc il est permis, il se peut que ça renvoie une
telle erreur. Cette formulation est floue et quasi copiée-collée
de POSIX.
J'ai encore une dernière petite interrogation si possible : la vérité ultime
c'est donc que le binaire n'est pas modifiable quand il est en cours d'exécution
(sur un Unix), mais alors pourquoi diable sur Linux les développeurs ont-ils
voulu implémenter une fonction access() qui ne dit pas la vérité (elle dit que
c'est modifiable alors que c'est faux) ? J'avoue que la logique m'échappe un peu.

J'imagine qu'ils pensent que la fonctionnalité d'access() est
seulement de vérifier les permissions du inode et qu'un ETXTBSY est
une situation exceptionnelle au-dehors de ce qu'access() doit faire.
--
Christian "naddy" Weisgerber
Francois Lafont
Le #26564990
Bonjour,
On 1/13/21 11:59 PM, Christian Weisgerber wrote:
La page dit:
| access() and faccessat() may fail if:
...
| ETXTBSY
| Write access was requested to an executable which is being
| executed.
https://man7.org/linux/man-pages/man2/access.2.html
C'est « may », donc il est permis, il se peut que ça renvoie une
telle erreur. Cette formulation est floue et quasi copiée-collée
de POSIX.

Ah oui, en effet. Bien vu. C'est comme avec les RFC avec les nuances entre
must, may etc.
J'ai encore une dernière petite interrogation si possible : la vérité ultime
c'est donc que le binaire n'est pas modifiable quand il est en cours d'exécution
(sur un Unix), mais alors pourquoi diable sur Linux les développeurs ont-ils
voulu implémenter une fonction access() qui ne dit pas la vérité (elle dit que
c'est modifiable alors que c'est faux) ? J'avoue que la logique m'échappe un peu.

J'imagine qu'ils pensent que la fonctionnalité d'access() est
seulement de vérifier les permissions du inode et qu'un ETXTBSY est
une situation exceptionnelle au-dehors de ce qu'access() doit faire.

Ok, ça se tient.
Et bien Merci beaucoup Christian de m'avoir aidé Í  approfondir le sujet, c'est
sympa. Je pense que tout est clair pour moi maintenant et je considère le fil
comme définitivement résolu. ;)
Merci encore.
À+
--
François Lafont
Poster une réponse
Anonyme