É uma boa idéia chamar comandos de shell de dentro de C?


50

Há um comando shell unix ( udevadm info -q path -n /dev/ttyUSB2) que eu quero chamar de um programa 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 que eu chame apenas popen("my_command", "r");, ou isso introduzirá problemas de segurança inaceitáveis ​​e encaminhará problemas de compatibilidade? Parece errado fazer algo assim, mas não sei por que isso seria ruim.


8
Uma coisa a considerar são os ataques de injeção.
MetaFight

8
Eu estava pensando principalmente no caso em que você forneceu a entrada do usuário como argumentos de comando do shell.
MetaFight

8
Alguém poderia colocar um udevadmexecutável malicioso no mesmo diretório do seu programa e usá-lo para escalar privilégios? Não sei muito sobre segurança unix, mas o seu programa será executado setuid root?
Andrew diz Reinstate Monica

7
@AndrewPiliser Se o diretório atual não estiver no PATH, então não - ele teria que ser executado ./udevadm. O Windows IIRC executaria executáveis ​​no mesmo diretório.
21917 Izkata

4
Sempre que possível, chame o programa desejado diretamente, sem passar pelo shell.
CodesInChaos

Respostas:


59

Não é particularmente ruim, mas existem algumas ressalvas.

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

Geralmente, sou 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 fazer isso também frequentemente.

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?


13
Forking a binary results in a lot more load and requires more resources than executing library calls (generally speaking). Se isso estivesse no Windows, eu concordo. No entanto, o OP especifica para o Unix que o uso de bifurcação é baixo o suficiente para ser difícil dizer se o carregamento dinâmico de uma biblioteca é pior do que o bifurcação.
wallyk

11
Eu normalmente esperaria que uma biblioteca fosse carregada uma vez , enquanto o binário pode ser executado várias vezes. Como sempre, é dependente de cenários de uso, mas precisa ser considerado (se demitido posteriormente)
Brian Agnew

3
Não há "bifurcação" no Windows, portanto, a cotação deve ser específica do Unix.
Cody Grey

5
O kernel do NT suporta bifurcação, embora não seja exposto pelo Win32. Enfim, eu acreditaria que o custo da execução de um programa externo viria mais do execque do fork. Carregando seções, vinculando objetos compartilhados, executando o código init para cada um. E depois ter que converter todos os dados em texto para serem analisados ​​do outro lado, tanto para entradas quanto para saídas.
espectros

8
Para ser justo, udevadm info -q path -n /dev/ttyUSB2não vai funcionar de qualquer maneira no Windows, então toda a discussão sobre bifurcação é um pouco teórica.
MSalters

37

É preciso muito cuidado para evitar vulnerabilidades de injeção depois de introduzir um vetor em potencial. Está na vanguarda de sua mente agora, mas mais tarde você pode precisar selecionar ttyUSB0-3, então essa lista será usada em outros lugares, para que ela seja levada em consideração para seguir o princípio de responsabilidade única; então, o cliente terá que informar um dispositivo arbitrário na lista, e o desenvolvedor que fizer essa alteração não fará idéia de que a lista eventualmente será usada de maneira insegura.

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

Segundo, 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. Talvez você esteja bem contando com eles para um script executado e que possa solucionar problemas sozinho, mas não para algo implantado em um cliente.

Terceiro, se você deseja uma maneira fácil de extrair um valor do seu software, é provável que alguém o queira também. Procure uma biblioteca que já faça o que você deseja. libudevjá 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ê precisar disso, algumas das funções relacionadas também podem ser úteis.


2
OBRIGADO. Passei tanto tempo procurando libudev. Eu simplesmente não consegui encontrar!
johnny_boy

2
Não vejo como "vulnerabilidades de injeção" são um problema aqui, a menos que o programa do OP seja chamado quando outro usuário tiver controle sobre seu ambiente, o que ocorre principalmente no caso de suid (também possivelmente, mas em menor grau, coisas como comando forçado cgi e ssh). Não há razão para que programas normais precisem ser reforçados contra o usuário em que estão executando.
R ..

2
@R ..: A "vulnerabilidade da injeção" é quando você generaliza ttyUSB2e usa sprintf(buf, "udevadm info -q path -n /dev/%s", argv[1]). Agora isso parece bastante seguro, mas e se argv[1]for "nul || echo OOPS"?
MSalters

3
@R ..: você só precisa de mais imaginação. Primeiro, é uma string fixa, depois é um argumento fornecido pelo usuário, depois é um argumento fornecido por algum outro programa executado pelo usuário, mas que eles não entendem, é um argumento que o navegador extraiu de uma página da web. Se as coisas continuarem "ladeira abaixo", eventualmente alguém terá que assumir a responsabilidade de higienizar a entrada, e Karl está dizendo que é preciso extremo cuidado para identificar esse ponto, onde quer que ele possa vir.
Steve Jessop

6
Embora se o princípio da precaução fosse realmente "suponha que o desenvolvedor mais descuidado que você conhece está fazendo uma alteração insegura", e eu precisei escrever um código agora para garantir que isso não resultaria em uma exploração, então pessoalmente não consegui sair da cama. Os desenvolvedores mais descuidados que conheço fazem com que Machievelli pareça que ele nem está tentando .
Steve Jessop

16

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

por exemplo, você pode dar uma olhada no que o udevadm está fazendo ao chamar no modo "info": https://github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.c e fazer chamadas equivalentes quanto aos que o udevadm está fazendo.

Isso evitaria muitas das desvantagens listadas na excelente resposta de Brian Agnew - por exemplo, não depender do binário existente em um determinado caminho, evitando as despesas de bifurcação, etc.


Obrigado, encontrei o repositório exato e acabei pesquisando um pouco.
johnny_boy

11
… E libudev não é particularmente difícil de usar. Seu tratamento de erros é um pouco ausente, mas enumerar, consultar e monitorar APIs são bastante diretas. A luta que seu medo pode levar a menos de 2 horas se você tentar.
espectros

7

Sua pergunta parecia exigir uma resposta da floresta, e as respostas aqui parecem respostas de árvores, então pensei em fornecer uma resposta da floresta.

É muito raramente como os programas em C são gravados. É sempre como os scripts de 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 o acesso direto de baixo nível às chamadas do sistema OS, bem como a velocidade. E C é uma linguagem difícil de escrever, portanto, se as pessoas não precisam dessas coisas, elas não usam C. Também se espera que os programas em C tenham dependências apenas de bibliotecas compartilhadas e arquivos de configuração.

Descascar para um subprocesso não é particularmente rápido e não requer acesso refinado e controlado a instalações de sistema de baixo nível, e introduz uma dependência possivelmente surpreendente de um executável externo, portanto, é incomum ver em programas C.

Existem algumas preocupações adicionais. As preocupações de segurança e portabilidade mencionadas pelas pessoas são completamente válidas. Eles são igualmente válidos para scripts de shell, é claro, mas as pessoas esperam esses tipos de problemas nos scripts de shell. Porém, normalmente não se espera que os programas C tenham essa classe de preocupação com 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 ele popenirá interagir com o restante do seu programa. popenprecisa 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 resultados confusos, e o stdin será o mesmo do seu programa, o que pode causar outros problemas interessantes. Você pode resolver isso incluindo </dev/null 2>/dev/nulla string para a qual você passa, uma popenvez que é interpretada pelo shell.

E popencria um processo filho. Se você fizer alguma coisa com os processos de manipulação ou bifurcação de SIGCHLDsinais, poderá receber sinais estranhos . Suas chamadas para waitpodem interagir de maneira estranha popene possivelmente criar condições de corrida estranhas.

As preocupações de segurança e portabilidade estão aí, é claro. Como eles são para scripts de shell ou qualquer coisa que inicie outros executáveis ​​no sistema. E você deve ter cuidado para que as pessoas que usam o seu programa não consigam colocar metacaracteres de shell na string que você passa, popenporque essa string é fornecida diretamente shcom sh -c <string from popen as a single argument>.

Mas não acho que sejam por isso que é estranho ver um programa em C usando popen. O motivo é estranho, porque C é tipicamente uma linguagem de baixo nível e popennão é de baixo nível. E porque o uso de popenrestrições de design coloca em seu programa, porque ele irá interagir estranhamente com a entrada e saída padrão do seu programa e dificulta o gerenciamento de processos ou a manipulação de sinais. E porque normalmente não se espera que os programas em C tenham dependências em executáveis ​​externos.


0

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

Do ponto de vista do seu programa, ele é executado em um ambiente normal, do ponto de vista da 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 prisão chroot para obter mais detalhes, consulte prisão chroot .

É normalmente usado para configurar servidores acessíveis ao público etc.


3
O OP está escrevendo um programa para outras pessoas executarem, não jogando com binários ou fontes não confiáveis ​​em seu próprio sistema.
Peter Cordes
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.