2009-01-24 11 views
5

He escrito una API que requiere que se inicialice un contexto y, posteriormente, pase a cada llamada API. La persona que llama asigna la memoria para el contexto y luego la pasa a la función de inicio con otros parámetros que describen cómo quieren que se comporten las llamadas posteriores de la API. El contexto es opaco, por lo que el cliente no puede realmente perder el tiempo allí; solo está destinado para el uso interno de las funciones API.Verificación de que la memoria se ha inicializado en C

El problema al que me estoy enfrentando es que las personas que llaman están asignando el contexto, pero no lo están inicializando. Como resultado, las funciones API posteriores se refieren a basura sin sentido como si fuera un contexto real.

Estoy buscando una manera de verificar que el contexto pasó a una función de API realmente se ha inicializado. No estoy seguro si esto es posible. Dos ideas en las que he pensado son:

  1. Use una constante predefinida y guárdela en un campo "mágico" del contexto que se verificará en el tiempo de invocación de la API.
  2. Utilice una suma de comprobación del contenido del contexto, almacénelo en el campo "mágico" y verifíquelo en el momento de la invocación.

Lamentablemente sé que cualquiera de estas opciones podría dar como resultado una verificación positiva falsa, ya sea porque la basura aleatoria en la memoria coincide con el número "mágico" o porque el contexto ocupa el mismo espacio que un inicializado previamente contexto. Creo que este último escenario es más probable.

¿Esto simplemente se reduce a una cuestión de probabilidad? ¿Que puedo evitar falsos positivos en la mayoría de los casos, pero no en todos? ¿Vale la pena utilizar un sistema que simplemente me da una probabilidad razonable de precisión, o esto solo haría que la depuración de otros problemas sea más difícil?

Respuesta

3

Su variable de contexto es probablemente en este momento un tipo de puntero a la memoria asignada.En lugar de esto, conviértalo en un token o handle que pueda ser verificado explícitamente. Cada vez que se inicializa un contexto, devuelve un token nuevo (no el objeto de contexto real) y almacena ese token en una lista interna. Luego, cuando un cliente le da un contexto más adelante, compruebe que es válido buscando en la lista. Si es así, el token puede convertirse al contexto real y utilizarse, de lo contrario, se devuelve un error.

typedef Context long; 

typedef std::map<Context, InternalContext> Contexts; 
Contexts _contexts; 

Context nextContext() 
{ 
    static Context next=0; 
    return next++; 
} 

Context initialise() 
{ 
    Context c=nextContext(); 
    _contexts.insert(make_pair(c, new InternalContext)); 
    return c; 
} 

void doSomethingWithContext(Context c) 
{ 
    Contexts::iterator it=_ _contexts.find(c); 
    if (it==_contexts.end()) 
    throw "invalid context"; 
    // otherwise do stuff with the valid context variable 
    InternalContext *internalContext=*it.second; 
} 

Con este método, no hay riesgo de acceso a memoria no válido, ya que solo utilizará referencias válidas de contexto.

0

Para eludir el problema de la ubicación de una memoria de un contexto anterior que se reutiliza, puede, además de liberar el contexto, restablecerlo y eliminar el número "mágico", asumiendo que el usuario libera el contexto utilizando su API. De esta forma, cuando el sistema devuelve ese mismo bloque de memoria para la siguiente solicitud de contexto, la verificación del número mágico fallará.

6

La mejor solución, creo, es agregar funciones create()/delete() a su API y usar create para asignar e inicializar la estructura. Puede poner una firma al comienzo de la estructura para verificar que el puntero que le pasa apunte a la memoria asignada con create() y use delete() para sobrescribir la firma (o el búfer completo) antes de liberar la memoria.

No se pueden evitar los falsos positivos en C porque la persona que llamó malloc'd la memoria que "sucedió" para comenzar con su firma; pero hace que la firma sea razonablemente larga (digamos 8 bytes) y las probabilidades son bajas. Sin embargo, tomar la asignación de las manos de la persona que llama al proporcionar una función create() será muy útil.

Y, sí, su mayor riesgo es que un búfer inicializado se libere sin usar delete(), y un malloc subsiguiente reutilice ese bloque de memoria.

1

Puede definir una nueva llamada de API que tome la memoria no inicializada y la inicialice de la manera que necesite. Entonces, parte de la API del cliente es que el cliente debe llamar a la función de inicialización del contexto, de lo contrario se producirá un comportamiento indefinido.

0

vea qué hace su sistema con menmory uninitialzed. m $ does: Uninitialized memory blocks in VC++

+0

Esto solo es cierto para las compilaciones de depuración: no puede confiar en esto para el código de versión –

3

Mire el artículo de Matt Bishop en Robust Programming. El uso de tickets o tokens (similar a los identificadores de archivos en algunos aspectos, pero también incluye un número nonce utilizado una vez) permite que su código de biblioteca se asegure de que el token que está utilizando sea válido. De hecho, asigna la estructura de datos en nombre del usuario y le devuelve al usuario un ticket que debe proporcionarse para cada llamada a la API que defina.

Tengo un código basado estrechamente en ese sistema. El encabezado incluye los comentarios:

/* 
** Based on the tickets in qlib.c by Matt Bishop ([email protected]) in 
** Robust Programming. Google terms: Bishop Robust Nonce. 
** http://nob.cs.ucdavis.edu/~bishop/secprog/robust.pdf 
** http://nob.cs.ucdavis.edu/classes/ecs153-1998-04/robust.html 
*/ 

También construí un sistema de asignación de memoria basado en arena usando tickets para identificar diferentes arenas.

Cuestiones relacionadas