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


50

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 implementarlo di nuovo da solo, ma non voglio farlo.

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


8
Una cosa da considerare è attacchi di iniezione.
MetaFight,

8
Pensavo principalmente al caso in cui hai fornito l'input dell'utente come argomento del comando shell.
MetaFight,

8
Qualcuno potrebbe mettere un udevadmeseguibile dannoso nella stessa directory del programma e usarlo per aumentare i privilegi? Non so molto sulla sicurezza di Unix, ma verrà eseguito il tuo programma setuid root?
Andrew dice Reinstate Monica il

7
@AndrewPiliser Se la directory corrente non è su PATH, allora no - dovrebbe essere eseguita con ./udevadm. Tuttavia, IIRC Windows eseguirà eseguibili nella stessa directory.
Izkata,

4
Quando possibile, invoca direttamente il programma desiderato senza passare attraverso la shell.
Codici A Caos il

Risposte:


59

Non è particolarmente male, ma ci sono alcuni avvertimenti.

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

Sono generalmente felice di eseguire i binari quando sono in un ambiente noto che posso prevedere, quando l'output binario è facile da analizzare (se necessario, potrebbe essere necessario solo un codice di uscita) e non ho bisogno di farlo anch'io spesso.

Come hai notato, l'altro problema è quanto lavoro è replicare quello che fa il binario? Utilizza una libreria che puoi anche sfruttare?


13
Forking a binary results in a lot more load and requires more resources than executing library calls (generally speaking). Se questo fosse su Windows, sarei d'accordo. Tuttavia, l'OP specifica Unix in cui il forking ha un costo abbastanza basso che è difficile dire se caricare dinamicamente una libreria sia peggio del fork.
Wallyk,

1
Normalmente mi aspetto che una libreria venga caricata una volta , mentre il binario può essere eseguito più volte. Come sempre, dipende dagli scenari di utilizzo, ma deve essere considerato (se successivamente respinto)
Brian Agnew,

3
Non c'è "biforcazione" su Windows, quindi la quotazione deve essere specifica per Unix.
Cody Grey,

5
Il kernel NT supporta il fork, sebbene non sia esposto tramite Win32. Ad ogni modo, ritengo che il costo di gestione di un programma esterno verrebbe più dal execche dal fork. Caricamento sezioni, collegamento in oggetti condivisi, esecuzione del codice di inizializzazione per ciascuno. E poi dover convertire tutti i dati in testo da analizzare dall'altra parte, sia per input che per output.
extra il

8
Ad essere onesti, udevadm info -q path -n /dev/ttyUSB2non funzionerà comunque su Windows, quindi l'intera discussione sui fork è un po 'teorica.
MSalters,

37

Ci vuole estrema cura contro le vulnerabilità dell'iniezione una volta introdotto un potenziale vettore. Ora è in prima linea nella tua mente, ma in seguito potresti aver bisogno della possibilità di selezionare ttyUSB0-3, quindi quell'elenco verrà utilizzato in altri luoghi in modo che venga preso in considerazione per seguire il principio della responsabilità singola, quindi un cliente avrà l'obbligo di mettere un dispositivo arbitrario nell'elenco e lo sviluppatore che apporta tale modifica non avrà idea che alla fine l'elenco verrà utilizzato in modo non sicuro.

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

In secondo luogo, l'output degli strumenti CLI non viene generalmente considerato come interfaccia stabile a meno che la documentazione non li contrassegni specificamente come tali. Potresti contare su di loro per uno script che esegui per risolvere te stesso, ma non per qualcosa che distribuisci a un cliente.

In terzo luogo, se vuoi un modo semplice per estrarre un valore dal tuo software, è probabile che anche qualcun altro lo desideri. Cerca una libreria che fa già quello che vuoi. libudevera 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;
}

C'è un'altra utile funzionalità in quella libreria. Immagino che se ne avessi bisogno, anche alcune delle funzioni correlate potrebbero tornare utili.


2
GRAZIE. Ho trascorso così tanto tempo a cercare libudev. Non riuscivo a trovarlo!
johnny_boy,

2
Non vedo come le "vulnerabilità dell'iniezione" siano un problema qui a meno che il programma di OP non sia invocato quando un altro utente ha il controllo del proprio ambiente, il che è principalmente il caso in caso di suid (anche possibilmente, ma in misura minore, cose come cgi e ssh comando forzato). Non c'è motivo per cui i normali programmi debbano essere rafforzati nei confronti dell'utente in cui sono in esecuzione.
R ..

2
@R ..: La "vulnerabilità dell'iniezione" è quando generalizzi ttyUSB2e usi sprintf(buf, "udevadm info -q path -n /dev/%s", argv[1]). Ora sembra abbastanza sicuro, ma cosa succede se lo argv[1]è "nul || echo OOPS"?
MSalters,

3
@R ..: hai solo bisogno di più immaginazione. Prima è una stringa fissa, quindi è un argomento fornito dall'utente, quindi è un argomento fornito da qualche altro programma gestito dall'utente ma che non capiscono, quindi è un argomento che il loro browser ha estratto da una pagina web. Se le cose continuano a "scendere", alla fine qualcuno deve assumersi la responsabilità di sanificare l'input, e Karl sta dicendo che ci vuole molta cura per identificare quel punto, ovunque possa arrivare.
Steve Jessop,

6
Sebbene se il principio di precauzione fosse davvero "supponiamo che lo sviluppatore più negligente che tu sappia stia apportando una modifica non sicura", e ho dovuto scrivere il codice ora per garantire che non possa comportare un exploit, personalmente non potrei alzarmi dal letto. Gli sviluppatori più negligenti che conosco fanno sembrare Machievelli come se non ci stesse nemmeno provando .
Steve Jessop,

16

Nel tuo caso specifico, dove vuoi invocare udevadm, sospetto che potresti udeventrare in una libreria e fare le chiamate di funzione appropriate in alternativa?

ad esempio, potresti dare un'occhiata a cosa sta facendo udevadm quando invoca in modalità "info": https://github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.c ed effettua chiamate equiv per quanto sta facendo udevadm.

Ciò eviterebbe la maggior parte dei compromessi negativi elencati nella risposta eccellente di Brian Agnew - ad esempio, non fare affidamento sul binario esistente in un determinato percorso, evitando le spese di fork, ecc.


Grazie, ho trovato il repository esatto e ho finito per guardarlo un po '.
johnny_boy,

1
... e libudev non è particolarmente difficile da usare. La sua gestione degli errori è un po 'carente, ma l'enumerazione, l'interrogazione e il monitoraggio delle API sono piuttosto semplici. La lotta che la tua paura potrebbe rivelare essere meno di 2 ore se ci provi.
extra

7

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

Questo è molto raro il modo in cui sono scritti i programmi C. È sempre il modo in cui vengono scritti gli script di shell e talvolta il modo in cui vengono scritti i programmi Python, perl o Ruby.

Le persone in genere scrivono in C per un facile utilizzo delle librerie di sistema e un accesso diretto a basso livello alle chiamate di sistema del sistema operativo, nonché per la velocità. E C è un linguaggio difficile in cui scrivere, quindi se le persone non hanno bisogno di quelle cose, allora non usano C. Inoltre, ci si aspetta che i programmi C abbiano dipendenze solo su librerie condivise e file di configurazione.

Rivolgersi a un processo secondario non è particolarmente veloce e non richiede un accesso controllato e dettagliato a strutture di sistema di basso livello e introduce una dipendenza forse sorprendente da un eseguibile esterno, quindi è raro vedere nei programmi C.

Ci sono alcune preoccupazioni aggiuntive. La sicurezza e la portabilità riguardano le persone menzionate che sono completamente valide. Ovviamente sono ugualmente validi per gli script di shell, ma le persone si aspettano questo tipo di problemi negli script di shell. Ma in genere non ci si aspetta che i programmi C presentino questa classe di problemi di sicurezza, il che lo rende più pericoloso.

Ma, a mio avviso, le maggiori preoccupazioni riguardano il modo in popencui interagiranno con il resto del programma. popendeve creare un processo figlio, leggere il suo output e raccogliere il suo stato di uscita. Nel frattempo, quel processo 'stderr sarà collegato allo stesso stderr del tuo programma, il che potrebbe causare risultati confusi, e il suo stdin sarà lo stesso del tuo programma, che potrebbe causare altri problemi interessanti. Puoi risolverlo includendo </dev/null 2>/dev/nullla stringa che passi popenpoiché è interpretata dalla shell.

E popencrea un processo figlio. Se fai qualcosa con la gestione del segnale o i processi di fork, potresti finire per ricevere SIGCHLDsegnali strani . Le tue chiamate a waitpossono interagire stranamente con popene forse creare condizioni di gara strane.

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 inserire i meta-charcater della shell nella stringa in cui passi popenperché quella stringa viene fornita direttamente shcon sh -c <string from popen as a single argument>.

Ma non credo che siano il motivo per cui è strano vedere un programma C in uso popen. Il motivo per cui è strano è che C è in genere un linguaggio di basso livello e popennon è di basso livello. E perché l'uso di popenvincoli progettuali pone sul tuo programma perché interagirà stranamente con l'input e l'output standard del tuo programma e renderà difficile fare la tua gestione dei processi o la gestione del segnale. E poiché non ci si aspetta che i programmi C abbiano dipendenze da eseguibili esterni.


0

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

Dal punto di vista del programma si sta eseguendo in un ambiente normale, da un aspetto della sicurezza, se qualcuno rompe il programma ha accesso solo agli elementi forniti al momento della copia.

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

Viene normalmente utilizzato per configurare server accessibili al pubblico, ecc.


3
L'OP sta scrivendo un programma per l'esecuzione di altre persone, non giocando con binari o fonti non attendibili sul proprio sistema.
Peter Cordes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.