ctypes en python se estrella con memset

Estoy tratando de borrar la cadena de contraseña de la memoria como se sugiere aquí .

Escribí ese pequeño fragmento:

import ctypes, sys def zerome(string): location = id(string) + 20 size = sys.getsizeof(string) - 20 #memset = ctypes.cdll.msvcrt.memset # For Linux, use the following. Change the 6 to whatever it is on your computer. print ctypes.string_at(location, size) memset = ctypes.CDLL("libc.so.6").memset memset(location, 0, size) print "Clearing 0x%08x size %i bytes" % (location, size) print ctypes.string_at(location, size) a = "asdasd" zerome(a) 

Curiosamente, este código funciona bien con IPython,

 [7] oz123@yenitiny:~ $ ipython a.py Clearing 0x02275b84 size 23 bytes 

Pero se estrella con Python:

 [8] oz123@yenitiny:~ $ python a.py Segmentation fault [9] oz123@yenitiny:~ $ 

¿Alguna idea de por qué?

Probé en Debian Wheezy, con Python 2.7.3.

pequeña actualización …

El código funciona en CentOS 6.2 con Python 2.6.6. El código se bloqueó en Debian con Python 2.6.8. Intenté pensar por qué funciona en CentOS, y no en Debian. La única razón, que vino de inmediato, es que mi Debian es multiarch y CentOS se ejecuta en mi computadora portátil más antigua con la CPU i686.

Por lo tanto, reinicié mi latido CentOS y cargué Debian Wheezy en él. El código funciona en Debian Wheezy, que no es de arco múltiple. Por lo tanto, sospecho que mi configuración en Debian es algo problemática …

ctypes ya tiene una función memset , por lo que no tiene que hacer un puntero de función para la función libc / msvcrt. Además, 20 bytes son para plataformas comunes de 32 bits. En sistemas de 64 bits es probable que sea de 36 bytes. Aquí está el diseño de un PyStringObject :

 typedef struct { Py_ssize_t ob_refcnt; // 4|8 bytes struct _typeobject *ob_type; // 4|8 bytes Py_ssize_t ob_size; // 4|8 bytes long ob_shash; // 4|8 bytes (4 on 64-bit Windows) int ob_sstate; // 4 bytes char ob_sval[1]; } PyStringObject; 

Por lo tanto, podría ser 5 * 4 = 20 bytes en un sistema de 32 bits, 8 * 4 + 4 = 36 bytes en Linux de 64 bits, o 8 * 3 + 4 * 2 = 32 bytes en Windows de 64 bits. Como una cadena no se rastrea con un encabezado de recolección de basura, puede usar sys.getsizeof . En general, si no desea que se incluya el tamaño del encabezado del GC (en la memoria es en realidad antes de la dirección base del objeto que obtiene de id ), entonces use el método __sizeof__ del objeto. Al menos esa es una regla general en mi experiencia.

Lo que quieres es simplemente restar el tamaño del búfer del tamaño del objeto. La cadena en CPython está terminada en nulo, así que simplemente agregue 1 a su longitud para obtener el tamaño del búfer. Por ejemplo:

 >>> a = 'abcdef' >>> bufsize = len(a) + 1 >>> offset = sys.getsizeof(a) - bufsize >>> ctypes.memset(id(a) + offset, 0, bufsize) 3074822964L >>> a '\x00\x00\x00\x00\x00\x00' 

Editar

Una mejor alternativa es definir la estructura PyStringObject . Esto hace que sea conveniente comprobar ob_sstate . Si es mayor que 0, eso significa que la cadena está internada y lo que hay que hacer es lanzar una excepción. Las cadenas de un solo carácter se internan, junto con las constantes de cadena en los objetos de código que constan de solo letras ASCII y subrayado, y también las cadenas utilizadas internamente por el intérprete para nombres (nombres de variables, atributos).

 from ctypes import * class PyStringObject(Structure): _fields_ = [ ('ob_refcnt', c_ssize_t), ('ob_type', py_object), ('ob_size', c_ssize_t), ('ob_shash', c_long), ('ob_sstate', c_int), # ob_sval varies in size # zero with memset is simpler ] def zerostr(s): """zero a non-interned string""" if not isinstance(s, str): raise TypeError( "expected str object, not %s" % type(s).__name__) s_obj = PyStringObject.from_address(id(s)) if s_obj.ob_sstate > 0: raise RuntimeError("cannot zero interned string") s_obj.ob_shash = -1 # not hashed yet offset = sizeof(PyStringObject) memset(id(s) + offset, 0, len(s)) 

Por ejemplo:

 >>> s = 'abcd' # interned by code object >>> zerostr(s) Traceback (most recent call last): File "", line 1, in  File "", line 10, in zerostr RuntimeError: cannot zero interned string >>> s = raw_input() # not interned abcd >>> zerostr(s) >>> s '\x00\x00\x00\x00'