【问题标题】:ctypes in python crashes with memsetpython中的ctypes与memset一起崩溃
【发布时间】:2013-03-12 23:46:41
【问题描述】:

我正在尝试从内存中删除密码字符串like it is suggested in here

我写了那个小sn-p:

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。 代码在使用 Python 2.6.8 的 Debian 上崩溃。 我试着思考为什么它可以在 CentOS 上运行,而不是在 Debian 上运行。唯一的理由, 有一点不同的是,我的 Debian 是 multiarch 和 CentOS 在我的带有 i686 CPU 的旧笔记本电脑上运行。

因此,我重新启动了我的 CentOS 笔记本电脑并在其上加载了 Debian Wheezy。 该代码适用于非多架构的 Debian Wheezy。 因此,我怀疑我在 Debian 上的配置有些问题......

【问题讨论】:

  • +20 的这个 hack 真的适用于任何 CPython 吗?毕竟它甚至没有记录。您确定它实际上清除了不会崩溃的配置的正确值吗?
  • 您刚刚说过“我不确定这段代码是否符合我的要求,但没关系”。
  • 当然,在托管环境中尝试擦除内存本身就是错误的事情,而且您似乎用错误的方式解决了错误的问题。

标签: python ctypes ipython


【解决方案1】:

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 标头大小(在内存中,它实际上位于您从 id 获得的对象的基地址之前),则使用对象的 __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
猜你喜欢
  • 2022-10-08
  • 2015-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多