比尔盖子 博客

Sticky post
把良好的编程风格教给那些之前曾经接触过 BASIC 的学生几乎是不可能的。作为可能的程序员,他们已精神残废,无重塑的可能了。

by E. W. Dijkstra

自由软件基金会的历史包袱 —— GFDL

当我们需要发布文档的时,可以选择有许多不同的许可证。事实上,老牌的 MIT、BSD 或 GPL 均可以用于任何作品,包括文档在内;但你可能会说,这些都并非专门为文档制定,因此需要一些更合适的,没问题,你可以立刻加入 CC 协议豪华午餐;但你可能又会说,你已经采用 GPL 许可证作为了项目的主许可,采用另一个组织发布的许可证,不太整齐划一,因此希望使用 GFDL 许可证。

这并非没有道理,《GNU 自由文档许可证》是 FSF 专门为文档量身定制的。如果你是 FSF 的支持者,FSF、GNU 和自由看上去都会是首选,更何况这也是一个老牌的许可证,比 AGPL 等许可证的岁数都大得多,看上去也挺靠谱的。然而,在 21 世纪 10 年代的今天,GFDL 已经不再是好选择,我将通过这篇文章证明这点。

不可变章节,中出叛徒

何为自由?不同的哲学家自然有不同的观点。但盖子讨论的显然不是这个了 —— 自由软件界的四大自由分别是:

自由之零:任意使用此作品,其目的不受任何限制;
自由之一:研究作品本身,并按照自己的意愿随意修改;
自由之二:分发作品的拷贝; 自由之三:分发自己修改过的拷贝。

但是反过来看看 GFDL,里面却说作者可以将自己作品中的某些章节,宣布为不可变章节。在其他人根据 GFDL 分发作品时,这些所谓的“不可变”章节就都是严禁修改的了。当年 Debian 的开发者看到了,就立即表示不服。GFDL 中居然允许公然违反自由之一和自由之三?这不是我们中出了个叛徒吗?

“什么?你说并非所有内容都是不可变的,这不就完全是一副‘自由作品里的不可变章节,能叫封闭吗?’的不讲理态度么?”,Debian 开发者纷纷这样表示。按照这个思路,盖子甚至还可以将文章所有的部分都“不可变”,然后以 GFDL 发布冒充自由文档,就等着有人上钩吧 lol. 而且这也导致了一些实用主义上的问题,假如我要翻译一本书中的不可变章节,那么我只能以中英对照这样的方式进行翻译。

但反过来想,不可变章节的规定并非是凭空出现的。很多作者都认为,自己的精神和思想体现在作品中,他人对其的任何改动都是糟蹋了作品,也就是糟蹋了作品的思想。更进一步,他们认为作者有必要在一定程度上对作品进行适当的控制,来避免这样的事情发生。事实上,黑客史的早期,许多黑客都以此为理由拒绝自由软件许可证。而如今的同人创作界不采纳自由许可证,这也是重要理由之一。就算你并不持有这样激进的看法,我相信当编辑将你文章里的内容,修改得与本意完全相反的时候,你自然也会十分不满,而不可变章节的相关规定,就是一种保护手段,避免原作者的文章写作意图被抹除掉,甚至是被修改成完全相反的意思进一步误导他人。

考虑到这份许可证本身就是为文档而量身定制的,这个规定并非无法理解。而且更进一步,如果你告诉我:因为 GFDL 允许不可变章节,就直接认为 GFDL 就会破坏自由。这时候正方就会让你就要想想了:不可变章节是谁规定的?到底是 GFDL 在破坏自由,还是你在破坏自由?如果你希望作品自由,你可以完全没有不可变章节。

附带协议,有害地球

GFDL 规定了,任何使用 GFDL 的作品都要在发布的时候完整的附带 GFDL 的许可证全文。要知道,每生产一吨纸就会消耗 24 棵树,而一棵树每年能吸收 20 千克的二氧化碳。每篇文档都要附带这么长的许可证,你当全球变暖是闹着玩的吗?不不不,这么说我就上当了,因为全球变暖是以微软为首的财团制约自由软件发展的骗局(大雾!

虽然 GFDL 的制定者表示,如果我们的读者并不知道 GFDL 许可里都说了些什么,那么自由许可也就失去意义了,因此我们要必要明确传达许可证的内容。这对一本书来说也许不成问题,但并非只有书籍才使用 GFDL,如果我用 GFDL 发布一份 100 个字的菜谱,我居然要附带上足足 10 页满满 A4 纸的许可证!事实上,当年一家旅游网站就遇到过这个问题,一张导游路线必须附带 GFDL,这还让不让人旅游了。维基百科在 2008 年左右也因为这个问题全面切换到了 CC 协议,这也是为什么 GFDL 和 CC 是维基百科双许可的原因。

但是,这依然不足以说服所有开发者,因为它们主要写的是技术书。

不兼容 GPL,匪夷所思

由于 GFDL 中允许作者主动加入不可变章节的限制,而 GPL 是个非常积极抵制限制的许可证,把这两个抖 S 放在一起,必然会导致有点匪夷所思的结果:GFDL 与 GPL 双向不兼容!这也就意味着,假如你用 GPL 发布了一个程序,我用 GFDL 写了一篇技术分析,并且引用了你的源代码,那么我就立刻变成了侵权者。你没有看错!反过来,你引用了我的 GFDL 技术书中的代码,写了一个 GPL 的程序,那么你也会成为侵权者。

这真是一个是一大败笔,但修复它就很纠结了,十分的困难。就连 FSF 的首席法律顾问伊本·莫格林,都在 2006 年召开的 GPLv3 制定大会上吐槽,说经过这么多年的努力,我们终于把 LGPL 以 GPL 附加条款的形式表述了出来,弱力、强力和电磁力就都被大统一了。然而 GFDL 这个烦人的引力我们就是纳入不了统一场论 —— 这实在是太难了。当时在大会上 FSF 确实说要改革 GFDL,但因太难,以至于到现在这万有理论依然是没动经。

Debian 与 FSF,官方逼死同人

由于以上的种种争议,Debian 开发者在大约 10 年前,终于通过了多论激烈的讨论,宣布 GFDL 不符合 Debian 的自由指导方针(DFSG),不承认 GFDL 是自由许可证。Debian 的自由指导方针在立场上和 GPL 很相似,于是一群人站在 GPL 的立场上,对本是同根生的 GFDL 判了死刑。所以所有用 GFDL 授权的文档在 Debian 里大都位于 non-free 软件源里。

此外,Debian 开发者还开发了一个工具,叫做 vrms,也就是史托曼模拟器(Virtual RMS),可以把电脑里安装的所有不自由软件全部列出来,以方便你进行卸载。然而问题是这个软件是既然是 Debian 开发的,自然是按照 Debian 的自由指导方针来写了……于是这位虚拟史托曼就会告诉你,这些使用 GFDL 的文档都是专有的,建议卸载。后来这事被 GNU 的一些开发者之后了,于是它们又以诬陷 GFDL 为理由,将 vrms 工具列入了 FSF 官方的“不自由列表”,说 vrms 这程序有害你的自由,请立刻卸载!

这是在十分可笑,但遗憾的是所有修改 GFDL 的尝试,都遇到了法律层次技术的问题而不太成功,相关讨论一直被无限搁置。

历史问题,后人解决

GFDL 的历史相当悠久,和 GPLv1 是在同一个时间出现的,是自由许可证的先驱。然而历史包袱也是本意为好的 GFDL 遇到了种种未曾预料到的问题。在 21 世纪 10 年代的今天,知识共享的 CC 许可证在制定时既然已经避免了 GFDL 遇到的这些陷阱,而且也得到了 FSF 以及整个社区的公认,没有理由不将 CC 协议看作 GFDL 的升级版,那么为何不去使用呢?

可见,GFDL 并不适合作为像 GPL 一样的默认选择,只有遇到特殊的需求才有必要使用它,其他通行的情况下,默认使用 CC 协议有助于整个开源和自由软件社区的所有人前进。

Chromium 还是 FireFox?

比尔盖子从去年开始进行全系统加固,无论是个人电脑还是服务器都部署了 Gentoo Hardened,并采取了不同程度的安全措施。Chromium、FireFox,以及基于 Webkit 的外壳浏览器是自由软件界的御三家,从特性上来说各有独自的优势 —— 如果要从这一点上进行谈论恐怕会触发圣战,这也并不是比尔盖子撰写本文的目的。本文企图从系统安全的角度来说明浏览器的选择。

Mozilla FireFox

Mozilla FireFox 是历史悠久功能、功能强大的浏览器,由于有官方的参与,可以认为是网络浏览器的开山鼻祖 Netscape 的精神续作。丰富强大的插件系统是其的生态特色,采用自有的 Gecko + 各种 Monkey 引擎:

综上所述,FireFox 存在的问题是缺少沙盒和隔离。我们最好对 FireFox 进行一些人工沙盒措施确保安全,然而这也并不能防止不同网页之间的攻击。

Chromium

Chromium 浏览器是 Google Chrome 的自由版本,最初主打性能和轻量级设计,采用 Blink + V8 引擎,其特点是:

综上所述,Chromium 存在的问题是代码库捆绑巨量程序库,而且其引擎严重依赖 JIT 且无法关闭,更大的攻击面容易造成程序漏洞,以及开发主要受到 Google 单一主导的问题。

Webkit

嵌入网络浏览器功能时必用的库,是 Safari 和 Chromium 两大浏览器的源头。

  • 庞大的 C++ 代码库。攻击面大:高风险
  • 不同的开发库都将 Webkit 作为了网页浏览方案,并派生出了 QtWebKit 和 WebKitGTK+,许多其它应用程序也嵌入了 Webkit。
  • Webkit 的一直在演化中,不同的应用程序捆绑了不同了 Webkit 版本。前些年的 Webkit2 使这种情况更加严重。
  • 在上述的大多数情景中,这些 Webkit 都无法获得任何安全更新!许多发行版和程序都依然还在使用版本极低的 Webkit。只有 ArchLinux 和 Fedora 紧密跟随上游及时获取更新,据我观察 Gentoo 的表现并不算差,但有些程序依然依赖过时的低版本 —— 而这是没有安全更新的。
  • 许多基于 Webkit 的网页嵌入和浏览器不验证 TLS 证书

综上所述,Webkit 存在的问题是终端用户得到安全更新的可能性很低,几乎没有办法放心使用,应该尽可能避免。

总论

完整浏览器的规模庞大,使用浏览器本身时时刻刻就承担着无法避免的高风险,因此我对每个浏览器都给出了类似的第一条评价。


因此,比尔盖子在一年前离开了 Chromium 而选择了更可控的 FireFox.

服务器维护初步结束

2013 年,爱德华·斯诺登披露了棱镜计划,于是人们,包括盖子自己终于学会了如何正确配置 TLS;2014 年,各研究者纷纷加入到 Linux 安全的研究行列中,随着一篇“炮打shell司令部root,我的一个 exploit”的 Bug 报告,以前那种“随便配置个 Linux 服务器让它跑”的欢乐时光已经彻底结束,真是让各运维苦不堪言,都快患上 Shell Shock 了,人们不得不以正确的态度开始对待安全;2015 年,越来越多的安全审计也被提上日常。

在这样的背景环境下,学习并应用的安全技术是多多益善的。因此,我在今年初部署了一台实验性的服务器,用于测试 PaX 技术。如今,虽然 grsecurity 以盖子的能力完全掌握还需要时间,但 PaX 已经完成测试可以部署了,而盖子的服务器运行了两年时间,也暴露出来一些问题。

因此盖子进行了一次大型服务器维护。而凑巧,在服务器进行维护之前的 8 月 15 日,忽然被 GFW 屏蔽了!一天之后,服务器也进入了关机维护状态,以至于不少人出现了“这服务器怎么挂了代理也不能翻墙访问”的错觉。

长话短说,这此维护的摘要如下(随着维护的进行,可能会更新):

  • 发行版切换到 Gentoo Hardened 稳定版
  • 全面启用 PaX 和其它系统加固措施
  • 服务器 IP 被 GFW 屏蔽,目前的新 IP 切换至 106.187.50.120。IP 配置和解析记录的切换将逐步进行,若要了解细节或需要帮忙,欢迎联系我。
  • 服务器 SSH 密钥重新生成:

    • RSA 指纹:SHA256:rdiQjAfAIG1OsgmKgSZgLeslb50QqcjsIa4oi2WnsA8
    • ED25519 指纹:SHA256:LGohlHLSTIP/PRW16JwNdQbjXrV887VZKeseJeGG2uQ
  • 禁用不活跃、以及不使用 PHP 用户的 php-fpm 进程,一些用户已经长期不活跃,还有一些用户不需要使用 PHP。但 php-fpm 却被盖子配置成默认运行,消耗系统资源。以下用户的 PHP 已被默认禁用。盖子计划未来对不活跃用户进行清理。

    • alleria0218
    • cnzh2005
    • colin4124
    • ffddybz
    • grinv
    • huangtao728
    • lengbamboo
    • pynix
    • roowe
    • tianyu
    • wangfeitong
    • xinmuontheway
  • 严禁运行 SSH 端口转发,以及 Shadowsocks 等代理服务器。这是服务器最后的备用 IP,作死的行为将导致服务器完全不可用。我会定期进行进程审计。

为什么 os.path 可以被直接导入

今天在微博上,@julyclyde 发现了一个看似简单但很有意思的问题,import os.path 的行为很奇怪。

Python 使用 import 语句来“导入”外部模块,而常见的情况有:

  • a 已经被导入了,a 已经被 sys.modules 记录在案,重复导入没有效果,只会返回 sys.modules 中的 现有的引用
  • a 是一个位于搜索路径中的文件,Python 导入该文件
  • a 是一个位于搜索路径中的目录,包含 __init__.py,Python 导入此 package
  • 使用 Import Hook 在 Python 的 import 语句中挂上钩子,改变 import 逻辑,实现自定义行为

在导入子模块 a.b 时,

  • a 是 package,Python 导入该 package 的子模块 b
  • a 定义了子模块 __path__ 列表,Python 根据这个搜索路径去寻找并导入 b
  • b 是 a 下的一个变量,不是子模块,因为不存在字面上名为 “a.b” 的模块,导入失败

但是 os.path 却不符合其中的任何一种条件。首先,os 是一个普通的文件, 位于 /usr/lib64/python[x].[y]/os.py,也没有 __init__.py,因此它并不是 package;通读源代码,没有发现它使用了任何 Hook,也没有定义 __path__,更不存在 os.path 这个模块,因此 import os.path 理应失败!然而,有趣的是,os.path 居然可以作为 os 的 子模块直接导入,这实在是太奇怪了。

仔细阅读 os,不难发现这段代码

if 'posix' in _names:
    ...
    import posixpath as path
    ...
elif 'nt' in _names:
    ...
    import ntpath as path
    ...
elif 'ce' in _names:
    ...
    import ntpath as path
    ...
else:
    raise ImportError('no os specific module found')

sys.modules['os.path'] = path

从中我们可以看出,os 会根据不同的系统平台导入不同的 os.path 实现模块,然后把该模块 的引用,强行插入到已导入模块 sys.modules 里。这样一样,import os.path 会优先在 sys.modules 搜索到目标,立刻返回。

问题在于,os 的代码是在何时被执行的呢?它是在 Python 启动时的 bootstrap 中执行的吗? 使用 python -v 调试,

import _frozen_importlib # frozen
import imp # builtin
import sys # builtin
# /usr/lib64/python3.4/__pycache__/os.cpython-34.pyc matches /usr/lib64/python3.4/os.py
# code object from '/usr/lib64/python3.4/__pycache__/os.cpython-34.pyc'
# /usr/lib64/python3.4/__pycache__/stat.cpython-34.pyc matches /usr/lib64/python3.4/stat.py
# code object from '/usr/lib64/python3.4/__pycache__/stat.cpython-34.pyc'
import 'stat' # <_frozen_importlib.SourceFileLoader object at 0x7f70160d1e10>
# /usr/lib64/python3.4/__pycache__/posixpath.cpython-34.pyc matches /usr/lib64/python3.4/posixpath.py
# code object from '/usr/lib64/python3.4/__pycache__/posixpath.cpython-34.pyc'
# /usr/lib64/python3.4/__pycache__/genericpath.cpython-34.pyc matches /usr/lib64/python3.4/genericpath.py
# code object from '/usr/lib64/python3.4/__pycache__/genericpath.cpython-34.pyc'
import 'genericpath' # <_frozen_importlib.SourceFileLoader object at 0x7f70160d6710>
import 'posixpath' # <_frozen_importlib.SourceFileLoader object at 0x7f70160d30b8>

>>> "os" in sys.modules
True
>>> "os.path" in sys.modules
True

可以看到,在 Python 解释器在初始化过程中,os 确实被执行了一次。而且,osos.path 存在于已导入列表中。但是,使用 -S 让 Python 不自动 import site 进行部分初始化之后,os 和 os.path 都不会自动导入了,但 import os.path 依然可以正常运作。

众所周知,在 Python 中,对象是被代码实时的创造出来,因此,import 必然要执行模块中的代码,否则变量、函数、类都不会存在,import 也就不可能导入任何东西了。但值得一提的是,在而导入子模块的时候,因此父模块的代码也会不可避免的被执行,原因之一是 Python 需要查阅 父模块的 __path__ 变量来决定子模块的位置,也就是说,考虑 hello.py

print("Hello, world!")

如果我们试图 import hello.should_not_exist

>>> import hello.should_not_exist
Hello, world  # hello.py 被执行了

# 执行完了,现在看看程序有没有定义好 `__path__`
Traceback (most recent call last):
  File "<frozen importlib._bootstrap>", line 2218, in _find_and_load_unlocked
AttributeError: 'module' object has no attribute '__path__'
# 然而 `__path__` 不存在,except AttributeError,继续尝试

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 'hello.should_not_exist'; 'hello' is not a package
# hello 也不是一个 package,因此出错

可见,我们在 import os.path 时,首先 os 被执行,注入 os.pathsys.modules 里,然后,Python 才尝试导入 os.path,这时它 已经位于导入列表,于是得以顺利导入。因此,与其把 os.path 看作 os 的子模块,不如把它看作名为 os.path 的独立模块。根据同样的原理,我们不难在自己的项目中使用同样的技巧。

开发者张峻锋被指参与恶意软件

今天下午,惊闻 MMD 宣布张峻锋(微博:@马鹿峻鋒連聊C)被认定参与了恶意软件的编写。按照我个人的了解,张峻锋在学业之外,积极参与自由软件开发,包括 AOSC 社区的一部分工作,不太可能从事利用蠕虫危害网络安全的事情。因此做了一些调查,事件始末如下。 Continue reading

宣布「盖子 CA」

很多时候,去大型正规 CA 机构获取证书是不现实的,包括但不限于:测试 应用、私人服务器。然而,单独为这些使用场景每次都现生成一个自签名证书 不便于管理:你必须手工核对大量的自签名证书指纹。

「盖子 CA」是比尔盖子为了对自己的自签名证书进行统一管理,解决其签名证书混乱,而自行设立的个人 CA。当然了,这个 CA 是不被任何厂商信任度的。比尔盖子的信任管理手段很简单,就是使用 PGP 对我的 CA 证书进行签名,这样,如果 你本来就信任我的 PGP 公钥,那么你就可以确信某证书的确是我签署的。

比尔盖子保证:

  • CA 的私钥是安全的,它是在断网情况下在 tmpfs 中生成的,签署完毕后,就被 GPG 加密保存到硬盘。
  • CA 的签名过程也是安全的,安全措施类似。
  • 我只会对我自己管理的应用进行签名,而不是代替别人,给别人的域名和应用,如 GMail 签名,更不会用来进行中间人攻击。
  • 如果有网友可通过 PGP 配合,证明其身份,以及服务器或域名的管理权,我可以帮他进行签名,同时我会在个人网站公开查验记录。

什么?你依然不敢把我的 CA 加入全局信任列表?我也并没有期待你这么做,因为我这么做的主旨仅仅是:让他人仅仅通过核对一个 CA 证书证明我的服务器是真实的,而不是核对大量证书。

目前使用这个证书的有

  • Starbrilliant 使用的 brilliant.biergaizi.info
  • erhandsome 上的 XMPP

那么这个证书有什么缺点呢?不支持吊销……因为这个 CA 我并不打算长期使用,我想如果发生证书泄露我会作废 CA,因此这似乎不是一个问题。

签名的信息和证书可以在这里查看

服务器 SSH 新增备用端口

由于众所周知的原因,服务器的 SSH 端口 22000 开始变得不太稳定,经常出现无法连接的情况。因此,现启用备用端口,如无法连接服务器,可尝试连接端口 12450

利用公共邮件列表进行邮件洪水攻击

A Flooded Mailbox

近日起,比尔盖子在 24 小时内收到了 20000 封来自公共邮件列表的订阅确认邮件,八成都来自自由和开源软件(FOSS)项目的邮件列表。而涵盖的项目也至少多达 20 个,不限于 OpenBSD、FreeBSD、GNU 计划、Ubuntu、CentOS、Qt、HostAp,甚至是以邮件著称的 Postfix。“订阅者”来自多个 IP。

在社交网络上分享经历后,不到 5 个小时,截至第一次发稿,就确认了受害者 @LI欣欣zn, @黑椒饼干, @路过的小新, @玩脱了的奶鱼, @機智的阿卡林chan, @06peng, @被窝型笨笨鱼, @和樹白翼, @无名小卒_路人A,似乎以技术圈为主。紧接下来的几天内,更是确认了萌娘百科官方邮箱,月光博客的作者 William Long,上海 Linux 用户组的 Thomas 等显著案例。部分受害者的邮箱因来信太多已经停止工作。个人用户只能暂时利用关键词过滤器规避攻击。更有甚者,@和樹白翼 紧急更换的新邮箱居然在新的一波攻击中遭殃。

邮件列表是 FOSS 项目的重要交流工具,日常开发几乎完全倚仗邮件列表。订阅邮件列表时,常见的程序均会给用户发送确认邮件,避免用户受到骚扰。然而,由于 FOSS 项目邮件列表大量存在,这就使得利用确认邮件本身加以骚扰他人成为可能。邮件列表通常均使用 GNU Mailman,而且通常不设有验证,更是为批量自动化操作提供了捷径。一个数字节的 POST 请求即可放大为内容更长的电子邮件。

这已经不是第一次发生类此状况,去年,GNOME 基金会管理的 FreeDesktop.org 列表就被利用进行攻击,管理者通过自行给 GNU Mailman 订阅增加了 reCAPTCHA 验证解决了问题。然而,还有大量的社区邮件列表处于没有保护的境地下(如 Fedora Project)。如果公共邮件列表一旦被广泛利用,这类低成本的攻击将会严重耗费 FOSS 项目系统资源,影响大量潜在的个人或机构正常使用电邮,如果这类邮件被归类为垃圾邮件,更会影响用户和开发者的正常工作。

« Older posts

Copyright © 2016 比尔盖子 博客

Theme by Anders NorenUp ↑