Est-ce une bonne idée d'appeler des commandes shell à partir de C?

50

Il y a une commande shell unix udevadm info -q path -n /dev/ttyUSB2que je veux appeler depuis un programme C. Avec probablement environ une semaine de lutte, je pourrais le ré-appliquer moi-même, mais je ne veux pas le faire.

Est-ce une bonne pratique largement acceptée pour moi d'appeler popen("my_command", "r");, ou est-ce que cela introduira des problèmes de sécurité inacceptables et des problèmes de compatibilité? Je me sens mal de faire quelque chose comme ça, mais je ne peux pas comprendre du doigt pourquoi ce serait mauvais.

johnny_boy
la source
8
Les attaques par injection sont une chose à considérer.
MetaFight le
8
Je pensais principalement au cas où vous avez fourni une entrée utilisateur en tant qu'argument de commande shell.
MetaFight le
8
Quelqu'un pourrait-il placer un udevadmfichier exécutable malveillant dans le même répertoire que votre programme et l'utiliser pour élever les privilèges? Je ne connais pas grand chose à la sécurité Unix, mais votre programme sera-t-il exécuté setuid root?
Andrew dit de réintégrer Monica le
7
@AndrewPiliser Si le répertoire en cours n'est pas sur le PATH, alors non - il devrait être exécuté avec ./udevadm. IIRC Windows exécuterait cependant les exécutables du même répertoire.
Izkata
4
Dans la mesure du possible, appelez directement le programme souhaité sans passer par le shell.
CodesInChaos

Réponses:

59

Ce n'est pas particulièrement grave, mais il y a quelques réserves.

  1. Dans quelle mesure votre solution sera-t-elle portable? Le fichier binaire choisi fonctionnera-t-il partout de la même manière, affichera les résultats dans le même format, etc.? La sortie sera-t-elle différemment sur les réglages de LANGetc.?
  2. combien de charge supplémentaire cela ajoute-t-il à votre processus? Déchiffrer un fichier binaire entraîne beaucoup plus de charge et nécessite plus de ressources que l'exécution d'appels à la bibliothèque (en général). Est-ce acceptable dans votre scénario?
  3. Y a-t-il des problèmes de sécurité? Quelqu'un peut-il remplacer votre binaire choisi par un autre et effectuer des actes néfastes par la suite? Utilisez-vous des arguments fournis par l'utilisateur pour votre fichier binaire, et pourraient-ils fournir ;rm -rf /(par exemple) (notez que certaines API vous permettent de spécifier des arguments de manière plus sécurisée que de simplement les fournir sur la ligne de commande)

Je suis généralement heureux d’exécuter des fichiers binaires lorsque je suis dans un environnement connu que je peux prédire, lorsque la sortie binaire est facile à analyser (si nécessaire - vous n’avez peut-être besoin que d’un code de sortie) et je n’ai pas besoin de le faire aussi. souvent.

Comme vous l'avez noté, l'autre problème est de savoir combien de travail est nécessaire pour répliquer ce que le binaire fait. Utilise-t-il une bibliothèque que vous pouvez également utiliser?

Brian Agnew
la source
13
Forking a binary results in a lot more load and requires more resources than executing library calls (generally speaking). Si cela était sur Windows, je serais d'accord. Cependant, l'OP spécifie Unix où le coût du forking est suffisamment bas pour qu'il soit difficile de dire si le chargement dynamique d'une bibliothèque est pire que le forking.
Wallyk
1
Je m'attendrais normalement à ce qu'une bibliothèque soit chargée une fois , alors que le binaire peut être exécuté plusieurs fois. Comme toujours, cela dépend des scénarios d'utilisation, mais il doit être pris en compte (si licencié par la suite)
Brian Agnew Le
3
Il n'y a pas de "forking" sous Windows, la citation doit donc être spécifique à Unix.
Cody Grey
5
Le noyau NT prend en charge le forgeage, bien qu'il ne soit pas exposé via Win32. Quoi qu'il en soit, je crois que le coût de l' exécution d' un programme externe viendrait plus de la execque de la fork. Chargement de sections, liens dans des objets partagés, exécution du code init pour chacun. Et ensuite, avoir à convertir toutes les données en texte à analyser de l'autre côté, à la fois pour les entrées et les sorties.
spectras
8
Pour être juste, udevadm info -q path -n /dev/ttyUSB2ne fonctionnera pas sous Windows de toute façon, donc toute la discussion sur la fourche est un peu théorique.
MSalters
37

Il est extrêmement prudent de se prémunir contre les vulnérabilités liées à l’injection une fois que vous avez introduit un vecteur potentiel. C’est au centre de vos préoccupations maintenant, mais plus tard, vous aurez peut-être besoin de la possibilité de choisir ttyUSB0-3. Cette liste sera utilisée ailleurs, afin que le principe de la responsabilité unique soit appliqué, puis qu'un client sera tenu de mettre un périphérique arbitraire dans la liste et le développeur qui effectue cette modification n'aura aucune idée du fait que la liste sera finalement utilisée de manière non sécurisée.

En d'autres termes, code comme si le développeur le plus imprudent que vous connaissez effectue un changement dangereux dans une partie du code apparemment sans rapport.

Deuxièmement, les outils de la CLI ne sont généralement pas considérés comme des interfaces stables, à moins que la documentation les marque spécifiquement. Vous pouvez peut-être compter sur eux pour un script que vous exécutez et que vous pouvez résoudre vous-même, mais pas pour quelque chose que vous déployez chez un client.

Troisièmement, si vous souhaitez extraire facilement une valeur de votre logiciel, il est fort probable que quelqu'un d'autre le souhaite également. Cherchez une bibliothèque qui fait déjà ce que vous voulez. libudevétait déjà installé sur mon système:

#include <libudev.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    struct stat statbuf;

    if (stat("dev/ttyUSB2", &statbuf) < 0)
        return -1;
    struct udev* udev = udev_new();
    struct udev_device *dev = udev_device_new_from_devnum(udev, 'c', statbuf.st_rdev);

    printf("%s\n", udev_device_get_devpath(dev));

    udev_device_unref(dev);
    udev_unref(udev);
    return 0;
}

Il existe d'autres fonctionnalités utiles dans cette bibliothèque. À mon avis, si vous en aviez besoin, certaines fonctions connexes pourraient également s'avérer utiles.

Karl Bielefeldt
la source
2
MERCI. J'ai passé si longtemps à chercher libudev. Je ne pouvais pas le trouver!
johnny_boy
2
Je ne vois pas en quoi les "vulnérabilités d'injection" sont un problème ici à moins que le programme d'OP ne soit appelé lorsqu'un autre utilisateur contrôle son environnement, ce qui est principalement le cas en cas de suid (également éventuellement, mais dans une moindre mesure, comme cgi et ssh commande forcée). Il n'y a aucune raison pour que les programmes normaux doivent être durcis contre l'utilisateur qu'ils exécutent.
R ..
2
@R ..: La "vulnérabilité d'injection" se produit lorsque vous généralisez le ttyUSB2et utilisez sprintf(buf, "udevadm info -q path -n /dev/%s", argv[1]). Maintenant, cela semble assez sûr, mais que argv[1]se passe- "nul || echo OOPS"t -il ?
MSalters
3
@R ..: vous avez juste besoin de plus d'imagination. D'abord c'est une chaîne fixe, ensuite c'est un argument fourni par l'utilisateur, ensuite c'est un argument fourni par un autre programme exécuté par l'utilisateur mais qu'ils ne comprennent pas, puis c'est un argument que leur navigateur a extrait d'une page Web. Si les choses ne cessent de se détériorer, il faut que quelqu'un finisse par assumer la responsabilité de l'assainissement des intrants, et Karl dit qu'il faut prendre un soin extrême pour identifier ce point, où qu'il se produise.
Steve Jessop
6
Même si le principe de précaution était vraiment "supposons que le développeur le plus imprudent que vous sachiez fasse un changement dangereux", et que je sois obligé d'écrire du code maintenant pour garantir que cela ne donnerait pas lieu à un exploit, alors, personnellement, je ne pouvais pas me lever du lit. Les la plupart des développeurs imprudents que je connais font paraître Machievelli comme il est même pas essayer .
Steve Jessop
16

Dans votre cas spécifique, où vous souhaitez invoquer udevadm, je soupçonne que vous pourriez vous connecter en udevtant que bibliothèque et faire les appels de fonction appropriés en guise d'alternative?

Par exemple, vous pouvez jeter un coup d'œil à ce que fait udevadm quand il appelle en mode "info": https://github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.c et faire des appels équivalents quant à ceux udevadm fait.

Cela éviterait beaucoup de compromis négatifs énumérés dans l'excellente réponse de Brian Agnew - par exemple, ne pas compter sur le binaire existant sur une certaine trajectoire, éviter les frais de forking, etc.

Adam Krouskop
la source
Merci, j'ai trouvé ce compte-rendu exact, et j'ai fini par le regarder un peu.
johnny_boy
1
… Et libudev n'est pas particulièrement difficile à utiliser. Sa gestion des erreurs est un peu absente, mais énumérer, interroger et surveiller les API est assez simple. Si vous essayez, la lutte que votre peur pourrait engendrer pourrait prendre moins de 2 heures.
spectras
7

Votre question semblait appeler une réponse de la forêt, et les réponses ici ressemblent à des réponses d'arbres, alors j'ai pensé vous donner une réponse de la forêt.

C’est très rarement la façon dont les programmes C sont écrits. C'est toujours comment les scripts shell sont écrits, et parfois comment les programmes Python, perl ou Ruby sont écrits.

Les utilisateurs écrivent généralement en C pour une utilisation facile des bibliothèques système et un accès direct de bas niveau aux appels système du système d'exploitation, ainsi que pour la rapidité. C étant un langage difficile à écrire, si les utilisateurs n'en ont pas besoin, ils n'utilisent pas C. De plus, les programmes C ne doivent généralement dépendre que de bibliothèques partagées et de fichiers de configuration.

Faire appel à un sous-processus n'est pas particulièrement rapide, il ne nécessite pas un accès fin et contrôlé aux installations système de bas niveau, et il introduit une dépendance éventuellement surprenante à un exécutable externe. Il est donc rare de voir dans les programmes en C.

Il y a quelques préoccupations supplémentaires. Les problèmes de sécurité et de portabilité que les gens mentionnent sont tout à fait valables. Bien sûr, ils sont également valables pour les scripts shell, mais les gens s’attendent à ce genre de problèmes dans les scripts shell. Mais les programmes C ne sont généralement pas censés présenter ce type de problème de sécurité, ce qui le rend plus dangereux.

Mais, à mon avis, la plus grande préoccupation concerne la manière dont le système popenva interagir avec le reste de votre programme. popendoit créer un processus enfant, lire sa sortie et collecter son statut de sortie. Dans l'intervalle, le processus stderr de ce processus sera connecté au même réseau que votre programme, ce qui peut entraîner des résultats confus, et son stdin sera identique à votre programme, ce qui pourrait entraîner d'autres problèmes intéressants. Vous pouvez résoudre ce problème en incluant </dev/null 2>/dev/nulldans la chaîne à laquelle vous passez popencar elle est interprétée par le shell.

Et popencrée un processus enfant. Si vous faites vous-même quelque chose avec des processus de traitement du signal ou de traitement du signal, vous risquez de recevoir des SIGCHLDsignaux étranges . Vos appels à waitpeuvent interagir étrangement popenet éventuellement créer d’étranges conditions de concurrence.

Les problèmes de sécurité et de portabilité sont bien sûr présents. Comme pour les scripts shell ou tout ce qui lance d'autres exécutables sur le système. Et vous devez faire attention que les personnes utilisant votre programme ne sont pas en mesure d'obtenir des méta-charcaters shell dans la chaîne que vous passez en popencar cette chaîne est donnée directement shavec sh -c <string from popen as a single argument>.

Mais je ne pense pas que ce soit la raison pour laquelle il est étrange de voir un programme C utiliser popen. La raison pour laquelle c'est étrange, c'est parce que C est généralement un langage de bas niveau et popenn'est pas un bas niveau. Et parce que l'utilisation de popencontraintes de conception sur votre programme entraîne des interactions étranges avec les entrées et sorties standard de votre programme, ce qui complique la gestion de votre propre processus ou de la gestion du signal. Et parce que les programmes C ne sont généralement pas censés avoir des dépendances sur les exécutables externes.

Très varié
la source
0

Votre programme peut être sujet au piratage, etc. Une façon de vous protéger de ce type d'activité consiste à créer une copie de votre environnement informatique actuel et à exécuter votre programme à l'aide d'un système appelé chroot.

Du point de vue de votre programme, il s'exécute dans un environnement normal, du point de vue de la sécurité, si quelqu'un casse votre programme, il n'a accès qu'aux éléments que vous avez fournis lors de la copie.

Une telle configuration s'appelle une prison chroot. Pour plus de détails, reportez-vous à la rubrique prison chroot .

Il est normalement utilisé pour configurer des serveurs accessibles au public, etc.

Dave
la source
3
L'OP est en train d'écrire un programme que d'autres personnes peuvent exécuter, sans jouer avec des fichiers binaires non approuvés ou des sources sur son propre système.
Peter Cordes