是否可以恢复损坏的“interned”字节对象

2024-02-04

众所周知,小bytes-对象由 CPython 自动“驻留”(类似于intern https://docs.python.org/3/library/sys.html#sys.intern- 字符串函数)。更正: As 解释了 https://stackoverflow.com/a/50709066/5769463通过@abarnert,它更像是整数池而不是内部字符串。

在被“实验性”第三方库损坏后,是否可以恢复驻留的字节对象,或者是重新启动内核的唯一方法?

概念证明可以使用 Cython 功能来完成(Cython>=0.28):

%%cython
def do_bad_things():
   cdef bytes b=b'a'
   cdef const unsigned char[:] safe=b  
   cdef char *unsafe=<char *> &safe[0]   #who needs const and type-safety anyway?
   unsafe[0]=98                          #replace through `b`

或按照@jfs 的建议ctypes:

import ctypes
import sys
def do_bad_things():
    b = b'a'; 
    (ctypes.c_ubyte * sys.getsizeof(b)).from_address(id(b))[-2] = 98

显然,通过滥用 C 功能,do_bad_things更改不可变(或者 CPython 认为)对象b'a' to b'b'并且因为这个bytes-对象被拘留,我们可以看到之后发生不好的事情:

>>> do_bad_things() #b'a' means now b'b'
>>> b'a'==b'b'  #wait for a surprise  
True
>>> print(b'a') #another one
b'b'

可以恢复/清除字节对象池,以便b'a' means b'a'再次?


一点旁注:似乎并不是每个bytes-创建过程正在使用该池。例如:

>>> do_bad_things()
>>> print(b'a')
b'b'
>>> print((97).to_bytes(1, byteorder='little')) #ord('a')=97
b'a'

Python 3 没有实习生bytes对象的方式str。相反,它像使用它一样保留它们的静态数组int.

这在幕后是非常不同的。不利的一面是,这意味着没有可操作的表(带有 API)。从好的方面来说,这意味着如果你能找到静态数组,你就可以修复它,就像处理整数一样,因为数组索引和字符串的字符值应该是相同的。

如果你看进去bytesobject.c https://github.com/python/cpython/blob/master/Objects/bytesobject.c#L24,数组在顶部声明:

static PyBytesObject *characters[UCHAR_MAX + 1];

……然后,例如,在PyBytes_FromStringAndSize:

if (size == 1 && str != NULL &&
    (op = characters[*str & UCHAR_MAX]) != NULL)
{
#ifdef COUNT_ALLOCS
    one_strings++;
#endif
    Py_INCREF(op);
    return (PyObject *)op;
}

请注意,该数组是static,因此无法从该文件外部访问它,并且它仍在对对象进行重新计数,因此调用者(甚至是解释器中的内部内容,更不用说 C API 扩展)无法判断发生了任何特殊情况。

因此,没有“正确”的方法来清理它。

但如果你想变得黑客……

如果您有对任何单字符字节的引用,并且知道它应该是哪个字符,则可以到达数组的开头,然后清理整个内容。

除非你搞砸的比你想象的还要多,否则你可以构建一个单字符bytes并减去它原来的字符supposed to be. PyBytes_FromStringAndSize("a", 1)将返回的对象是supposed to be 'a',即使碰巧发生actually hold 'b'。我们怎么知道这一点?因为这正是您要解决的问题。

实际上,可能有一些方法可以让事情变得更糟……这一切看起来都不太可能,但为了安全起见,让我们使用一个你不太可能破坏的角色a, like \x80:

PyBytesObject *byte80 = (PyBytesObject *)PyBytes_FromStringAndSize("\x80", 1);
PyBytesObject *characters = byte80 - 0x80;

The only other caveat is that if you try to do this from Python with ctypes instead of from C code, it would require some extra care,1 but since you're not using ctypes, let's not worry about that.

所以,现在我们有一个指向characters,我们可以步行。我们不能只是删除对象来“取消”它们,因为这会影响任何引用它们的人,并可能导致段错误。但我们不必这样做。表中的任何对象,我们都知道它应该是什么——characters[i]应该是一个字符bytes谁的一个字符是i。因此,只需将其设置回原样,并使用如下循环:

for (size_t char i=0; i!=UCHAR_MAX; i++) {
    if (characters[i]) {
        // do the same hacky stuff you did to break the string in the first place
    }
}

这里的所有都是它的。


Well, except for compilation.2

幸运的是,在交互式解释器中,每个完整的顶级语句都是其自己的编译单元,因此......您应该可以接受运行修复程序后键入的任何新行。

但是您导入的模块必须在字符串损坏的情况下进行编译?你可能搞砸了它的常数。除了强制重新编译和重新导入每个模块之外,我想不出一个好方法来清理这个问题。


1. The compiler might turn your b'\x80' argument into the wrong thing before it even gets to the C call. And you'd be surprised at all the places you think you're passing around a c_char_p and it's actually getting magically converted to and from bytes. Probably better to use a POINTER(c_uint8).

2. If you compiled some code with b'a' in it, the consts array should have a reference to b'a', which will get fixed. But, since bytes are known immutable to the compiler, if it knows that b'a' == b'b', it may actually store the pointer to the b'b' singleton instead, for the same reason that 123456 is 123456 is true, in which case fixing b'a' may not actually solve the problem.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否可以恢复损坏的“interned”字节对象 的相关文章

随机推荐