`
RednaxelaFX
  • 浏览: 3009580 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

以Python为例讨论高级编程语言程序的wire format与校验

阅读更多
作者:RednaxelaFX
主页:http://rednaxelafx.iteye.com
日期:2009-05-09

(若要转载请先与我联系,谢谢 ^ ^)
相关链接:
Python 2.6.2的.pyc文件格式
Python 2.6.2的字节码指令集一览

写在前面:本文完全没有宣传“Python是一种不安全的平台”的意图;与Python相比,别的平台也未必真的安全多少,毕竟诸多手段都只能防君子而防不了小人。
本文的主要目的是讨论“wire format”与高级语言的虚拟机所需要做的校验的问题,顺便以Python为实例来讨论,仅此而已。为避免误会特别写在前面,希望这段话没有被无视……


本文内关于Python的讨论都指CPython,例子是在Python-2.6.2-msvc9-win32上运行的。

背景基础

背景
Lang.NET 2009中,Lars Bak接受了Channel 9的访问,介绍了V8虚拟机的特性。在访问的后半段,Channel 9主持人之一的Charles提出“V8以后会不会采用IL”的问题,然后Lars、Charles与Erik Meijer三人围绕“wire format”展开了一些讨论。有跟随我的“闲聊”的同学应该留意到之前我对此的评论。Anyway,还是写一帖稍微详细点讲解一下。
更新:Charles说在录像结束后他与Lars、Erik三个人继续争论了IL的必要性的话题,已经不是专门针对V8或JavaScript,而是笼统的讨论。Lars是认为根本不需要IL。

Wire Format
这个词组到底是什么意思?要是用Wikipedia来查,会被重定向到Communications protocol的词条上,作罢。其实不必wiki,这词本身就很形象:“wire format”直译为“在线上传输/交换(的数据)的格式”,也就是数据传输格式。
那么以“程序”为上下文,“wire format”指的是什么呢?例如,我们从网上下载一个别人用C或C++写的程序,下载的可能是二进制可执行文件,此时这个二进制可执行文件的格式就是一种“wire format”;或者当我们浏览一个使用了Ajax技术的网页时,网页中内嵌的JavaScript以源码的形式被下载并运行,此时源码(文本)也是一种“wire format”。
程序的wire format不仅强调其在传输/交换时的作用,也强调其“可执行”的属性——接收的一方在获取以wire format为格式的程序后,应该有环境能直接执行该程序;既可以是本地代码的直接执行,也可以是由解释器解释执行。

高级编程语言编写的程序的wire format
高级编程语言(high-level language, HLL)编写的程序在wire format上有许多选择:追求执行效率的语言多选择封装本地代码的格式,如PE或ELF等格式;追求可移植性、同时也注重执行效率的程序会选择封装某种形式的中间代码的格式,如Java的.class/.jar、.NET的PE/PE+、Python的.pyc/.pyo、ActionScript 3的.abc/.swf等格式;脚本语言大多直接选择发布源码,也就是文本格式,如Perl、Python、Ruby、JavaScript等。

Python在这当中是种“少数派”语言:它主要以源码为wire format,同时规范也允许包含编译后的Python字节码的.pyc文件与包含优化编译后的Python字节码的.pyo文件为wire format。上述三种格式都可以由Python解释器执行。后文会围绕这点再展开讨论。

封装本地代码为wire format
封装本地代码的wire format受的限制最少,只要当前用户有相应权限,几乎什么操作都可以做。数据类型基本上就是不同长度的整型、浮点数,外加字符串,本地代码一般不强调“类型安全”。代码与数据间没有明确的界线,代码段中可能混杂着数据。同时,控制流受的限制非常小,有可执行权限的内存页中的任意位置都是潜在的跳转目标;在DEPASLR等技术应用之前,这种任意执行代码的能力方便了许多shell coder肆意利用缓存溢出来获取系统的控制权。

若要采用静态方式分析本地代码,则可能遇到“代码发现”问题,也就是静态分析程序无法准确的把程序代码都定位出来的状况。造成该问题的原因有以下几点:
1、间接跳转
本地代码一般允许操纵跳转目标的能力,例如任意计算一个地址作为函数指针然后通过call指令跳转过去。由于跳转目标要等到运行时才能计算出来,静态方法不能保证准确分析出所有代码的位置。
2、代码段中夹杂数据
有时候编译器为了能更高效的访问到一些编译时常量,会把常量及使用到它的代码放在一起。最典型的例子就是C的switch语句的编译,紧挨着switch的代码之后会存有对应的跳转表。
3、可变长度指令集
常见于CISC指令集,根据指令使用频率的高低(和历史原因)而将指令设计为不等长的,以期提高代码密度(和保持向后兼容性)。
4、为对齐而填充数据
机器在访问以字长对齐的数据时效率通常较高,例如以4字节为单位;而cache line则可能在更大的单位上访问对齐的数据,例如以64字节为单位。编译器可能考虑这些因素,为了将跳转目标放在对齐边界上而使用空指令去填充空间(padding)。
因为代码发现有困难,使用静态分析工具去准确分析本地代码并判断其安全性并不现实。分析本地代码可以使用更动态的手段,例如靠动态基本块(dynamic basic block)来跟踪程序代码。

用户在使用封装本地代码的wire format时,必须自行判断其可靠性,确认自己“信任”程序才去执行它。

以源码为wire format
高级编程语言一般在语法和语义上有明确的、相对严格的规定,有一定的类型系统,对控制流也有所要求。在类型系统方面,如果语言有较强的类型系统(与静态/动态无关),则程序无法以任意方式去解释/转换数据,而必须遵循较严的规则。在控制流方面,即使是有goto语句的语言(如C#),控制流也可能受限制。例如下面的C#代码片段不合法:
goto label_in_loop;
for (var i = 0; i < 3; i++) {
label_in_loop:
    Console.WriteLine("hey");
}

因为C#规定循环结构只能有单一入口,而不能从循环外部直接跳入循环内的任意位置。这是为了保持代码良好的结构化而做的规定。
许多提供自动内存管理功能的高级编程语言都不再允许显式的指针算术运算,也使语言的实现得以简化。

以拥有上述特性的源码为wire format,保证程序受到诸多限制,相对安全一些。
以JavaScript为例。ECMAScript和JavaScript的语言规范都只规定了源代码的语法和语义,但并没有对程序的发布格式做要求。一般来说JavaScript程序通用的wire format就是其源码。各JavaScript解释器在拿到程序源码后采用何种方式去执行,规范并没有限制。对抽象语法树解释执行也可以,对线性的中间代码(一般是字节码)解释执行也可以,一口气编译为本地代码再执行也可以。
无论采用何种方式来执行,都必须保证JavaScript的语义;过程中肯定存在解析代码的步骤,这就包含了对wire format的校验。JavaScript对控制流有严格的规定,不能在程序中任意跳转,而必须采用遵循结构化设计的语言结构来实现控制流;同时,由于程序无法访问裸的内存,它就无法以任意数据为代码来执行(即便eval(),作为参数的字符串也必须是合法的JavaScript代码),也就不存在“代码发现”的问题。

有不少JavaScript解释器的实现在内部有采用字节码,但一般这些字节码不会被保存到磁盘上;不用JavaScript解释器的设计实现不同,即便采用了字节码也无法通用。因而对JavaScript而言字节码不是wire format。
如果字节码只是在解释器内部使用,那么JavaScript的执行过程可以粗略看成:
  源码->(解析并校验,生成字节码)->字节码->(执行)
从wire format到执行之间至少有一次校验,能杜绝许多错误了。编译器自己当然不会乱生成代码,因而后续执行步骤都是可信任的。

封装中间代码为wire format
设计思路的差异使各语言所采用的中间代码的形式也大相径庭,有的与其源语言非常接近,保留源语言中的对象系统、名字空间等多种特性;有的与常见的实际硬件非常接近,数据类型扁平化,控制流的限制也有所放松;有了两个极端,自然也有介乎两者中间的设计。
这些中间代码一般以地址(偏移量)为标签,单就代码表达力而言至少可以实现函数作用域内的任意跳转,有活脱脱的goto的能力。如不加以限制则可能带来隐患。为此,许多中间代码都有附加规定的约束(constraints);执行环境可以在执行中间代码前先检查约束是否满足。例如,JVM规范中就有对JVM字节码的约束的规定

现有多数高级语言的中间语言都会选择区分代码区与数据区,只有代码区的数据是可执行的;如此一来阻止了自我引用/自我修改代码,二来避免代码夹杂数据而造成代码发现问题。
代码区一般以函数(方法)为单位来划分,可以明确定位到每个函数(方法)代码的起始位置。
出于安全性的考虑,也为了中间代码能被更高效的执行,主流的高级语言的中间代码一般不允许间接跳转,跳转地址写死在指令里;跳转目标一般也限定在函数(方法)的内部,不然只能是函数(方法)的调用或返回;对填充数据也有严格的规定。

这样,虽然许多中间代码采用可变长度的设计,但从明确的起始位置出发,经过固定地址的局部跳转或易于分析的调用/返回跳转,代码发现就不成问题了。

Java的JVM、.NET的CLI、Android的Dalvik、ActionScript 3的AVM2等主流虚拟机,都在规范中要求实现必须在执行中间代码前有“校验”(verification)阶段。显然,不可能有程序能把所有“应该能行”的程序都找出来,这是停机问题的一个变种。因此“校验”并不以找出所有正确的程序为目标,而是根据精心设计的规则找出其子集——“肯定能行”的程序。不在该子集内的程序就被认为是不合法的。
如何校验,校验些什么呢?既然校验在执行之前,校验就是对程序的静态分析。通过代码发现,校验器可以模拟出程序在任意时间点的一些特性。上一段所举的几种虚拟机都采用基于栈的架构,对它们来说可校验的特性包括:求值栈平衡;求值栈的实际深度没有超过其声称的最大值;局部跳转目标是有效指令的起始位置;经过不同路径到达控制流的汇集点时,求值栈的状态统一;存储区访问没有越界;存储单元类型匹配,类型的声明与定义一致……等等。之前我的一帖,一个通不过Java字节码校验的例子,就是上述校验过程的一例。

对中间代码的校验,其实是对源码校验的延续。如果中间代码不是由受信任的编译器生成的,那么本应由编译器贯彻的一些约束在中间代码是否得到了体现?如果封装中间代码为wire format,这个问题就值得关注了。
与封装本地代码相比,封装中间代码的wire format与执行环境的校验步骤结合,可以让用户更放心的执行“不受信任”的代码。

同时允许源码和中间代码为wire format
前文提到了,以源码为wire format时,为实现语义,解释器必须对源码进行解析。解析源码就包含了对wire format的校验。以中间代码为wire format时,为安全起见也应该对中间代码做校验。同时允许两者为wire format,一般意味着源码解析后会被转换成同为wire format的中间代码,然后虚拟机只要执行中间代码即可。
再次注意:为执行程序,传的是源码的话则无法避免对源码的解析(包含校验),但却并不一定要对中间代码校验。

如果对中间代码校验,则以源码为wire format时的执行过程会包括:
  源码->(解析并校验源码,生成中间代码)->中间代码->(校验中间代码)->(执行)
这个过程包括了两次校验,安全性足够了但显得冗余。Lars Bak在访问中提到的“you'll have to check twice”指的就是这个。他尖锐的指出:因为JavaScript的wire format就是源码,所以没必要专门维护一种中间代码;更没必要以中间代码为wire format,否则就要校验两次。(注意前提是源码已经是wire format了)

如果不对中间代码校验,则以中间代码为wire format时的执行过程会包括:
  中间代码->(执行)
什么?一次校验也没有了!这不就糟糕了么……

Python正好属于后一种情况。下面就以Python 2.6.2为例来演示不校验中间代码可能带来的后果。

实例部分

Python有两种封装中间代码的wire format,这里就以.pyc为例来讨论。
Python 2.6.2的.pyc文件格式请点击链接查看。本文关注的重点是校验问题,所以把文件格式分析剥离为单独一帖了。

Python/import.c可以看到,Python在加载.pyc文件时会检查pyc_magic和mtime,以期拒绝掉格式不正确、或Python版本不对应、或对应源文件有更新的.pyc文件。显然这样只能防住君子,却防不了.pyc文件中字节码的“意外破损”“人为修改”。
可以把一个.pyc文件拿过来,任意修改其中的字节码,而Python解释器在检查文件头通过之后就会毫不犹豫的加载和执行这文件。即使对应的源文件不在旁边,Python解释器也不会抱怨(.pyc文件确实是wire format的一种)。

回顾之前分析.pyc文件格式时用的例子:
demo.py:
class A():
  x = 1

print(A.x) # 1

# increment A.x by 2
A.x += 2
print(A.x) # 3

# create an instance of A
a = A()
print(a.x) # 3

# increment A.x by 4
a.x += 4
print(a.x) # 7
print(A.x) # 3

demo.pyc:
  1           0 LOAD_CONST               0 ('A')
              3 LOAD_CONST               5 (())
              6 LOAD_CONST               1 (<code object A at 01237BA8, file "a.py", line 1>)
              9 MAKE_FUNCTION            0
             12 CALL_FUNCTION            0
             15 BUILD_CLASS
             16 STORE_NAME               0 (A)

  4          19 LOAD_NAME                0 (A)
             22 LOAD_ATTR                1 (x)
             25 PRINT_ITEM
             26 PRINT_NEWLINE

  7          27 LOAD_NAME                0 (A)
             30 DUP_TOP
             31 LOAD_ATTR                1 (x)
             34 LOAD_CONST               2 (2)
             37 INPLACE_ADD
             38 ROT_TWO
             39 STORE_ATTR               1 (x)

  8          42 LOAD_NAME                0 (A)
             45 LOAD_ATTR                1 (x)
             48 PRINT_ITEM
             49 PRINT_NEWLINE

 11          50 LOAD_NAME                0 (A)
             53 CALL_FUNCTION            0
             56 STORE_NAME               2 (a)

 12          59 LOAD_NAME                2 (a)
             62 LOAD_ATTR                1 (x)
             65 PRINT_ITEM
             66 PRINT_NEWLINE

 15          67 LOAD_NAME                2 (a)
             70 DUP_TOP
             71 LOAD_ATTR                1 (x)
             74 LOAD_CONST               3 (4)
             77 INPLACE_ADD
             78 ROT_TWO
             79 STORE_ATTR               1 (x)

 16          82 LOAD_NAME                2 (a)
             85 LOAD_ATTR                1 (x)
             88 PRINT_ITEM
             89 PRINT_NEWLINE

 17          90 LOAD_NAME                0 (A)
             93 LOAD_ATTR                1 (x)
             96 PRINT_ITEM
             97 PRINT_NEWLINE
             98 LOAD_CONST               4 (None)
            101 RETURN_VALUE

注意到这个顶层PyCodeObject的字节码的地址范围是0-101,与PyCodeObject.co_code这个PyStringObject的长度信息吻合。

现在让我们修改字节码的2个字节,在字节码内偏移量为42-43、在文件内地址为0x48-0x49的位置。原本内容为:
  8          42 LOAD_NAME                0 (A)

也就是0x65 0x00 0x00。修改为0x71 0x70 0x00,改后对应的字节码是JUMP_ABSOLUTE 112,也就是跳转到顶层代码的字节码中从头开始算偏移量为112的地方。

修改后保存为_demo.pyc,内容是:
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   D1 F2 0D 0A B3 34 04 4A  63 00 00 00 00 00 00 00   羊..?.Jc.......
00000010   00 03 00 00 00 40 00 00  00 73 66 00 00 00 64 00   .....@...sf...d.
00000020   00 64 05 00 64 01 00 84  00 00 83 00 00 59 5A 00   .d..d..?.?.YZ.
00000030   00 65 00 00 69 01 00 47  48 65 00 00 04 69 01 00   .e..i..GHe...i..
00000040   64 02 00 37 02 5F 01 00  71 70 00 69 01 00 47 48   d..7._..qp.i..GH
00000050   65 00 00 83 00 00 5A 02  00 65 02 00 69 01 00 47   e..?.Z..e..i..G
00000060   48 65 02 00 04 69 01 00  64 03 00 37 02 5F 01 00   He...i..d..7._..
00000070   65 02 00 69 01 00 47 48  65 00 00 69 01 00 47 48   e..i..GHe..i..GH
00000080   64 04 00 53 28 06 00 00  00 74 01 00 00 00 41 63   d..S(....t....Ac
00000090   00 00 00 00 00 00 00 00  01 00 00 00 42 00 00 00   ............B...
000000A0   73 0E 00 00 00 65 00 00  5A 01 00 64 00 00 5A 02   s....e..Z..d..Z.
000000B0   00 52 53 28 01 00 00 00  69 01 00 00 00 28 03 00   .RS(....i....(..
000000C0   00 00 74 08 00 00 00 5F  5F 6E 61 6D 65 5F 5F 74   ..t....__name__t
000000D0   0A 00 00 00 5F 5F 6D 6F  64 75 6C 65 5F 5F 74 01   ....__module__t.
000000E0   00 00 00 78 28 00 00 00  00 28 00 00 00 00 28 00   ...x(....(....(.
000000F0   00 00 00 73 07 00 00 00  64 65 6D 6F 2E 70 79 52   ...s....demo.pyR
00000100   00 00 00 00 01 00 00 00  73 02 00 00 00 06 01 69   ........s......i
00000110   02 00 00 00 69 04 00 00  00 4E 28 00 00 00 00 28   ....i....N(....(
00000120   03 00 00 00 52 00 00 00  00 52 03 00 00 00 74 01   ....R....R....t.
00000130   00 00 00 61 28 00 00 00  00 28 00 00 00 00 28 00   ...a(....(....(.
00000140   00 00 00 73 07 00 00 00  64 65 6D 6F 2E 70 79 74   ...s....demo.pyt
00000150   08 00 00 00 3C 6D 6F 64  75 6C 65 3E 01 00 00 00   ....<module>....
00000160   73 10 00 00 00 13 03 08  03 0F 01 08 03 09 01 08   s...............
00000170   03 0F 01 08 01                                     .....

demo.py的顶层代码对应的字节码总共就102字节,而跳转目标在绝对偏移量112处——这么一跳转就跑到字节码范围之外了。
在Python解释器运行_demo.py时,实际被执行的顶层PyCodeObject的字节码如下,从0x012361B4的那个0x64开始。
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

012361B0               64 00 00 64  05 00 64 01 00 84 00 00       d..d..d..?.
012361C0   83 00 00 59 5A 00 00 65  00 00 69 01 00 47 48 65   ?.YZ..e..i..GHe
012361D0   00 00 04 69 01 00 64 02  00 37 02 5F 01 00 71 70   ...i..d..7._..qp
012361E0   00 69 01 00 47 48 65 00  00 83 00 00 5A 02 00 65   .i..GHe..?.Z..e
012361F0   02 00 69 01 00 47 48 65  02 00 04 69 01 00 64 03   ..i..GHe...i..d.
01236200   00 37 02 5F 01 00 65 02  00 69 01 00 47 48 65 00   .7._..e..i..GHe.
01236210   00 69 01 00 47 48 64 04  00 53 00 53 00 00 00 00   .i..GHd..S.S....
01236220   A0 63 23 01 88 A0 1D 1E  64 00 00 00 FF FF FF FF   燾#.垹..d...

绝对偏移量0x70(=112)把我们带到0x1236224处,是一个0x88,LOAD_DEREF,后面的参数是0xA0 0x1D,合起来就是LOAD_DEREF 7584,这偏移量早就不知道越界多少了。然后继续执行下去……我的Python解释器就崩溃了。

只消随便改两个字节就足以让Python解释器崩溃。然而更糟糕的事情还在后头:如果有人在发布其用Python写的程序时,只发布了.pyc文件,那我们如何信任代码不会把解释器弄崩溃?他(或者不受信任的中间者)可以任意编织.pyc文件中的字节码,只要总体格式能让marshal满意就行。
他甚至可以把一个行为正常的.py源文件与一个乱七八糟、但文件头能通过Python解释器检验的.pyc文件放在一起打包起来给我们用。阅读.py源码将无法解释执行该脚本而造成的任何诡异状况。

如果.pyc文件格式不曾是Python程序的wire format,又或者Python解释器会对.pyc文件做字节码校验的话,都可以让人少一分担心。
从现实意义上看,Python用户要规避这类隐患很简单:纯Python程序中,只使用以源码发布的而不使用以独立的.pyc/.pyo文件发布的程序。只要有源码,Python解释器总能构造出合适的.pyc或.pyo文件。那些只发布中间代码而不发布源码的Python程序提供者不过是在无力的挣扎而已——要反编译Python的中间代码很简单,不发布源码也保护不了多少秘密。
至于使用了本地扩展的Python程序,使用前就要想清楚了:你应该确实信任其本地扩展的行为,Python解释器无法为本地扩展提供保护。

重复本文最开头的声明:本文没有任何意图声称Python程序是不安全的;还有很多因素都可以带来安全隐患,况且其它环境也有各自的安全隐患……总之我不想,也不是“反Python”。
但不对所有wire format做校验的行为确实有让人担心的地方。这里只是以Python为实例印证Lars Bak关于“两次校验”的观点,说明校验的必要性。
4
0
分享到:
评论
6 楼 sulong 2009-05-09  
RednaxelaFX 写道

sulong 写道那也就是说用本地代码做wire format的语言,如c, c++和python一样都是不安全的了?前者因为“代码发现”问题,无法校验,后者是没有校验。 而以只以源码或中间码作为fire format的程序必须得校验一次,就更加安全些了,比如java, c#?
嗯……微妙。我觉得用“更安全”来描述不太合适。
安全漏洞如同“短板效应”:两个本来一样高的木桶都有破损,即便木桶A有3块短板,木桶B只有1块短板,如果两者最短的一块板都一样长,那么它们最多能装的水也一样多。无论是Python、Java还是.NET,它们的常见实现部分中总有是本地代码的部分;也就是说如果本地代码带来了安全隐患,那么大家都一样会遭殃。
看看那么多VBScript写的病毒、蠕虫和木马,它们可都是以源码形式“发布”的,但它们显然算不上“安全”。

我觉得这是“信任”与“安全”的一个权衡。

在网络环境下,用户接触到潜在不安全的代码的几率非常大。如果你想在浏览网页时看到了一个在线涂鸦程序,高兴的玩起来,你显然不会希望那个程序能肆意访问自己机器上的资源,或者是在毫无警告的情况下连接到别的网站去下载了什么。
这种使用网上未知程序的应用场景,属于“低信任度”的场景。此时以本地代码为wire format确实有更高的风险,因为其执行几乎不受限制(一般仅受用户在系统中所拥有的权限限制);而以源码或中间代码为wire format,一般伴随着更受限制的执行环境(例如沙箱模型的安全机制),这样即使是不受信任的代码也可以执行,通过校验wire format和后续的运行时权限检查,我们可以更好的防范不受信任的代码可能带来的威胁。
所谓“Wire format”有一个隐藏的倾向,那就是程序在下载后无需用户干涉就可以自动运行。像Flash、Silverlight、Java Applet都是这样,只要用户最初同意了程序的运行,程序下载完成后无须安装就会自动开始运行。

但是自己机器上的CRT、浏览器之类的,不都也是本地代码么?这就是“信任”与“安全”权衡的另一面,“高信任度”场景。你所安装使用的这些本地代码的程序,必须受到你的信任。你的信任赋予程序以高度的权限,同时你也要为自己的信任而负责。这样需要高度信任的程序一般不会是在网上直接下载运行。

现在越来越多程序都是通过网络(over-the-wire)获取的,所以我觉得“wire format”广义的描述程序的交换格式也可以。但信任度的高低差异会还是给运行模型带来不同影响。广义上谈“安全”的话,交换格式或许并不是最短的一块木板;但在“低信任度”场景里,拥有更多的校验,对程序有更多的限制和控制,显然是合适的。

我说的不是很清楚,我是想说“在这一方面更安全” 
5 楼 cajon 2009-05-09  
RednaxelaFX 写道

这篇东西昨晚还在草稿状态的时候我手滑点了提交结果就发出来了……当时标题还是“Python字节码,改啊改啊改”。然后我就把误发的草稿秒删了。对点击到无效链接的同学表示抱歉 &lt;(_ _)>

哈哈,我说呢,怎么读的没头没尾的。这一篇,看完就比较清楚了。
4 楼 RednaxelaFX 2009-05-09  
sulong 写道
那也就是说用本地代码做wire format的语言,如c, c++和python一样都是不安全的了?前者因为“代码发现”问题,无法校验,后者是没有校验。 而以只以源码或中间码作为fire format的程序必须得校验一次,就更加安全些了,比如java, c#?

嗯……微妙。我觉得用“更安全”来描述不太合适。
安全漏洞如同“短板效应”:两个本来一样高的木桶都有破损,即便木桶A有3块短板,木桶B只有1块短板,如果两者最短的一块板都一样长,那么它们最多能装的水也一样多。无论是Python、Java还是.NET,它们的常见实现部分中总有是本地代码的部分;也就是说如果本地代码带来了安全隐患,那么大家都一样会遭殃。
看看那么多VBScript写的病毒、蠕虫和木马,它们可都是以源码形式“发布”的,但它们显然算不上“安全”。

我觉得这是“信任”与“安全”的一个权衡。

在网络环境下,用户接触到潜在不安全的代码的几率非常大。如果你想在浏览网页时看到了一个在线涂鸦程序,高兴的玩起来,你显然不会希望那个程序能肆意访问自己机器上的资源,或者是在毫无警告的情况下连接到别的网站去下载了什么。
这种使用网上未知程序的应用场景,属于“低信任度”的场景。此时以本地代码为wire format确实有更高的风险,因为其执行几乎不受限制(一般仅受用户在系统中所拥有的权限限制);而以源码或中间代码为wire format,一般伴随着更受限制的执行环境(例如沙箱模型的安全机制),这样即使是不受信任的代码也可以执行,通过校验wire format和后续的运行时权限检查,我们可以更好的防范不受信任的代码可能带来的威胁。
所谓“Wire format”有一个隐藏的倾向,那就是程序在下载后无需用户干涉就可以自动运行。像Flash、Silverlight、Java Applet都是这样,只要用户最初同意了程序的运行,程序下载完成后无须安装就会自动开始运行。

但是自己机器上的CRT、浏览器之类的,不都也是本地代码么?这就是“信任”与“安全”权衡的另一面,“高信任度”场景。你所安装使用的这些本地代码的程序,必须受到你的信任。你的信任赋予程序以高度的权限,同时你也要为自己的信任而负责。这样需要高度信任的程序一般不会是在网上直接下载运行。

现在越来越多程序都是通过网络(over-the-wire)获取的,所以我觉得“wire format”广义的描述程序的交换格式也可以。但信任度的高低差异会还是给运行模型带来不同影响。广义上谈“安全”的话,交换格式或许并不是最短的一块木板;但在“低信任度”场景里,拥有更多的校验,对程序有更多的限制和控制,显然是合适的。
3 楼 sulong 2009-05-09  
那也就是说用本地代码做wire format的语言,如c, c++和python一样都是不安全的了?前者因为“代码发现”问题,无法校验,后者是没有校验。 而以只以源码或中间码作为fire format的程序必须得校验一次,就更加安全些了,比如java, c#?
2 楼 night_stalker 2009-05-09  
不检查很好…… 可以做字节码 hack,实现一些神奇的功能~~
1 楼 RednaxelaFX 2009-05-09  
这篇东西昨晚还在草稿状态的时候我手滑点了提交结果就发出来了……当时标题还是“Python字节码,改啊改啊改”。然后我就把误发的草稿秒删了。对点击到无效链接的同学表示抱歉 <(_ _)>

相关推荐

Global site tag (gtag.js) - Google Analytics