manejo - ¿Es posible garantizar que el código que realiza las escrituras en memoria no se optimice en C++?
lectura y escritura de datos en c++ (5)
Los compiladores de C ++ pueden optimizar las escrituras en la memoria :
{
//all this code can be eliminated
char buffer[size];
std::fill_n( buffer, size, 0);
}
Cuando se trata con datos confidenciales, el enfoque típico consiste en utilizar punteros volatile*
para garantizar que el compilador emita las escrituras en memoria. Aquí se SecureZeroMemory()
cómo se SecureZeroMemory()
función SecureZeroMemory()
en la biblioteca en tiempo de ejecución de Visual C ++ (WinNT.h):
FORCEINLINE PVOID RtlSecureZeroMemory(
__in_bcount(cnt) PVOID ptr, __in SIZE_T cnt )
{
volatile char *vptr = (volatile char *)ptr;
#if defined(_M_AMD64)
__stosb((PBYTE )((DWORD64)vptr), 0, cnt);
#else
while (cnt) {
*vptr = 0;
vptr++;
cnt--;
}
#endif
return ptr;
}
La función convierte el puntero pasado en un puntero volatile*
y luego escribe a través de este último. Sin embargo si lo uso en una variable local:
char buffer[size];
SecureZeroMemory( buffer, size );
La variable en sí misma no es volatile
. Por lo tanto, de acuerdo con la definición estándar de C ++ del comportamiento observable, las escrituras en el buffer
no cuentan como comportamiento observable y parece que pueden optimizarse.
Ahora hay muchos comentarios a continuación sobre los archivos de páginas, cachés, etc., que son todos válidos, pero ignorémoslos en esta pregunta. La única cuestión de esta pregunta es si el código para las escrituras en memoria está optimizado o no.
¿Es posible asegurarse de que el código que realiza las escrituras en la memoria no se optimice en C ++? ¿Es la solución en SecureZeroMemory()
compatible con C ++ Standard?
Con funciones primarias como SecureZeroMemory
, los escritores de la biblioteca normalmente se esforzarán por garantizar que el compilador no incorpore dichas funciones. Esto significa que en el fragmento
char buffer[size];
SecureZeroMemory( buffer, size );
el compilador no sabe lo que hace SecureZeroMemory
con el buffer
, por lo que el optimizador no puede probar que eliminar el fragmento no afecta el comportamiento observable del programa. En otras palabras, los escritores de la biblioteca ya habrán hecho todo lo posible para garantizar que dicho código no se optimice.
La palabra clave volatile
se puede aplicar a un puntero (o referencia, en C ++) sin requerir un lanzamiento, lo que significa que los accesos a través de este puntero no deben optimizarse. La declaración de la variable no importa.
El comportamiento es análogo a const
:
char buffer[16];
char const *p = buffer;
buffer[0] = ''a''; // okay
p[0] = ''b''; // error
Que exista un puntero const
en el búfer no altera el comportamiento de la variable de ninguna manera, solo el comportamiento del puntero modificado. Si la variable se declara const
, entonces está prohibido generarle punteros no const
:
char const buffer[16];
char *p = buffer; // error
Similar,
char buffer[16];
char volatile *p = buffer;
buffer[0] = ''a''; // may be optimized out
p[0] = ''b''; // will be emitted
y
char volatile buffer[16];
char *p = buffer; // error
El compilador es libre de eliminar accesos a través de lvalues no volatile
, así como de llamadas a funciones, donde puede probar que no se producen accesos a valores volatile
.
La función RtlSecureZeroMemory
es segura de usar porque el compilador puede ver la definición (incluido el acceso volatile
dentro del bucle o, dependiendo de la plataforma, la declaración del ensamblador, que es opaca al compilador y, por lo tanto, se supone que no se puede optimizar), o Tiene que asumir que la función realizará un acceso volatile
.
Si desea evitar la dependencia en el archivo de encabezado <winnt.h>, entonces una función similar funcionará bien con cualquier compilador conforme.
Ni el estándar C ni el estándar C ++ imponen requisitos sobre cómo las implementaciones almacenan cosas en la memoria física. Sin embargo, las implementaciones son libres de especificar tales cosas, y las implementaciones de calidad que son adecuadas para aplicaciones que requieren ciertos comportamientos de memoria física especificarán que se comportarán de manera consistente y adecuada.
Muchas implementaciones procesan al menos dos dialectos distintos. Al procesar su dialecto de "optimizaciones deshabilitadas", a menudo documentan con gran detalle cuántas acciones interactuarán con la memoria física. Desafortunadamente, la habilitación de las optimizaciones por lo general cambiará en un dialecto semánticamente más débil que no garantiza casi nada sobre cómo las acciones interactuarán con la memoria física. Si bien debería ser posible procesar muchas optimizaciones simples y directas mientras aún se procesan las cosas de una manera que sea coherente con el dialecto de "optimizaciones deshabilitadas" en ciertos casos fácilmente identificables en los que es probable que importen, los compiladores del compilador no están interesados en Proporcionando modos que se centran en la fruta segura de baja altura.
La única forma confiable de garantizar que la memoria física se trate de una manera determinada es usar un dialecto que prometa tratar la memoria física de esa manera. Si uno hace eso, generalmente será fácil obtener el tratamiento requerido. Si uno no lo hace, nada garantizará que una implementación "creativa" no hará algo inesperado.
No hay solución portátil. Si lo desea, el compilador podría haber hecho copias de los datos mientras lo usaba en varios lugares de la memoria y cualquier función cero podría poner a cero solo la que está usando en ese momento. Cualquier solución será no portátil.
Siempre hay una condición de carrera entre cuando hay información confidencial en la memoria y la hora en que la borras. En esa ventana de tiempo, su aplicación podría bloquearse y volcar el núcleo o un usuario malintencionado podría obtener un volcado de memoria del espacio de direcciones del proceso con información confidencial en texto sin formato.
Es posible que no deba almacenar información confidencial en la memoria en texto plano. De esta manera, logrará una mejor seguridad y evitará este problema por completo.