python - 是否可以恢复损坏的“interned”字节对象
问题描述
众所周知,bytes
CPython 会自动“实习”小对象(类似于字符串的intern函数)。更正:正如@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'
更改为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'
意味着b'a'
再次?
一点旁注:似乎不是每个bytes
-creation 进程都在使用这个池。例如:
>>> 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)。从好的方面来说,这意味着如果你能找到静态数组,你就可以修复它,就像处理 int 一样,因为数组索引和字符串的字符值应该是相同的。
如果您查看bytesobject.c
,则该数组在顶部声明:
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 扩展)无法判断有什么特别的事情发生.
因此,没有“正确”的方法来清理它。
但是,如果您想变得 hacky……</p>
如果您有对任何单字符字节的引用,并且您知道它应该是哪个字符,则可以到达数组的开头,然后清理整个内容。
除非你搞砸的比你想象的还要多,否则你可以构造一个单字符bytes
并减去它应该是的字符。PyBytes_FromStringAndSize("a", 1)
将返回应该是的对象'a'
,即使它碰巧实际持有'b'
。我们怎么知道?因为这正是您要解决的问题。
实际上,您可能有一些方法可以使事情变得更糟……这一切似乎都不太可能,但为了安全起见,让我们使用一个您不太可能破坏的角色a
,例如\x80
:
PyBytesObject *byte80 = (PyBytesObject *)PyBytes_FromStringAndSize("\x80", 1);
PyBytesObject *characters = byte80 - 0x80;
唯一需要注意的是,如果您尝试使用 Pythonctypes
而不是 C 代码执行此操作,则需要格外小心,1但由于您没有使用ctypes
,所以不用担心。
所以,现在我们有一个指向 的指针characters
,我们可以遍历它。我们不能只删除对象以“取消实习”它们,因为这会影响任何引用它们的人,并可能导致段错误。但我们不必这样做。表中的任何对象,我们都知道它应该是什么——<code>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
}
}
这里的所有都是它的。
好吧,除了编译。2
幸运的是,在交互式解释器中,每个完整的顶级语句都是它自己的编译单元,所以……你应该可以在运行修复程序后输入任何新行。
但是你导入的一个模块,必须编译,而你有损坏的字符串?你可能搞砸了它的常数。除了强制重新编译和重新导入每个模块之外,我想不出一个好方法来清理它。
1. 编译器可能会在你的b'\x80'
参数到达 C 调用之前把它变成错误的东西。你会惊讶于所有你认为你正在经过的地方 ac_char_p
并且它实际上正在神奇地转换为和从bytes
. 可能更好地使用POINTER(c_uint8)
.
2. 如果你在其中编译了一些代码b'a'
,consts 数组应该有一个对 的引用b'a'
,这将得到修复。但是,由于bytes
编译器知道它们是不可变的,如果它知道b'a' == b'b'
,它实际上可能会存储指向b'b'
单例的指针,原因与此相同123456 is 123456
,在这种情况下修复b'a'
可能无法真正解决问题。
推荐阅读
- python - 如何在django中将不同功能的两个数据框写入一个excel文件
- c# - UWP 应用中的runFullTrust 模式是否默认支持broadFileAccess?
- sql - how do I return the sql data types from my query in pgsql?
- java - 如何读取 CSV 文件中的特定行
- javascript - 带有位置的垂直文本侧边栏:固定
- javascript - 如何将值从复选框数组传递到 3 个输入,而不覆盖
- regex - 如何在 Perl 中替换连续且相同的字符?
- batch-file - Mule 4 - 空 VM 队列错误消耗消息
- php - 如何使用 PHP 退出我的网站?
- c# - 使用 C# 调用的 Keycloak REST API