Regla 0x5015h: No invoques el interprete de comandos con system() o popen()
Compilacion, preprocesador y seguridad (0x50XX)
0x5015h: No invoques el interprete de comandos con system() o popen()¶
Enunciado normativo¶
NO DEBE invocarse
system()nipopen(); usá las APIs especificas de la biblioteca estandar.
Síntoma en el código del estudiante¶
El programa delega trabajo al intérprete de comandos del sistema en lugar de resolverlo con la biblioteca de C:
system("pause");
system("clear");
popen("ls -l", "r");El caso grave es cuando el argumento se arma con una entrada externa.
Diagnóstico¶
Mecanismo del defecto¶
system no ejecuta el comando directamente: le pasa la cadena al procesador
de comandos del sistema operativo mediante sh -c (C11 §7.22.4.8). Ese
paso por el shell es el problema. El shell vuelve a interpretar la cadena
y trata a sus metacaracteres (;, |, &, `, $(), salto de línea)
como separadores y operadores. Si el argumento contiene datos que el usuario
controla, un atacante puede cerrar el comando previsto y encadenar el suyo:
pasar archivo; rm -rf datos convierte una simple lectura en una ejecución
arbitraria. Es la vulnerabilidad de inyección de comandos.
popen abre una tubería hacia el shell y arrastra el mismo mecanismo; si no
se consume el flujo, deja el proceso hijo y el descriptor abiertos.
Consecuencia observable¶
Comandos que dependen de la plataforma fallan:
pausees delcmdde Windows y en Linux el shell respondesh: 1: pause: not found.Ejecución de código arbitrario con los privilegios del proceso cuando la cadena se arma con entrada no saneada (riesgo crítico).
Sin verificar el valor de retorno, el programa continúa como si el comando hubiera funcionado.
Fundamento en el estándar C11¶
system está declarada en <stdlib.h> (C11 §7.22.4.8), pero con un
argumento distinto de NULL el comportamiento depende de que exista un
procesador de comandos, algo ajeno al lenguaje y no portable. La biblioteca
estándar cubre casi todo lo que el estudiante suele pedirle al shell: pausar
(getchar), limpiar pantalla (secuencias de control o system nunca es
necesario), listar y leer archivos (fopen, fgets, readdir), copiar o
mover datos (stdio.h). Cuando de verdad haga falta lanzar un programa,
la alternativa segura evita el shell y pasa argumentos ya separados con la
familia exec*, de modo que ningún metacaracter sea reinterpretado.
Corrección idiomática¶
❌ Código con el antipatrón¶
#include <stdlib.h>
void pausar_consola(void)
{
system("pause");
}✅ Código refactorizado¶
#include <stdio.h>
void pausar_consola(void)
{
getchar();
}✅ Código refactorizado — sin concatenar entrada en un comando¶
#include <stdio.h>
void mostrar_archivo(const char *ruta)
{
FILE *archivo = fopen(ruta, "r");
if (archivo == NULL) {
perror("fopen");
return;
}
int caracter;
while ((caracter = fgetc(archivo)) != EOF) {
putchar(caracter);
}
fclose(archivo);
}La ruta se usa como dato, nunca se reensambla en una cadena que el shell vaya a interpretar: así los metacaracteres no tienen poder alguno.
Errores típicos al compilar o ejecutar¶
sh: 1: pause: not foundY en un caso de inyección, el shell ejecuta lo que no debía:
$ ./programa "datos; rm -rf ./salida"
rm: cannot remove './salida': No such file or directoryChecklist de verificación¶
¿Queda alguna llamada a
systemopopen?Si hay que esperar una tecla, ¿uso
getchar/fgets?¿La operación pedida al shell se puede hacer con
stdio.horeaddir?¿Alguna cadena de comando se arma con datos del usuario?
¿Evité depender del shell para tareas de E/S o de archivo?
Reglas relacionadas¶
0x5006h: Preferí fgets sobre gets y scanf para leer cadenas — entradas de texto seguras con
fgets.0x5004h: Todas las operaciones con cadenas deben ser seguras — construcción segura de cadenas (
snprintf), no concatenación cruda.0x5014h: Inclusión explícita obligatoria de cabeceras para funciones de biblioteca estándar — llamadas de sistema restringidas fuera de consigna.
Síntoma en el código del estudiante¶
El ejercicio usa llamadas a sistema de control de procesos, señales o red que la consigna no autoriza:
fork();
execve("/bin/ls", argv, NULL);
kill(pid, SIGKILL);
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
socket(AF_INET, SOCK_STREAM, 0);También entra en esta familia pipe, mmap, clone y shmget cuando
aparecen como atajo para resolver un problema de memoria o de concurrencia.
Diagnóstico¶
Mecanismo del defecto¶
Estas funciones no pertenecen a ISO C11: son parte de POSIX y viven en
<unistd.h>, <signal.h>, <sys/ptrace.h>, <sys/socket.h>, etc. Con
-std=c11 estricto, el estándar oculta esas declaraciones salvo que se
pida explícitamente la interfaz POSIX con una macro de prueba de
características (_POSIX_C_SOURCE/_XOPEN_SOURCE). El compilador entonces
las trata como funciones desconocidas y, según la versión, emite una
declaración implícita o un error. Aun cuando el enlace contra la libc
encuentre los símbolos, el código deja de ser portable y queda fuera del
modelo de la materia.
En un entorno de corrección automática, fork, exec, socket o ptrace
permiten crear procesos, abrir red o inspeccionar memoria: por eso los
sandboxes pedagógicos las bloquean y abortan la ejecución.
Consecuencia observable¶
Falla la compilación con
-std=c11:implicit declaration of function 'fork'.El sandbox de evaluación mata el proceso por intentar una operación no permitida, y el trabajo se marca como no presentado.
El código no compila fuera de Linux o depende de una versión de POSIX.
Fundamento en el estándar C11¶
ISO C11 no define estas llamadas; solo incluye la biblioteca estándar
(<stdio.h>, <stdlib.h>, <string.h>, <math.h>, etc.). Para usarlas
habría que declarar la macro de característica POSIX antes de cualquier
#include y aceptar que deja de ser C estándar. La consigna de la cátedra
resuelve cada ejercicio con las primitivas de E/S y los algoritmos vistos
en clase, de modo que no hacen falta. Si el TP las autoriza, se declaran
formalmente en la cabecera correspondiente y se documenta la dependencia.
Corrección idiomática¶
❌ Código con el antipatrón¶
#include <unistd.h>
void crear_tarea(void)
{
fork();
}✅ Código refactorizado¶
#include <stdio.h>
void ejecutar_algoritmo(const int datos[], size_t cantidad)
{
size_t suma = 0;
for (size_t i = 0; i < cantidad; i++) {
suma += (size_t)datos[i];
}
printf("Resultado: %zu\n", suma);
}El problema se resuelve con arreglos, lazos y funciones del alcance de la materia, sin delegar en el sistema operativo. Si de verdad se necesita la interfaz POSIX, se documenta la macro de características al tope del fuente:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>Errores típicos al compilar o ejecutar¶
aviso.c:6:5: warning: implicit declaration of function 'fork' [-Wimplicit-function-declaration]Con -Werror o una versión estricta del compilador:
aviso.c:6:5: error: implicit declaration of function 'fork'; did you mean 'fread'? [-Wimplicit-function-declaration]En el evaluador automático:
[nostromo] operación no permitida: se bloqueó fork()Checklist de verificación¶
¿La consigna autoriza llamadas de sistema de procesos, señales o red?
¿Compila con
-std=c11 -Wall -Wextrasin macros POSIX ocultas?¿Puedo resolver el ejercicio con arreglos, funciones y
stdio.h?Si uso POSIX, ¿declaré
_POSIX_C_SOURCEy lo documenté?¿Evité
fork/exec/socketcomo atajo fuera del alcance del TP?
Reglas relacionadas¶
0x5014h: Inclusión explícita obligatoria de cabeceras para funciones de biblioteca estándar — exige incluir explícitamente la cabecera de cada función.
0x5002h: Desarrollá y compilá siempre con todas las advertencias del compilador activadas — compilá con todas las advertencias activadas.
0x5012h: Detección de inclusiones cíclicas entre archivos de cabecera — no delegues en el intérprete de comandos.