C Funciones seguras

En el lenguaje C, para mejorar la seguridad del código, especialmente para prevenir problemas de seguridad comunes como el desbordamiento de búfer, el estándar C11 introdujo algunasfunciones seguras, también conocidas comoAnnex Kfunciones de biblioteca estándar. Estas funciones seguras son principalmente versiones mejoradas de las funciones estándar de operación de cadenas y memoria, que proporcionan una mejor detección y manejo de errores mediante la adición de parámetros (como el tamaño del búfer).

Características de las funciones seguras:

  • Verificación del tamaño del búfer: Todas las funciones seguras requieren pasar el parámetro de tamaño del búfer de destino para prevenir el desbordamiento de búfer.
  • Verificación del valor de retorno: La mayoría de las funciones devuelvenerrno_tcódigos de error de tipo, que permiten verificar si la función se ejecutó correctamente.
  • Mejor manejo de errores: Cuando el tamaño del búfer es insuficiente o surgen otros problemas, estas funciones devuelven un código de error e intentan limpiar o inicializar el búfer de salida.

Las funciones seguras están bien soportadas en compiladores como Visual Studio, pero pueden no estar disponibles en algunas versiones antiguas de compiladores, por lo que se debe prestar atención a la compatibilidad.

La siguiente es una comparación de las funciones seguras comunes en C y sus correspondientes funciones tradicionales:

1. Funciones seguras de operación de cadenas

strcpy_s: Versión segura de strcpy, copia la cadena y verifica el tamaño del búfer de destino.

errno_t strcpy_s(char *dest, rsize_t destsz, const char *src);

strcat_s: Versión segura de strcat, añade la cadena fuente al final de la cadena de destino y verifica el tamaño del búfer.

errno_t strcat_s(char *dest, rsize_t destsz, const char *src);

strncpy_s: Versión segura de strncpy, copia como máximo n caracteres y verifica el tamaño del búfer.

errno_t strncpy_s(char *dest, rsize_t destsz, const char *src, rsize_t count);

strncat_s: Versión segura de strncat, añade como máximo n caracteres al final de la cadena de destino y verifica el tamaño del búfer.

errno_t strncat_s(char *dest, rsize_t destsz, const char *src, rsize_t count);

strtok_s: Versión segura de strtok, introduce un parámetro de contexto para resolver el problema de seguridad de hilos.

char *strtok_s(char *str, const char *delim, char **context);

2. Funciones seguras de salida formateada

sprintf_s: Versión segura de sprintf, verifica el tamaño del búfer al formatear la salida en una cadena.

int sprintf_s(char *buffer, rsize_t buffer_size, const char *format, ...);

snprintf_s: Versión segura de snprintf, limita el número de caracteres y verifica el tamaño del búfer al formatear la salida.

int snprintf_s(char *buffer, rsize_t buffer_size, const char *format, ...);

vsprintf_s: Versión segura de vsprintf, recibe una lista de parámetros va_list y verifica el tamaño del búfer.

int vsprintf_s(char *buffer, rsize_t buffer_size, const char *format, va_list argptr);

3. Funciones seguras de operación de memoria

memcpy_s: Versión segura de memcpy, verifica el tamaño del búfer de destino al copiar regiones de memoria.

errno_t memcpy_s(void *dest, rsize_t destsz, const void *src, rsize_t count);

memmove_s: Versión segura de memmove, copia regiones de memoria, permite solapamiento y verifica el tamaño del búfer de destino.

errno_t memmove_s(void *dest, rsize_t destsz, const void *src, rsize_t count);

memset_s: Versión segura de memset, rellena el bloque de memoria con el carácter especificado y verifica el tamaño del búfer.

errno_t memset_s(void *dest, rsize_t destsz, int ch, rsize_t count);

4. Otras funciones seguras comunes

_itoa_sy_ultoa_s: Versión segura de la función de conversión de enteros, verifica el tamaño del búfer de destino al convertir un entero en una cadena.

errno_t _itoa_s(int value, char *buffer, size_t buffer_size, int radix);
errno_t _ultoa_s(unsigned long value, char *buffer, size_t buffer_size, int radix);

_strlwr_sy_strupr_s: Versión segura que convierte cadenas a minúsculas o mayúsculas.

errno_t _strlwr_s(char *str, size_t numberOfElements);
errno_t _strupr_s(char *str, size_t numberOfElements);

Ejemplos

A continuación se muestran ejemplos de uso de funciones seguras de C para operaciones de cadenas y memoria, que demuestran cómo evitan los problemas comunes de desbordamiento de búfer y proporcionan una forma de programación más segura.

Ejemplo 1: strcpy_s y strcat_s

Ejemplo

#include <stdio.h>
#include <string.h>

int main() {
    char dest[20]; // El tamaño del búfer de destino es 20
    const char *src = "Hello, World!";

    // Usar strcpy_s para copiar src a dest
    if (strcpy_s(dest, sizeof(dest), src) != 0) {
        printf("strcpy_s failed!\n");
        return 1; // Devuelve el código de error
    } else {
        printf("After strcpy_s: %s\n", dest);
    }

    // Usar strcat_s para añadir " C Language" a dest
    const char *appendStr = " C Language";
    if (strcat_s(dest, sizeof(dest), appendStr) != 0) {
        printf("strcat_s failed!\n");
        return 1; // Devuelve el código de error
    } else {
        printf("After strcat_s: %s\n", dest);
    }

    return 0;
}

Salida:

After strcpy_s: Hello, World!
strcat_s failed!

En el código anterior, strcpy_s copió correctamente la cadena "Hello, World!" a dest, pero como dest tiene un tamaño de 20, que no es suficiente para contener "Hello, World! C Language", strcat_s detecta que el búfer es insuficiente y devuelve un código de error.

Ejemplo 2: memcpy_s

Ejemplo

#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Sensitive Data";
    char dest[15]; // El tamaño del búfer de destino es 15

    // Usar memcpy_s para copiar datos a dest
    if (memcpy_s(dest, sizeof(dest), src, strlen(src) + 1) != 0) {
        printf("memcpy_s failed!\n");
        return 1; // Devuelve el código de error
    } else {
        printf("After memcpy_s: %s\n", dest);
    }

    return 0;
}

Salida:

After memcpy_s: Sensitive Data

En este ejemplo, memcpy_s verifica si el búfer de destino dest es lo suficientemente grande para contener los datos de src, incluido el carácter nulo al final de la cadena. Si destsz es menor que strlen(src) + 1, la función devuelve un error y no realiza la copia de memoria.

Ejemplo 3: strtok_s

Ejemplo

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "apple,orange,banana";
    char *token;
    char *context = NULL;

    // Usar strtok_s para dividir la cadena
    token = strtok_s(str, ",", &context);
    while (token != NULL) {
        printf("Token: %s\n", token);
        token = strtok_s(NULL, ",", &context);
    }

    return 0;
}

Salida:

Token: apple
Token: orange
Token: banana

En este ejemplo, strtok_s utiliza el parámetro context para guardar información de contexto al dividir la cadena, evitando así el problema de que strtok no es seguro para hilos.

Ejemplo 4: sprintf_s

Ejemplo

#include <stdio.h>

int main() {
    char buffer[50];
    int num = 42;
    const char *str = "Hello";

    // Usar sprintf_s para formatear la cadena y verificar el tamaño del búfer
    if (sprintf_s(buffer, sizeof(buffer), "Number: %d, String: %s", num, str) < 0) {
        printf("sprintf_s failed!\n");
        return 1; // Devuelve el código de error
    } else {
        printf("Formatted String: %s\n", buffer);
    }

    return 0;
}

Salida:

Formatted String: Number: 42, String: Hello

Aquí, sprintf_s acepta el tamaño del búfer como parámetro al formatear la cadena. Si la cadena formateada supera el tamaño de buffer, la función devuelve un error, evitando así el desbordamiento de búfer.

Los ejemplos anteriores muestran el uso de funciones seguras de C para copia de cadenas, concatenación, copia de memoria, división de cadenas y salida formateada. Estas funciones proporcionan verificación del tamaño del búfer, lo que mejora significativamente la seguridad del código.


Manual de referencia

Las funciones seguras están diseñadas principalmente para prevenir el desbordamiento de búfer (Buffer Overflow). Este tipo de vulnerabilidad generalmente se origina porque las funciones no verifican el tamaño del búfer de destino.

Actualmente, los estándares principales de funciones seguras provienen principalmente del Anexo K (interfaz de verificación de límites) del estándar C11, así como de las versiones mejoradas proporcionadas por varios sistemas operativos (como MSVC CRT en Windows).

La siguiente tabla muestra la tabla de referencia de las funciones seguras de C.

1. Procesamiento de cadenas

Esta es el área más propensa a desbordamientos. Las funciones seguras generalmente requieren pasar el tamaño del búfer de destino.

Función original (no segura) Función segura alternativa Principales mejoras
strcpy strcpy_s Se añade el parámetro de tamaño del búfer de destino; si la cadena fuente es demasiado larga, se invoca un manejador de restricciones.
strcat strcat_s Se añade el parámetro de tamaño del espacio restante del búfer de destino, evitando desbordamientos al concatenar.
strncpy strncpy_s Se asegura que la cadena de destino termine con\0y se añaden verificaciones de errores en tiempo de ejecución.
strtok strtok_s Se introduce un puntero de contexto (Context), lo que la convierte ensegura para hilosy más robusta.
strlen strnlen_s Se añade una longitud máxima de verificación para evitar, en ausencia de\0bucles infinitos en datos corruptos.

2. Entrada/Salida formateada

Las funciones de formato inseguras pueden provocar vulnerabilidades de cadena de formato o desbordamiento de búfer.

Función original (no segura) Función segura alternativa Principales mejoras
sprintf snprintf / sprintf_s snprintfLimita el número de caracteres escritos;sprintf_sverifica la validez de la cadena de formato.
vsprintf vsnprintf_s Añade un límite de tamaño de búfer, adecuado para listas de argumentos variables.
scanf scanf_s Sobre%sy%cy otros especificadores de conversión,se exigeque se proporcione el tamaño del búfer.
fscanf fscanf_s Se añade un límite de longitud al leer entradas de archivos.
sscanf sscanf_s Se añade un límite de longitud al leer desde cadenas.

3. Entrada/Salida estándar
Función original (no segura) Función segura alternativa Principales mejoras
gets gets_s / fgets getsHa quedado obsoleta en C11.gets_sSe debe especificar un límite máximo de lectura.
tmpnam tmpnam_s Se añade una verificación del tamaño del búfer al generar nombres de archivos temporales.

4. Operaciones de memoria

Función original (no segura) Función segura alternativa Principales mejoras
memcpy memcpy_s Se añade una verificación del tamaño del búfer de destino. Si se produce solapamiento o desbordamiento, devuelve un error.
memmove memmove_s Se añade una verificación de límite de tamaño al manejar regiones de memoria solapadas.
memset memset_s Garantiza que el compilador no omita la operación de borrado de memoria debido a optimizaciones (se usa comúnmente para borrar datos sensibles como contraseñas).
Otras extensiones