比尔盖子 博客

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

by E. W. Dijkstra

4.3BSD 系统上手体验(从磁带获取 /usr/src 源代码)

前几天为了测试一个程序,在 SimH 模拟的 DEC VAX-11/780 计算机中安装了 4.3BSD 系统(大概是历史上最伟大的操作系统之一?)。本系统于 1986 年夏天在加州伯克利新鲜出炉,安装磁带绝赞发售中……

# dmesg | head
4.3 BSD UNIX #1: Fri Jun  6 19:55:29 PDT 1986
karels@monet.Berkeley.EDU:/usr/src/sys/GENERIC
real mem  = 8388608

系统的安装过程完全是按照一篇教程进行的。当完成教程后,就可以启动 VAX-11/780 并进入 BSD 系统,这里就不重复了。

登录系统

4.3 BSD UNIX (bsd.my.domain) (console)

login: root
Last login: Mon Jan 27 09:19:47 on console
Jan 27 10:20:58 bsd login: ROOT LOGIN console
4.3 BSD UNIX #1: Fri Jun  6 19:55:29 PDT 1986

Would you like to play a game?

Don't login as root, use su
bsd#

一切仿佛就在昨天,Unix 的 getty 这么多年都没有变化过。另外发现原来连 4.3BSD 都会嘲笑 root 敢死队——小心点,如果误操作,你会引起第三次世界核大战的……(”Would you like to play a game” 显然是 BSD 开发者在玩 1983 年好莱坞科幻《Wargames》的梗)。

系统中甚至有有个名为 /usr/games/wargames 的游戏……

# /usr/games/wargames
Would you like to play a game? yes
Funny, the only way to win is not to play at all

不管你回答什么,它都只会打印 “Funny, the only way to win is not to play at all”,纯粹是为了玩梗而存在。如今技术圈的社交媒体的人们经常玩科幻、电影、动画相关的各种梗,看来一直是历史传统了。

寻找源代码

不玩游戏了,回到测试工作。

众所周知,BSD 操作系统不只是一个内核,而是一套完整的操作系统。因此,所有程序的全套源代码都可以在 /usr/src 中找到。为了进行测试,我需要调试并修改某个系统工具的源代码。于是我使用了 find 快速递归列出文件。

# find /usr/src
/usr/src
...
/usr/src/bin/as
...
/usr/src/bin/awk
...
/usr/src/bin/cat.c
/usr/src/bin/cc.c
/usr/src/bin/chgrp.c
/usr/src/bin/chmod.c
/usr/src/bin/cmp.c
/usr/src/bin/cp.c
/usr/src/bin/csh
...
/usr/src/bin/date.c
/usr/src/bin/dd.c
/usr/src/bin/df.c
/usr/src/bin/diff
/usr/src/bin/diff/diff.c

源代码是应有尽有,一目了然。

见鬼的是这个目录居然不存在?!

# ls /usr/src
/usr/src not found

显然 find 对系统中存在的文件似乎有不同的理解。不过没有什么是看文档解决不了的问题,RTFM 一下。

# man find

SYNOPSIS
find pathname-list expression
find pattern

DESCRIPTION
...
The second form rapidly searches a database for all path-
names which match pattern.  Usually the database is recom-
puted weekly and contains the pathnames of all files which
are publicly accessible.

原来 BSD 的 find(1) 居然这么强大,还包含了文件索引的功能(GNU/Linux 用户可以理解为 updatedb + locate)。换句话说,现在列出的文件都是安装盘建立时的索引,并不实际存在。

磁带

显然,/usr/src 在这台机器上还没有被安装,需要从磁带安装。要使用磁带,必须先将磁带放入磁带机,并根据磁带机的型号在 /dev 下创建设备文件。在 SimH 模拟器的 .ini 配置文件中,我已经配置好了磁带机设备 ts0,它对应的是 DEC TS-11 型磁带机。从名字就可以看出,这台磁带机最初来自 PDP-11 计算机,后来在 VAX-11 时代沿用。

att ts 43.tap

开机时可以在 dmesg 中看到磁带机。

# dmesg | grep ts0
ts0 at zs0 slave 0

但是却找不到相关的设备文件。

# ls /dev/ts0 /dev/zs0
/dev/ts0 not found
/dev/zs0 not found

原来 ts0 和 zs0 都是内核驱动的硬件设备名,并非真正的设备文件。因此,首先创建设备文件。由于 Unix 支持大量不同的设备,创建设备文件是一项十分繁琐的任务。好在 BSD 的同学们为我们准备了一个名为 ./MAKEDEV 的 shell 脚本。

# head /dev/MAKEDEV
#!/bin/sh -
#
# Copyright (c) 1980 Regents of the University of California.
# All rights reserved.  The Berkeley software License Agreement
# specifies the terms and conditions for redistribution.
#
#       @(#)MAKEDEV     4.27 (Berkeley) 4/15/86

创建 ts0 磁带机的设备文件时,只需要输入如下命令(不知为何,./MAKEDEV 没有可执行权限,于是使用 sh ./MAKEDEV)

# cd /dev
# sh ./MAKEDEV ts0

但运行后发现 MAKEDEV 创建的好几个设备文件,包括 /dev/mt0, /dev/mt4, /dev/mt8, /dev/mt12, /dev/rmt0, /dev/rmt4, /dev/rmt8, /dev/rmt12, /dev/nmt0, /dev/mnt8, /dev/nrmt0, /dev/nmrt8,不知应使用哪个文件。

实在不会了,只能上网搜索看看。我首先了解到知道的是,BSD 控制磁带机的工具是 mt,所以先看看本机中 mt 的文档(不能看网络上的,版本都不一样。看来在文档难求的年代,man page 能解决不少问题)。

# man mt
...
Mt is used to give commands to a magnetic tape drive.  If a
tape name is not specified, the environment variable TAPE is
used;  if TAPE does not exist, mt uses the device
/dev/rmt12.

status
Print status information about the tape unit.

原来可以查看磁带机的状态,试一试:

# mt status
ts11 tape drive, residual=0
ds=0
er=152<onl,ies,ped,bot></onl,ies,ped,bot>

确实能识别。

接下来意识到了 Unix 从磁带读取文件的工具是 tar(这显然是废话),也许 tar 的文档里有什么说明,继续 RTFM。

# man tar

t       The names of the specified files are listed each
time they occur on the tape.  If no file argument is
given, all of the names on the tape are listed.

f       Tar uses the next argument as the name of the
archive instead of /dev/rmt?.

原来当 tar 没有指定任何文件时,它会去读取默认的磁带设备文件(不仅如今还有多少人使用过这个功能,反正我没有……)。这就试一试。

# tar t
tar: blocksize = 1
tar: directory checksum error (0 != 217)

tar 提示校验和错误。后来又使用 tar tf /dev/rmtX 把所有的设备文件都试了一遍,没有一个是能正常读取的。

彻底卡在这里了,只能从网络上搜索并重新阅读 SimH 的 4.3BSD 安装教程。原来 tar 不能读取磁带的原因是因为之前没有倒带到开头。

# mt rewind

倒带之后,还需要向后移动三条“空白计数文件”的距离。作为新手,我还并不清楚什么是空白计数文件,也不知道为什么是 3 个。不过先运行一下,日后慢慢了解。

# mt fsf 3

FSF 不是自由软件基金会,而 Forward Space Count Files 的缩写。

再试试 tar,成功!

# tar t
./
./cassette/
./dist/group
./dist/fstab.hp
./dist/motd
./dist/passwd
./dist/rc.local
./conf/touch.c
./h/mount.h
./h/signal.h
./h/quota.h
./netinet/tcp.h
./netinet/udp.h

能看到许多熟悉的文件,但并没有我要找的 /usr 或者 /usr/src。磁带后面还有更多文件,继续倒带。但运行 mt fsf 或者 mt fsf 3 没有效果。后来猜测之前的记录是 mt fsf 3,下一条可能是 mt fsf 6,果然如此。

# tar t
Makefile
...
bin/df.c
bin/hostid.c
bin/mail.c
bin/du.c
bin/strip.c
bin/stty.c
bin/cat.c
bin/ld.c
bin/login.c
bin/time.c
bin/ed.c
bin/nm.c
bin/dd.c
bin/su.c
bin/rm.c
bin/pr.c
bin/cp.c
bin/cmp.c
bin/true.sh
bin/false.sh
bin/ln.c
bin/grep.c
bin/write.c
bin/mv.c
bin/passwd.c
bin/echo.c
bin/hostname.c
bin/ps.c
bin/cc.c
bin/tar.c
bin/kill.c
bin/sync.c
bin/size.c
bin/date.c
bin/tee.c
bin/wall.c
bin/chmod.c
bin/test.c
bin/rmail.c
bin/od.c
bin/chgrp.c
bin/ar.c
bin/expr.y
bin/rcp.c
bin/ls.c
bin/Makefile
bin/mkdir.c
bin/mt.c
bin/nice.c
bin/pagesize.c
bin/pwd.c
bin/rmdir.c
bin/who.c

找到了,这就是 /usr/src。接下来只需要再重新倒带,向前快进并解压磁带上的tar 归档即可。

# mt rewind
# mt fsf 6
# mkdir /usr/src
# cd /usr/src
# tar x

大功告成。

# ls
Makefile  etc/      include/  local/    ucb/      usr.bin/
bin/      games/    lib/      old/      undoc/    usr.lib/

关机

输入 halt。

# halt
Jan 27 11:01:03 bsd halt: halted by root
Jan 27 11:01:04 bsd syslogd: going down on signal 15
syncing disks... done
halting (in tight loop); hit
^P
HALT

由于 Unix 的传统 halt 命令是只关机不断电的(您现在可以安全的关闭计算机了?),因此模拟器此时会报 HALT 指令死循环错误。

HALT
Infinite loop, PC: 8002AFCD (BRB 8002AFCD)

输入 quit 退出 DEC VAX-11/780 模拟器。

Linode 旧东京服务器已永久退役

从上月起,比尔盖子的旧东京服务器已永久退役。至此,服务器的旧 IP 地址 106.187.x.x2400:8900 彻底作废。如果你在使用博主的服务器,请及时更新你的 IP 地址。新 IP 地址即 tomli.blog 的 A 记录和 AAAA 记录,可自行通过 pingdig 查询。

此外,由于多年来零零碎碎部署的许多服务,难免有疏漏。如果你发现我维护的某个网站或服务因忘记更新 IP 无法访问,请留言告知我,谢谢。

x86 的六种重启方法

31337 h4x0r 有一回对我说道,“你读过 x86 平台的底层代码么?”我略略点一点头。他说,“读过底层,……我便考你一考。x86 的电脑上,怎样重启的?”我想,脚本小子一样的人,也配考我么?便回过脸去,不再理会。

31337 h4x0r 等了许久,很恳切的说道,“不会重启电脑罢?……我教给你,记着!这些方法应该记着。将来做黑客的时候,开发要用。”我暗想我和黑客的等级还很远呢,而且我们编程序也不是用来重启机器;又好笑,又不耐烦,懒懒的答他道,“谁要你教,不就是 ACPI 去写一个 ResetReg 么?”

31337 h4x0r 显出极高兴的样子,将两个指头的长指甲敲着键盘,点头说,“对呀对呀!……x86 有六样重启法,你知道么?”我愈不耐烦了,努着嘴走远。

31337 h4x0r 刚启动了 Emacs,想在编辑器上打字,见我毫不热心,便又叹一口气,显出极惋惜的样子。

Linux 内核支持全部六种方法……但为什么要支持六种?

有一些重启方法可能存在硬件实现问题,例如 8042 键盘控制器的方法,是支持最广泛,但也是电脑硬件 bug 最可能发生的方法。尽管 Linux 内核会逐一尝试每一种方法,很可能在尝试其中一种后导致硬件卡死。由于这些机器多半只在 Windows 上测试过,因此直到 2010 年左右,在 Linux 上重新启动机器时无法断电依然是比较常见的问题。此时,用户就需要逐一尝试这些重启方法,找到一种能用的,并向上游报告,上游将机器的型号和需要的重启方法加入表中。在现在出产的新机器上,这种问题已经非常罕见。如果你日后遇到了重启的问题,希望你能想起这篇文章,并对你有所帮助。

没错,六种方法重启,而且每种都不一定能工作,重启时需要逐一尝试,必然时候还需要查表,这就是充满历史遗产的 x86 世界的现状。

方法一:8042 键盘控制器的错误使用方法

8042 键盘控制器控制 x86 计算机上的 PS/2 键盘鼠标,但这大概也是 x86 计算机上能力最强大的硬件设备,因为它名义上是键盘控制器,但由于 IBM 工程师的脑洞,实际上还拥有控制 CPU 和内存的功能。历史上,8042 与 CPU 的 Reset 信号相连,还与内存总线上的一个逻辑门相连。

向 8042 键盘控制器(端口 0x64)写入 0xFE. 这会触发 CPU 的重置信号,并重启计算机。

ioperm(0x64, 1, 1);
outb(0xfe, 0x64);

在 Linux 上,可以使用内核参数 reboot=kbd,让 Linux 内核使用这种方式实现重新启动的功能。

方法二:向 PCI 的 0xCF9 端口发送重置信号

Intel 的 ICH/PCH 南桥芯片同时负责部分电源工作。向 PCI 的 0xCF9 端口发送重置信号,可以要求南桥芯片重启计算机。

ioperm(0xcf9, 1, 1);
uint8_t reboot_code = 0x06                 /* 热启动,冷启动 0x0E */
uint8_t cf9 = inb(0xcf9) & ~reboot_code;
outb(cf9|2, 0xcf9);                                    /* Request hard reset */
usleep(50);
outb(cf9|reboot_code, 0xcf9);            /* Actually do the reset */

在 Linux 上,可以使用内核参数 reboot=pci,让 Linux 内核使用这种方式实现重新启动的功能。

方法三:让 CPU 产生三重异常

如果代码触发了 CPU 的一个异常,并触发了事先注册的异常处理代码,但是异常处理代码本身也触发了异常,这就叫做双重异常。但这个异常处理代码本身也注册了异常处理代码,而且在执行这个异常处理代码的异常处理代码时又触发了异常,就产生了三重异常。从 80286 开始,三重异常会导致 CPU 进入 SHUTDOWN 状态,主板电路会重新启动系统。

例如:

load_idt(&no_idt);              /* 清空中断描述表 */
__asm__ __volatile__("int3");   /* 触发中断 */
/* 此时发生中断,由于 IDT 为空,无论是中断的异常处理代码还是双重异常处理代码都不存在,触发了三重异常
/* CPU 进入 SHUTDOWN 状态,主板重置 */

在 Linux 上,可以使用内核参数 reboot=triple,让 Linux 内核使用这种方式实现重新启动的功能。

尽管这个特性就在 80286 的手册附录里,而且 IBM 的工程师看到了而且还实现了必要的电路,然而 IBM 的工程师完全没有意识到这可以用来在切换到实模式时重置 CPU,而是将键盘控制器的一个端口接到 CPU 的 Reset 信号线上来进行重置 CPU 实现模式切换……

这个方法的成功率几乎是最高的,但大多数操作系统(包括 Linux 内核)不到万不得已,不会使用。虽然原则上这是为了正确进行电源管理,然而事实上的原因是,三重异常往往意味着操作系统出现了严重的故障,例如将内存中负责处理 Swap 的代码本身也移动到了 Swap 里,会立刻导致整个系统重启,数据荡然无存,开发者本来对三重异常感到恐惧,然而现在却要自己主动触发一个,实在是不能接受。此外,开发者也都认为触发个三重异常让 CPU 被迫重置实在是太残忍了,不忍心下手……因此,这种方法只会被开发者当作最终的手段。

方法四:切入实模式,跳转到 BIOS 代码重启

禁用各种东西,将 CPU 从保护模式切换回实模式,然后 ljmp $0xffff, $0x0000 跳转到 BIOS 的重置代码。 至于 BIOS 怎么重启,谁知道呢?不过只要 BIOS 实现正确,就可以工作(仅限于 32 位计算机)。

例如:

movl %cr0, %eax
andl $0x00000011, %eax
orl  $0x60000000, %eax
movl %eax, %cr0
movl %eax, %cr3
movl %cr0, %ebx
andl $0x60000000, %ebx
jz   f
invd
f:   andb $0x10, al
movl %eax,    %cr0
ljmp $0xffff, $0x0000

在 Linux 上,可以使用内核参数 reboot=bios,让 Linux 内核使用这种方式实现重新启动的功能。

方法五:通过 ACPI 重新启动

ACPI 是现代 x86 计算机必不可少的标准电源管理接口,自然也提供了重新启动计算机的功能。

在支持 ACPI 的机器固件提供的「固定 ACPI 描述表」(Fixed ACPI Description Table, FADT)中,保存有 reset_registerreset_value 两个数值,告知操作系统重启的方法。

rr = &acpi_gbl_FADT.reset_register;      /* 获取重启寄存器   */
reset_value = acpi_gbl_FADT.reset_value; /* 获取重启魔法数值 */

/* The reset register can only exist in I/O, Memory or PCI config space
 * on a device on bus 0. */

/* 在 PCI、内存和 IO 地址中寻找重启寄存器 */
switch (rr->space_id) {

/* 如果寄存器在 PCI,就写 PCI */
case ACPI_ADR_SPACE_PCI_CONFIG:
    /* The reset register can only live on bus 0. */
    bus0 = pci_find_bus(0, 0);
    if (!bus0)
        return;
    /* Form PCI device/function pair. */
    devfn = PCI_DEVFN((rr->address >> 32) & 0xffff,
              (rr->address >> 16) & 0xffff);
    printk(KERN_DEBUG "Resetting with ACPI PCI RESET_REG.");
    /* Write the value that resets us. */
    pci_bus_write_config_byte(bus0, devfn,
            (rr->address & 0xffff), reset_value);
    break;

/* 如果是内存或者 I/O 地址,直接用 writeb() / outb() 写内存或 I/O */
/* acpi_reset() 调用的是 acpi_os_write_port(), acpi_hw_write() */
/* 但只是多了一些检查,最后依然调用的是 writeb() / outb() */
case ACPI_ADR_SPACE_SYSTEM_MEMORY:
case ACPI_ADR_SPACE_SYSTEM_IO:
    printk(KERN_DEBUG "ACPI MEMORY or I/O RESET_REG.\n");
    acpi_reset();
    break;
}

写入数值后触发硬件重启。 在 Linux 上,可以使用内核参数 reboot=acpi,让 Linux 内核使用这种方式实现重新启动的功能。

方法六:通过 EFI/UEFI 重启

EFI/UEFI 是更现代计算机固件的接口标准,其中包括 UEFI 启动服务和 UEFI 运行服务,它们提供了一组标准的函数调用,提供一些和硬件相关的服务。好比系统调用为应用程序提供服务,UEFI 服务则为操作系统提供服务。在操作系统启动后,会运行 ExitBootServices 禁用 UEFI 启动服务,但是 UEFI 运行服务的代码会一直处于内存中,而且随时可以被系统调用执行。

efi_mode = EFI_RESET_WARM;
/* 或 efi_mode = EFI_RESET_COLD; */

/* reset_system 是 efi 结构体中的函数指针,其原型是:*/
/* void efi_reset_system_t (int reset_type, efi_status_t status, unsigned long data_size, efi_char16_t *data); */
/* 系统在通过 EFI 启动时,正确的内存地址会被初始化 */
efi.reset_system(efi_mode, EFI_SUCCESS, 0, NULL);

由于 EFI 的高度复杂性,可以直接阅读 EFI 相关文献。 在 Linux 上,可以使用内核参数 reboot=efi,让 Linux 内核使用这种方式实现重新启动的功能。

Linux 内核的逐一尝试

Linux 内核中,默认使用一个无限循环的 switch {} 语句逐一执行这些重启方法,如果成功了,那么机器就会重启,否则就会切换到下一种方法。如果硬件有已知问题,Linux 内核会查表,并根据机器型号选择合适的方式,并可能会进行一些 workaround。最后,有专门支持的特定计算机硬件会使用硬件自身的方法重启。

for (;;) {
    switch (reboot_type) {
    case BOOT_ACPI:
        // ...
        reboot_type = BOOT_KBD;
        break;

    case BOOT_KBD:
        /* 代码 */
        if (attempt == 0 && orig_reboot_type == BOOT_ACPI) {
            attempt = 1;
            reboot_type = BOOT_ACPI;
        } else {
            reboot_type = BOOT_EFI;
        }
        break;

    case BOOT_EFI:
        /* 代码 */
        reboot_type = BOOT_BIOS;
        break;

    case BOOT_BIOS:
        /* 代码 */
        reboot_type = BOOT_CF9_SAFE;
        break;

    case BOOT_CF9_FORCE:
        port_cf9_safe = true;
        /* Fall through */
    case BOOT_CF9_SAFE:
        /* 代码 */
        reboot_type = BOOT_TRIPLE;
        break;

    case BOOT_TRIPLE:
        /* 代码 */
        /* We're probably dead after this, but... */
        reboot_type = BOOT_KBD;
        break;
    }
}

为什么用键盘控制器能重启电脑?

随着个人计算机和半导体技术突飞猛进的发展,到了 1984 年,IBM 发表了具有划时代意义的 IBM PC/AT 计算机(即 IBM 5170)。PC/AT 搭载了强大的 80268 CPU,支持的内存也从 20 位的 1 MiB 变成了 24 位的 16 MiB。这一扩展地址可就惨了。在旧的 8086 上,尝试访问大于 1 MiB 的内存,会溢出回到内存的开头,后来有程序员也发现了这个现象,并利用它优化程序。但在 80286 上,由于大于 1 MiB 的内存确实存在,因此不会溢出也不会回到内存开头……本来 80286 默认使用的是实模式(而不是新的保护模式),就是为了完美兼容现有的 8086 程序的,但由于 Intel 并没有意识到这个问题,因此导致大量现有的程序停止工作。

IBM 的工程师为了解决这个兼容问题,他们在主板的内存地址总线的信号线上装了一个逻辑门,作为信号开关。当关闭时,就会一直产生信号 0,相当于关闭了超过 1 MiB 的内存,同时由于电路的特点,也能让这个溢出魔法重新恢复正常。但是,在哪里控制这个开关呢?PC AT 计算机使用 8042 芯片控制键盘,工程师看到芯片上正好有多余的端口,于是脑洞大开,用这个键盘芯片来控制 1 MiB 内存的开启。这就是著名的 A20。至今,让键盘控制器开启 A20 依然是进入保护模式不可或缺的一步。

同时,由于很多时候还需要在系统里兼容旧程序,有时还需要进入新的保护模式后,再切换回旧的实模式,但 80268 进行这个模式切换时,必须进行 CPU 重置。于是,IBM 的工程师把 8042 键盘控制器上另一个端口,连接到了CPU 的 Reset 信号线上……直到今天,x86 计算机上依然需要兼容 AT 机的 8042,因此,8042 键盘控制器能用来重启几乎一切 x86 电脑。

后来发现,键盘控制器的反应速度实在是太慢了,如果需要频繁 A20 切换会有性能问题,因为 IBM 当初的这个坑爹设计,日后系统开发者和 Intel 还花费了很大的代价解决。到了今天,x86 处理器里有一个专门模拟 A20 的 I/O 口……

如果说 8086 芯片是通往 x86 大坑的一把洛阳铲,那么这台 IBM 电脑则是造就了 x86 体系无数大坑的挖掘机。觉得 AT 很陌生?那在 AT 后面加个「X」呢?此外,你垃圾键盘右上角的 3 个刺眼的指示灯,Windows 用户不知道有什么用的 SysRq 按键,以及主板里一没电就掉时间日期,还时不时漏液损坏主板的纽扣电池,还有那经常出现诡异问题不能正常引导系统的 MBR,全都是 AT 的设计。更别说那个电源适配器负载一低就没有 +12V 输出,于是 IBM 在低端机器上安装了个 50 W 的功率电阻用来费电,幸好这一点上我们没有保持兼容……无论怎么说,这开启了一个时代,你现在的计算机很可能依然或多或少的兼容 AT。我们所说的「IBM 兼容机」指的就是 PC/AT 兼容。

GnuPG 2.1.18 不识别智能卡的解决方法

如果你通过 PC/SC 访问 OpenPGP Card,在升级到 GnuPG 2.1.18 后,你的智能卡将会停止工作。智能卡将无法被 gpg --card-status 正常识别,提示各类错误,例如:

gpg: selecting openpgp failed: No such device
gpg: OpenPGP card not available: No such device

在 GnuPG 中,本身就通过 ccid 支持了标准的 OpenPGP Card,一张标准的智能卡和兼容的读卡器可以直接和 GnuPG 一起工作,无需修改任何配置或安装任何程序。

但是,很多 OpenPGP Card 并非完全遵守 v2 标准,例如开发版本的 gnuk(遵守 v3 标准);或者 Yubikey(带有扩展特性),我们就需要通过 PC/SC 作为中间层访问智能卡。

这时,竟态条件就产生了。初次使用智能卡时,GnuPG 会和 PC-SC 抢占智能卡设备的控制权,过去 PC-SC 总是赢家,偶然,如果 GnuPG 赢了,智能卡就会无法正常工作。

因此,可以通过禁用 GnuPG 内建的智能卡支持,解决这个问题。

echo "disable-ccid" >> ~/.gnupg/scdaemon.conf

重启 pcscdgpg-agentscdaemon 后见效。如果你是 PC-SC 用户,建议总是禁用 ccid 避免冲突。

那么为何升级到 2.1.18 之后才会出现这个问题?日本 FSIJ 的 Niibe Yutaka,也就是 gnuk 的核心开发者主动跳出来表示,他把一个 bug 添加进了 scdaemon。以前,scdaemon 发现内建的 ccid 无法识别智能卡后,会顺便调用 PC/SC 再试试看,但重构代码时忘记保留这个特性,他已经编写了这个补丁修复了问题,而且已经被 Debian 采纳。

来源:https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=852702

最近的 OpenSSH 漏洞究竟有多大危害?

OpenSSH 刚刚更新到了 7.4 版,修复了已公开的 CVE-2016-10009, CVE-2016-10010, CVE-2016-10011, CVE-2016-10012 以及一个没有 CVE 的漏洞。其中,CVE-2016-10009 可以用来“任意执行代码”,因此,强烈建议大家立即更新所有的服务器,而且客户端也不能除外。但是,这些漏洞究竟有多大危害呢?是否可以利用这些漏洞控制服务器呢?我们一起来仔细看看:

CVE-2016-10009:sshd 服务器可以利用转发的 agent-socket 文件欺骗本机的 ssh-agent 加载一个恶意 PKCS#11 模块,任意执行代码。换句话说,是恶意服务器在客户端的机器上远程执行代码。

谁让你连恶意服务器的????

CVE-2016-10010:关闭 UsePrivilegeSeparation 权限隔离功能时,ssh 转发的 socket 文件将以 root 权限创建。

谁让你关闭权限隔离的????UsePrivilegeSeparation sandbox 是标准配置。

CVE-2016-10011:即使打开权限隔离,假如攻击者可以让 sshd 创建的子进程提权,那么服务器私钥理论上可能通过 realloc() 的过程泄漏给子进程。

泄漏私钥,听上去有点危险。前提是这攻击是理论上的,要使用,你得先有办法让子进程提权啊?????你有办法吗???再者,你们的 sshd 都应该开前向安全了吧……

CVE-2016-10012:sshd 服务器如果启用了认证前压缩(也就是 Compression yes),其中的某些边界检查可能会被编译优化去除,导致攻击者有可能从经过权限隔离的子进程中攻击高权限的父进程。

谁让你打开认证前压缩的?你当 CRIME 攻击是闹着玩的啊????CRIME 攻击之后 sshd 的默认配置都是 Compression delayed,也就是认证后压缩,如果你这都不知道那你的服务器就已经不安全了。再者,从子进程中怎么攻击父进程?目前还没有已知的方法。

无名漏洞AllowUsersDenyUsers 选项可以接受无效的 IP 段。假如管理员想拉黑用户,但却输入了无效的 IP 段,那么用户就不会被禁止登录,而管理员也不知道。

谁让你把 IP 段输错了?????再者,你拉黑恶意 IP 不使用 iptables 防火墙吗???


可见,这些漏洞确实不容忽视,强烈建议所有人立刻更新。然而,如果你想搞个大新闻说利用这些漏洞可以控制服务器,那还是图样图森破了。如果你平时在运维服务器的时候已经遵循了最佳安全实践,那么其实际危害是很低的。

新服务器密钥指纹

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

* RSA
    * SHA256 | DSsHk7JuE8iNPV9yVESiRptOPv4eepQKpWxaDoOk9m4
    * MD5 | 92:a7:2f:a8:07:76:3f:f7:03:f2:54:e9:06:ea:19:9e

* Ed25519
    * SHA256 | MTJ6TdQezosVmHLR258Fp967mSZlfK96V4oQprP96qw
    * MD5 | 6f:b2:9b:0d:05:51:12:25:23:02:ea:74:33:3d:28:d5
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCAAdFiEEJVIRsjlaWj4OSKDx+tPrBeiOjW0FAlhLgf8ACgkQ+tPrBeiO
jW2raw/9HKZ2c9tjs/u001P1TL7+sLriK12SzBkXR7jfTCRjXAR4Qxh9QnU5hZ8/
tnXH5qmKQxQXTrZqZtkbsNNQbwLBT5c0N7ZfYoJAh1occveAOaE9z6W7CT/8y1H5
1KiYXKhWs0ShbFs+VT9yasvgwGInTjERvXUk9JBa5DCjz1tFBDAzE51h5fQD8CA2
5USJHMbx4wDVAskZ5uSeXWV+xMY1U9JgDbZeSefFJLV+rzd1tt9rCWrgz7lQRDmT
ufQ8hMOX3Dkp6Zllyi1OVrxbep8zMSZgRPaIhcdnIG9A+9dBRQ5QJV6uBzMocXlP
FlSjqtlGqjCdcdqQg7ka9fMBhox/hE34lKeNdubT9TVv7jWy8EmUxUhIKd+72wUC
9sdt6g/yLeboPa88YwKH9LvmOlsJS69P4m40i4rJprZSaERyNTkGP1GoFl167v1k
nDIrwkmI6Os53H1rt7e3jYbgg/cJxOF7+734T+RbVsHO+t/GvgoD9OCP3avpzbF/
SNl4rGBlNbbqN7Trw4a8w+SRl3FLiH6HSt64xrNqhttb/wJHO0SmR/aaXqYVa3Og
VKCaXgIuOqaOw3aDuoSt4Qh//BDRInC/89QLEbHBxjvFxnGF+BVIg3Kij3lVIlXi
cJmkh+a4j94KfTvV3ZQkM61jTkeASAUc2UP10cZTPABinUT1fdk=
=0t8H
-----END PGP SIGNATURE-----

使用 cryptsetup 加密 swap

Swap 分区不利于系统安全。当 RAM 中的数据被写入到硬盘等永久存储设备时,就会面临更高的数据泄漏风险。如果内存中的密钥恰好被写入到了 Swap 中,就大大降低了攻击者的难度,只需要简单得进行数据恢复,无论是什么加密算法都不在话下。不过,随着现在的计算机内存容量越来越来,Swap 的使用已经没有那么广泛了。

不过,很多情况下 Swap 依然是非常有用的,这时候我们可以使用 cryptsetup 对 Swap 加密一下消除安全隐患,整个过程非常简单,也不需要记忆任何的密钥。作为副作用,这意味着系统无法在关机后解密 Swap,因此无法休眠了,此外,写入 Swap 时的 CPU 占用率也会有所上升,但如果你的 CPU 支持 AES 指令集,影响会十分有限。

思路非常简单,首先,我们使用 cryptsetup 使用一个随机密钥打开 Swap 分区或文件,然后,再将 cryptsetup 在 /dev/mapper/ 中创建的抽象设备作为 Swap 使用。由于密钥是随机的,因此每次抽象设备中的数据也都是完全随机的,我们在使用之前还需要重新 mkswap 创建 Swap。

我们假设已经有一个 Swap 文件位于 /swap,因此抽象设备将是 /dev/mapper/swap。如果你使用其他文件或分区自然没有问题,记得手工执行 cryptsetup 并观察一下 /dev/mapper 里的设备叫什么名字。如果你想自己创建 /swap,别忘了在创建后使用 chmod 600 /swap 保护原始文件。

cryptsetup open --type plain /swap swap --key-file /dev/urandom 
chmod 600 /dev/mapper/swap 
mkswap /dev/mapper/swap
swapon /dev/mapper/swap

创建此脚本,赋予可执行权限,并执行此脚本,Swap 立刻可以使用了。由于这是一个一次性 Swap,我们并不需要关闭它,但如果测试需要,可以这样:

swapoff /dev/mapper/swap
cryptsetup close /dev/mapper/swap

为了方便,我使用了一个 Systemd 的 Unit 文件来做到自动挂载。

[Unit]
Requires=swap.target

[Service]
ExecStart=/usr/local/sbin/encrypted-swap
Type=oneshot

[Install]
WantedBy=multi-user.target

其中 Requires=swap.target 代表此脚本在 Systemd 执行内建的 swap 服务后执行,避免可能的竞态条件;/usr/local/sbin/encrypted-swap 代表脚本的位置;Type=oneshot 代表此服务只运行一次,然后立刻退出。首先关闭现有的 Swap,把此文件命名为 encrypted-swap.service,并 systemd start encrypted-swap 看看是否正常,若正常便可 systemctl enable encrypted-swap 开机自启动了。如果你有现有的自动挂载 Swap 配置,如 /etc/fstab,你需要手工取消,避免冲突。

此外,cryptsetup 加密 plain 文件的默认算法是 aes-cbc-essiv:sha256,没有安全问题。你可以使用 cryptsetup --help 查看默认的 plain 类型加密算法。

Linode Xen 下 grsecurity >= 4.3 崩溃的解决

更新:官方已在 grsecurity-3.1-4.7.4-201609152234.patch 中修复问题,不再需要此 workaround。

自从 Linux 4.3 开始,在 Linode 上使用 PaX/grsecurity 时,内核会在被 pv-grub 执行后不久立即崩溃。由于崩溃是在启动后极早期立刻发生的,没有任何可以用来调试的日志,同时公司也不是盖子开的,也没有办法得到母机上有意义的调试信息。这导致了盖子的 VPS 内核从去年 12 月开始被锁定在 4.2.7。由于不知什么时候产生了 Linode 东京机房会在 2016 年 6 月从 Xen 迁移到 KVM 的错觉,也没有花精力去尝试调试这个问题。

Continue reading

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

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

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

Continue reading

网络维护日

日子快到了。

当年,HD DVD 密钥被泄漏了,随后就遭到的财阀的施压,导致这个能破解拷贝 DVD 的密钥到处被封杀。于是,美国网民创作了一面“言论自由之旗”,这面旗帜把遭到封杀的高清 DVD 密钥用旗帜上的颜色条表示了出来,从而绕过了审查。

而如今一到了中国互联网维护日,各种形式的自我审查就会接踵而至。很明显,中国的网民也需要自己的言论自由之旗。受到美国网民的启发,我设计了“中国版言论自由之旗”,同样象征言论自由。这是一面纯色的旗帜,旗帜颜色的 HTML 编号为 #890604。出乎意料很巧合的,这是一面暗红色的旗帜。在就连“蜡烛”图标都会遭到屏蔽的网络环境下,一种颜色就可以表达无声的抗议。

Chinese Free Speech Flag

本文章和这面旗帜均采用公有领域授权,尺寸比例是 2:3 或 3:5。请将本文转载到你的博客上;并将本旗帜和传播到社交网络的四面八方。没有任何文字说明,也不会遭到封杀,是蜡烛的绝佳替代品。同样,大家可以在任何艺术作品/设计中使用颜色 #890604。只需要注意一点:很多时候输出或上传的图片会被无损压缩,甚至是上传后的二次无损压缩,因此请多多测试,选择适当的格式传播。通常,gif 格式的图片不会被二次压缩破坏信息。SVG 矢量图的版本可以在维基共享里找到

« Older posts

Copyright © 2021 比尔盖子 博客

Theme by Anders NorenUp ↑