April 2012 Archives

目前多数软件产品的安全更新都是在周二或者周三公布了。这种做法 据说 是微软开始采用的,不过具体是谁先开始这么做,目前我还没有找到非常可靠的资料。

一般来说,软件开发者在发布安全更新之前,需要做下面这些事情:

  • 确认问题存在,即,修正的确实是一个有安全影响的问题;
  • 申请一个 CVE 编号;
  • 找到修正问题的最小变动----有时,新版的软件由于代码重构等原因已经不再包含同样的漏洞,但是将重构向回移植可能会引入新的问题,或是导致接口不再兼容,因此有时需要实现另外的修正;
  • 测试修正确实改正了问题;
  • 测试兼容性;
  • 发表;

而在用户这边,通常也需要进行少量的测试才可以大面积部署。由于时差的原因,周一或周五发表安全更新有可能会导致有些人必须在周末工作,因此不合适;周三发表安全公告会给 IT 人员留下充足的测试和部署时间,同时又避开了周末。

FreeBSD 的 strlen(3)

| 2 Comments | No TrackBacks

之前只有一篇关于较早版本的 strlen(3) 实现的笔记,这里补上我在 2010 年做的新增改进。

与 Pascal 等语言不同,C 的字符串并不保存串的长度,而是在字符串末尾以 nul 字符('\0')来表示字符串结束。这个设计决策是上世纪 60 年代作出的,有都市传说是为了省几个字节的空间,不过我个人认为也可能是因为汇编里面到处都是判断是否碰到了 0 的操作。不管怎么说,这个设计令 strlen 变成了一个 O(n) 的操作。

早期的 BSD Unix 采用的 strlen 是非常简单的循环比较每一个字符是不是 nul。1993年,J.T. Conklin 为 i386 系统撰写了一个汇编的版本,这个版本的核心用的是 REP SCASB,实际上和 C 版本的算法是一样的(不知道为什么 C 编译器不能写出同样的代码)。

为了配合 x86_64 平台,后来又有了一个新的汇编版本,这个版本的核心算法是按字匹配,找到包含 nul 字符的字之后,再在其中用原始的算法找到 nul 字符。

我在 2009 年根据这个 x86_64 版本的汇编的思路重写了一个 C 的版本,并在 2010 年做了一次最终的变动,形成了目前的版本。这个版本的大致流程如下:

  • 判断第一个字中是否存在 nul,如果存在,扫描查找其中有效位置的 nul 字符;
  • 按字扫描剩余的字符串;如果发现字中带 nul,则扫描并返回其位置。

实现细节

整个算法中,比较难理解的是判断字中是否带 nul 字符。具体的方法是计算两个中间变量:

  1. a = (x - 0x01010101)
  2. b = (~x & 0x80808080)
  3. 计算 a & b == 0

这里的 0x01010101 和 0x80808080 可以进一步扩展。第一步,如果每个字节都 <= 0x7f,只要那个字节不是 0,做差必然得到一个 < 0x80 的结果(换言之,最高位是0);如果有字节 >= 0x81,做差必然得到一个 > 0x80 的结果。对于等于 0x80 的情况,我们会得到 0x7f,但这并不重要。

注意到,此处,任何一个字节的最高 bit 是 1 的话,则必然是前面两种情况之一:要么这个字节是 0,要么它 >= 0x81。如果不考虑后一种情况,我们直接把结果 & 0x80808080 即可;然而,由于需要考虑后一种情况,我们接着计算 ~x & 0x80808080。若某一字节 >= 0x80,则对应的结果将是那个位置上的一个 0x80。

将两个结果做逻辑与,若结果非 0 则说明至少有一个字节是 nul。

这里说起来的过程很复杂,但事实上计算机计算这些要比一个一个去判断每个字节是否为 0 要快。这里有几方面的原因:

  • 按字长做操作,令 CPU 无需模拟按字节为长度操作的情况,后者是比较耗时的;
  • 前两步操作(分别计算a, b)可以并发执行;
  • 最后一步操作可以直接在两个寄存器之间进行,且是速度较快的与运算;

在实际的实现中,还有一些其他的技巧。

第一个技巧是,从第一个小节就开始用字长的操作。一般来说,内存分配器在分配内存时是以字边界开始的,因此,通常 strlen() 的操作的指针都是对齐的。不过,即使不是,这个指针往前退到第一个整字位置(例如字长=8,指针 0x9,则退回 0x8)开始的一个整字必然是在同一个内存页上,因此这个访问不会越界。如果在这个整字中有 nul 字符,我们只需从指针开始处扫描到第一个整字结尾的地方即可知道是不是真的找到了字符串的末尾。

由于整个字已经在处理器缓存中,后续的循环也不会太慢。

第二个技巧与此类似,我们一直都用整字的操作。如果字的起始地址在内存页中,则终止地址也必然在同一个内存页中。这个访问同样也不会发生意外越界(尽管在分配内存时可能出现类似分配了 4 个字节,但访问了 8 个字节的情况)。换言之,如果程序原先不会发生越界异常,则现在也不会。

这个版本的 strlen 源代码可以在 这里 找到。

最近两次失败记录的教训。

首先是联编 world 和 kernel (make buildworld buildkernel),这个没啥可说的。假定 / 是 UFS(即,可使用 nextboot),且配置了 watchdog,则操作步骤为:

  1. make installkernel KODIR=/boot/kernel.new; nextboot -k kernel.new 【令系统尝试一次新内核启动;若失败,则下次仍会启动旧内核】
  2. shutdown -r now
  3. mergemaster -p 【建议做这步操作,可以避免选错】
  4. make installkernel installworld 【此处再做一遍installkernel;如果不做,可能导致系统从旧内核启动并导致失败】
  5. mergemaster -Ui
  6. yes | make delete-old 【删去不需要的文件和库;不要make delete-old-libs,因为可能导致某些新程序无法运行】
  7. portmaster -BDavf 【重新联编所有 package】
  8. yes | make delete-old-libs 【此时删除旧 .so 库应该是安全的了】
  9. shutdown -r now,测试

当然,更简单的方法是先接好IPMI。

delphijfork 9.0更新

| 6 Comments | No TrackBacks

可以在 这里 下载。

和过去一样,风险自担 USE AT YOUR OWN RISK!

这个版本 (20120402) 和上一个版本 (20120114) 相比的改动:

  • 集成了近期 ZFS 的全部可靠性和性能改进;
  • 增加了允许 jail 中挂载 ZFS 的功能;
  • mps(4)性能改进;
  • 对 ULE 调度器的重要性能改进(支持超线程的处理器上许多测试可看到 10%-15% 的改善);
  • 对 Intel TurboBoost 技术的支持(在需要时可将 CPU 频率临时提高到标称值的 110%);
  • 对交换设备热拔时的可靠性改进;
  • 对虚拟内存子系统的性能和可靠性改进;
  • 优化了 rtld-elf 的内存占用【注意,必须配合 FreeBSD 9.x 内核使用,如果从 FreeBSD 8.x 或更早版本升级,请确认在 installkernel 之后重启过系统】;
  • 新增了 posix_fadvise(2) 系统调用;
  • 对于 SU+J 的若干可靠性改进;
  • 对 tmpfs 的性能和可靠性改进,增加了 NFS export 支持;
  • 为 msdosfs 和 smbfs 增加了 Unicode 支持;
  • 为 PCIe 增加了 4GB 边界限制,这项变动消除了许多潜在的数据损坏问题;
  • 其他若干小改进。

全部改动来自 -CURRENT 或 -STABLE,并已经做过至少两周的疲劳测试。

此版本改变了部分 VFS 接口,因此外部的涉及文件系统的内核模块可能必须重新编译才能使用。其它方面,它和向下兼容之前为 FreeBSD 9.0-RELEASE 或 delphijfork 20120114 编译的可执行文件。

Monthly Archives

Pages

OpenID accepted here Learn more about OpenID
Powered by Movable Type 5.2.3