比尔盖子 博客

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

by E. W. Dijkstra

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 的错觉,也没有花精力去尝试调试这个问题。

然而今年 Linode 周年庆时硬件全部翻倍,惟独东京机房除外。而根据官方最新的说法,新机房乐观估计要第四季度上线。解决内核问题就不得不提上了盖子的日程,首先是手工修复了不少 CVE 高危漏洞,随后又祭出 diff 折腾半天,内核始终会在启动后立刻死亡。而由于 grsecurity 并不提供 git 源,所以 git bisect 也是不可能的,唯一可用的工具只有 Linux 4.2.7 / 补丁文件,与 Linux 4.3.3 / 补丁文件。

在阅读代码差异时,一个很大的挑战是如何区分上游内核的修改与下游 PaX/grsecurity 补丁的修改。直接比较补丁文件会导致代码上下文丢失,让代码的意图不可理解。最后盖子打算编写一个名为 metadiff 的工具,自动比较并去除在上游中出现的代码段,以便仅仅对 PaX/grsecurity 的代码进行比较,就连名字都想好了就叫 metadiff,但一直没有动手。

直到上个月和 Shawn 聊天时,提到了自己装个 Xen 也不是不可行;于是周六终于动手在 VirutalBox 虚拟机里撞了个 Debian + Xen,又在 Xen 里启动了一个虚拟机,果然很快就得到了内核崩溃的 traceback。

rip: ffffffff8100b2b0 pmu_msr_read+0x10
flags: 00000282 i s nz
rsp: ffffffff81aeff30
rax: 8000000000000000   rcx: 0000000000000001   rdx: ffffffff81aeffcc
rbx: 00000000c0000080   rsi: ffffffff81aeffa0   rdi: 00000000c0000080
rbp: ffffffff81aeffa0    r8: 0000000000000001    r9: 00000000ffffffff
r10: ffffffff81cf9000   r11: 0000000000000000   r12: ffffffff81aeffcc
r13: ffffffff81aeffc4   r14: ffffffff81aeffc0   r15: 6f73b764afec1c9d
 cs: e033        ss: e02b        ds: 0000        es: 0000
 fs: 0000 @ 0000000000000000
 gs: 0000 @ 0000000000000000/0000000000000000
Code (instr addr ffffffff8100b2b0)
00 00 00 00 00 41 54 49 89 d4 55 48 89 f5 53 89 fb 48 83 ec 10 <65> 48 8b 04 25 28 00 00 00 48 89 


Stack:
 0000000000000001 0000000000000000 0000000000000000 ffffffff8100b2b0
 000000010000e030 0000000000010082 ffffffff81aeff70 000000000000e02b
 0000000000000000 0000000000000000 00000000c0000080 ffffffff81aeffcc
 ffffffff81aeffc8 ffffffff810041c8 ffffffff81aeffc8 ffffffff81aeffcc

Call Trace:
  [<ffffffff8100b2b0>] pmu_msr_read+0x10  <--
  [<ffffffff8100b2b0>] pmu_msr_read+0x10
  [<ffffffff810041c8>] xen_read_msr_safe+0x18
  [<ffffffff81be93eb>] xen_start_kernel+0x1b9

哦?可见内核在 xen_start_kernel 不久就崩溃了,这是 /* First C function to be called on Xen boot */,在如此早期就崩溃,什么错误日志到看不到也就不奇怪了。来看看 xen_read_msrpmu_msr_read 在 4.2 和 4.3 之间有什么改变:

--- ../../4.2.7/linux-4.2.7/arch/x86/xen/enlighten.c    2016-09-11 00:44:12.010022936 +0800
+++ arch/x86/xen/enlighten.c    2015-12-15 13:41:43.000000000 +0800

@@ -1030,6 +1034,9 @@ static u64 xen_read_msr_safe(unsigned in
 {
        u64 val;

+       if (pmu_msr_read(msr, &val, err))
+               return val;
+
        val = native_read_msr_safe(msr, err);
        switch (msr) {
        case MSR_IA32_APICBASE:
@@ -1074,9 +1081,11 @@ static int xen_write_msr_safe(unsigned i
                /* Fast syscall setup is all done in hypercalls, so
                   these are all ignored.  Stub them out here to stop
                   Xen console noise. */
+               break;

        default:
-               ret = native_write_msr_safe(msr, low, high);
+               if (!pmu_msr_write(msr, low, high, &ret))
+                       ret = native_write_msr_safe(msr, low, high);
        }

        return ret;

可见 pmu_msr_read 完全是个新东西,使用 git blame 继续追查。

xen/PMU: Initialization code for Xen PMU 65d0cf0be79feebeb19e7626fd3ed41ae73f642d
xen/PMU: Describe vendor-specific PMU registers e27b72df01109c689062caeba1defa013b759e0e
xen/PMU: Intercept PMU-related MSR and APIC accesses 6b08cd6328c58a2ae190c5ee03a2ffcab5ef828e
xen/PMU: PMU emulation code bf6dfb154d935725c9a2005033ca33017b9df439

发现 PMU 是 Xen 在 4.3 进入主线内核的新特性,于是解决方法就很简单了,把 bf6dfb6b08cd 都撤销就好,接下来的事情就让 PaX Team 和 spender 去追查吧。最后的补丁是:

    diff -uprN linux-4.7.3-hardened/arch/x86/xen/apic.c linux-4.7.3-hardened.good/arch/x86/xen/apic.c
    --- linux-4.7.3-hardened/arch/x86/xen/apic.c   2016-07-24 19:23:50.000000000 +0000
    +++ linux-4.7.3-hardened.good/arch/x86/xen/apic.c   2016-09-10 20:05:21.450647009 +0000
    @@ -7,7 +7,6 @@
     #include <xen/xen.h>
     #include <xen/interface/physdev.h>
     #include "xen-ops.h"
    -#include "pmu.h"
     #include "smp.h"

     static unsigned int xen_io_apic_read(unsigned apic, unsigned reg)
    @@ -73,10 +72,8 @@ static u32 xen_apic_read(u32 reg)

     static void xen_apic_write(u32 reg, u32 val)
     {
    -   if (reg == APIC_LVTPC) {
    -      (void)pmu_apic_update(reg);
    +   if (reg == APIC_LVTPC)
           return;
    -   }

        /* Warn to see if there's any stray references */
        WARN(1,"register: %x, value: %x\n", reg, val);
    diff -uprN linux-4.7.3-hardened/arch/x86/xen/enlighten.c linux-4.7.3-hardened.good/arch/x86/xen/enlighten.c
    --- linux-4.7.3-hardened/arch/x86/xen/enlighten.c   2016-09-10 19:59:29.237313676 +0000
    +++ linux-4.7.3-hardened.good/arch/x86/xen/enlighten.c   2016-09-10 20:06:49.683980342 +0000
    @@ -1031,9 +1031,6 @@ static u64 xen_read_msr_safe(unsigned in
     {
        u64 val;

    -   if (pmu_msr_read(msr, &val, err))
    -      return val;
    -
        val = native_read_msr_safe(msr, err);
        switch (msr) {
        case MSR_IA32_APICBASE:
    @@ -1081,13 +1078,17 @@ static int xen_write_msr_safe(unsigned i
           break;

        default:
    -      if (!pmu_msr_write(msr, low, high, &ret))
    -         ret = native_write_msr_safe(msr, low, high);
    +      ret = native_write_msr_safe(msr, low, high);
        }

        return ret;
     }

    +unsigned long long xen_read_pmc(int counter)
    +{
    +   return 0;
    +}
    +
     static u64 xen_read_msr(unsigned int msr)
     {
        /*
    diff -uprN linux-4.7.3-hardened/arch/x86/xen/pmu.c linux-4.7.3-hardened.good/arch/x86/xen/pmu.c
    --- linux-4.7.3-hardened/arch/x86/xen/pmu.c   2016-07-24 19:23:50.000000000 +0000
    +++ linux-4.7.3-hardened.good/arch/x86/xen/pmu.c   2016-09-10 20:05:21.450647009 +0000
    @@ -13,20 +13,11 @@
     /* x86_pmu.handle_irq definition */
     #include "../events/perf_event.h"

    -#define XENPMU_IRQ_PROCESSING    1
    -struct xenpmu {
    -   /* Shared page between hypervisor and domain */
    -   struct xen_pmu_data *xenpmu_data;

    -   uint8_t flags;
    -};
    -static DEFINE_PER_CPU(struct xenpmu, xenpmu_shared);
    -#define get_xenpmu_data()    (this_cpu_ptr(&xenpmu_shared)->xenpmu_data)
    -#define get_xenpmu_flags()   (this_cpu_ptr(&xenpmu_shared)->flags)
    -
    -/* Macro for computing address of a PMU MSR bank */
    -#define field_offset(ctxt, field) ((void *)((uintptr_t)ctxt + \
    -                   (uintptr_t)ctxt->field))
    +/* Shared page between hypervisor and domain */
    +static DEFINE_PER_CPU(struct xen_pmu_data *, xenpmu_shared);
    +#define get_xenpmu_data()    per_cpu(xenpmu_shared, smp_processor_id())
    +

     /* AMD PMU */
     #define F15H_NUM_COUNTERS   6
    @@ -60,8 +51,6 @@ static __read_mostly int amd_num_counter
     /* Alias registers (0x4c1) for full-width writes to PMCs */
     #define MSR_PMC_ALIAS_MASK          (~(MSR_IA32_PERFCTR0 ^ MSR_IA32_PMC0))

    -#define INTEL_PMC_TYPE_SHIFT        30
    -
     static __read_mostly int intel_num_arch_counters, intel_num_fixed_counters;


    @@ -178,232 +167,6 @@ static int is_intel_pmu_msr(u32 msr_inde
        }
     }

    -static bool xen_intel_pmu_emulate(unsigned int msr, u64 *val, int type,
    -              int index, bool is_read)
    -{
    -   uint64_t *reg = NULL;
    -   struct xen_pmu_intel_ctxt *ctxt;
    -   uint64_t *fix_counters;
    -   struct xen_pmu_cntr_pair *arch_cntr_pair;
    -   struct xen_pmu_data *xenpmu_data = get_xenpmu_data();
    -   uint8_t xenpmu_flags = get_xenpmu_flags();
    -
    -
    -   if (!xenpmu_data || !(xenpmu_flags & XENPMU_IRQ_PROCESSING))
    -      return false;
    -
    -   ctxt = &xenpmu_data->pmu.c.intel;
    -
    -   switch (msr) {
    -   case MSR_CORE_PERF_GLOBAL_OVF_CTRL:
    -      reg = &ctxt->global_ovf_ctrl;
    -      break;
    -   case MSR_CORE_PERF_GLOBAL_STATUS:
    -      reg = &ctxt->global_status;
    -      break;
    -   case MSR_CORE_PERF_GLOBAL_CTRL:
    -      reg = &ctxt->global_ctrl;
    -      break;
    -   case MSR_CORE_PERF_FIXED_CTR_CTRL:
    -      reg = &ctxt->fixed_ctrl;
    -      break;
    -   default:
    -      switch (type) {
    -      case MSR_TYPE_COUNTER:
    -         fix_counters = field_offset(ctxt, fixed_counters);
    -         reg = &fix_counters[index];
    -         break;
    -      case MSR_TYPE_ARCH_COUNTER:
    -         arch_cntr_pair = field_offset(ctxt, arch_counters);
    -         reg = &arch_cntr_pair[index].counter;
    -         break;
    -      case MSR_TYPE_ARCH_CTRL:
    -         arch_cntr_pair = field_offset(ctxt, arch_counters);
    -         reg = &arch_cntr_pair[index].control;
    -         break;
    -      default:
    -         return false;
    -      }
    -   }
    -
    -   if (reg) {
    -      if (is_read)
    -         *val = *reg;
    -      else {
    -         *reg = *val;
    -
    -         if (msr == MSR_CORE_PERF_GLOBAL_OVF_CTRL)
    -            ctxt->global_status &= (~(*val));
    -      }
    -      return true;
    -   }
    -
    -   return false;
    -}
    -
    -static bool xen_amd_pmu_emulate(unsigned int msr, u64 *val, bool is_read)
    -{
    -   uint64_t *reg = NULL;
    -   int i, off = 0;
    -   struct xen_pmu_amd_ctxt *ctxt;
    -   uint64_t *counter_regs, *ctrl_regs;
    -   struct xen_pmu_data *xenpmu_data = get_xenpmu_data();
    -   uint8_t xenpmu_flags = get_xenpmu_flags();
    -
    -   if (!xenpmu_data || !(xenpmu_flags & XENPMU_IRQ_PROCESSING))
    -      return false;
    -
    -   if (k7_counters_mirrored &&
    -       ((msr >= MSR_K7_EVNTSEL0) && (msr <= MSR_K7_PERFCTR3)))
    -      msr = get_fam15h_addr(msr);
    -
    -   ctxt = &xenpmu_data->pmu.c.amd;
    -   for (i = 0; i < amd_num_counters; i++) {
    -      if (msr == amd_ctrls_base + off) {
    -         ctrl_regs = field_offset(ctxt, ctrls);
    -         reg = &ctrl_regs[i];
    -         break;
    -      } else if (msr == amd_counters_base + off) {
    -         counter_regs = field_offset(ctxt, counters);
    -         reg = &counter_regs[i];
    -         break;
    -      }
    -      off += amd_msr_step;
    -   }
    -
    -   if (reg) {
    -      if (is_read)
    -         *val = *reg;
    -      else
    -         *reg = *val;
    -
    -      return true;
    -   }
    -   return false;
    -}
    -
    -bool pmu_msr_read(unsigned int msr, uint64_t *val, int *err)
    -{
    -   if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) {
    -      if (is_amd_pmu_msr(msr)) {
    -         if (!xen_amd_pmu_emulate(msr, val, 1))
    -            *val = native_read_msr_safe(msr, err);
    -         return true;
    -      }
    -   } else {
    -      int type, index;
    -
    -      if (is_intel_pmu_msr(msr, &type, &index)) {
    -         if (!xen_intel_pmu_emulate(msr, val, type, index, 1))
    -            *val = native_read_msr_safe(msr, err);
    -         return true;
    -      }
    -   }
    -
    -   return false;
    -}
    -
    -bool pmu_msr_write(unsigned int msr, uint32_t low, uint32_t high, int *err)
    -{
    -   uint64_t val = ((uint64_t)high << 32) | low;
    -
    -   if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) {
    -      if (is_amd_pmu_msr(msr)) {
    -         if (!xen_amd_pmu_emulate(msr, &val, 0))
    -            *err = native_write_msr_safe(msr, low, high);
    -         return true;
    -      }
    -   } else {
    -      int type, index;
    -
    -      if (is_intel_pmu_msr(msr, &type, &index)) {
    -         if (!xen_intel_pmu_emulate(msr, &val, type, index, 0))
    -            *err = native_write_msr_safe(msr, low, high);
    -         return true;
    -      }
    -   }
    -
    -   return false;
    -}
    -
    -static unsigned long long xen_amd_read_pmc(int counter)
    -{
    -   struct xen_pmu_amd_ctxt *ctxt;
    -   uint64_t *counter_regs;
    -   struct xen_pmu_data *xenpmu_data = get_xenpmu_data();
    -   uint8_t xenpmu_flags = get_xenpmu_flags();
    -
    -   if (!xenpmu_data || !(xenpmu_flags & XENPMU_IRQ_PROCESSING)) {
    -      uint32_t msr;
    -      int err;
    -
    -      msr = amd_counters_base + (counter * amd_msr_step);
    -      return native_read_msr_safe(msr, &err);
    -   }
    -
    -   ctxt = &xenpmu_data->pmu.c.amd;
    -   counter_regs = field_offset(ctxt, counters);
    -   return counter_regs[counter];
    -}
    -
    -static unsigned long long xen_intel_read_pmc(int counter)
    -{
    -   struct xen_pmu_intel_ctxt *ctxt;
    -   uint64_t *fixed_counters;
    -   struct xen_pmu_cntr_pair *arch_cntr_pair;
    -   struct xen_pmu_data *xenpmu_data = get_xenpmu_data();
    -   uint8_t xenpmu_flags = get_xenpmu_flags();
    -
    -   if (!xenpmu_data || !(xenpmu_flags & XENPMU_IRQ_PROCESSING)) {
    -      uint32_t msr;
    -      int err;
    -
    -      if (counter & (1 << INTEL_PMC_TYPE_SHIFT))
    -         msr = MSR_CORE_PERF_FIXED_CTR0 + (counter & 0xffff);
    -      else
    -         msr = MSR_IA32_PERFCTR0 + counter;
    -
    -      return native_read_msr_safe(msr, &err);
    -   }
    -
    -   ctxt = &xenpmu_data->pmu.c.intel;
    -   if (counter & (1 << INTEL_PMC_TYPE_SHIFT)) {
    -      fixed_counters = field_offset(ctxt, fixed_counters);
    -      return fixed_counters[counter & 0xffff];
    -   }
    -
    -   arch_cntr_pair = field_offset(ctxt, arch_counters);
    -   return arch_cntr_pair[counter].counter;
    -}
    -
    -unsigned long long xen_read_pmc(int counter)
    -{
    -   if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD)
    -      return xen_amd_read_pmc(counter);
    -   else
    -      return xen_intel_read_pmc(counter);
    -}
    -
    -int pmu_apic_update(uint32_t val)
    -{
    -   int ret;
    -   struct xen_pmu_data *xenpmu_data = get_xenpmu_data();
    -
    -   if (!xenpmu_data) {
    -      pr_warn_once("%s: pmudata not initialized\n", __func__);
    -      return -EINVAL;
    -   }
    -
    -   xenpmu_data->pmu.l.lapic_lvtpc = val;
    -
    -   if (get_xenpmu_flags() & XENPMU_IRQ_PROCESSING)
    -      return 0;
    -
    -   ret = HYPERVISOR_xenpmu_op(XENPMU_lvtpc_set, NULL);
    -
    -   return ret;
    -}
    -
     /* perf callbacks */
     static int xen_is_in_guest(void)
     {
    @@ -476,37 +239,26 @@ static void xen_convert_regs(const struc

     irqreturn_t xen_pmu_irq_handler(int irq, void *dev_id)
     {
    -   int err, ret = IRQ_NONE;
    +   int ret = IRQ_NONE;
        struct pt_regs regs;
        const struct xen_pmu_data *xenpmu_data = get_xenpmu_data();
    -   uint8_t xenpmu_flags = get_xenpmu_flags();

        if (!xenpmu_data) {
           pr_warn_once("%s: pmudata not initialized\n", __func__);
           return ret;
        }

    -   this_cpu_ptr(&xenpmu_shared)->flags =
    -      xenpmu_flags | XENPMU_IRQ_PROCESSING;
        xen_convert_regs(&xenpmu_data->pmu.r.regs, &regs,
               xenpmu_data->pmu.pmu_flags);
        if (x86_pmu.handle_irq(&regs))
           ret = IRQ_HANDLED;

    -   /* Write out cached context to HW */
    -   err = HYPERVISOR_xenpmu_op(XENPMU_flush, NULL);
    -   this_cpu_ptr(&xenpmu_shared)->flags = xenpmu_flags;
    -   if (err) {
    -      pr_warn_once("%s: failed hypercall, err: %d\n", __func__, err);
    -      return IRQ_NONE;
    -   }
    -
        return ret;
     }

     bool is_xen_pmu(int cpu)
     {
    -   return (get_xenpmu_data() != NULL);
    +   return (per_cpu(xenpmu_shared, cpu) != NULL);
     }

     void xen_pmu_init(int cpu)
    @@ -536,8 +288,7 @@ void xen_pmu_init(int cpu)
        if (err)
           goto fail;

    -   per_cpu(xenpmu_shared, cpu).xenpmu_data = xenpmu_data;
    -   per_cpu(xenpmu_shared, cpu).flags = 0;
    +   per_cpu(xenpmu_shared, cpu) = xenpmu_data;

        if (cpu == 0) {
           perf_register_guest_info_callbacks(&xen_guest_cbs);
    @@ -565,6 +316,6 @@ void xen_pmu_finish(int cpu)

        (void)HYPERVISOR_xenpmu_op(XENPMU_finish, &xp);

    -   free_pages((unsigned long)per_cpu(xenpmu_shared, cpu).xenpmu_data, 0);
    -   per_cpu(xenpmu_shared, cpu).xenpmu_data = NULL;
    +   free_pages((unsigned long)per_cpu(xenpmu_shared, cpu), 0);
    +   per_cpu(xenpmu_shared, cpu) = NULL;
     }
    diff -uprN linux-4.7.3-hardened/arch/x86/xen/pmu.h linux-4.7.3-hardened.good/arch/x86/xen/pmu.h
    --- linux-4.7.3-hardened/arch/x86/xen/pmu.h   2016-07-24 19:23:50.000000000 +0000
    +++ linux-4.7.3-hardened.good/arch/x86/xen/pmu.h   2016-09-10 20:05:21.453980342 +0000
    @@ -7,9 +7,5 @@ irqreturn_t xen_pmu_irq_handler(int irq,
     void xen_pmu_init(int cpu);
     void xen_pmu_finish(int cpu);
     bool is_xen_pmu(int cpu);
    -bool pmu_msr_read(unsigned int msr, uint64_t *val, int *err);
    -bool pmu_msr_write(unsigned int msr, uint32_t low, uint32_t high, int *err);
    -int pmu_apic_update(uint32_t reg);
    -unsigned long long xen_read_pmc(int counter);

     #endif /* __XEN_PMU_H */

打好补丁再编译内核,被智子锁定版本的内核果然升级成功了。

$ uname -r
4.7.3-hardened

自由软件基金会的历史包袱 —— 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 的外壳浏览器是自由软件界的御三家,从特性上来说各有独自的优势 —— 如果要从这一点上进行谈论恐怕会触发圣战,这也并不是比尔盖子撰写本文的目的。本文企图从系统安全的角度来说明浏览器的选择。

Continue reading

服务器维护初步结束

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

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

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

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

Continue reading

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

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

Continue reading

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

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

宣布「盖子 CA」

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

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

服务器 SSH 新增备用端口

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

« Older posts

Copyright © 2016 比尔盖子 博客

Theme by Anders NorenUp ↑