É uma boa ideia chamar comandos shell a partir do C?

49

Existe um comando shell unix ( udevadm info -q path -n /dev/ttyUSB2 ) que eu quero chamar de um programa em C. Com provavelmente cerca de uma semana de luta, eu poderia reimplementá-lo, mas não quero fazer isso.

É uma boa prática amplamente aceita chamar apenas popen("my_command", "r"); , ou isso apresentará problemas de segurança inaceitáveis e problemas de compatibilidade de encaminhamento? Parece-me errado fazer algo assim, mas não posso dizer por que seria ruim.

    
por johnny_boy 19.06.2017 / 16:58
fonte

5 respostas

58

Não é particularmente ruim, mas há algumas ressalvas.

  1. qual a sua solução portátil? Seu binário escolhido funcionará da mesma maneira em todos os lugares, gerará os resultados no mesmo formato, etc.? Será que a saída será diferente nas configurações de LANG etc.?
  2. quanta carga extra isso adiciona ao seu processo? Bifurcar um binário resulta em muito mais carga e requer mais recursos do que a execução de chamadas de biblioteca (em geral). Isso é aceitável em seu cenário?
  3. Existem problemas de segurança? Alguém pode substituir seu binário escolhido por outro e realizar ações nefastas a partir de então? Você usa args fornecidos pelo usuário para seu binário e eles poderiam fornecer ;rm -rf / (por exemplo) (observe que algumas APIs permitirão que você especifique args com mais segurança do que apenas fornecê-los na linha de comando)

Geralmente fico feliz em executar binários quando estou em um ambiente conhecido que posso prever, quando a saída binária é fácil de analisar (se necessário - você pode precisar apenas de um código de saída) e não preciso faça isso com muita frequência.

Como você observou, a outra questão é quanto trabalho é replicar o que o binário faz? Ele usa uma biblioteca que você também pode aproveitar?

    
por 19.06.2017 / 17:05
fonte
37

É preciso ter muito cuidado para proteger contra vulnerabilidades de injeção depois que você introduz um vetor em potencial. Está na vanguarda da sua mente agora, mas mais tarde você pode precisar da opção para selecionar ttyUSB0-3 , então essa lista será usada em outros lugares para que seja fatorada para seguir o princípio de responsabilidade única, então um cliente terá um requisito para colocar um dispositivo arbitrário na lista, e o desenvolvedor fazendo essa alteração não terá idéia de que a lista será usada de maneira insegura.

Em outras palavras, codifique como se o desenvolvedor mais descuidado que você conhece estivesse fazendo uma alteração insegura em uma parte aparentemente não relacionada do código.

Em segundo lugar, a saída das ferramentas CLI geralmente não são consideradas interfaces estáveis, a menos que a documentação as marque especificamente como tal. Você pode estar bem contando com eles para um script que você executa que você pode solucionar o problema, mas não para algo que você implantar para um cliente.

Em terceiro lugar, se você quiser uma maneira fácil de extrair um valor do seu software, é provável que outra pessoa também o deseje. Procure uma biblioteca que já faz o que você quer. libudev já estava instalado no meu 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;
}

Há outras funcionalidades úteis nessa biblioteca. Meu palpite é que, se você precisasse disso, algumas das funções relacionadas poderiam ser úteis também.

    
por 20.06.2017 / 01:31
fonte
16

No seu caso específico, onde você deseja invocar udevadm , eu suspeitava que você poderia extrair udev como uma biblioteca e fazer as chamadas de função apropriadas como uma alternativa?

Por exemplo, você poderia dar uma olhada no que o próprio udevadm está fazendo quando invocar no modo "info": link e fazer chamadas equivalentes para aqueles que o udevadm está fazendo.

Isso evitaria muitas desvantagens enumeradas na excelente resposta de Brian Agnew - por exemplo, não contando com o binário existente em um determinado caminho, evitando a despesa de bifurcação, etc.

    
por 19.06.2017 / 21:54
fonte
7

Sua pergunta pareceu exigir uma resposta da floresta, e as respostas aqui parecem respostas de árvore, então pensei em lhe dar uma resposta da floresta.

Isso é muito raro como os programas em C são escritos. É sempre assim que os scripts shell são escritos e, às vezes, como os programas Python, perl ou Ruby são escritos.

As pessoas normalmente escrevem em C para facilitar o uso das bibliotecas do sistema e direcionar o acesso de baixo nível às chamadas do sistema operacional, assim como para a velocidade. E C é uma linguagem difícil de escrever, portanto, se as pessoas não precisam dessas coisas, então elas não usam C. Também se espera que os programas em C dependam apenas de bibliotecas compartilhadas e arquivos de configuração.

O envio para um subprocesso não é particularmente rápido e não exige acesso controlado e de baixa granularidade a recursos de sistema de baixo nível, e apresenta uma dependência possivelmente surpreendente em um executável externo, portanto, incomum para ver em programas C.

Existem algumas preocupações adicionais. As preocupações de segurança e portabilidade mencionadas são completamente válidas. Eles são igualmente válidos para shell scripts, é claro, mas as pessoas estão esperando esses tipos de problemas em scripts de shell. Mas normalmente não se espera que os programas em C tenham essa classe de preocupação com a segurança, o que a torna mais perigosa.

Mas, na minha opinião, as maiores preocupações têm a ver com a maneira como o popen irá interagir com o resto do seu programa. popen tem que criar um processo filho, ler sua saída e coletar seu status de saída. Enquanto isso, o stderr desse processo será conectado ao mesmo stderr do seu programa, o que pode causar uma saída confusa, e seu stdin será o mesmo do seu programa, o que pode causar outros problemas interessantes. Você pode resolver isso incluindo </dev/null 2>/dev/null na string que você passa para popen desde que seja interpretada pelo shell.

E popen cria um processo filho. Se você fizer qualquer coisa com processos de manuseio de sinalização ou bifurcação você pode acabar recebendo sinais estranhos SIGCHLD . Suas chamadas para wait podem interagir de forma estranha com popen e possivelmente criar condições de corrida estranhas.

As preocupações com segurança e portabilidade estão lá, é claro. Como são para scripts de shell ou qualquer coisa que inicie outros executáveis no sistema. E você precisa ter cuidado para que as pessoas que usam seu programa não consigam obter meta-charcaters do shell na string que você passa para popen porque essa string é fornecida diretamente para sh with sh -c <string from popen as a single argument> .

Mas eu não acho que seja por isso que é estranho ver um programa em C usando popen . A razão é estranho porque C é tipicamente uma linguagem de baixo nível, e popen não é baixo. E como o uso de popen coloca restrições de projeto em seu programa, pois ele interage de maneira estranha com a entrada e a saída padrão do seu programa e dificulta o gerenciamento de processos ou a manipulação de sinais. E como os programas em C geralmente não devem ter dependências em executáveis externos.

    
por 20.06.2017 / 17:22
fonte
0

Seu programa pode estar sujeito a pirataria, etc. Uma forma de se proteger desse tipo de atividade é criar uma cópia do seu ambiente de máquina atual e executar seu programa usando um sistema chamado chroot.

Do ponto de vista do seu programa, ele está sendo executado em um ambiente normal, a partir de um aspecto de segurança, se alguém interrompe seu programa, ele só tem acesso aos elementos que você forneceu quando fez a cópia.

Essa configuração é chamada de chroot jail para mais detalhes, consulte chroot jail .

É normalmente usado para configurar servidores publicamente acessíveis, etc.

    
por 20.06.2017 / 12:54
fonte

Tags