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 devuelven
errno_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 <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 <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 <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
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). |