¿Por qué strlcpy y strlcat se consideran inseguros?
security strncpy (6)
Entiendo que strlcpy
y strlcat
fueron diseñados como reemplazos seguros para strncpy
y strncat
. Sin embargo, algunas personas aún son de la opinión de que son inseguras y simplemente causan un tipo diferente de problema .
¿Puede alguien dar un ejemplo de cómo el uso de strlcpy
o strlcat
(es decir, una función que siempre termina sus cadenas) puede conducir a problemas de seguridad?
Ulrich Drepper y James Antill afirman que esto es cierto, pero nunca brinden ejemplos ni aclaren este punto.
Creo que Ulrich y otros creen que dará una falsa sensación de seguridad. Accidentalmente truncar cadenas puede tener implicaciones de seguridad para otras partes del código (por ejemplo, si se trunca una ruta del sistema de archivos, es posible que el programa no realice operaciones en el archivo previsto).
Cuando la gente dice, " strcpy()
es peligroso, use strncpy()
lugar" (o declaraciones similares sobre strcat()
etc., pero voy a usar strcpy()
aquí como mi enfoque), quieren decir que no hay límites revisando strcpy()
. Por lo tanto, una cadena demasiado larga dará lugar a sobrepasamientos del búfer. Ellos son correctos. El uso de strncpy()
en este caso evitará los desbordamientos del búfer.
Siento que strncpy()
realmente no soluciona errores: resuelve un problema que un buen programador puede evitar fácilmente.
Como programador en C, debe conocer el tamaño del destino antes de intentar copiar cadenas. Esa es la suposición en los últimos parámetros de strncpy()
y strlcpy()
también: les proporcionas ese tamaño. También puede conocer el tamaño de la fuente antes de copiar cadenas. Luego, si el destino no es lo suficientemente grande, no llame a strcpy()
. Reasigne el búfer o haga otra cosa.
¿Por qué no me gusta strncpy()
?
- En la mayoría de los casos,
strncpy()
es una mala solución: su cadena se truncará sin previo aviso. Prefiero escribir un código adicional para resolver esto por mí mismo y luego tomar el curso de acción que quiero tomar, en lugar de dejar que algunos función decidir para mí sobre qué hacer. -
strncpy()
es muy ineficiente. Escribe en cada byte en el búfer de destino. No necesita esos miles de''/0''
al final de su destino. - No escribe un
''/0''
terminación si el destino no es lo suficientemente grande. Entonces, debes hacerlo tú mismo de todos modos. La complejidad de hacer esto no vale la pena.
Ahora, venimos a strlcpy()
. Los cambios de strncpy()
hacen mejor, pero no estoy seguro si el comportamiento específico de strl*
garantiza su existencia: son demasiado específicos. Aún debes saber el tamaño del destino. Es más eficiente que strncpy()
porque no necesariamente escribe en cada byte en el destino. Pero resuelve un problema que puede resolverse haciendo: *((char *)mempcpy(dst, src, n)) = 0;
.
No creo que nadie diga que strlcpy()
o strlcat()
pueden generar problemas de seguridad, lo que ellos (y yo) decimos que pueden provocar errores, por ejemplo, cuando esperas que se escriba la cadena completa en lugar de una parte de eso.
El problema principal aquí es: ¿cuántos bytes copiar? El programador debe saber esto y si no lo hace, strncpy()
o strlcpy()
no lo salvarán.
strlcpy()
y strlcat()
no son estándar, ni ISO C ni POSIX. Entonces, su uso en programas portátiles es imposible. De hecho, strlcat()
tiene dos variantes diferentes: la implementación de Solaris es diferente de las demás para los casos extremos que implican la longitud 0. Esto lo hace incluso menos útil que lo contrario.
En primer lugar, strlcpy
nunca ha sido concebido como una versión segura de strncpy
(y strncpy
nunca ha sido concebido como una versión segura de strcpy
). Estas dos funciones no tienen ninguna relación. strncpy
es una función que no tiene ninguna relación con las cadenas C (es decir, cadenas terminadas en nulo). El hecho de que tenga el prefijo str...
en su nombre es simplemente un error histórico. La historia y el propósito de strncpy
son bien conocidos y están bien documentados. Esta es una función creada para trabajar con las llamadas cadenas de "ancho fijo" (no con C-strings) utilizadas en algunas versiones históricas del sistema de archivos Unix. Algunos programadores hoy en día se confunden por su nombre y suponen que se supone que strncpy
sirve como una función de copia C-string de longitud limitada (un hermano "seguro" de strcpy
), que en realidad es una tontería completa y conduce a malas prácticas de programación. La biblioteca estándar C en su forma actual no tiene ninguna función para la copia de C-string de longitud limitada. Aquí es donde strlcpy
encaja. strlcpy
es de hecho una verdadera función de copia de longitud limitada creada para trabajar con C-strings. strlcpy
hace correctamente todo lo que una función de copia de longitud limitada debería hacer. La única crítica que uno puede apuntar es que, lamentablemente, no es estándar.
En segundo lugar, strncat
, por otro lado, es de hecho una función que funciona con C-strings y realiza una concatenación de longitud limitada (de hecho es un hermano "seguro" de strcat
). Para utilizar esta función correctamente, el programador debe tener especial cuidado, ya que el parámetro de tamaño que acepta esta función no es realmente el tamaño del búfer que recibe el resultado, sino el tamaño de la parte restante (también, el carácter del terminador). se cuenta implícitamente). Esto podría ser confuso, ya que para vincular ese tamaño con el tamaño del búfer, el programador debe recordar realizar algunos cálculos adicionales, que a menudo se utilizan para criticar al strncat
. strlcat
se ocupa de estos problemas, cambiando la interfaz para que no sean necesarios cálculos adicionales (al menos en el código de llamada). Nuevamente, la única base en la que veo que se puede criticar es que la función no es estándar. Además, las funciones del grupo strcat
son algo que no verá en el código profesional muy a menudo debido a la capacidad de uso limitada de la idea misma de la concatenación de cadenas basada en reescaneo.
En cuanto a cómo estas funciones pueden conducir a problemas de seguridad ... Simplemente no pueden. No pueden conducir a problemas de seguridad en mayor grado que el lenguaje C en sí mismo puede "conducir a problemas de seguridad". Verá, durante bastante tiempo hubo un fuerte sentimiento de que el lenguaje C ++ tiene que moverse en la dirección de convertirse en un sabor extraño de Java. Este sentimiento a veces también se extiende al dominio del lenguaje C, lo que da como resultado una crítica bastante desorientada y forzada de las características del lenguaje C y las características de la biblioteca estándar C. Sospecho que también podríamos estar lidiando con algo así en este caso, aunque seguramente espero que las cosas no sean realmente tan malas.
La crítica de Ulrich se basa en la idea de que un truncamiento de cadena que no es detectado por el programa puede conducir a problemas de seguridad, a través de una lógica incorrecta. Por lo tanto, para estar seguro, debe verificar el truncamiento. Hacer esto para una concatenación de cadenas significa que está haciendo un control a lo largo de las líneas de esto:
if (destlen + sourcelen > dest_maxlen)
{
/* Bug out */
}
Ahora, strlcat
efectivamente realiza esta comprobación, si el programador recuerda verificar el resultado, para que pueda usarlo de manera segura:
if (strlcat(dest, source, dest_bufferlen) >= dest_bufferlen)
{
/* Bug out */
}
El punto de Ulrich es que, dado que tienes que tener destlen
y destlen
around (o recalcularlos, que es lo que strlcat
efectivamente hace), también podrías usar la memcpy
más eficiente de memcpy
modos:
if (destlen + sourcelen > dest_maxlen)
{
goto error_out;
}
memcpy(dest + destlen, source, sourcelen + 1);
destlen += sourcelen;
(En el código anterior, dest_maxlen
es la longitud máxima de la cadena que se puede almacenar en dest
- uno menos que el tamaño del buffer de dest
. dest_bufferlen
es el tamaño completo del dest buffer
).
No creo que strlcpy
y strlcat
se consideren inseguros o al menos no es la razón por la que no están incluidos en glibc, después de todo, glibc incluye strncpy e incluso strcpy.
La crítica que recibieron fue que supuestamente son ineficientes, no inseguros .
Según el documento de Secure Portability de Damien Miller:
Las API strlcpy y strlcat comprueban correctamente los límites del búfer de destino, nul-terminan en todos los casos y devuelven la longitud de la cadena de origen, lo que permite la detección de truncamiento. Esta API ha sido adoptada por la mayoría de los sistemas operativos modernos y muchos paquetes de software independientes, incluidos OpenBSD (donde se originó), Sun Solaris, FreeBSD, NetBSD, kernel de Linux, rsync y el proyecto GNOME. La notable excepción es la biblioteca C estándar de GNU, glibc [12], cuyo mantenedor se niega rotundamente a incluir estas API mejoradas, etiquetándolas de "mierda BSD horriblemente ineficiente" [4], a pesar de la evidencia previa de que son más rápidos que las API ellos reemplazan [13]. Como resultado, más de 100 de los paquetes de software presentes en el árbol de puertos de OpenBSD mantienen sus propios reemplazos strlcpy y / o strlcat o API equivalentes, lo que no es un estado ideal.
Es por eso que no están disponibles en glibc, pero no es cierto que no estén disponibles en Linux. Están disponibles en Linux en libbsd:
Están empaquetados en Debian y Ubuntu y otras distros. También puede tomar una copia y usarla en su proyecto; es corta y está bajo una licencia permisiva:
Hay dos "problemas" relacionados con el uso de las funciones de strl:
- Debe verificar los valores de retorno para evitar el truncamiento.
Los escritores de borrador estándar de c1x y Drepper argumentan que los programadores no verifican el valor de retorno. Drepper dice que de alguna manera deberíamos saber la longitud y usar memcpy y evitar por completo las funciones de cadena. El comité de estándares argumenta que el strcpy seguro debería devolver no cero en el truncamiento a menos que se indique lo contrario con la bandera _TRUNCATE
. La idea es que las personas sean más propensas a usar if (strncpy_s (...)).
- No se puede usar en cadenas.
Algunas personas piensan que las funciones de cadena nunca deberían colisionar incluso cuando se les suministre datos falsos. Esto afecta funciones estándar como strlen que en condiciones normales segfault. El nuevo estándar incluirá muchas de esas funciones. Los cheques por supuesto tienen una penalización de rendimiento.
Lo bueno de las funciones estándar propuestas es que puedes saber la cantidad de datos que te perdiste con las funciones strl .