想请教几个有关 py3 的 exec、字节码和代码对象的问题

2021-08-01 14:56:42 +08:00
 O5oz6z3
一个低水平小白的几个无关痛痒的困惑,如果有人能帮忙指明搜索方向也非常感谢。任何其他意见也欢迎。

1. `exec` 命令从 python2 中的语句变成 python3 的内置函数,是出于什么原因?在哪里能找到相关的说明?安全是不是因素之一?

2. 在 python3 中怎么把 字节码(bytecode) 转换为可执行的 代码对象(coed object)?
不知道是表达和理解是否有误,具体来说,代码对象指的是 `function.__code__`,字节码指的是 `function.__code__.co_code`。可以把 `co_code` 转换回 `__code__` 吗?

3. 在 python3 中调用 `` 内置函数时,在清空了参数 `globals={…}` 中 `__builtins__` 变量的情况下,还可能有什么其他的安全隐患?不包括死循环和递归溢出等人为疏忽。

4. X-Y 问题:最初的想法是,在 python3 中能否实现一个简单的隔离沙箱运行环境给第三方脚本运行?比如 问题 3. 中提到的方法是否在一定程度上足够安全?
1729 次点击
所在节点    Python
14 条回复
hsfzxjy
2021-08-01 17:10:50 +08:00
1. 是易用性问题。函数可以作为表达式一部分,语句不行。

2. 使用 types.CodeType

3. 不安全,可以用内置对象构建任意函数 https://www.floyd.ch/?p=584

4. 建议用 docker 容器
ysc3839
2021-08-01 17:45:56 +08:00
关于 Python 的沙盒,我个人猜测 CPython 解释器本身应该是安全的,问题出在众多库函数。比如 open 什么的内置函数都没做限制,要一个个加上限制的话太麻烦,且很容易遗漏。
O5oz6z3
2021-08-01 19:52:37 +08:00
@hsfzxjy 谢谢!
>1. 原来如此,我之前以为在 python3 中 exec 作为内置函数,可以通过覆盖 __builtins__ 来屏蔽掉,但是在 python2 中作为语句的 exec 就没办法屏蔽了,所以改成内置函数会安全些。

>2. 我去看看

>4. 明白了。不过这就已经超出 python 解释器的层面了,最初是想在脚本层面简单实现一个粗糙的隔离环境给 exec 运行。

不看不知道,一看吓一跳,这 __builtins__ 在很多标准库模块里都能获取到,泄露成筛子似的,看来确实很难在脚本层面实现简陋的沙箱。
罪魁祸首看来是 __subclasses__,也许可以归结于 python 本来就不注重属性的访问限制? python 在自省和透明的路上真是越走越远。
O5oz6z3
2021-08-01 19:55:33 +08:00
@hsfzxjy
>3. “构建任意函数”听上去似乎没有安全问题,因为在 exec 里面执行的代码字符串本来就可以随意定义函数,这个“任意函数”好像没有什么特别。
如果我没理解错的话,只是构造不正确的字节码让解释器崩溃,那么效果上似乎和递归溢出没有区别。
hsfzxjy
2021-08-01 19:57:25 +08:00
@O5oz6z3 我的意思是可以访问到原先的 builtins,这就很危险了
O5oz6z3
2021-08-01 20:10:11 +08:00
@ysc3839 确实,虽然可以通过替换覆盖 __builtins__ 模块内的内置函数来添加限制,但除了 __import__ 和 open 我也想不到哪里会不安全,的确又麻烦又容易遗漏。
用这个思路做沙盒确实很麻烦,可能方向不对,又或者是 python 在设计上本来就不打算支持沙盒功能。
O5oz6z3
2021-08-01 20:22:43 +08:00
@hsfzxjy #5
“可以通过内置对象访问到原先的 builtins”,回过头来发现,exec 本质上就是在当前解释器环境解释执行代码,复用了当前环境的内置对象,那么能够通过自省访问到原先的 builtins 也就不奇怪了。看来想简单地用 exec 来实现沙盒不太可行。
no1xsyzy
2021-08-01 22:27:12 +08:00
沙箱最大的问题就是,插件作者调用第三方库不一定预设了你是沙箱内的,可能去访问一些不允许访问的资源。
所以沙箱要么是 deno 这种处于解释器-操作系统边界的,要么是纯函数的。
话说如果自己愿意写的话用 ast 模块重新写一个解释器也行。
O5oz6z3
2021-08-02 07:30:28 +08:00
@no1xsyzy #8
你说的沙箱最大的问题:“沙箱权限的细粒度控制”可以很复杂,对我反而不重要,因为我只是想简单粗暴地做一个最小可用沙箱的概念验证 /POC,具体来说就是只能使用基本语法,不能使用任何 builtins 内置函数或 import 功能。

长见识了,原来还有 deno 这种沙箱。

至于用 ast 重新写解释器,先不说我没那个水平,原来 ast 还能这么用?我还以为 ast 只能静态修改抽象语法树,不能用来解释运行,而且也无法识别出表达式的最终结果。

不过这启发了我想到另一种实现沙箱的大致思路:针对 builtins 的泄露问题,使用 ast 或者 tokenize 识别第三方脚本中关键的内省属性,比如 __subclasses__、__globals__,然后要么抛错要么替换要么禁用就能解决。缺点是只能针对这种已知的访问 builtins 的方法,对未知的就没有办法预防了。
比如说 exception 附带的堆栈也许有可能会泄露 builtins,就没有办法了。
no1xsyzy
2021-08-02 09:24:02 +08:00
@O5oz6z3 不是粒度问题,可能意外地访问一些不可用资源
打个比方,一个库可能默认去访问 $HOME 或 $XDG_CONFIG_HOME 下的配置文件,而你尝试在沙箱里用这个库,就会产生意外的权限要求。因为不是沙箱原生代码,而且 Python 和 C 互操作不依赖单一 ffi 接口,所以麻烦且诡异。
当然,限制到非常小语法集,仅提供最简单的代码执行甚至纯数据 literal 也是可以的(当然最小纯数据可以 ast.eval_literal )。

元解释器不是大问题,sicp 第三章就是在写解释器来着。
你一个个部分去判断是什么语法结构然后手动执行不就行了,又不是让你写成编译到字节码的。
当然,效率嘛……

你这是黑名单用法。话说,也可以白名单。
O5oz6z3
2021-08-02 11:05:12 +08:00
@no1xsyzy #10
如果我没又理解错的话,你说的意外访问不可用资源指的是“文件系统的权限问题”?这确实是可能会在现实发生的情况,不过我不打算解决这类复杂的问题,而且访问权限不够似乎也不是沙箱的责任。至于 ffi 接口我就不懂了……

eval_literal 我也了解过,不过那就只是数据不是代码了,所以没考虑。

原来重写解释器是指元解释器,似乎可行,不过这既不简单也不粗暴,工作量令我望而生畏,所有也不考虑了。

你这么一说确实是黑名单策略。至于白名单应该不太可行,一是限制了语法的自由度,二是我也无法穷举出所有安全的属性名。
abersheeran
2021-08-02 11:43:13 +08:00
如果只是沙箱,docker 容器解万难。当然 docker 也不是不能逃逸,但如果连 docker 都能逃逸,这种级别的大黑客你就别想着拦了,随他去吧。
O5oz6z3
2021-08-02 12:03:53 +08:00
@abersheeran #12 明白了,docker yyds !说到底在脚本层面实现简单的沙箱只是我一个待验证的突发奇想而已,根本不能和正经沙箱相提并论,更别说对付逃逸沙箱水平的黑客……
no1xsyzy
2021-08-02 12:35:27 +08:00
@O5oz6z3 主要是如果引第三方库的话,有时会出现你根本没预料到它会去访问的资源。
比如 requests 库会访问环境变量(*_proxy ),但你确实不希望它访问环境变量只希望它访问网络,这里就可能出错。
Python 一个卖点就是和 C 的互操作方便,不依赖于语言提供 ffi 接口,可以使用符合某种形式的 C 标准动态链接文件( dll 存在某符号并改后缀为 pyd )来互操作。当然这也导致了 GIL 的问题。

话说目前我看到过自带沙盒的语言是 racket

docker 确实是个好主意。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/792987

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX