changer / proc / PID / environ après le démarrage du processus

11
$ k=v p &
[1] 3028

existe-t-il un moyen pde modifier le contenu de /proc/3028/environne pas le mentionner k=v pendant qu'il p est toujours en cours d'exécution?

Cetin Sert
la source
Avez-vous essayé de modifier le fichier?
123
De quel fichier vous demandez-vous?
ctrl-alt-delor

Réponses:

12

Sous Linux, vous pouvez remplacer la valeur des chaînes d'environnement sur la pile.

Vous pouvez donc masquer l'entrée en la remplaçant par des zéros ou toute autre chose:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char* argv[], char* envp[]) {
  char cmd[100];

  while (*envp) {
    if (strncmp(*envp, "k=", 2) == 0)
      memset(*envp, 0, strlen(*envp));

    envp++;
  }

  sprintf(cmd, "cat /proc/%u/environ", getpid());

  system(cmd);
  return 0;
}

Courir comme:

$ env -i a=foo k=v b=bar ./wipe-env | hd
00000000  61 3d 66 6f 6f 00 00 00  00 00 62 3d 62 61 72 00  |a=foo.....b=bar.|
00000010

le k=va été remplacé par \0\0\0.

Notez que setenv("k", "", 1)pour écraser la valeur ne fonctionnera pas comme dans ce cas, une nouvelle "k="chaîne est allouée.

Si vous n'avez pas autrement modifié la kvariable d'environnement avec setenv()/ putenv(), vous devriez également pouvoir faire quelque chose comme ça pour obtenir l'adresse de la k=vchaîne sur la pile (enfin, l'une d'entre elles):

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(int argc, char* argv[]) {
  char cmd[100];
  char *e = getenv("k");

  if (e) {
    e -= strlen("k=");
    memset(e, 0, strlen(e));
  }

  sprintf(cmd, "cat /proc/%u/environ", getpid());

  system(cmd);
  return 0;
}

Notez cependant qu'il supprime une seule des k=ventrées reçues dans l'environnement. Habituellement, il n'y en a qu'un, mais rien n'empêche quiconque de passer les deux k=v1et k=v2(ou k=vdeux) dans la liste env passée à execve(). Cela a été la cause de failles de sécurité dans le passé, comme CVE-2016-2381 . Cela peut réellement se produire bashavant le shellshock lors de l'exportation à la fois d'une variable et d'une fonction du même nom.

Dans tous les cas, il y aura toujours une petite fenêtre pendant laquelle la chaîne env var n'a pas encore été surchargée, donc vous voudrez peut-être trouver un autre moyen de transmettre les informations secrètes à la commande (comme un tube par exemple) si vous l'exposez via /proc/pid/environest une préoccupation.

Notez également que contrairement à /proc/pid/cmdline, /proc/pid/environmentn'est accessible que par des processus avec le même euid ou racine (ou root uniquement si l'euid et le ruid du processus ne sont pas les mêmes).

Vous pouvez leur cacher cette valeur /proc/pid/environ, mais ils peuvent toujours obtenir toute autre copie que vous avez faite de la chaîne en mémoire, par exemple en y attachant un débogueur.

Voir https://www.kernel.org/doc/Documentation/security/Yama.txt pour savoir comment empêcher au moins les utilisateurs non root de le faire.

Stéphane Chazelas
la source
8

Il n'a pas été nécessaire d'écraser les chaînes ci-dessus (pas vraiment sur ) la pile du thread principal sous Linux depuis 2010.

Les deux /proc/self/cmdlineet /proc/self/environsont modifiables par le processus lui-même à l'exécution, à force d'appeler la prctl()fonction avec respectivement PR_SET_MM_ARG_START+ PR_SET_MM_ARG_ENDou PR_SET_MM_ENV_START+ PR_SET_MM_ENV_END. Ceux-ci placent directement les pointeurs de mémoire dans l'espace mémoire d'application du processus, détenu par le noyau pour chaque processus, qui sont utilisés pour récupérer le contenu de /proc/${PID}/cmdlineet /proc/${PID}/environ, et donc la ligne de commande et l'environnement signalés par la pscommande.

Il suffit donc de construire un nouvel argument ou une nouvelle chaîne d'environnement (pas de vecteur, remarquez - la mémoire pointée doit être les données de chaîne réelles, concaténées et délimitées) et d'indiquer au noyau où il se trouve.

Ceci est documenté dans la page de manuel Linux pour la prctl(2)fonction ainsi que dans la environ(7)page de manuel. Ce qui n'est pas documenté, c'est que le noyau rejette toute tentative de définir l'adresse de début au-dessus de l'adresse de fin, ou l'adresse de fin sous l'adresse de début; ou pour (re) remettre à zéro l'une ou l'autre des adresses. De plus, ce n'est pas le mécanisme original proposé par Bryan Donlan en 2009, qui permettait de régler atomiquement le début et la fin en une seule opération. De plus, le noyau ne fournit aucun moyen d' obtenir les valeurs actuelles de ces pointeurs.

Il est donc difficile de modifier l'environnement et les zones de ligne de commande avec prctl(). Il faut appeler la prctl()fonction jusqu'à quatre fois car les premières tentatives peuvent entraîner des tentatives pour définir le pointeur de début plus haut que le pointeur de fin, selon l'endroit où les anciennes et les nouvelles données sont en mémoire. Il faut appeler un autre quatre fois si l' on veut faire en sorte que cela ne se traduit pas par une fenêtre d'opportunité pour d' autres processus sur le système pour inspecter une gamme arbitraire du processus espace mémoire dans la période où le nouveau début / fin a été définie mais la nouvelle fin / début ne l'a pas été.

Un appel système atomique unique qui définit la gamme entière en une seule fois aurait été beaucoup plus facile à utiliser en toute sécurité pour les programmes d'applications.

Une autre ride est que, sans vraiment bonne raison (compte tenu des contrôles dans le noyau, le overwritability des zones de données originales de toute façon , et le fait que les équivalents ne sont pas des opérations privilégiées sur l' un des BSDs), sur Linux , cela nécessite super - utilisateur privilèges.

J'ai écrit assez simple setprocargv()et des setprocenvv()fonctions pour mes boîtes à outils, qui emploient cela. Les programmes de chargement en chaîne à partir des jeux d'outils qui sont intégrés, comme setenvet foreground, reflètent donc les arguments de commande enchaînés et l'environnement, où Linux le permet.

# / package / admin / nosh / command / clearenv setenv WIBBLE wobble premier plan pause \; vrai &
[1] 1057
# hexdump -C / proc / 1057 / cmdline
00000000 66 6f 72 65 67 72 6f 75 6e 64 00 70 61 75 73 65 | pause au premier plan |
00000010 00 3b 00 74 72 75 65 00 |.;. True. |
00000018
# hexdump -C / proc / 1057 / environ
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 | WIBBLE = wobble. |
0000000e
# hexdump -C / proc / 1058 / cmdline
00000000 70 61 75 73 65 00 | pause. |
00000006
# hexdump -C / proc / 1058 / environ
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 | WIBBLE = wobble. |
0000000e
# 

Notez que cela ne milite pas contre les choses qui tracent le processus et accèdent directement à sa mémoire par d'autres moyens (plutôt que via ces deux pseudo-fichiers), et laisse bien sûr une fenêtre avant que les chaînes ne soient modifiées où ces informations peuvent être vues, juste comme l'écrasement des données au-dessus de la pile du thread principal. Et tout comme pour l'écrasement des données, cela ne tient pas compte des bibliothèques d'exécution de langage qui font des copies de l'environnement (sur le tas) dans diverses circonstances. En général, ne considérez pas cela comme un aussi bon mécanisme pour transmettre des "secrets" à un programme que (par exemple) le fait d'hériter d'un descripteur de fichier ouvert à la fin de lecture d'un canal sans nom, lu dans un tampon d'entrée entièrement sous votre contrôle que vous essuyez ensuite.

Lectures complémentaires

JdeBP
la source
2
Depuis le noyau 3.18, il est possible d'utiliser PR_SET_MM_MAP qui prend une struct prctl_mm_map et ne nécessite pas de root.
filbranden
2
JdeBP, @filbranden Depuis le noyau 3.5 , vous pouvez lire les valeurs actuelles des pointeurs env / argv de /proc/$pid/stat( en plus d' autres valeurs que vous pourriez avoir besoin struct prctl_mm_map). Voir aussi mon exemple filter_env.c pour une petite démo. JdeBP, pouvez-vous ajouter des liens vers vos setprocargv()/ setprocenvv()fonctions?
maxschlepzig