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.