PEP 330 – Python 字节码验证
- 作者:
- Michel Pelletier <michel at users.sourceforge.net>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2004年6月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