Je ne peux pas ouvrir / proc / self / oom_score_adj quand j’ai la bonne capacité

J’essaie de définir le réglage du score de tueur OOM pour un processus, inspiré par oom_adjust_setup dans le oom_adjust_setup d’OpenSSH . Pour ce faire, j’ouvre /proc/self/oom_score_adj , lit l’ancienne valeur et écrit une nouvelle valeur. De toute évidence, mon processus doit être root ou avoir la capacité CAP_SYS_RESOURCE pour le faire.

Je reçois un résultat que je ne peux pas expliquer. Lorsque mon processus n’a pas la capacité, je suis capable d’ouvrir ce fichier et de lire et d’écrire des valeurs, bien que la valeur que j’écris ne prenne pas effet (assez juste):

 $ ./a.out CAP_SYS_RESOURCE: not effective, not permitted, not inheritable oom_score_adj value: 0 wrote 5 bytes oom_score_adj value: 0 

Mais lorsque mon processus a la capacité, je ne peux même pas ouvrir le fichier: il échoue avec EACCES:

 $ sudo setcap CAP_SYS_RESOURCE+eip a.out $ ./a.out CAP_SYS_RESOURCE: effective, permitted, not inheritable failed to open /proc/self/oom_score_adj: Permission denied 

Pourquoi ça fait ça? Qu’est-ce que je rate?


Un peu plus de googling m’a conduit à ce poste de lkml par Azat Khuzhin le 20 octobre 2013 . Apparemment, CAP_SYS_RESOURCE vous permet de changer oom_score_adj pour n’importe quel processus mais vous-même. Pour modifier votre propre ajustement de score, vous devez le combiner avec CAP_DAC_OVERRIDE , c’est-à-dire désactiver les contrôles d’access pour tous les fichiers. (Si je le voulais, j’aurais fait ce programme setuid root.)

Donc, ma question est la suivante: comment puis-je y parvenir sans CAP_DAC_OVERRIDE ?


J’utilise Ubuntu xenial 16.04.4, la version 4.13.0-45-generic du kernel. Mon problème est similaire mais différent de cette question : il s’agit d’une erreur d’ write , lorsque vous n’avez pas la capacité.

Mon exemple de programme:

 #include  #include  #include  #include  void read_value(FILE *fp) { int value; rewind(fp); if (fscanf(fp, "%d", &value) != 1) { fprintf(stderr, "read failed: %s\n", ferror(fp) ? strerror(errno) : "cannot parse"); } else { fprintf(stderr, "oom_score_adj value: %d\n", value); } } void write_value(FILE *fp) { int result; rewind(fp); result = fprintf(fp, "-1000"); if (result < 0) { fprintf(stderr, "write failed: %s\n", strerror(errno)); } else { fprintf(stderr, "wrote %d bytes\n", result); } } int main() { FILE *fp; struct __user_cap_header_struct h; struct __user_cap_data_struct d; h.version = _LINUX_CAPABILITY_VERSION_3; h.pid = 0; if (0 != capget(&h, &d)) { fprintf(stderr, "capget failed: %s\n", strerror(errno)); } else { fprintf(stderr, "CAP_SYS_RESOURCE: %s, %s, %s\n", d.effective & (1 << CAP_SYS_RESOURCE) ? "effective" : "not effective", d.permitted & (1 << CAP_SYS_RESOURCE) ? "permitted" : "not permitted", d.inheritable & (1 << CAP_SYS_RESOURCE) ? "inheritable" : "not inheritable"); } fp = fopen("/proc/self/oom_score_adj", "r+"); if (!fp) { fprintf(stderr, "failed to open /proc/self/oom_score_adj: %s\n", strerror(errno)); return 1; } else { read_value(fp); write_value(fp); read_value(fp); fclose(fp); } return 0; } 

Celui-ci était très intéressant à craquer, m’a pris du temps.

Le premier indice était cette réponse à une autre question: https://unix.stackexchange.com/questions/364568/how-to-read-the-proc-pid-fd-directory-of-a-process-which- a-a-linux-capabil – je voulais juste donner le crédit.

La raison pour laquelle cela ne fonctionne pas tel quel

La vraie raison pour laquelle vous obtenez une “permission refusée”, il y a des fichiers sous /proc/self/ sont la propriété de root si le processus a des capacités – il ne s’agit pas de CAP_SYS_RESOURCE ou des fichiers oom_* spécifiquement. Vous pouvez vérifier cela en appelant stat et en utilisant des capacités différentes. Citant l’ man 5 proc :

/ proc / [pid]

Il existe un sous-répertoire numérique pour chaque processus en cours d’exécution. le sous-répertoire est nommé par l’ID du processus.

Chaque sous-répertoire / proc / [pid] contient les pseudo-fichiers et répertoires décrits ci-dessous. Ces fichiers appartiennent normalement à l’utilisateur effectif et à l’ID de groupe effectif du processus. Toutefois, en tant que mesure de sécurité, le propriétaire est root: root si l’atsortingbut “dumpable” du processus est défini sur une valeur autre que 1. Cet atsortingbut peut changer pour les raisons suivantes:

  • L’atsortingbut a été explicitement défini via l’opération prctl (2) PR_SET_DUMPABLE.

  • L’atsortingbut a été réinitialisé à la valeur dans le fichier / proc / sys / fs / suid_dumpable (décrit ci-dessous), pour les raisons décrites dans prctl (2).

La réinitialisation de l’atsortingbut “dumpable” à 1 ramène la propriété des fichiers / proc / [pid] / * au véritable UID et au vrai GID du processus.

Cela donne déjà une idée de la solution, mais commençons par creuser un peu plus et voyons que man prctl :

PR_SET_DUMPABLE (depuis Linux 2.3.20)

Définit l’état de l’indicateur “dumpable”, qui détermine si des sauvegardes core sont générées pour le processus appelant à la livraison d’un signal dont le comportement par défaut est de produire un core dump.

Dans les kernelx jusqu’au 2.6.12 inclus, arg2 doit être 0 (SUID_DUMP_DISABLE, le processus n’est pas vidable) ou 1 (SUID_DUMP_USER, le processus est vidable). Entre les kernelx 2.6.13 et 2.6.17, la valeur 2 était également permise, ce qui entraînait que tout fichier binary qui normalement ne serait pas sauvegardé soit lisible par root uniquement; pour des raisons de sécurité, cette fonctionnalité a été supprimée. (Voir aussi la description de / proc / sys / fs / suid_dumpable dans proc (5).)

Normalement, cet indicateur est défini sur 1. Cependant, il est réinitialisé à la valeur actuelle contenue dans le fichier / proc / sys / fs / suid_dumpable (qui par défaut a la valeur 0), dans les circonstances suivantes:

  • L’ID utilisateur ou de groupe effectif du processus est modifié.

  • L’identifiant utilisateur ou groupe du système de fichiers du processus est modifié (voir informations d’identification (7)).

  • Le processus exécute (execve (2)) un programme set-user-ID ou set-group-ID, entraînant une modification de l’ID utilisateur effectif ou de l’ID groupe effectif.

  • Le processus exécute (execve (2)) un programme doté de fonctionnalités de fichiers (voir capacités (7)), mais uniquement si les capacités autorisées sont supérieures à celles déjà autorisées pour le processus.

Les processus qui ne peuvent pas être vidés ne peuvent pas être attachés via ptrace (2) PTRACE_ATTACH; voir ptrace (2) pour plus de détails.

Si un processus n’est pas vidable, la propriété des fichiers dans le répertoire / proc / [pid] du processus est affectée comme décrit dans proc (5).

Maintenant, tout est clair: notre processus n’a pas de capacité que le shell utilisé pour le lancer, donc l’atsortingbut dumpable a été défini sur false, donc les fichiers sous /proc/self/ sont la propriété de root plutôt que de l’utilisateur actuel.

Comment le faire fonctionner

Le correctif est aussi simple que de redéfinir cet atsortingbut de vidage avant d’essayer d’ouvrir le fichier. Collez ce qui suit ou quelque chose de similaire avant d’ouvrir le fichier:

 prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); 

J’espère que cela pourra aider 😉

Ce n’est pas une réponse ( dvk a déjà fourni la réponse à la question posée), mais un commentaire étendu décrivant les effets secondaires souvent négligés, voire très dangereux, de la réduction de /proc/self/oom_score_adj .

En résumé, l’utilisation de prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) permettra à un processus ayant la capacité CAP_SYS_RESOURCE (transmise via des capacités de système de fichiers, par exemple) de modifier oom_score_adj de tout autre processus appartenant au même utilisateur, y compris le leur.

(Par défaut, un processus doté de fonctionnalités ne peut pas être vidé, un core dump n’est donc pas généré même si le processus est tué par un signal dont la disposition doit générer un core.)

Les dangers que je voudrais commenter sont la façon dont la gamme oom_score_adj est héritée et ce que cela signifie de le changer pour les processus qui créent des processus enfants. (Merci à dvk pour quelques corrections.)


Le kernel Linux conserve une valeur interne, oom_score_adj_min , pour chaque processus. L’utilisateur (ou le processus lui-même) peut modifier le oom_score_adj à n’importe quelle valeur entre oom_score_adj_min et OOM_SCORE_ADJ_MAX . Plus la valeur est élevée, plus le processus risque d’être tué.

Lorsqu’un processus est créé, il hérite de son oom_score_adj_min de son parent. Le parent d’origine de tous les processus, init, a un initial oom_score_adj_min de 0.

Pour réduire le oom_score_adj sous oom_score_adj_min , un processus qui possède des privilèges de superutilisateur ou possède le paramètre CAP_SYS_RESOURCE et peut être vidé, écrit le nouveau score dans /proc/PID/oom_score_adj . Dans ce cas, oom_score_adj_min est également défini sur la même valeur.

(Vous pouvez le vérifier en examinant fs / proc / base.c: __ set_oom_adj () dans le kernel Linux; voir les affectations à task->signal->oom_score_adj_min .)

Le problème est que la valeur oom_score_adj_min , sauf en cas de mise à jour par un processus doté de la capacité CAP_SYS_RESOURCE. (Note: Au départ, je pensais que ça ne pouvait pas être soulevé du tout, mais j’avais tort.)

Par exemple, si vous avez un démon de service à valeur élevée dont l’ oom_score_adj_min réduit, exécuté sans la capacité CAP_SYS_RESOURCE, augmenter le oom_score_adj avant de oom_score_adj les processus enfants entraînera l’inheritance des nouveaux oom_score_adj , mais oom_score_adj_min origine. Cela signifie que ces processus enfants peuvent réduire leur oom_score_adj à celui de leur démon de service parent, sans privilèges ni capacités.

(Parce qu’il ya seulement deux mille et une valeurs possibles oom_score_adj ( -1000 à 1000 inclus), et que seulement un millier de ces valeurs réduisent les chances qu’un processus soit tué (les valeurs négatives, zéro étant la valeur par défaut) par rapport à “default “, un processus néfaste doit seulement faire dix ou onze écritures dans /proc/self/oom_score_adj pour que le tueur de MOO l’évite le plus possible, en utilisant une recherche binary: tout d’abord, il essaiera -500. Si cela réussit, oom_score_adj_min est compris entre -1000 et -500. S’il échoue, oom_score_adj_min situe entre -499 et 1000. En réduisant de moitié la plage à chaque tentative, il peut définir oom_score_adj sur le minimum interne du kernel pour ce processus, oom_score_adj_min , sur dix ou onze écrit selon la valeur initiale de oom_score_adj .)


Bien sûr, il existe des mesures d’atténuation et des stratégies pour éviter le problème de l’inheritance.

Par exemple, si vous avez un processus important que le tueur de MOO doit laisser seul, qui ne doit pas créer de processus enfants, vous devez l’exécuter à l’aide d’un compte utilisateur dédié dont la valeur est RLIMIT_NPROC .

Si vous avez un service qui crée de nouveaux processus enfants, mais que vous souhaitez que le parent soit moins susceptible d’être tué que les autres processus, et que vous ne souhaitiez pas que les enfants en héritent, il existe deux approches.

  1. Votre service peut, au démarrage, créer un processus enfant pour créer d’autres processus enfants, avant de oom_score_adj son oom_score_adj . Cela fait que les processus enfants héritent de leurs oom_score_adj_min (et oom_score_adj ) du processus qui a démarré le service.

  2. Votre service peut conserver CAP_SYS_RESOURCE dans l’ensemble CAP_PERMITTED , mais l’append ou le supprimer de l’ensemble CAP_EFFECTIVE si nécessaire.

    Lorsque CAP_SYS_RESOURCE est dans l’ensemble CAP_EFFECTIVE , l’ajustement de oom_score_adj définit également oom_score_adj_min sur cette même valeur.

    Lorsque CAP_SYS_RESOURCE n’est pas dans l’ensemble CAP_EFFECTIVE , vous ne pouvez pas décrémenter oom_score_adj sous l’ oom_score_adj_min correspondant. oom_score_adj_min est inchangé même si oom_score_adj est modifié.

Il est logique de placer le travail pouvant être annulé / tué dans une situation de MOO dans des processus enfants avec des valeurs oom_score_adj plus élevées. Si une situation de MOO se produit, par exemple sur une appliance embarquée, le démon de service principal a beaucoup plus de chances de survivre, même lorsque les processus fils de travail sont supprimés. Bien sûr, le démon central ne devrait pas allouer de mémoire dynamic en réponse aux demandes des clients, car tout bogue dans ce démon ne ferait pas que planter ce démon, mais mettrait fin à tout le système (dans une situation de MOO cause, le démon de base, est tué).