Dale Hagglund est dans le coup. Je vais donc dire la même chose, mais d'une manière différente, avec quelques détails et exemples. ☺
La bonne chose à faire dans le monde Unix et Linux est :
- d'avoir un petit programme simple, facilement contrôlable, qui s'exécute en tant que super-utilisateur et qui lie la socket d'écoute ;
- d'avoir un autre petit programme simple, facilement contrôlable, qui supprime les privilèges, engendré par le premier programme ;
- d'avoir la chair du service, dans un programme troisième séparé, exécuté sous un compte non-super-utilisateur et une chaîne chargée par le second programme, en espérant simplement hériter d'un descripteur de fichier ouvert pour la socket.
Vous avez une mauvaise idée de l'endroit où se situe le risque élevé. Le risque élevé se situe dans la lecture depuis le réseau et l'action sur ce qui est lu et non dans les simples actes d'ouvrir une socket, de la lier à un port et d'appeler listen()
. C'est la partie d'un service qui effectue la communication proprement dite qui constitue le risque élevé. Les parties qui ouvrent, bind()
, et listen()
, et même (dans une certaine mesure) la partie qui accepts()
, ne constituent pas le risque élevé et peuvent être exécutées sous l'égide du super utilisateur. Elles n'utilisent pas et n'agissent pas sur les données (à l'exception des adresses IP sources dans le cas de accept()
) qui sont sous le contrôle d'étrangers non fiables sur le réseau.
Il existe de nombreuses façons de le faire.
inetd
Comme le dit Dale Hagglund, l'ancien “super serveur réseau” inetd
fait cela. Le compte sous lequel le processus de service est exécuté est l'une des colonnes de inetd.conf
. Il ne sépare pas la partie d'écoute et la partie d'abandon des privilèges en deux programmes séparés, petits et facilement contrôlables, mais il sépare le code principal du service en un programme séparé, exec()
ed dans un processus de service qu'il engendre avec un descripteur de fichier ouvert pour la socket.
La difficulté de l'audit n'est pas un problème majeur, car il suffit d'auditer un seul programme. Le problème majeur de inetd
n'est pas tant l'audit mais plutôt le fait qu'il ne permet pas un contrôle fin et simple du service d'exécution, comparé à des outils plus récents.
UCSPI-TCP et démonstrateurs
Les paquets UCSPI-TCP et démonstrateurs de Daniel J. Bernstein ont été conçus pour faire cela conjointement. On peut également utiliser le jeu d'outils daemontools-encore largement équivalent de Bruce Guenter.
Le programme permettant d'ouvrir le descripteur de fichier de socket et de se lier au port local privilégié est tcpserver
, de l'UCSPI-TCP. Il fait à la fois le listen()
et le accept()
.
tcpserver
engendre alors soit un programme de service qui supprime les privilèges root lui-même (parce que le protocole servi implique de commencer comme super-utilisateur puis de se “connecter”, comme c'est le cas avec, par exemple, un démon FTP ou SSH) ou setuidgid
qui est un petit programme autonome et facilement vérifiable qui ne fait que supprimer des privilèges et enchaîne ensuite les chargements au programme de service proprement dit (dont aucune partie ne fonctionne donc jamais avec des privilèges de super-utilisateur, comme c'est le cas, par exemple, de qmail-smtpd
).
Un script de service run
serait donc par exemple (celui-ci pour dummyidentd pour fournir le service null IDENT) :
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
nosh
Mon paquet nosh est conçu pour faire cela. Il a un petit utilitaire setuidgid
, comme les autres. Une légère différence est qu'il est utilisable avec les services “LISTEN_FDS” de type systemd
ainsi qu'avec les services UCSPI-TCP, de sorte que le programme tcpserver
traditionnel est remplacé par deux programmes distincts : tcp-socket-listen
et tcp-socket-accept
.
Là encore, les services à usage unique se développent et se chargent mutuellement. Une particularité intéressante de la conception est que l'on peut supprimer les privilèges de super-utilisateur après listen()
mais avant même accept()
. Voici un script run
pour qmail-smtpd
qui fait exactement cela :
#!/bin/nosh
fdmove -c 2 1
clearenv --keep-path --keep-locale
envdir env/
softlimit -m 70000000
tcp-socket-listen --combine4and6 --backlog 2 ::0 smtp
setuidgid qmaild
sh -c 'exec \
tcp-socket-accept -v -l "${LOCAL:-0}" -c "${MAXSMTPD:-1}" \
ucspi-socket-rules-check \
qmail-smtpd \
'
Les programmes qui fonctionnent sous l'égide du super-utilisateur sont les petits outils de chargement de chaîne de diagnostic de service fdmove
, clearenv
, envdir
, softlimit
, tcp-socket-listen
, setuidgid
, et sh
. Au moment où smtp
est lancé, la socket est ouverte et liée au port daemontools
, et le processus n'a plus les privilèges du super-utilisateur.
s6, s6-networking, et execline
Les paquets s6 et s6-networking de Laurent Bercot ont été conçus pour faire cela conjointement. Les commandes sont structurellement très similaires à celles de run
et de l'UCSPI-TCP. Les scripts
s6-tcpserver
seraient à peu près les mêmes, sauf pour la substitution de tcpserver
à s6-setuidgid
et de setuidgid
à chpst
. Toutefois, on pourrait également choisir d'utiliser en même temps le jeu d'outils execline de M. Bercot.
Voici un exemple de service FTP, légèrement modifié par rapport à l'original de Wayne Marshall , qui utilise execline, s6, s6-networking, et le programme de serveur FTP de publicfile :
#!/command/execlineb -PW
multisubstitute {
define CONLIMIT 41
define FTP_ARCHIVE "/var/public/ftp"
}
fdmove -c 2 1
s6-envuidgid pubftp
s6-softlimit -o25 -d250000
s6-tcpserver -vDRH -l0 -b50 -c ${CONLIMIT} -B '220 Features: a p .' 0 21
ftpd ${FTP_ARCHIVE}
ipsvd
Le programme ipsvd de Gerrit Pape est un autre ensemble d'outils qui fonctionne selon les mêmes principes que ucspi-tcp et s6-networking. Les outils sont tcpsvd
et fnord
cette fois, mais ils font la même chose, et le code à haut risque qui fait la lecture, le traitement et l'écriture des choses envoyées sur le réseau par Les clients non fiables font toujours l'objet d'un programme distinct.
Voici l'exemple de M. Pape de l'exécution de run
dans un script systemd
:
#!/bin/sh
exec 2>&1
cd /public/10.0.5.4
exec \
chpst -m300000 -Uwwwuser \
tcpsvd -v 10.0.5.4 443 sslio -v -unobody -//etc/fnord/jail -C./cert.pem \
fnord
systemd
inetd
, le nouveau système de supervision et d'init du service que l'on peut trouver dans certaines distributions Linux, est destiné à faire ce que systemd
peut faire . Cependant, il n'utilise pas une suite de petits programmes autonomes. Il faut malheureusement vérifier systemd
dans son intégralité.
Avec systemd
on crée des fichiers de configuration pour définir une socket sur laquelle systemd
écoute, et un service que systemd
démarre. Le fichier “unité” du service a des paramètres qui permettent de contrôler le processus du service, y compris l'utilisateur sous lequel il fonctionne.
Avec cet utilisateur défini comme non-super-utilisateur, listen()
fait tout le travail d'ouverture de la socket, la lie à un port, et appelle accept()
(et, si nécessaire, 0x6&) dans le processus #1 en tant que super-utilisateur, et le processus de service qu'il engendre s'exécute sans privilèges de super-utilisateur.