PEP 330 – Python 字节码验证
- 作者:
- Michel Pelletier <michel at users.sourceforge.net>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2004-06-17
- Python 版本:
- 2.6
- 历史记录:
摘要
如果 Python 虚拟机 (PVM) 字节码不是“格式良好的”,则可能会通过导致各种错误(例如,值栈下溢/上溢或读写 PVM 程序空间的任意区域)来崩溃或利用 PVM。通过在执行之前验证 PVM 字节码是否违反了一组简单的约束,可以消除大多数这类错误。
本 PEP 提出了一组关于 Python 虚拟机 (PVM) 字节码格式和结构的约束,并提供了一个用 Python 实现的验证过程。
宣告
Guido 认为验证工具具有一定价值。如果有人想将其添加到 Tools/scripts
中,则不需要 PEP。
这样的工具可能对验证“字节码黑客”或直接编辑 PYC 文件的输出很有价值。作为安全措施,它的价值有限,因为完全有效的字节码仍然可以做可怕的事情。如果受限执行的概念能够成功复活,这种情况可能会改变。
动机
Python 虚拟机执行已从 Python 语言编译成字节码表示的 Python 程序。PVM 假设任何正在执行的字节码在某些隐式约束方面是“格式良好的”。其中一些约束在运行时检查,但大多数没有检查,因为这会带来开销。
在调试模式下运行时,PVM 确实会进行一些运行时检查,以确保任何特定的字节码不会违反这些约束,这些约束在一定程度上防止了字节码崩溃或利用解释器。这些检查会给解释器带来可衡量的开销,并且通常在普通使用中被禁用。
格式不正确的字节码由不在调试模式下运行的 PVM 执行可能会产生各种致命和非致命错误。通常,格式不正确的代码会导致 PVM 出现段错误,并导致操作系统立即突然终止解释器。
可以想象,格式不正确的字节码可能会利用解释器,并允许 Python 字节码执行任意的 C 级机器指令或修改解释器中的私有内部数据结构。如果使用巧妙,这可能会破坏应用程序可能希望对其对象应用的任何形式的安全策略。
实际上,恶意用户很难将无效字节码“注入”到 PVM 中以用于利用,但并非不可能。缓冲区溢出和内存覆盖攻击是常见的,尤其是在利用有效负载通过网络以未加密方式传输时,或在使用文件或网络安全权限弱点作为进一步攻击的立足点时。
理想情况下,不应允许任何字节码读取或写入底层的 C 级数据结构来破坏 PVM 的运行,无论字节码是恶意制作的还是非恶意的。一个简单的预执行验证步骤可以确保字节码在运行时不能使值栈上溢/下溢或访问 PVM 程序空间的其他敏感区域。
本 PEP 提出了一些在 PVM 执行 Python 字节码之前应该采取的验证步骤,以便它符合其指令及其操作数的静态和结构约束。这些步骤很简单,并且可以捕获一大类会导致崩溃的无效字节码。还有一些可能通过验证过程消除一些运行时检查。
当然,没有办法验证字节码是否“完全安全”,对于完全和安全的每个定义。即使有字节码验证,Python 程序也可能而且将来很可能由于各种原因出现段错误,并继续导致许多不同类型的运行时错误,无论是致命的还是非致命的。这里提出的验证步骤只是填补了一个容易导致字节码级别出现大量致命和微妙错误的漏洞。
目前,Java 虚拟机 (JVM) 正在以一种与这里提出的方法非常相似的方式验证 Java 字节码。因此,JVM 规范版本 2 [1] 中的第 4.8 节和第 4.9 节被用作下面解释的一些约束的基础。任何 Python 字节码验证实现至少必须强制执行这些约束,但可能不限于它们。
字节码指令的静态约束
- 字节码字符串不能为空。(
len(co_code) > 0
)。 - 字节码字符串不能超过最大大小(
len(co_code) < sizeof(unsigned char) - 1
)。 - 字节码字符串中的第一个指令从索引 0 开始。
- 字节码字符串中只能包含具有正确操作数数量的有效字节码。
字节码指令操作数的静态约束
- 跳转指令的目标必须在代码边界内,并且必须落在指令上,不能落在指令与其操作数之间。
LOAD_*
指令的操作数必须是其对应数据结构中的有效索引。STORE_*
指令的操作数必须是其对应数据结构中的有效索引。
字节码指令之间的结构约束
- 每个指令都必须仅使用值栈中适当数量的参数执行,无论导致其调用的执行路径是什么。
- 如果一个指令可以沿着几个不同的执行路径执行,则在执行该指令之前,无论采取哪种路径,值栈都必须具有相同的深度。
- 在执行期间,值栈的深度不能超过
co_stacksize
所隐含的深度。 - 执行永远不会从
co_code
的底部掉下来。
实现
本 PEP 是用 Python 编写的 Python 字节码验证实现的工作文档。此实现不会在 PVM 执行任何字节码之前隐式使用,而是由关注可能无效字节码的用户显式使用,使用以下代码段
import verify
verify.verify(object)
verify
模块提供了一个 verify
函数,该函数接受与 dis.dis
相同类型的参数:类、方法、函数或代码对象。它验证对象字节码是否符合本 PEP 的规范。
如果代码格式良好,则对 verify
的调用将静默返回,不会出现错误。如果遇到错误,它会抛出一个 VerificationError
,其参数指示失败的原因。程序员可以选择是否以某种方式处理错误或无论如何执行无效代码。
Phillip Eby 提出了一种用于参考实现的字节码堆栈深度验证的伪代码算法。
验证问题
本 PEP 只描述了少量验证。虽然讨论和分析会带来更多验证,但很有可能未来的验证可能需要进行或自定义,项目特定的验证。因此,可能需要在测试实现中添加一个验证注册接口来注册未来的验证器。这种需求很小,因为自定义验证器可以子类化和扩展当前实现以获得附加的行为。
所需更改
Armin Rigo 指出,为了对其堆栈效应进行静态分析,几个字节码需要修改。这些是 END_FINALLY
、POP_BLOCK
和 MAKE_CLOSURE
。Armin 和 Guido 已经就如何纠正这些指令达成了一致。目前,Python 实现对这些指令不做任何处理。
本 PEP 并不建议将验证步骤添加到解释器中,而是只提供标准库中的 Python 实现以供可选使用。此验证过程是否被翻译成 C,是否包含在 PVM 中或以任何方式强制执行,留待以后讨论。
参考资料
版权
本文件已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0330.rst
最后修改: 2023-09-09 17:39:29 GMT