2009-05-22 19 views
23

Estoy portando algún código a Windows, y el compilador de Microsoft (Visual C++ 8) me dice que strerror() no es seguro.¿Por qué no puedo usar strerror?

Dejando a un lado el factor de molestia en todas las cosas seguras de cadenas de Microsoft, realmente puedo ver que algunas de las funciones obsoletas son peligrosas. Pero no puedo entender lo que podría estar mal con strerror(). Toma un código (int) y devuelve la cadena correspondiente, o la cadena vacía si ese código no se conoce.

¿Dónde está el peligro?

¿Hay una buena alternativa en C?

¿Existe una buena alternativa en C++?

[editar]

Habiendo tenido algunas buenas respuestas, y ahora entendiendo que algunas implementaciones pueden ser lo suficientemente loco como para escribir en realidad a un buffer común compartido - inseguro para la reentrada dentro de un solo hilo, no importa entre hilos! - mi pregunta deja de ser "¿Por qué no puedo usarla y cuáles son las alternativas?" a "¿Hay alguna alternativa decente, sucinta en C y/o C++?"

Gracias de antemano

+28

No puede usarlo porque Microsoft dice "el estándar ISO C sea condenado; no le dejaremos usarlo, a menos que anule la advertencia o el error con un pragma". También han prohibido memcpy() - eso es ridículo porque le dices cuántos bytes copiar y si no puedes pensarlo y sabes que hay suficiente espacio en el espacio objetivo para el número de bytes antes de llamar a memcpy() , no perteneces a un código de escritura de equipo en C o C++. –

Respuesta

18

strerror está en desuso porque no es seguro para subprocesos. strerror funciona en un búfer estático interno, que puede sobrescribirse por otros hilos concurrentes. Debe usar una variante segura llamada strerror_s.

La variante segura requiere que el tamaño del búfer se pase a la función para validar que el búfer sea lo suficientemente grande antes de escribir en él, lo que ayuda a evitar saturaciones de búfer que podrían permitir la ejecución de código malicioso.

+3

Er, la pregunta era: _¿Por qué_ se considera inseguro? Me has dicho exactamente nada. – JamieH

+10

Él _did_ le dirá por qué. Funciona en un búfer estático interno, es decir, uno que se comparte entre subprocesos. Eso no es seguro. – nsayer

+1

Ah, sí. Mis disculpas. Ahora, la siguiente pregunta, para el mundo en general, es: ¿por qué demonios alguien lo implementaría así? – JamieH

5

No puede confiar en la cadena devuelta por strerror() porque puede cambiar con la siguiente llamada a la función. Los valores devueltos anteriormente pueden quedar obsoletos. Especialmente en entornos multiproceso, no puede asegurarse de que la cadena sea válida cuando accede a ella.

Imagínese esto:

Thread #1: 
char * error = strerror(1); 
            Thread #2 
            char * error = strerror(2); 
printf(error); 

Dependiendo de la implementación de strerror(), este código imprime el código de error para el código de error 2, no para el código de error 1.

+6

¿Por qué mecanismo cambiaría? Presumiblemente, las cadenas se definen, estáticamente, en el interior de la implementación de la biblioteca C runtime y, por lo tanto, nunca cambiarán. ¿O es incorrecto, y de hecho pueden cambiar de forma dinámica? – JamieH

+1

¿Por qué asumirías esto? Sí, se PODRÍA hacer de esta manera, pero PODRÍA hacerse completamente diferente, no lo sabemos;) .. – beef2k

+3

El problema principal es que Posix no requiere que strerror() sea seguro para subprocesos. Tienes que usar strerror_r() en su lugar. En Windows use strerror_s(). –

1

Aunque no sé razones de Microsoft , Observo que strerror devuelve un carácter no const *, lo que significa que existe el riesgo de que Merry Prankster haya llamado strerror antes que tú y haya modificado el mensaje.

+2

Si es "const char *", el mismo Merry Prankster podría intentar lanzar a (char *) y cambiar el código después de eso;) .. Declarar algo como "const" no significa que no se puede cambiar. Simplemente da una sugerencia de optimización para el compilador. – beef2k

+3

Estoy de acuerdo en que, desde una perspectiva de pura consistencia, esto es cierto, pero sospecho fuertemente que la falta de const es simplemente histórica, y los usuarios están obligados a no alterar el contenido de la cadena de la misma manera que con muchas de esas partes no const-pero-deberían-ser de la biblioteca estándar. _Si_ ese es el caso, aún no veo ninguna razón para la desaprobación de strerror(). – JamieH

+1

por lo que es const char * y diciendo que la cosa no debe ser cambiada sería suficiente para que sea segura * si * te aseguras de haber sincronizado las llamadas para que los usos no se intercalen. En cualquier caso, no puede prohibirle al programador que bloquee su programa. Aún podía crear una matriz y escribir fuera de los límites de la matriz. y podría matar a los otros hilos usando Terminate o algo :) Hacer que la devolución sea const char * es una protección efectiva contra los cambios accidentales. –

16

strerror por sí solo no es inseguro. En los viejos tiempos antes de enhebrarlo simplemente no era un problema. Con subprocesos, dos o más subprocesos podrían llamar al strerror dejando el búfer devuelto en un estado indefinido. Para programas de un solo hilo, no debería doler usar strerror a menos que jueguen algunos juegos extraños en libc, como la memoria común para todas las aplicaciones en una DLL.

Para hacer frente a esto hay una nueva interfaz para la misma funcionalidad:

int strerror_r(int errnum, char *buf, size_t buflen); 

Tenga en cuenta que la persona que llama proporciona el espacio de amortiguación y el tamaño del búfer. Esto resuelve el problema. Incluso para aplicaciones de una sola rosca, puede que también lo use. No va a doler un poco, y es mejor que te acostumbres a hacerlo de la manera más segura.

NOTA: el prototipo anterior es la especificación XSI. Puede variar según la plataforma o con las opciones del compilador o los símbolos #define. GNU, por ejemplo, hace que ni su propia versión disponible en función de un #define

+1

Y también está TR24731 que define strerror_s(). –

+0

"En los viejos tiempos antes de enhebrar, simplemente no era un problema". Tampoco es reentrante, pero nunca afirmó ser invocable por los manejadores de señal. –

+0

No hay problema, solo 'longjmp' con el manejador de señal;) –

2

Para un envoltorio sucinta, puede utilizar STLSoft 's stlsoft::error_desc, como en:

std::string errstr = stlsoft::error_desc(errno); 

Si examina el código, parece que está implementado en términos de strerror(), lo que significa que será seguro para la reentrada dentro de un hilo (es decir, si se usa varias veces dentro de un enunciado dado), pero no aborda el problema del multihilo.

Parece que operan ciclos de liberación bastante rápidos para defectos, por lo que podría intentar solicitar un mod.

+0

Gracias. Podría intentar eso. – JamieH

12

Habiendo tenido algunas buenas respuestas, y ahora entendiendo que algunas implementaciones pueden ser lo suficientemente loco como para escribir en realidad a un buffer común compartido - inseguro para la reentrada dentro de un solo hilo, no importa entre hilos! - mi pregunta deja de ser "¿Por qué no puedo usarla y cuáles son las alternativas?" a "¿Hay alguna alternativa decente, sucinta en C y/o C++?"

POSIX especifica strerror_r(), y en Windows se puede utilizar strerror_s(), que es un poco diferente, pero tiene el mismo objetivo. Hago esto:

#define BAS_PERROR(msg, err_code)\ 
    bas_perror(msg, err_code, __FILE__, __LINE__) 

void bas_perror (const char* msg, int err_code, const char* filename, 
       unsigned long line_number); 


void 
bas_perror (const char* usr_msg, int err_code, const char* filename, 
      unsigned long line_number) 
{ 
    char sys_msg[64]; 

#ifdef _WIN32 
    if (strerror_s(sys_msg, sizeof sys_msg, err_code) != 0) 
    { 
    strncpy(sys_msg, "Unknown error", taille); 
    sys_msg[sizeof sys_msg - 1] = '\0'; 
    } 
#else 
    if (strerror_r(err_code, sys_msg, sizeof sys_msg) != 0) 
    { 
    strncpy(sys_msg, "Unknown error", sizeof sys_msg); 
    sys_msg[sizeof sys_msg - 1] = '\0'; 
    } 
#endif 

    fprintf(stderr, "%s: %s (debug information: file %s, at line %lu)\n", 
      usr_msg, sys_msg, filename, line_number); 
} 

Escribí esta función porque hebras POSIX funciones no modifican errno, que devuelven un código de error. Así que esta función es básicamente la misma que perror(), excepto que le permite proporcionar un código de error que no sea errno, y también muestra cierta información de depuración. Puedes adaptarlo a tu necesidad.

Cuestiones relacionadas