Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Regla 0x5004h: Todas las operaciones con cadenas deben ser seguras

Compilacion, preprocesador y seguridad (0x50XX)

Universidad Nacional de Río Negro

0x5004h: Todas las operaciones con cadenas deben ser seguras

Enunciado normativo

DEBEN usarse funciones de cadena que acoten el tamaño del destino (snprintf, strncpy, strncat) o que operen sobre longitudes conocidas. NO DEBEN usarse strcpy, strcat ni sprintf sin control de límite.

¿Por qué existe esta regla?

El problema

En C una cadena es un char[] terminado en '\0'; ninguna función sabe cuánto mide el buffer de destino. strcpy y strcat copian hasta encontrar el nulo de la fuente, así que si la fuente es más larga que el destino escriben más allá del final: desbordamiento de búfer. sprintf hace lo mismo con el texto formateado, y su longitud no se conoce hasta evaluar el formato en runtime.

El desbordamiento de pila es explotable: pisa variables locales y la dirección de retorno, y puede ejecutar código arbitrario (stack smashing). Aun sin atacante, corrompe memoria y el programa aborta con SIGABRT.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Comportamiento indefinidoEscritura fuera de límites: SIGSEGV, SIGABRT o corrupción silenciosa.
VulnerabilidadDesbordamiento de pila explotable; el programa puede ejecutar datos de entrada.
Bug silenciosostrncpy puede no terminar en '\0' y la cadena «sigue» leyendo basura.
MantenibilidadCada copia insegura exige auditar el tamaño máximo a mano.

Fundamento en el estándar y en la cátedra

ISO/IEC 9899:2011 §7.24 regula <string.h> y §7.21.6.6/§7.21.6.5 snprintf/ printf. strncpy se define en §7.24.2.4: copia a lo sumo n bytes y no garantiza el terminador. La cátedra prohíbe las variantes sin límite porque el código se evalúa en un sandbox con entradas adversarias.

Alcance y excepciones

Aplica a toda manipulación de cadenas, tanto de entrada como de salida. Excepción: no aplica a buffers cuyo tamaño se valida con memcpy (n <= sizeof(dest)). Comparar cadenas siempre con strcmp (o strncmp): == compara direcciones, que es el defecto de 0x5004h.

Ejemplos exhaustivos

❌ Contraejemplo 1 — strcpy/strcat sin límite

#include <string.h>

void componer(char *dest, const char *nombre)
{
    strcpy(dest, "Usuario: ");
    strcat(dest, nombre);
}

Por qué falla: si nombre es más largo que dest, strcat escribe fuera del buffer. El error no se detecta en la llamada sino en el uso posterior de memoria vecina; con -D_FORTIFY_SOURCE=2 el programa aborta.

❌ Contraejemplo 2 — sprintf sin sizeof

#include <stdio.h>

void etiquetar(char *dest, const char *id, int codigo)
{
    sprintf(dest, "ID: %s, Cod: %d", id, codigo);
}

Por qué falla: el texto resultante depende de id, así que puede exceder el destino sin que nada lo impida. Reemplazarlo por snprintf(dest, tam, ...) obliga a declarar el tamaño real (0x5004h).

✅ Ejemplo conforme 1 — snprintf con sizeof

#include <stdio.h>

void etiquetar(char dest[], size_t tam, const char *id, int codigo)
{
    snprintf(dest, tam, "ID: %s, Cod: %d", id, codigo);
}

snprintf escribe a lo sumo tam - 1 caracteres y agrega '\0'. El valor de retorno permite detectar truncamiento comparándolo con tam; pasarlo como parámetro evita el error clásico de sizeof sobre un puntero.

✅ Ejemplo conforme 2 — copia acotada con terminación explícita y comparación correcta

#include <string.h>

void copiar(char *dest, size_t tam, const char *orig)
{
    strncpy(dest, orig, tam - 1);
    dest[tam - 1] = '\0';
}

int es_admin(const char *rol)
{
    return strcmp(rol, "admin") == 0;
}

strncpy no agrega el nulo si la fuente es más larga, por eso se fija dest[tam - 1] a mano. La comparación usa strcmp == 0, que resuelve 0x5004h.

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cReporta 0x5004h ante strcpy, strcat, sprintf y == entre cadenas.
gcc / clanggcc -std=c11 -Wall -Wextra -Werror -pedantic -D_FORTIFY_SOURCE=2 archivo.cwarning: 'strcpy' writing N bytes into a region of size M.
cppcheckcppcheck --enable=warning archivo.cBuffer is not null-terminated / String comparison.

Checklist de autocontrol

Reglas relacionadas

Antipatrón: Comparación directa de cadenas con == o !=

Síntoma en el código del estudiante

Aparece un operador == o != comparando un char * contra un literal entrecomillado, o contra otro char *:

if (nombre == "admin")
if (opcion != "salir")

A simple vista parece una comparación de texto, y ahí está la trampa: el operador compara direcciones de memoria, no caracteres.

Diagnóstico

Mecanismo del defecto

En una expresión, un arreglo char nombre[16] y un literal "admin" decaen a char * apuntando a su primer carácter (C11 §6.3.2.1p3). El operador == sobre punteros compara los valores de esas direcciones, no el contenido apuntado. El literal "admin" vive en una región de solo lectura del binario; el búfer nombre vive en la pila o en el heap. Son objetos distintos, de modo que nombre == "admin" es falso aunque el texto coincida letra por letra.

Agrega una vuelta de tuerca que el estándar no obliga a que dos literales idénticos ocupen direcciones distintas (C11 §6.4.5p6): así, "hola" == "hola" puede dar 1 o 0 según el compilador y los flags. El resultado es no especificado, no simplemente falso.

Consecuencia observable

Fundamento en el estándar C11

== sobre punteros compara identidad de objeto (C11 §6.5.9). Para comparar el contenido lexicográfico de dos cadenas se usa strcmp (C11 §7.24.4.2), que recorre ambos arreglos hasta el '\0' y devuelve 0 si son iguales, un valor negativo si la primera precede a la segunda y uno positivo si la sigue. La cátedra exige además comparar de forma explícita (strcmp(...) == 0) en lugar de depender de la veracidad implícita del entero, conforme a 0x1005h: Reemplazá las condiciones ambiguas basadas en la ‘veracidad’ (truthiness) del tipo de dato.

Corrección idiomática

❌ Código con el antipatrón
#include <stdio.h>
#include <string.h>

static int es_administrador(const char *usuario)
{
    if (usuario == "admin") {
        return 1;
    }
    return 0;
}
✅ Código refactorizado
#include <stdio.h>
#include <string.h>

static int es_administrador(const char *usuario)
{
    if (usuario == NULL) {
        return 0;
    }

    return strcmp(usuario, "admin") == 0;
}

Para orden alfabético se comparan los signos: strcmp(a, b) < 0 significa que a precede a b. Para prefijos acotados se usa strncmp(a, b, n) == 0.

Errores típicos al compilar o ejecutar

aviso.c:6:19: warning: comparison with string literal results in unspecified behavior [-Waddress]
    6 |     if (usuario == "admin") {
      |                  ^~
aviso.c:6:19: note: use 'strcmp' to compare strings

Sin -Wall no hay diagnóstico y el programa simplemente valida mal.

Checklist de verificación

Reglas relacionadas

Antipatrón: Formateo inseguro con sprintf() sin comprobación de destino

Síntoma en el código del estudiante

Llamadas a sprintf cuyo primer argumento es un búfer de tamaño fijo y que nunca reciben ese tamaño:

char salida[32];
sprintf(salida, "ID: %s, Cod: %d", id, codigo);

El patrón se repite con strcpy y strcat, pero acá el vector es el formateo: el largo final depende de los datos de entrada y el compilador no lo puede acotar por sí solo.

Diagnóstico

Mecanismo del defecto

sprintf escribe el resultado formateado y agrega un '\0' final, pero no conoce el tamaño de salida: recorre el formato y escribe tantos bytes como produzca la expansión. Si %s apunta a una cadena de 40 caracteres y el destino mide 32, los últimos 9 bytes más el terminador se escriben fuera del arreglo, sobre las variables vecinas o sobre la dirección de retorno del marco de pila. En C11 esa escritura fuera de límites es comportamiento indefinido (§6.5.6 y §7.1.4).

Si el formato proviniera del usuario, se agrega la vulnerabilidad de format string: un %n o un %s inesperado altera la lectura o la escritura de la pila.

Consecuencia observable

Fundamento en el estándar C11

La variante acotada es snprintf (C11 §7.21.6.5): recibe size_t n y escribe a lo sumo n - 1 caracteres más el terminador, nunca desborda. Devuelve la cantidad de caracteres que habrían sido necesarios; si ese valor es negativo o mayor o igual que n, hubo truncamiento y hay que decidir qué hacer (avisar, recortar o reintentar). sprintf (C11 §7.21.6.6) no ofrece ninguna garantía de tamaño.

Corrección idiomática

❌ Código con el antipatrón
#include <stdio.h>

void formatear_mensaje(const char *id, int codigo)
{
    char salida[32];
    sprintf(salida, "ID: %s, Cod: %d", id, codigo);
    printf("%s\n", salida);
}
✅ Código refactorizado
#include <stdio.h>

void formatear_mensaje(const char *id, int codigo)
{
    char salida[32];
    int escritos = snprintf(salida, sizeof(salida), "ID: %s, Cod: %d", id, codigo);

    if (escritos < 0 || (size_t)escritos >= sizeof(salida)) {
        printf("Aviso: mensaje truncado\n");
        return;
    }

    printf("%s\n", salida);
}

Si el destino es dinámico o llega como parámetro, el largo no se obtiene con sizeof: se pasa explícitamente como size_t, porque un char * decaído mide solo el puntero (ver 0x3013h: Asignación de memoria con sizeof sobre puntero en lugar del tipo apuntado).

Errores típicos al compilar o ejecutar

*** stack smashing detected ***: terminated
Aborted (core dumped)

Con advertencias altas, el compilador detecta el caso acotado:

aviso.c:6:5: warning: 'sprintf' output between 12 and 2147483658 bytes into a destination of size 32 [-Wformat-overflow=]

Checklist de verificación

Reglas relacionadas

Antipatrón: Uso de funciones inseguras de manipulación de cadenas (strcpy/sprintf)

Síntoma en el código del estudiante

strcpy, strcat y sprintf aparecen sin ningún argumento de tamaño:

char destino[16];
strcpy(destino, nombre);
strcat(destino, apellido);
sprintf(destino, "%s: %d", nombre, valor);

El patrón es siempre el mismo: la función descubre el largo del origen mientras copia, y el destino mide menos que el resultado.

Diagnóstico

Mecanismo del defecto

strcpy copia desde el origen hasta el '\0' inclusive, sin conocer la capacidad de destino (C11 §7.24.2.3). strcat busca el terminador del destino y agrega desde allí (C11 §7.24.3.1). Ambas escriben la cantidad de bytes que dicte el origen. Cuando ese largo supera el del destino, la copia pisa memoria ajena: variables contiguas, metadatos del heap o la dirección de retorno del marco de pila. Es la definición misma de desbordamiento de búfer y, en C11, comportamiento indefinido.

sprintf arrastra el mismo defecto amplificado: el largo depende del formato y de los valores, y tampoco recibe límite.

Consecuencia observable

Fundamento en el estándar C11

Las funciones de la familia str* sin n no reciben tamaño y por eso no pueden acotar (C11 §7.24.2). La alternativa acotada correcta es snprintf(dest, sizeof(dest), "%s", src), que escribe a lo sumo sizeof(dest) - 1 caracteres más el terminador (§7.21.6.5).

Cuidado: strncpy no es una versión segura de strcpy. Si el origen tiene n o más caracteres, no agrega el '\0' (C11 §7.24.2.4) y el destino queda sin terminar. Solo se usa cuando el terminador se agrega a mano.

Corrección idiomática

❌ Código con el antipatrón 1 — copia sin límite
#include <string.h>

void guardar_nombre(const char *nombre)
{
    char copia[16];
    strcpy(copia, nombre);
}
❌ Código con el antipatrón 2 — concatenación sin límite
#include <string.h>

void guardar_completo(const char *nombre, const char *apellido)
{
    char completo[16];
    strcpy(completo, nombre);
    strcat(completo, apellido);
}
✅ Código refactorizado 1 — copia acotada
#include <stdio.h>

void guardar_nombre(const char *nombre)
{
    char copia[16];
    snprintf(copia, sizeof(copia), "%s", nombre);
}
✅ Código refactorizado 2 — concatenación acotada
#include <stdio.h>

void guardar_completo(const char *nombre, const char *apellido)
{
    char completo[16];
    snprintf(completo, sizeof(completo), "%s%s", nombre, apellido);
}

Cuando el destino es dinámico o llega como parámetro, el tamaño se pasa explícitamente en lugar de usar sizeof.

Errores típicos al compilar o ejecutar

*** buffer overflow detected ***: terminated
Aborted (core dumped)

Con -Wall el compilador avisa sobre los cruces detectables:

aviso.c:6:5: warning: 'strcpy' writing 32 bytes into a region of size 16 overflows the destination [-Wstringop-overflow=]

Checklist de verificación

Reglas relacionadas