È una buona idea chiamare i comandi di shell da C?

49

C'è un comando shell unix ( udevadm info -q path -n /dev/ttyUSB2 ) che voglio chiamare da un programma C. Con probabilmente circa una settimana di lotta, potrei ri-implementarlo da solo, ma non voglio farlo.

Per me è ampiamente accettata una buona pratica per chiamare solo popen("my_command", "r"); , o che introdurrà problemi di sicurezza inaccettabili e inoltrerà problemi di compatibilità? Mi sembra sbagliato fare qualcosa del genere, ma non riesco a capire perché sarebbe male.

    
posta johnny_boy 19.06.2017 - 16:58
fonte

5 risposte

58

Non è particolarmente brutto, ma ci sono alcuni avvertimenti.

  1. quanto sarà portatile la tua soluzione? Il tuo binario scelto funzionerà lo stesso ovunque, produrrà i risultati nello stesso formato, ecc.? Produrrà in modo diverso sulle impostazioni di LANG ecc.?
  2. quanto carico extra questo aggiunge al tuo processo? Il bending di un binario comporta un carico molto maggiore e richiede più risorse rispetto all'esecuzione di chiamate di libreria (in generale). È accettabile nel tuo scenario?
  3. Ci sono problemi di sicurezza? Qualcuno può sostituire il tuo binario scelto con un altro e compiere azioni nefande in seguito? Utilizzi gli argomenti forniti dall'utente per il tuo binario e potrebbero fornire ;rm -rf / (ad esempio) (nota che alcune API ti permetteranno di specificare gli argomenti in modo più sicuro che non fornirli sulla riga di comando)

In genere sono contento di eseguire i binari quando sono in un ambiente noto che posso prevedere, quando l'output binario è facile da analizzare (se necessario, potrebbe essere sufficiente un codice di uscita) e non è necessario fallo troppo spesso

Come hai notato, l'altro problema è quanto lavoro è necessario per replicare ciò che fa il binario? Usa una libreria che puoi anche sfruttare?

    
risposta data 19.06.2017 - 17:05
fonte
37

Ci vuole estrema cautela per evitare le vulnerabilità di iniezione dopo aver introdotto un vettore potenziale. È in cima alla tua mente ora, ma in seguito potresti aver bisogno della possibilità di selezionare ttyUSB0-3 , poi quella lista sarà usata in altri posti in modo che venga presa in considerazione per seguire il principio di responsabilità singola, quindi un cliente avrà un l'obbligo di inserire un dispositivo arbitrario nell'elenco e lo sviluppatore che modifica che non ha idea che l'elenco alla fine venga utilizzato in un modo non sicuro.

In altre parole, codice come se lo sviluppatore più distratto che conosci stia apportando una modifica non sicura in una parte del codice apparentemente non correlata.

In secondo luogo, l'output degli strumenti CLI non è generalmente considerato come interfacce stabili a meno che la documentazione non li contrassegni specificamente come tali. Potresti stare bene contando su di loro per uno script che esegui che puoi risolvere da solo, ma non per qualcosa che distribuisci a un cliente.

Terzo, se vuoi un modo semplice per estrarre un valore dal tuo software, è probabile che anche qualcun altro lo voglia. Cerca una libreria che faccia già ciò che vuoi. libudev era già installato sul mio sistema:

#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;
}

Ci sono altre utili funzionalità in quella libreria. La mia ipotesi è che se ne avessi bisogno, alcune delle funzioni correlate potrebbero esserti utili anche.

    
risposta data 20.06.2017 - 01:31
fonte
16

Nel tuo caso specifico, in cui vuoi invocare udevadm , sospetto che potresti inserire udev come libreria e effettuare le chiamate alle funzioni appropriate come alternativa?

Ad esempio, puoi dare un'occhiata a ciò che udevadm sta facendo quando invochi in modalità "info": link e effettua chiamate equiv come a quelle che udevadm sta facendo.

Questo eviterebbe molti degli svantaggi-negativi enumerati nella risposta eccellente di Brian Agnew - ad es., non basandosi sul binario esistente in un determinato percorso, evitando le spese di biforcazione, ecc.

    
risposta data 19.06.2017 - 21:54
fonte
7

La tua domanda sembrava richiedere una risposta alla foresta, e le risposte qui sembrano risposte ad albero, quindi ho pensato di darti una risposta alla foresta.

Questo è molto raramente come vengono scritti i programmi C. È sempre come vengono scritti gli script della shell, e talvolta come vengono scritti i programmi Python, perl o Ruby.

Le persone tipicamente scrivono in C per un facile utilizzo delle librerie di sistema e dirigono l'accesso a basso livello alle chiamate del sistema operativo e alla velocità. E C è un linguaggio difficile da scrivere, quindi se le persone non hanno bisogno di queste cose, allora non usano C. Anche i programmi C in genere dovrebbero avere solo dipendenze su librerie condivise e file di configurazione.

L'eliminazione di un sottoprocesso non è particolarmente veloce e non richiede un accesso preciso e controllato a funzioni di sistema di basso livello e introduce una dipendenza potenzialmente sorprendente su un eseguibile esterno, quindi è non comune da vedere nei programmi C.

Ci sono alcune preoccupazioni aggiuntive. La sicurezza e la portabilità riguardano le persone menzionate sono completamente valide. Sono ugualmente validi per gli script di shell, ovviamente, ma le persone si aspettano questo tipo di problemi negli script di shell. Di solito, i programmi C non hanno questa preoccupazione per la sicurezza, il che lo rende più pericoloso.

Ma, a mio parere, le maggiori preoccupazioni riguardano il modo in cui popen interagirà con il resto del programma. popen deve creare un processo figlio, leggere il suo output e raccogliere il suo stato di uscita. Nel frattempo, quel processo 'stderr sarà connesso allo stderr stesso del tuo programma, che potrebbe causare un output confuso, e il suo stdin sarà lo stesso del tuo programma, il che potrebbe causare altri problemi interessanti. Puoi risolverlo includendo </dev/null 2>/dev/null nella stringa che passi a popen poiché è interpretata dalla shell.

E popen crea un processo figlio. Se fai qualcosa con la gestione del segnale o processi di forking tu stesso potresti finire per ottenere strani segnali di SIGCHLD . Le tue chiamate a wait potrebbero interagire in modo strano con popen e possibilmente creare strane condizioni di gara.

I problemi di sicurezza e portabilità ci sono ovviamente. Come lo sono per gli script di shell o qualsiasi cosa che avvii altri eseguibili sul sistema. E devi stare attento che le persone che usano il tuo programma non sono in grado di ottenere i meta-caratteri della shell nella stringa che passi in popen perché quella stringa è data direttamente a sh con sh -c <string from popen as a single argument> .

Ma non penso che siano il motivo per cui è strano vedere un programma in C che usa popen . Il motivo per cui è strano è perché C è in genere un linguaggio di basso livello e popen non è di basso livello. E poiché l'utilizzo di popen pone vincoli di progettazione sul tuo programma perché interagirà in modo strano con gli input e output standard del tuo programma e renderà difficile la tua gestione dei processi o la gestione dei segnali. E poiché in genere non ci si aspetta che i programmi C abbiano dipendenze da file eseguibili esterni.

    
risposta data 20.06.2017 - 17:22
fonte
0

Il tuo programma potrebbe essere soggetto a hacking, ecc. Un modo per proteggersi da questo tipo di attività è creare una copia del tuo attuale ambiente macchina ed eseguire il programma usando un sistema chiamato chroot.

Dal punto di vista del tuo programma, eseguirlo in un ambiente normale, da un aspetto di sicurezza, se qualcuno interrompe il tuo programma ha solo accesso agli elementi che hai fornito quando hai fatto la copia.

Tale configurazione è chiamata prigione chroot per maggiori dettagli vedi chroot jail .

Normalmente è usato per configurare server accessibili pubblicamente, ecc.

    
risposta data 20.06.2017 - 12:54
fonte

Leggi altre domande sui tag