Python中的ctypes与memset冲突导致崩溃

6

我试图从内存中删除密码字符串就像这里建议的那样

我写了这个小代码段:

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)

奇怪的是,这段代码在IPython上运行良好,

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

但是使用Python时会出现崩溃:

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

有什么想法吗?

我在Debian Wheezy上测试,使用Python 2.7.3。

小更新...

代码在CentOS 6.2上使用Python 2.6.6可以正常运行。 代码在Debian上使用Python 2.6.8崩溃了。 我试着思考为什么它在CentOS上可以工作而在Debian上不行。唯一的原因是, 我的Debian是多架构的,而CentOS则在我的旧笔记本电脑上运行,它具有i686 CPU。

因此,我重新启动了我的CentOS笔记本电脑并加载了Debian Wheezy。 代码可以在非多架构的Debian Wheezy上工作。 因此,我怀疑我的Debian配置存在问题...


这个 +20 的 hack 在任何 CPython 上都能正常工作吗?毕竟它甚至没有被记录在文档中。你确定它在不会崩溃的配置下确实清除了正确的值吗? - wRAR
你刚才说:“我不确定这段代码是否符合我的要求,但还可以”。 - wRAR
当然,在托管环境中尝试擦除内存本身就是错误的,您似乎正在通过错误的方式解决错误的问题。 - wRAR
1个回答

8

ctypes已经有了memset函数,因此您不必为libc/msvcrt函数创建函数指针。此外,20个字节适用于常见的32位平台。在64位系统上,它可能是36个字节。这是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; 

在32位系统上,它可能是5*4=20字节,在64位Linux上,它可能是8*4+4=36字节,在64位Windows上,它可能是8*3+4*2=32字节。由于字符串没有垃圾收集头进行跟踪,因此可以使用sys.getsizeof。一般情况下,如果您不想在内存中包括GC头大小(实际上在对象的基地址之前),则使用对象的__sizeof__方法。至少在我的经验中这是一个通用规则。
你要做的就是从对象大小中减去缓冲区大小。在CPython中,字符串是以空字符结尾的,因此只需将其长度加1即可得到缓冲区大小。例如:
>>> 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'

编辑

一个更好的选择是定义PyStringObject结构。这样可以方便地检查ob_sstate。如果它大于0,那么意味着该字符串被内部化,应该引发异常。单个字符的字符串被内部化,以及代码对象中仅包含ASCII字母和下划线的字符串常量,还有解释器内部用于名称(变量名、属性)的字符串。

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))

例如:

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

>>> s = raw_input() # not interned
abcd
>>> zerostr(s)
>>> s
'\x00\x00\x00\x00'

谢谢!你应该在我之前引用的链接中也发布答案,那里也很相关!+1 - oz123

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接