- El proyecto cURL, después de haber eliminado antes
strncpy(), ahora también prohíbe por completo strcpy() en su base de código
strcpy() tiene una API simple, pero existe el riesgo de separar la validación del tamaño del búfer, lo que la vuelve insegura en el mantenimiento a largo plazo
- Como reemplazo, se introdujo una nueva función llamada
curlx_strcopy(), que recibe como argumentos tanto el tamaño del búfer de destino como la longitud de la cadena para verificar si la copia es posible antes de ejecutarla
- Esta función usa internamente
memcpy() y también garantiza el manejo del carácter nulo de terminación
- Con este cambio se busca mejorar la seguridad y la consistencia del código, además de reducir el problema de que la IA genere reportes erróneos de vulnerabilidades
Contexto de la eliminación de strcpy
- cURL ya había eliminado en el pasado todas las llamadas a
strncpy(), señalando los problemas de esta función: API poco intuitiva, falta de garantía de terminación nula y relleno innecesario con ceros
- En los casos donde se necesita copiar subcadenas, se cambió a usar
memcpy() y manejar la terminación nula manualmente
strcpy() tiene una API simple, pero no especifica el tamaño del búfer, así que durante el mantenimiento existe el riesgo de que el código de validación y la llamada de copia queden separados
- Si el código es modificado durante décadas por distintos desarrolladores, existe la posibilidad de que la validación del tamaño del búfer quede anulada
Introducción de una nueva función de copia de cadenas
- Para evitar estos riesgos, se introdujo una función alternativa llamada
curlx_strcopy()
- Recibe como argumentos el búfer de destino, el tamaño del búfer, el búfer de origen y la longitud de la cadena de origen
- Solo realiza la copia con
memcpy() si tanto la copia como la terminación nula son posibles
- Si falla, inicializa el búfer de destino como una cadena vacía
- Esta función requiere más argumentos y más código que
strcpy(), pero garantiza la seguridad al acoplar estrechamente la validación del búfer con la operación de copia
- El uso de
strcpy() queda completamente prohibido en la base de código de cURL y se elimina igual que strncpy()
Detalles de implementación
- Un ejemplo de la definición de la función es el siguiente
void curlx_strcopy(char *dest, size_t dsize, const char *src, size_t slen)
{
DEBUGASSERT(slen < dsize);
if(slen < dsize) {
memcpy(dest, src, slen);
dest[slen] = 0;
}
else if(dsize)
dest[0] = 0;
}
- Mediante
DEBUGASSERT, se detectan errores de forma temprana durante el desarrollo, y está diseñada para que en entornos de despliegue real siempre tenga éxito
- No tiene valor de retorno como
strcpy, y se adopta un enfoque de detectar errores en las fases de pruebas y fuzzing
Reacción de la comunidad
- Algunos desarrolladores señalaron que se parece a
strcpy_s() (C11 Annex K), aunque cURL sigue usando el estándar C89
- Otras opiniones propusieron agregar un valor de retorno o mejorar la forma de manejar fallos del búfer
- Ante esto, cURL explicó que “como fue diseñada para ser una función que siempre tiene éxito, un valor de retorno es innecesario”
Efecto adicional relacionado con la IA
- Este cambio también puede evitar el problema de que chatbots de IA detecten erróneamente el uso de strcpy en el código de cURL y afirmen que es vulnerable
- Aun así, el autor mencionó que “la IA todavía podría inventar otros reportes falsos”, señalando las limitaciones del análisis de código basado en IA
5 comentarios
Lo correcto es usar
snprintfen lugar destrcpy. Si haystrcpyen el código, hay que averiguar dónde vive el desarrollador que lo hizo.Así era como trabajaba con código de depuración cuando estaba en una empresa de videojuegos hace 25 años, y no era solo
strcpy. En la versión de lanzamiento se volvía a desactivar para el servicio por la mejora de velocidad. De hecho, en el área de juegos los choques de memoria son de lo más sensible, así que trabajábamos con muchísimo cuidado y atención, e incluso creamos y usamos nuestro propio depurador de memoria. Pero al verlo hoy en día, resulta que eso estaba haciendo recolección de basura. Qué recuerdos tan nostálgicos.Error C4996: "strcpy": esta función o variable puede no ser segura. Considere usar
strcpy_sen su lugar. Para deshabilitar la advertencia de obsolescencia, use_CRT_SECURE_NO_WARNINGS. Consulte la ayuda en línea para más detalles.Comentarios en Hacker News
strcpy()no solo es malo desde el punto de vista de la seguridad, sino también en rendimientoAntes se pensaba que
strcpy()era eficiente cuando no se conocía la longitud de la cadena, pero en realidad, como copia de a un byte por vez, la CPU tiene que hacer predicción de saltos, y eso es ineficientestrcpyuse un bucle escalar. Me pregunto si eso solo ocurre en arquitecturas ARMTodas las rutinas de cadenas de C tienen grandes limitaciones, así que siempre me parecieron poco útiles
Por eso creo que hace falta una biblioteca que registre, junto con el puntero de cadena, el tamaño de la memoria asignada
Como ejemplo, vale la pena ver la biblioteca bstring
strncpyapareció para copiar nombres de archivo de longitud fija. Para más detalles, ver esta respuesta de StackOverflowstrncpyoriginalmente era una función para manejar campos de cadena de ancho fijo. Por ejemplo, servía para rellenar con NUL un campo comochar username[20]. Ver la documentación relacionada en el manual string_copying.7Me resulta extraño que
curlx_strcopyno devuelva si tuvo éxito o noSe podría revisar
dest[0], pero eso tiene alta probabilidad de inducir errores y no es intuitivoDEBUGASSERT(slen < dsize);pasa, entonces fue exitoso, pero en un build release pueden eliminarse los assert. Creo que sería mejor un código de error explícitostrncpy()originalmente no era para cadenas terminadas en null, sino para campos de longitud fijaEl problema empezó cuando los analizadores estáticos recomendaron usar
strncpyen lugar destrcpy. Las alternativas reales eransnprintfostrlcpystrlcpyes una función de la familia BSD, así que no está en POSIX. La recomendación oficial esstpecpy, pero casi no hay implementaciones reales. Ver documentación relacionadastrncpyrellena después del null era permitir comparaciones eficientes en campos de nombres de longitud fija, como las entradas de directorio. Eso también está especificado en los documentos de fundamento del estándar ANSI CEsta API se siente como Annex-K: el tamaño del buffer de destino incluye el espacio para NUL, pero el tamaño de origen no lo incluye
Francamente, me parece mejor usar
memcpydirectamenteMe llamó la atención la frase del artículo: “
strcpyes un señuelo para que la IA genere reportes de vulnerabilidades incorrectos”strcpycomo problema, sino que genera demostraciones complejas con errores lógicos, y los mantenedores terminan sufriendo para verificarlasEl principio de “validar cerca del código” es bueno, pero se vuelve ambiguo cuando hay que validar temprano en el ciclo de vida de los datos
Creo que sería bueno poder distinguir por tipo, como con
Resulten Rust, si se trata de “datos validados”Resultsolo contiene éxito/fracaso, pero no garantiza un estado validado. En cambio, sería mejor tener un tipo separado que solo pueda crearse tras pasar por el proceso de validación. Esa es la filosofía de “parse, don’t validate”La diferencia off-by-one entre el tamaño del buffer y la longitud de la cadena es un problema de usabilidad terrible. Es muy probable que siga causando errores
La nueva función de copia de cadenas propuesta vacía el buffer de destino y devuelve
voidsi no se puede copiarPero en estos casos me parece mejor tratarlo como error y no tocar el buffer. Confiar solo en
DEBUGASSERTda poca seguridadFelicidades por completar el proyecto. Incluso en C/C++ se puede lograr seguridad de memoria si se pone esfuerzo
Pero en móviles el tamaño de letra de las gráficas es demasiado pequeño y dificulta la lectura
strcpyno vuelve al código seguro en memoria automáticamenteTambién está bien pasarse por completo a C3. Como es un proyecto que mantiene la sintaxis de C con cambios mínimos y añade funciones modernas, la migración también es sencilla.