Détecter les dépassements de limites sous CentOS 6
Sous Linux, des limites de consommations de ressources permettent de s’assurer qu’un seul utilisateur ne sature pas un serveur partagé. Ces limites peuvent causer des pannes silencieuses.
Pour détecter ces dépassements, il est possible d’utiliser le démon
auditd
1. Celui-ci a la capacité de détecter des échecs d’appels
systèmes.
Pour identifier l’appel système en cause et son code de retour, nous
allons utiliser strace
, qui permet de tracer tous les appels système
d’une application.
Trop de fichiers ouverts
Ouverture de fichiers
Commençons par utiliser gawk
par créer 1024 fichiers sans les fermer
(ce qui est une très mauvaise idée de manière générale)…
BEGIN {
# Création de 1024 fichiers
for ( I=1 ; I <= 1024 ; I++ ){
FICHIER = "test/" I
print "TEST" > FICHIER
}
}
Enregistrons le programme gawk dans le fichier nofile.awk
, puis
lançons le script awk
sous strace
:
mkdir -p test
strace gawk -f nofile.awk 2> nofile.log
Le fichier nofile.log
est très long. En remontant à partir de la fin,
on trouve :
open("tmp/1021", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 1023
fstat(1023, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
fcntl(1023, F_SETFD, FD_CLOEXEC) = 0
fcntl(1023, F_GETFL) = 0x8001 (flags O_WRONLY|O_LARGEFILE)
fcntl(1023, F_GETFL) = 0x8001 (flags O_WRONLY|O_LARGEFILE)
fstat(1023, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc140450000
lseek(1023, 0, SEEK_CUR) = 0
ioctl(1023, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffea301dcd0) = -1 ENOTTY (Inappropriate ioctl for device)
open("tmp/1022", O_WRONLY|O_CREAT|O_TRUNC, 0666) = -1 EMFILE (Too many open files)
Donc, l’appel système open
reçoit l’erreur EMFILE
(« trop de
fichiers ouverts »). Ce qui nous donne la règle d’audit suivante :
-a always,exit -F arch=b64 -S open -F exit=-EMFILE -k nofile
Ouverture de connexions réseau
Le nombre maximum de fichiers ouverts peut concerner des connexions
réseau. Essayons de reproduire cette erreur avec un petit programme
gawk
ouvrant 1024 connexions UDP :
BEGIN {
# Ouverture de 1024 connexions UDP
for ( I = 1 ; I <= 1024 ; I++ ){
ADRESSE = "/inet/udp/0/127.0.0.1/" (1024 + I)
print "TEST" |& ADRESSE
}
}
Après exécution de ce programme sous strace
, on retombe sur la même
erreur lors d’un appel de dup
pour dupliquer un descripteur de
fichiers.
socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) = 1023
bind(1023, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
connect(1023, {sa_family=AF_INET, sin_port=htons(1535), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
fstat(1023, {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0
fcntl(1023, F_SETFD, FD_CLOEXEC) = 0
fcntl(1023, F_GETFL) = 0x2 (flags O_RDWR)
fstat(1023, {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0fe056f000
lseek(1023, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
dup(1023) = -1 EMFILE (Too many open files)
close(1023) = 0
Ce qui nous donne une nouvelle version de la règle d’audit, qui gère à
la fois l’ouverture d’un fichier (open
) et la duplication d’un
descripteur de fichiers (dup
) :
-a always,exit -F arch=b64 -S open -S dup -F exit=-EMFILE -k nofile
Cela dit, cette erreur pourrait aussi être émise par l’appel système
socket
(l’appel système utilisé pour créer un connecteur réseau).
Essayons d’ouvrir un premier fichier pour décaler les descripteurs de
fichiers d’un cran (il y a à chaque fois un appel de socket
pour
ouvrir un connecteur réseau, suivi d’un appel de dup
pour le
dupliquer) :
BEGIN {
# Ouverture d'un fichier
print "TEST" > "test1"
# Ouverture de 1024 connexions UDP
for ( I = 1 ; I <= 1024 ; I++ ){
ADRESSE = "/inet/udp/0/127.0.0.1/" (1024 + I)
print "TEST" |& ADRESSE
}
}
Après exécution du script gawk
sous strace
, on constate que cette
fois, l’erreur tombe bien sur l’appel de socket
. La sortie de
strace
montre que gawk
tente de créer un connecteur réseau de
plusieurs façons différentes, essayant en vain de contourner le
problème :
socket(PF_NETLINK, SOCK_RAW, 0) = -1 EMFILE (Too many open files)
socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = -1 EMFILE (Too many open files)
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = -1 EMFILE (Too many open files)
socket(PF_NETLINK, SOCK_RAW, 0) = -1 EMFILE (Too many open files)
socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) = -1 EMFILE (Too many open files)
socket(PF_NETLINK, SOCK_RAW, 0) = -1 EMFILE (Too many open files)
socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) = -1 EMFILE (Too many open files)
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({1, 0}, 0x7fffeb2c3570) = 0
Ce qui nous donne la règle de détection suivante :
-a always,exit -F arch=b64 -S socket -F exit=-EMFILE -k nofile
Les règles de détection (trop de fichiers ouverts)
Après quelques recherches dans les pages de manuels, nous aboutissons aux règles d’audit suivantes, qui prennent en compte les différents appels systèmes pouvant rencontrer cette erreur :
-a always,exit -F arch=b64 -S open -S openat -S dup -F exit=-EMFILE -k nofile
-a always,exit -F arch=b32 -S open -S openat -S dup -F exit=-EMFILE -k nofile
-a always,exit -F arch=b64 -S socket -F exit=-EMFILE -k nofile
-a always,exit -F arch=b32 -S socketcall -F a0=1 -F exit=-EMFILE -k nofile
Nous avons ajouté l’appel système openat
(ouverture d’un fichier dans
un répertoire spécifique) et les appels équivalents en 32 bits.
Trop de processus
Deuxième erreur très classique, trop de processus lancés en parallèle. Là aussi, nous allons reproduire l’erreur pour identifier les paramètres des règles d’audit :
BEGIN {
# Lancer 1024 sous-processus « yes »
for ( I = 1 ; I <= 1024 ; I++ ){
# La chaîne contenant la commande doit être différente
# à chaque fois, sinon gawk ne va créer qu'un seul
# sous-processus
COMMANDE = "yes #" I
COMMANDE | getline
}
}
Enregistrons le programme gawk dans le fichier nproc.awk
, puis
lançons le script awk
sous strace
:
ulimit -u 512
strace gawk -f nproc.awk 2> nproc.log
Note : nous réduisons le nombre maximum de processus pour éviter de saturer le nombre maximum de fichiers ouverts avant de saturer le nombre maximum de processus…
En compulsant la sortie de strace
, on tombe sur un appel de clone
,
qui est utilisé pour créer un nouveau processus, échouant avec l’erreur
EAGAIN
(« veuillez ré-essayer plus tard ») :
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f13af5b49d0) = 3549
close(978) = 0
fcntl(977, F_SETFD, FD_CLOEXEC) = 0
ioctl(977, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffd6bb37d50) = -1 EINVAL (Invalid argument)
fstat(977, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
read(977, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = 4096
pipe([978, 979]) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f13af5b49d0) = -1 EAGAIN (Resource temporarily unavailable)
Comme précédemment, on peut en déduire (après quelques recherches complémentaires) les règles d’audit à appliquer :
-a always,exit -F arch=b64 -S clone -S fork -S vfork -F exit=-EAGAIN -k nproc
-a always,exit -F arch=b32 -S clone -S fork -S vfork -F exit=-EAGAIN -k nproc
Les règles de détection (finales)
En conclusion, voici les règles d’audit à appliquer pour détecter à la fois le dépassement du nombre maximum de fichiers ouverts et le dépassement du nombre maximum de processus :
-a always,exit -F arch=b64 -S open -S openat -S dup -F exit=-EMFILE -k nofile
-a always,exit -F arch=b32 -S open -S openat -S dup -F exit=-EMFILE -k nofile
-a always,exit -F arch=b64 -S socket -F exit=-EMFILE -k nofile
-a always,exit -F arch=b32 -S socketcall -F a0=1 -F exit=-EMFILE -k nofile
-a always,exit -F arch=b64 -S clone -S fork -S vfork -F exit=-EAGAIN -k nproc
-a always,exit -F arch=b32 -S clone -S fork -S vfork -F exit=-EAGAIN -k nproc