01.12
2020
Na plataforma MS Windows pouco se usa a linha de comando, mas C/C++ é muito utilizado para back end, e nesse caso a linha de comando é muito mais comum. Pra começar vamos entender como a linguagem C recebe as informações do sistema operacional. Vamos também entender quais cuidados temos que ter com argumentos de linha de comando.
A linguagem C recebe os argumentos da linha de comando através de dois parâmetros na função main(). O primeiro parâmetro, um int, representa a quantidade de argumentos. O segundo parâmetro, um array de ponteiros char, cada parâmetro, separado por espaço, passado pela linha de comando, inclusive o próprio binário. Vamos a um exemplo:
#include <stdio.h>
int main(int argc, char* argv[]) {
for (int ct = 0; ct < argc; ct++)
printf("%s ", argv[ct]);
printf("\n");
}
Normalmente os argumentos são nomeados argc (argument counter) e argv (argument values), mas podem receber outros nomes. O programa acima lista todos os argumentos, reconstruindo a linha de comando novamente. Note que o primeiro argumento, índice 0, é o próprio binário, incluindo caminho conforme chamado na linha de comando. Dessa forma:
$ ./args 1 2 3 4 5
./args 1 2 3 4 5
Note que apesar a linha de comando contar com vários espaços entre os diversos argumentos, a reconstrução é feita com somente um espaço. Além disso o primeiro parâmetro é reproduzido tal qual foi digitado pelo usuário.
Cuidados
Muito cuidado ao utilizar dados passados pela linha de comando por conta de alguns motivos.
Quantidade de Parâmetros
Muita atenção com a quantidade de parâmetros. Antes de indexar o array de ponteiros char procure saber se existem argumentos suficiente. Portanto sempre é boa prática validar a quantidade de argumentos:
int main(int argc, char* argv[]) {
if (argc >= 2)
strcpy(config.input, argv[1]);
if (argc >= 3) {
if (!strcmp(argv[2], "COPY")) {
config.copy = 1;
config.move = 0;
} else if (!strcmp(argv[2], "MOVE")) {
config.copy = 0;
config.move = 1;
} else {
config.copy = 0;
config.move = 0;
strcpy(config.output, argv[2]);
}
}
if (!config.output && argc >= 4)
strcpy(config.output, argv[3]);
}
Note como é verificado se cada argumento está efetivamente disponível. Isso é necessário para evitar acessar uma espaço de memória não alocado, causando problemas e instabilidade no programa.
Tamanho dos Parâmetros
É necessário cuidado com o tamanho dos parâmetros passado pelo usuário. Durante o tempo de codificação não é possível definir qual será o tamanho do argumento passado pelo usuário. Por isso é recomendado limitar o tamanho do argumento, ou fazer a alocação do buffer em tempo de execução:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[]) {
char name[16];
char *path = NULL;
if (argc < 3) {
printf("Insufficient arguments\n");
exit(1);
}
if (strlen(argv[2]) > sizeof(name) - 1) {
printf("Maximum name size: 15 characters\n");
exit(1);
}
if ((path = (char*)malloc(strlen(argv[3]) + 1)) == NULL) {
printf("Cannot allocate enough memory\n");
exit(1);
}
strcpy(path, argv[3]);
free(path);
}
Com o primeiro argumento (argv[2]) é verificado o tamanho do argumento, se é compatível com o tamanho do buffer name, menos um caractere, necessário para completar o caracter de final de string. Também seria possível utilizar strncpy, limitando a quantidade de bytes copiados.
Com o segundo argumento (argv[3]) o buffer é alocado em tempo de execução, garantindo que haverá espaço suficiente para o argumento.
Essa validação é necessária para evitar bugs de buffer overflow, que irá causar problemas de estabilidade, mas pode abrir caminho para falhas de segurança.
Conteúdo dos Parâmetros
O conteúdo dos parâmetros passados pelo usuário é uma questão de segurança. Com um pouco de conhecimento das funcionalidades do programa é possível inserir textos maliciosos para se ter acesso a informações indevidamente, ou até mesmo destruir informações.
A questão é tão sensível que na linguagem Perl existe um termo para determinar quando os dados não foram validados: tainted, ou manchados.
É necessário validar se os argumentos recebidos não estão tentando acessar outros diretórios, ou se estão tentando executar comando maliciosos, ou ainda se pretendem adicionar comandos adicionais a comandos já existentes no código.
E neste caso não há uma receita de bolo para eliminar todos os problemas. Mas no caso de arquivos dentro de um diretório, verifique se o argumento conta com o caractere /. No caso de SQL, deve-se fazer o escape de caracteres de string: ‘. Linhas de comando precisam se certificar que não existem espaços em branco ou ;. E assim por diante.