标签 内核 下的文章

Linux 内核开发很少吸引像华盛顿邮报这样主流媒体的关注,内核社区在安全方面进展的冗长功能列表就更少人看了。所以当这样一个专题发布到网上,就吸引了很多人的注意(LCTT 译注:华盛顿邮报发表了一篇很长的专题文章,批评 Linux “没有一个系统性的机制以在骇客之前发现和解决安全问题,或引入更新的防御技术”,“Linux 内核开发社区没有一个首席安全官”等等)。关于这篇文章有不同的反应,很多人认为这是对 Linux 直接的攻击。文章背后的动机很难知道,但是从历史经验来看,它也可以看作对我们早就该前进的方向的一次非常必要的推动。

回顾一件昏暗遥远过去的事件 - 确切地说是在 1999 年 4 月。一家叫 Mindcraft 的分析公司发布了一份报告显示 Windows NT 在 Web 服务器开销方面完胜 Red Hat Linux 5.2 加 Apache。Linux 社区,包括当时还很年轻的 LWN,对此反应很迅速而且强烈。这份报告是微软资助的 FUD 的一部分,用来消除那些全球垄断计划的新兴威胁。报告中所用的 Linux 系统有意配置成低性能,同时选择了当时 Linux 并不能很好支持的硬件,等等。

在大家稍微冷静一点后,尽管如此,事实很明显:Mindcraft 的人,不管什么动机,说的也有一定道理。当时 Linux 确实在性能方面存在一些已经被充分认识到的问题。然后社区做了最正确的事情:我们坐下来解决问题。比如,单独唤醒的调度器可以解决接受连接请求时的惊群问题。其他很多小问题也都解决了。在差不多一年里,内核在这类开销方面的性能已经有了非常大的改善。

这份 Mindcraft 的报告,某种意义上来说,往 Linux 屁股上踢了很有必要的一脚,推动整个社区去处理一些当时被忽略的事情。

华盛顿邮报的文章明显以负面的看法看待 Linux 内核以及它的贡献者。它随意地混淆了内核问题和其他根本不是内核脆弱性引起的问题(比如,AshleyMadison.com 被黑)。不过供应商没什么兴趣为他们的客户提供安全补丁的事实,就像一头在房间里巨象一样明显。还有谣言说这篇文章后面的黑暗势力希望打击一下 Linux 的势头。这些也许都是真的,但是也不能掩盖一个简单的事实,就是文章说的确实是真的。

我们会合理地测试并解决问题。而这些问题,不管是不是安全相关,能很快得到修复,然后再通过稳定更新的机制将这些补丁发布给内核用户。比起外面很多应用程序(自由的和商业的),内核的支持工作做的非常好。但是指责我们解决问题的能力时却遗漏了关键的一点:解决安全问题终究来说是一个打鼹鼠游戏。总是会出来更多的鼹鼠,其中有一些在攻击者发现并利用后很长时间我们都还不知道(所以没法使劲打下去)。尽管 Linux 的商业支持已经非常努力地在将补丁传递给用户,这种问题还是会让我们的用户很受伤 - 只是这并不是故意的。

关键是只是解决问题并不够,一些关心安全性的开发者也已经开始尝试做些什么。我们必须认识到,缺陷永远都解决不完,所以要让缺陷更难被发现和利用。这意思就是限制访问内核信息,绝对不允许内核执行用户空间内存中的指令,让内核去侦测整形溢出,以及 Kee Cook 在十月底内核峰会的讲话中所提出的其他所有事情。其中许多技术被其他操作系统深刻理解并采用了;另外一些需要我们去创新。但是,如果我们想充分保护我们的用户免受攻击,这些改变是必须要做的。

为什么内核还没有引入这些技术?华盛顿邮报的文章坚定地指责开发社区,特别是 Linus Torvalds。内核社区的传统就是相对安全性更侧重于性能和功能,在需要牺牲性能来改善内核安全性时并不愿意折衷处理。这些指责一定程度上是对的;好的一面是,因为问题的范围变得清晰,态度看上去有所改善。Kee 的演讲都听进去了,而且很明显让开发者开始思考和讨论这些问题了。

而被忽略的一点是,并不仅仅是 Linus 在拒绝有用的安全补丁。而是就没有多少这种补丁在内核社区里流传。特别是,在这个领域工作的开发者就那么些人,而且从没有认真地尝试把自己的工作整合到上游。要合并任何大的侵入性补丁,需要和内核社区一起工作,为这些改动编写用例,将改动分割成方便审核的碎片,处理审核意见,等等。整个过程可能会有点无聊而且让人沮丧,但这却是内核维护的运作方式,而且很明显只有这样才能在长时间的开发中形成更有用更可维护的内核。

几乎没有人会走这个流程来将最新的安全技术引入内核。对于这类补丁可能收到的不利反应,有人觉得也许会导致“寒蝉效应”,但是这个说法并不充分:不管最初的反应有多麻烦,多年以来开发者已经合并了大量的改动。而少数安全开发者连试都没试过。

他们为什么不愿意尝试?一个比较明显的答案是,几乎没有人会因此拿到报酬。几乎所有引入内核的工作都由付费开发者完成,而且已经持续多年。公司能看到利润的领域在内核里都有大量的工作以及很好的进展。而公司觉得和它们没关系的领域就不会这样了。为实时 Linux 的开发找到赞助支持的困难就是很明显的例子。其他领域,比如文档,也在慢慢萧条。安全性很明显也属于这类领域。可能有很多原因导致 Linux 落后于防御式安全技术,但是其中最关键的一条是,靠 Linux 赚钱的公司没有重视这些技术的开发和应用。

有迹象显示局面已有所转变。越来越多的开发人员开始关注安全相关问题,尽管对他们工作的商业支持还仍然不够。对于安全相关的改变已经没有之前那样的下意识反应了。像内核自我保护项目这样,已经开始把现有的安全技术集成进入内核了。

我们还有很长的路要走,但是,如果能有一些支持以及正确的观念,短期内就能有很大的进展。内核社区在确定了自己的想法后可以做到很让人惊叹的事情。幸运的是,华盛顿邮报的文章将有助于提供形成这种想法的必要动力。以历史的角度看,我们很可能会把这次事件看作一个转折点,我们最终被倒逼着去完成之前很明确需要做的事情。Linux 不应该再继续讲述这个安全不合格的故事了。


via: https://lwn.net/Articles/663474/

作者:Jonathan Corbet 译者:zpl1025 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

头条消息

今天的头条消息显然是开发了两月的 Linux 4.6 内核发布了正式的产品版,这个版本的开发非常顺利,以至于 Linus 曾考虑提前结束 RC 周期。4.6 内核中引入了 OrangeFS 和 USB 3.1 SSP 支持,此外还有各种你或许搞不明白的改进和新特性。Linus 说,他明天就开始 4.7 的开发——我觉得每个里程碑版本完成后都不歇一歇么?感觉看着都很累。

另外一件事是,Debian 前一段时间支持了 ZFS 文件系统,这个开发已经持续了一年了,终于见了分晓。虽然社区表示欢迎,但是也有一些人表示,ZFS 的许可协议不是和 Debian 的原则(Debian 自由软件指南)冲突么?这是怎么搞的?Debian 的人表示,“我们并没有将 ZFS 的二进制放到“main”仓库中,而是将源代码放到了“contrib”仓库了,需要的话你自己编译好了”——我们并没有许可证的冲突,用户自己编译就没有关系了,所以我们并没有违例——感觉许多自由软件纯化论者往往有些自欺欺人的倾向。

Geary 是一个非常时尚和易于使用的 GNOME 3 下的邮件客户端,但是去年其开发组织 Yorba 发布了退休公告后,该项目就没有动静了。不过,今天我们得到了一个令人振奋的好消息,Geary 复活归来。这次发布了新的 0.11.0 版本,修复了 0.10.0 的一些问题,也有一些新的特性改进,但是这一切都比不过 Geary 重生值得高兴。

版本更迭

  • KDE Frameworks 5.22.0 发布,这个版本没有什么重要的变化,主要是一些修复
  • 基于 Arch Linux 的发行版 LinHES R8.4 发布,这是一个类似 Mythbuntu 的发行版,主要用于多媒体体验,因此其中包含了 Kodi 16.1、MythTV 0.28 和 OpenPHT 等软件。

今天,美国时间 5 月 15 日, Linus Torvalds 宣布了 Linux 4.6 内核的正式发布。

经过了两个月的开发,历经了 7 个 RC 版本,Linux 4.6 内核终于发布了最终产品版本,带来了一些引人关注的新功能、更新的驱动程序以及一些安全改进。

“好在我不用中断 RC 周期,上周我们如预期的收到了几个修复,但是没有什么出乎意料的事情。所以, 4.6 就按照正常的计划发布了,这也意味着我明天就可以启动 4.7 的合并窗口啦。” Linus Torvalds 说。

Linux 4.6 内核的新功能

Linux 4.6 内核的最引人注目的新功能是 OrangeFS 分布式文件系统、支持 USB 3.1 SuperSpeed Plus (SSP) 协议、提供了高达 10Gbps 的传输速度、改进了 OOM 任务处理器 Out Of Memory task killer 的可靠性、并支持了 Intel 内存保护键。

此外,Linux 4.6 内核也带有 内核连接多路转接器 Kernel Connection Multiplexor 、一个用于加速应用层协议的新部件、802.1AE MAC 级加密支持(MACsec)、OCFS2 文件系统的在线 inode 检查器、支持 BARMAN V 协议、支持 pNFS SCSI 布局。

最后,Linux 4.6 内核也包括了对 cgroup 名字空间和 dma-buf 的支持、一个新打造的 ioctl 专门用于管理 CPU 和 GPU 之间的缓存关联性。当然,也更新了不少驱动程序、修复了许多问题。

Greg Kroah-Hartman 是 Linux 内核社区的二号人物(第一位显然是 Linus Torvalds),他在设备驱动方面做了许多工作,他也是当前 Linux 内核稳定分支的负责人。

在本周柏林举办的 CoreOS Fest 上,Kroah-Hartman 做了一场讲演介绍了 Linux 内核项目是如何的巨大。之后我找了个机会邀请他聊了聊内核和安全方面的话题。

我们的话题先从代码方面开始,Kroah-Hartman 说刚刚于两个月前发布 4.5 版已经包含了 2100 万行代码!

可能很多人觉得在那么小的设备上运行这么多的代码有点夸张,事实上,并不是所有的 2100 万行代码都运行在他们的设备上,只有需要的部分才会运行在里面。正如 Kroah-Hartman 说的,“你不会全部用到它们。在内核里面包含了各种硬件的驱动。我的笔记本上运行的内核代码大概有 160 万行,而你的电话可能运行了 250 万行代码。”

这里面,内核的核心部分是一定算在其中的,而它只占整个 Linux 内核代码的 5%,而剩下的还有 35% 是网络部分,40% 是设备驱动。

比代码规模更让人印象深刻的是它的参与人数规模,去年就有大约 4000 名开发人员参与了开发,至少 440 个公司向内核提交了贡献,这使得 Linux 成为了世界上最大的软件项目!Kroah-Hartman 说:“这是计算机有史以来最大的软件开发项目,无论是使用它的人数、开发它的人数,还是与之相关的公司数量,规模都很大。”

greg kernel

Greg Kroah-Hartman 在 CoreOS Fest, Berlin 的讲演

每天,平均有超过 10800 行的代码增加, 5300 行代码被删除,并且还有 1875 行代码被修改,也就是说每秒钟都有超过 8 行代码的变化!

这是非常大的数量,这意味着 Linux 内核不像其它的技术,它在不断的变化,变得越来越好。

Kroah-Hartman 说,“当我第一次参与这个项目时,我们每个小时可以完成 2.5 个变更。每个人都这么说,‘哦,天哪,我们不可能更快了,那不现实’。微软和苹果也说,‘你赢了’,他们一字一顿的说,‘我们比不上,你们干的比任何人都要快,我们是望尘莫及啊’。而我们的开发速度越来越快,我们每次都会更快一些。”

但是,如果你的公司的发展依靠着 Linux,那这个变化速度看起来就很可怕。Kroah-Hartman 解释了为什么会有这么多变更:“我们提交了很多变更,但是不是因为我们为变更而变更的,那可需要很多的工作。我们其实很懒,我们做这么多的变更的原因是因为我们必须做,是因为这个世界不断变化而需要我们做这么多的变更。那种‘你做了个东西,然后啥也不用管,将它丢在一边就好了’的模式已经不可行了,因为这个东西已经连通了世界,而世界每时每刻在变化。事物都在互相作用,所以你必须跟着进化。如果你的操作系统不能改变,那它就没用了,这毋庸置疑。如果你的设备不能跟着与之互动的世界变化,那它同样也没用,这也毋庸置疑。所以,你可以看看那些不能与时俱进的操作系统,根本没有人用它们。”

为了做到这些变更,Linux 内核社区需要做到两点。首先,我们要有个按时间进行的发布计划。其次,我们需要小步快跑。我们完成一个发布就要开始下一个发布的开发。下一个发布的第一个 RC 版本里面就要包含进去所有开发人员丢进去的各种东西,包括各种新的东西、新的功能,而且它们需要经过严格考验。当所有的东西都测试良好,我们才会放出第一个 RC 版本,之后的 RC 版本就是各种问题的修复。这样,我们就能在一个分支的 7-8 个 RC 版本之后赶走所有发现的问题。

当我们准备好发布一个新的内核发布版本时,它已经经过了详尽的测试。但是仍然有问题时,人们使用的稳定版怎么修复问题呢,他们可不想在产品环境中使用 RC 版本,那么他们怎么修复问题?在 15 年前,内核社区就找到了解决方案,而这就是 Kroah-Hartman 的任务,他会对稳定版本进行分支,比如说 4.2 版,它的问题修复版本会以 4.2.1 、4.2.2、4.2.3 等等发布。

“这个版本规则就是它必须是一个问题修复版,而且它必须是一个正确的版本号或者是新的设备 ID,它必须出现在 Linux 内核代码树上。在我将它放到稳定分支之前,它就必须出现在 Linux 内核代码树上。这可以确保人们运行我们的稳定内核时,如果跳到一个新的分支版本时,不会发生中断,没有什么不一样的变化。这就是规则,而且一直以来运作良好。”Kroah-Hartman 说。

而当下一个新的分支(4.3)出来时,Kroah-Hartman 就会从当前的分支(4.2)离开而去维护 4.3。这样内核社区就完美的保证了当新的版本发布时任何事情都很连贯。

“每个版本我每周都会做一次发布,每周会对稳定分支打 100 到 150 个补丁。这很多,许多东西都变化了,也修复了许多东西。而这就是我们做的——稳定的分支。当 4.3 发布时,最好的事情是我解脱了,我说,‘啊!4.2,我再也不要见到你了’,然后我就跑到新的分支了,因为我们的工作,这一切都可以继续发展下去,所有人都很满意。”

Linux 内核中自己实现了双向链表,可以在 include/linux/list.h 找到定义。我们将会首先从双向链表数据结构开始介绍内核里的数据结构。为什么?因为它在内核里使用的很广泛,你只需要在 free-electrons.com 检索一下就知道了。

首先让我们看一下在 include/linux/types.h 里的主结构体:

struct list_head {
    struct list_head *next, *prev;
};

你可能注意到这和你以前见过的双向链表的实现方法是不同的。举个例子来说,在 glib 库里是这样实现的:

struct GList {
  gpointer data;
  GList *next;
  GList *prev;
};

通常来说一个链表结构会包含一个指向某个项目的指针。但是 Linux 内核中的链表实现并没有这样做。所以问题来了:链表在哪里保存数据呢?。实际上,内核里实现的链表是侵入式链表(Intrusive list)。侵入式链表并不在节点内保存数据-它的节点仅仅包含指向前后节点的指针,以及指向链表节点数据部分的指针——数据就是这样附加在链表上的。这就使得这个数据结构是通用的,使用起来就不需要考虑节点数据的类型了。

比如:

struct nmi_desc {
    spinlock_t lock;
    struct list_head head;
};

让我们看几个例子来理解一下在内核里是如何使用 list_head 的。如上所述,在内核里有很多很多不同的地方都用到了链表。我们来看一个在杂项字符驱动里面的使用的例子。在 drivers/char/misc.c 的杂项字符驱动 API 被用来编写处理小型硬件或虚拟设备的小驱动。这些驱动共享相同的主设备号:

#define MISC_MAJOR              10

但是都有各自不同的次设备号。比如:

ls -l /dev |  grep 10
crw-------   1 root root     10, 235 Mar 21 12:01 autofs
drwxr-xr-x  10 root root         200 Mar 21 12:01 cpu
crw-------   1 root root     10,  62 Mar 21 12:01 cpu_dma_latency
crw-------   1 root root     10, 203 Mar 21 12:01 cuse
drwxr-xr-x   2 root root         100 Mar 21 12:01 dri
crw-rw-rw-   1 root root     10, 229 Mar 21 12:01 fuse
crw-------   1 root root     10, 228 Mar 21 12:01 hpet
crw-------   1 root root     10, 183 Mar 21 12:01 hwrng
crw-rw----+  1 root kvm      10, 232 Mar 21 12:01 kvm
crw-rw----   1 root disk     10, 237 Mar 21 12:01 loop-control
crw-------   1 root root     10, 227 Mar 21 12:01 mcelog
crw-------   1 root root     10,  59 Mar 21 12:01 memory_bandwidth
crw-------   1 root root     10,  61 Mar 21 12:01 network_latency
crw-------   1 root root     10,  60 Mar 21 12:01 network_throughput
crw-r-----   1 root kmem     10, 144 Mar 21 12:01 nvram
brw-rw----   1 root disk      1,  10 Mar 21 12:01 ram10
crw--w----   1 root tty       4,  10 Mar 21 12:01 tty10
crw-rw----   1 root dialout   4,  74 Mar 21 12:01 ttyS10
crw-------   1 root root     10,  63 Mar 21 12:01 vga_arbiter
crw-------   1 root root     10, 137 Mar 21 12:01 vhci

现在让我们看看它是如何使用链表的。首先看一下结构体 miscdevice

struct miscdevice
{
      int minor;
      const char *name;
      const struct file_operations *fops;
      struct list_head list;
      struct device *parent;
      struct device *this_device;
      const char *nodename;
      mode_t mode;
};

可以看到结构体miscdevice的第四个变量list 是所有注册过的设备的链表。在源代码文件的开始可以看到这个链表的定义:

static LIST_HEAD(misc_list);

它实际上是对用list_head 类型定义的变量的扩展。

#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

然后使用宏 LIST_HEAD_INIT 进行初始化,这会使用变量name 的地址来填充prevnext 结构体的两个变量。

#define LIST_HEAD_INIT(name) { &(name), &(name) }

现在来看看注册杂项设备的函数misc_register。它在一开始就用函数 INIT_LIST_HEAD 初始化了miscdevice->list

INIT_LIST_HEAD(&misc->list);

作用和宏LIST_HEAD_INIT一样。

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

接下来,在函数device_create 创建了设备后,我们就用下面的语句将设备添加到设备链表:

list_add(&misc->list, &misc_list);

内核文件list.h 提供了向链表添加新项的 API 接口。我们来看看它的实现:

static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}

实际上就是使用3个指定的参数来调用了内部函数__list_add

  • new - 新项。
  • head - 新项将会插在head的后面
  • head->next - 插入前,head 后面的项。

__list_add的实现非常简单:

static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}

这里,我们在prevnext 之间添加了一个新项。所以我们开始时用宏LIST_HEAD_INIT定义的misc 链表会包含指向miscdevice->list 的向前指针和向后指针。

这儿还有一个问题:如何得到列表的内容呢?这里有一个特殊的宏:

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

使用了三个参数:

  • ptr - 指向结构 list_head 的指针;
  • type - 结构体类型;
  • member - 在结构体内类型为list_head 的变量的名字;

比如说:

const struct miscdevice *p = list_entry(v, struct miscdevice, list)

然后我们就可以使用p->minor 或者 p->name来访问miscdevice。让我们来看看list_entry 的实现:

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

如我们所见,它仅仅使用相同的参数调用了宏container_of。初看这个宏挺奇怪的:

#define container_of(ptr, type, member) ({                      \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

首先你可以注意到花括号内包含两个表达式。编译器会执行花括号内的全部语句,然后返回最后的表达式的值。

举个例子来说:

#include <stdio.h>

int main() {
    int i = 0;
    printf("i = %d\n", ({++i; ++i;}));
    return 0;
}

最终会打印出2

下一点就是typeof,它也很简单。就如你从名字所理解的,它仅仅返回了给定变量的类型。当我第一次看到宏container_of的实现时,让我觉得最奇怪的就是表达式((type *)0)中的0。实际上这个指针巧妙的计算了从结构体特定变量的偏移,这里的0刚好就是位宽里的零偏移。让我们看一个简单的例子:

#include <stdio.h>

struct s {
        int field1;
        char field2;
     char field3;
};

int main() {
    printf("%p\n", &((struct s*)0)->field3);
    return 0;
}

结果显示0x5

下一个宏offsetof会计算从结构体起始地址到某个给定结构字段的偏移。它的实现和上面类似:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

现在我们来总结一下宏container_of。只需给定结构体中list_head类型 字段的地址、名字和结构体容器的类型,它就可以返回结构体的起始地址。在宏定义的第一行,声明了一个指向结构体成员变量ptr的指针__mptr,并且把ptr 的地址赋给它。现在ptr__mptr 指向了同一个地址。从技术上讲我们并不需要这一行,但是它可以方便地进行类型检查。第一行保证了特定的结构体(参数type)包含成员变量member。第二行代码会用宏offsetof计算成员变量相对于结构体起始地址的偏移,然后从结构体的地址减去这个偏移,最后就得到了结构体。

当然了list_addlist_entry不是<linux/list.h>提供的唯一功能。双向链表的实现还提供了如下API:

  • list\_add
  • list\_add\_tail
  • list\_del
  • list\_replace
  • list\_move
  • list\_is\_last
  • list\_empty
  • list\_cut\_position
  • list\_splice
  • list\_for\_each
  • list\_for\_each\_entry

等等很多其它API。


via: https://github.com/0xAX/linux-insides/blob/master/DataStructures/dlist.md

译者:Ezio 校对:Mr小眼儿

本文由 LCTT 原创编译,Linux中国 荣誉推出

头条消息

Linux 内核 4.6 将于 5/15 日发布。在上周日的例行发布中,Linus Torvalds 发布了 4.6 内核的最后一个 RC 版本:RC7。如果一切都如现在一样正常的话,那么一周后,我们就能迎来 4.6 内核的正式发布。既然 4.6 这么快要发布了,而且看起来开发一切顺利,显然质量乐观,所以各个发行版肯定会在下一个版本中积极跟进。比如,Canonical 就宣布 Ubuntu 16.10 将基于 Linux 内核 4.6。而 Manjaro Linux 也宣布/)它的 15.12 会支持 4.6 内核。当然,赶在 4.6 发布之前释出的那些 Linux 发行版不会等着 4.6 发布,比如 Porteus Linux 3.2 发布了第二个 RC 版本,还是支持 4.5.3。4MLinux 发布的 18.0 支持 4.4.8 LTS。

Debian 9 中将停止对 32 位架构的支持。

单板计算机版的 Chromium OS 开始发售,现在可以从该项目购买树莓派3,同时会带有预安装好的单板计算机版 Chromium OS 的 SD 卡。

版本更迭

  • Waha Linux 发布了 8.4 版本,不过似乎跳过了 8.3 版本。它是一个基于 Debian 的发行版,本次升级主要是解决了来自上游 Debian 的一些安全问题。这应该是一个来自阿拉伯世界的发行版,其发行公告都是阿拉伯语的。
  • 自称为“终极 Linux”的 ExTiX 发布了 16.2 版本,它吸收了不少来自 Debian 8.4 Jessie/Debian 9 和 Ubuntu 16.04 的内容,但是去除了 Unity,而安装了 KDE。