CVE-2022-47929:流量控制 noqueue 没有问题?
2023-01-31
在 6.1.6 之前的 Linux 内核中,流量控制子系统中的一个空指针解引用 bug 允许非特权用户通过用“tc qdisc”和“tc class”命令设置的精心设计的流量控制配置触发拒绝服务(系统崩溃)...
\n \n
许多命名空间都不存在争议的,例如 UTS 命名空间,其允许主机系统隐藏其主机名和时间。其他一些命名空间复杂但直接明了,例如,NET 和 NS (mount) 命名空间令人难以理解。最后,还有一个非常特殊、很不寻常的 USER 命名空间。USER 名称空间之所以特殊,是因为它允许通常没有特权的所有者在其中作为根运行。这样我们才能拥有非作为真正根运行的工具,例如 Docker,以及无根容器之类的东西。
由于其性质,允许非特权用户访问 USER 命名空间总是存在巨大的安全风险。在它的帮助下,非特权用户事实上可以运行通常需要根权限的代码。这种代码往往未经充分测试和存在 bug。今天我们将研究一个此类案例,即通过 USER 命名空间来利用一个内核缺陷,从而导致非特权的拒绝服务攻击。
\n2019 年,我们正在探索利用 Linux 流量控制的 队列规则 (qdisc),使用 Hierarchy Token Bucket (HTB) classful qdisc 策略为我们的服务之一调度数据包。Linux 流量控制是一个用户配置的系统,用于调度和过滤网络数据包。队列规则是调度数据包的策略。具体而言,我们想从一个接口过滤和调度某些数据包,并将其他数据包丢入 noqueue qdisc。
noqueue 是 qdisc 的一个特例,调度到其中的数据包应该被丢弃。实践中情况并非如此。Linux 对 noqueue 处理方式导致数据包被通过而不是被丢弃(大部分情况下)。 文档也很能说明问题。它还指出,“不可能将 noqueue 排队规则分配给物理设备或类”。那么,当我们把 noqueue 分配给一个类时会发生什么呢?
让我们写一些 shell 命令来显示这个问题的实际情况。
首先我们需要以根身份登录,这样我们将获得 CAP_NET_ADMIN 权限,从而能够配置流量控制。
然后我们将一个网络接口分配给一个变量。这些可以通过 ip a
找到。虚拟接口可以通过调用 ls /sys/devices/virtual/net
定位。这些将匹配 ip a
的输出。
我们的接口目前被分配给 pfifo_fast qdisc,所以我们用 HTB classful qdisc 来代替它,并把它分配给 1:
的句柄。我们可以把它看作是树中的根节点。“默认 1”配置的结果是,未分类的流量被路由直接通过这个 qdisc,后者会回退到 pfifo_fast 排队。(下文将进一步说明)
接下来我们给我们的根 qdisc 添加一个类 1:
,把它分配给根 1: 的第一个叶节点 1:1
,并给它一些合理的配置默认值。
最后,我们将 noqueue qdisc 添加到层级结构中的第一个叶节点: 1:1
。这实际意味着,在这里路由的流量将被调度到 noqueue。
1. $ sudo -i\n2. # dev=enp0s5\n3. # tc qdisc replace dev $dev root handle 1: htb default 1\n4. # tc class add dev $dev parent 1: classid 1:1 htb rate 10mbit\n5. # tc qdisc add dev $dev parent 1:1 handle 10: noqueue
\n 假设我们的设置顺利执行,我们将收到类似于这个内核错误的:
我们知道该根用户负责在接口设置 qdisc,如果根用户可以导致内核崩溃,那怎么办呢?我们不要将 HTB qdisc 应用到一个 HTB qdisc 的 noqueue qdisc 就是了。
\nBUG: kernel NULL pointer dereference, address: 0000000000000000\n#PF: supervisor instruction fetch in kernel mode\n...\nCall Trace:\n<TASK>\nhtb_enqueue+0x1c8/0x370\ndev_qdisc_enqueue+0x15/0x90\n__dev_queue_xmit+0x798/0xd00\n...\n</TASK>\n
\n 在这里,我们利用 HTB 的默认情况,其中分配一个类 id 1:2 以被限速(A),并隐含地没有将 qdisc 设置另一个类(例如 id 1:1) (B)。排队到 (A) 的数据包将被过滤到 HTB_DIRECT ,排队到 (B) 的数据包将被过滤到 pfifo_fast。
\n# dev=enp0s5\n# tc qdisc replace dev $dev root handle 1: htb default 1\n# tc class add dev $dev parent 1: classid 1:2 htb rate 10mbit // A\n// B is missing, so anything not filtered into 1:2 will be pfifio_fast
\n 因为我们不熟悉代码库的这一部分,我们通知了邮件列表并创建了一个工单。当时这个 bug 对我们来说似乎并不那么重要。
快进到 2022 年,我们正在推进 USER 命名空间创建强化。我们用一个新的 LSM 钩子扩展了 Linux LSM 框架: userns_create ,以利用 eBPF LSM 提供保护,并鼓励其他人也这样做。最近,在梳理我们的积压工单时,我们重新考虑了这个 bug。我们自问:"我们能不能利用 USER 命名空间来触发这个 bug ?” 简短的答案是肯定的!
\n这个漏洞可以用任何一个假设 struct Qdisc.enqueue 函数不为空的 classful qdisc 来执行(后面会详细介绍),但在本例中,我们只用 HTB 来演示。
我们用 “lo” 接口来证明这个 bug 可以用虚拟接口触发。这对容器来说很重要,因为它们在大多数时候都是被提供虚拟接口,而不是物理接口。正因为如此,我们可以使用一个容器以非特权用户的身份使主机崩溃,从而执行拒绝服务攻击。
\n$ unshare -rU –net\n$ dev=lo\n$ tc qdisc replace dev $dev root handle 1: htb default 1\n$ tc class add dev $dev parent 1: classid 1:1 htb rate 10mbit\n$ tc qdisc add dev $dev parent 1:1 handle 10: noqueue\n$ ping -I $dev -w 1 -c 1 1.1.1.1
\n \n 为了更好地理解这个问题,我们需要回顾一下最初的补丁系列,但特别是引入了这个 bug 的提交 。这一系列之前,在接口上实现 noqueue 依赖于一种 hack:如果设备有 tx_queue_len = 0,则将设备 qdisc 设为 noqueue。提交 d66d6c3152e8("net: sched: register noqueue qdisc")通过显式允许用 tc 命令添加 noqueue 来解决这个问题,无需绕过以上限制。
内核检查我们是否处于 noqueue 情况的方式,就是简单地检查 qdisc 是否有 NULL enqueue() 函数。记得前面说过,noqueue 在实践中不一定会丢弃数据包?在以上检查失败后,下面的逻辑处理 noqueue 的功能。为了不通过检查,作者不得不以欺骗方式将 noop_enqueue() 重新赋值为 NULL,方式是在 init 中使 enqueue = NULL,后者将在运行时在register_qdisc()后调用。
这就是 classful qdiscs发挥作用的地方了。对入队函数的检查不再为 NULL。在此调用路径中,它现在设置为 HTB(在我们的示例中),因此允许通过调用 htb_enqueue() 函数,将 struct skb 排入队列。进入那里后,HTB 会进行查找以提取分配给叶节点的 qdisc,并最终尝试将 struct skb 排入选定的 qdisc,最终到达这个函数:
include/net/sch_generic.h
我们可以看到,排队过程对物理/虚拟接口是相当无关的。权限和验证检查是在向接口添加队列时进行的,这就是为什么 classful qdics 假定队列不是 NULL。了解这一点使我们找到了一些可以考虑的解决方案。
\nstatic inline int qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch,\n\t\t\t\tstruct sk_buff **to_free)\n{\n\tqdisc_calculate_pkt_len(skb, sch);\n\treturn sch->enqueue(skb, sch, to_free); // sch->enqueue == NULL\n}
\n \n 我们有几个解决方案,介于从我们认为最好到最坏的。
遵循 tc-noqueue 文档,不允许将 noqueue 分配给一个 classful qdisc
不检查 NULL,而是检查 struct noqueue_qdisc_ops,并将 noqueue 重设为 noop_enqueue
对于每个 classful qdisc,检查是否有 NULL 和回退
我们最终选择了第一个选项: “disallow noqueue for qdisc classes(不允许对 qdisc 类的 noqueue) ”,而第三个选项在代码中造成了大量的混乱,且没有彻底解决问题。未来的 qdiscs 实现可能会忘记这个重要的检查以及维护者。然而,不采用第二个选项的原因更为有意思。
我们之所以没有采用这种方法,是因为我们需要首先回答这些问题:
为什么不允许将 noqueue 分配给 classful qdisc?
这背离了文档。文档确实有一些在实践中不被完全遵循的先例,但我们需要对其更新,以反映目前的状况。这样做很好,但是除了删除 NULL 解引用错误之外,并不能解决行为变化问题。
如果我们允许将 noqueue 分配给 qdisc,会发生什么行为变化?
这个问题更难回答,因为我们需要确定这种行为应该是什么。目前,当 noqueue 被作为根 qdisc 为一个接口应用时,其路径基本上是允许数据包被处理。声明了回退的类则是另一回事。它们可能每个都有自己的回退,我们如何知道什么是正确的回退?在 HTB中,有时回退是通过 HTB_DIRECT,有时是 pfifo_fast。其他类又如何呢?也许我们应该回到默认的 noqueue 行为,就像对根 qdiscos 那样?
我们觉得走这条路只会给排队增加混乱和额外的复杂性。我们还可以提出一个观点,即这样的更改可以被视为特性的添加,而不一定是 bug 的修复。可以说,对于目前防止漏洞而言,遵守当前文档似乎是更有吸引力的方法,其他事情可以日后再解决。
\n首先,也是最重要的,尽快应用这个补丁 。同时,考虑通过设置 setting sysctl -w
kernel.unprivileged_userns_clone
=0
,只允许根在 Debian 内核中创建 USER 命名空间, sysctl -w
user.max_user_namespaces
=[number]
用于进程层级,或者考虑回退到这两个补丁:[security_create_user_ns()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7cd4c5c2101cb092db00f61f69d24380cf7a0ee8)
和 SELinux 实现 (现在为 Linux 6.1.x),允许您用 eBPF 或者 SELinux 保护自己的系统。如果确定没有使用 USER命名空间和处于极端情况下,您可以考虑用 CONFIG_USERNS=n
关闭该功能。这只是利用命名空间进行攻击的众多例子之一,未来肯定会有更多严重程度不同的例子出现。
特别感谢 Ignat Korchagin 和 Jakub Sitnicki 的代码审查工作,并帮助在实践中演示这个 bug。
"],"published_at":[0,"2023-01-31T14:00:00.000+00:00"],"updated_at":[0,"2024-10-09T23:22:44.690Z"],"feature_image":[0,"https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4cx8klIwTkZ9PPyHj3jWAP/b3142a3b98fc65cd097e7d9d10a09d51/cve-2022-47929-traffic-control-noqueue-no-problem.png"],"tags":[1,[[0,{"id":[0,"383iv0UQ6Lp0GZwOAxGq2p"],"name":[0,"Linux"],"slug":[0,"linux"]}],[0,{"id":[0,"6Mp7ouACN2rT3YjL1xaXJx"],"name":[0,"安全"],"slug":[0,"security"]}],[0,{"id":[0,"2pFyOCtANFB5qS6nbtQbVp"],"name":[0,"漏洞"],"slug":[0,"vulnerabilities"]}],[0,{"id":[0,"mc0IiHhdcsCq82cDdpVdb"],"name":[0,"CVE"],"slug":[0,"cve"]}]]],"relatedTags":[0],"authors":[1,[[0,{"name":[0,"Frederick Lawler"],"slug":[0,"frederick"],"bio":[0,null],"profile_image":[0,"https://cf-assets.www.cloudflare.com/zkvhlag99gkb/69T4vzED8P3z82sjq20ktB/3602d21ed4cd710e4d34e5e99a61245d/frederick.jpg"],"location":[0,null],"website":[0,null],"twitter":[0,null],"facebook":[0,null]}]]],"meta_description":[0,"In the Linux kernel before 6.1.6, a NULL pointer dereference bug in the traffic control subsystem allows an unprivileged user to trigger a denial of service (system crash) via a crafted traffic control configuration that is set up with \"tc qdisc\" and \"tc class\" commands."],"primary_author":[0,{}],"localeList":[0,{"name":[0,"CVE-2022-47929: traffic control noqueue no problem? Config"],"enUS":[0,"English for Locale"],"zhCN":[0,"Translated for Locale"],"zhHansCN":[0,"No Page for Locale"],"zhTW":[0,"Translated for Locale"],"frFR":[0,"No Page for Locale"],"deDE":[0,"No Page for Locale"],"itIT":[0,"No Page for Locale"],"jaJP":[0,"No Page for Locale"],"koKR":[0,"No Page for Locale"],"ptBR":[0,"No Page for Locale"],"esLA":[0,"No Page for Locale"],"esES":[0,"No Page for Locale"],"enAU":[0,"No Page for Locale"],"enCA":[0,"No Page for Locale"],"enIN":[0,"No Page for Locale"],"enGB":[0,"No Page for Locale"],"idID":[0,"No Page for Locale"],"ruRU":[0,"No Page for Locale"],"svSE":[0,"No Page for Locale"],"viVN":[0,"No Page for Locale"],"plPL":[0,"No Page for Locale"],"arAR":[0,"No Page for Locale"],"nlNL":[0,"No Page for Locale"],"thTH":[0,"No Page for Locale"],"trTR":[0,"No Page for Locale"],"heIL":[0,"No Page for Locale"],"lvLV":[0,"No Page for Locale"],"etEE":[0,"No Page for Locale"],"ltLT":[0,"No Page for Locale"]}],"url":[0,"https://blog.cloudflare.com/cve-2022-47929-traffic-control-noqueue-no-problem"],"metadata":[0,{"title":[0,"CVE-2022-47929:流量控制 noqueue 没有问题?"],"description":[0,"In the Linux kernel before 6.1.6, a NULL pointer dereference bug in the traffic control subsystem allows an unprivileged user to trigger a denial of service (system crash) via a crafted traffic control configuration that is set up with \"tc qdisc\" and \"tc class\" commands."],"imgPreview":[0,"https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4KPEix3BW4iyvY788QiVpt/80502cb8e6a1c9cefae0f6f4a9f0f420/cve-2022-47929-traffic-control-noqueue-no-problem-yQgAFu.png"]}]}],"locale":[0,"zh-cn"],"translations":[0,{"posts.by":[0,"作者"],"footer.gdpr":[0,"GDPR"],"lang_blurb1":[0,"这篇博文也有 {lang1} 版本。"],"lang_blurb2":[0,"这篇博文也有 {lang1} 和{lang2}版本。"],"lang_blurb3":[0,"这篇博文也有 {lang1}、{lang2} 和{lang3}版本。"],"footer.press":[0,"新闻"],"header.title":[0,"Cloudflare 博客"],"search.clear":[0,"清除"],"search.filter":[0,"过滤"],"search.source":[0,"来源"],"footer.careers":[0,"招聘"],"footer.company":[0,"公司"],"footer.support":[0,"支持"],"footer.the_net":[0,"theNet"],"search.filters":[0,"过滤器"],"footer.our_team":[0,"我们的团队"],"footer.webinars":[0,"网络研讨会"],"page.more_posts":[0,"更多帖子"],"posts.time_read":[0,"{time} 分钟阅读时间"],"search.language":[0,"语言"],"footer.community":[0,"社区"],"footer.resources":[0,"资源"],"footer.solutions":[0,"解决方案"],"footer.trademark":[0,"商标"],"header.subscribe":[0,"订阅"],"footer.compliance":[0,"合规性"],"footer.free_plans":[0,"Free 计划"],"footer.impact_ESG":[0,"影响/ESG"],"posts.follow_on_X":[0,"在 X 上关注"],"footer.help_center":[0,"帮助中心"],"footer.network_map":[0,"网络地图"],"header.please_wait":[0,"请稍候"],"page.related_posts":[0,"相关帖子"],"search.result_stat":[0,"针对 {search_keyword} 的第 {search_range} 个搜索结果(共 {search_total} 个结果)"],"footer.case_studies":[0,"案例研究"],"footer.connect_2024":[0,"Connect 2024"],"footer.terms_of_use":[0,"服务条款"],"footer.white_papers":[0,"白皮书"],"footer.cloudflare_tv":[0,"Cloudflare TV"],"footer.community_hub":[0,"社区中心"],"footer.compare_plans":[0,"比较各项计划"],"footer.contact_sales":[0,"联系销售"],"header.contact_sales":[0,"联系销售团队"],"header.email_address":[0,"电子邮件地址"],"page.error.not_found":[0,"未找到页面"],"footer.developer_docs":[0,"开发人员文档"],"footer.privacy_policy":[0,"隐私政策"],"footer.request_a_demo":[0,"请求演示"],"page.continue_reading":[0,"继续阅读"],"footer.analysts_report":[0,"分析报告"],"footer.for_enterprises":[0,"企业级服务"],"footer.getting_started":[0,"开始使用"],"footer.learning_center":[0,"学习中心"],"footer.project_galileo":[0,"Project Galileo"],"pagination.newer_posts":[0,"较新的帖子"],"pagination.older_posts":[0,"较旧的帖子"],"posts.social_buttons.x":[0,"在 X 上讨论"],"search.icon_aria_label":[0,"搜索"],"search.source_location":[0,"来源/位置"],"footer.about_cloudflare":[0,"关于 Cloudflare"],"footer.athenian_project":[0,"Athenian Project"],"footer.become_a_partner":[0,"成为合作伙伴"],"footer.cloudflare_radar":[0,"Cloudflare Radar"],"footer.network_services":[0,"网络服务"],"footer.trust_and_safety":[0,"信任与安全"],"header.get_started_free":[0,"免费开始使用"],"page.search.placeholder":[0,"搜索 Cloudflare"],"footer.cloudflare_status":[0,"Cloudflare 状态"],"footer.cookie_preference":[0,"Cookie 首选项"],"header.valid_email_error":[0,"必须是有效的电子邮件地址。"],"search.result_stat_empty":[0,"显示第 {search_range} 个结果(共 {search_total} 个结果)"],"footer.connectivity_cloud":[0,"全球连通云"],"footer.developer_services":[0,"开发人员服务"],"footer.investor_relations":[0,"投资者关系"],"page.not_found.error_code":[0,"错误代码:404"],"search.autocomplete_title":[0,"请输入查询内容。按回车键发送"],"footer.logos_and_press_kit":[0,"标识与媒体资料包"],"footer.application_services":[0,"应用程序服务"],"footer.get_a_recommendation":[0,"获得推荐"],"posts.social_buttons.reddit":[0,"在 Reddit 上讨论"],"footer.sse_and_sase_services":[0,"SSE 和 SASE 服务"],"page.not_found.outdated_link":[0,"您可能使用了过期的链接,或者输入了错误的地址。"],"footer.report_security_issues":[0,"报告安全问题"],"page.error.error_message_page":[0,"抱歉,我们找不到您要打开的页面。"],"header.subscribe_notifications":[0,"订阅以接收新文章的通知:"],"footer.cloudflare_for_campaigns":[0,"Cloudflare for Campaigns"],"header.subscription_confimation":[0,"订阅已确认。感谢订阅!"],"posts.social_buttons.hackernews":[0,"在 Hacker News 上讨论"],"footer.diversity_equity_inclusion":[0,"多元、公平与包容"],"footer.critical_infrastructure_defense_project":[0,"关键基础设施防护项目"]}]}" ssr="" client="load" opts="{"name":"PostCard","value":true}" await-children="">2023-01-31
在 6.1.6 之前的 Linux 内核中,流量控制子系统中的一个空指针解引用 bug 允许非特权用户通过用“tc qdisc”和“tc class”命令设置的精心设计的流量控制配置触发拒绝服务(系统崩溃)...
2023-01-16
Linux 内核的虚拟以太网驱动程序中一个竞态条件造成偶尔的数据包内容损坏,进而导致我们的一个 DDoS 缓解系统出现不必要的丢包。本文介绍了我们排查这一复杂问题的思考过程和使用的技术...
2022-11-28
许多泄漏事件的发生是源于软件缺陷和安全漏洞。本文我们将学习 Linux 内核如何帮助保护加密密钥抵御这类潜在安全漏洞:内存访问违规...
2022-07-26
在这篇博客文章中,我准备谈一谈自己钻研 Linux 网络堆栈的过程,在这期间,我认真学习了 TCP 连接接收端的内存和窗口管理...
2022年6月29日 11:45
使用 LSM BPF,开发人员能够在无需配置或加载内核模块的情况下编写精细策略。LSM BPF 程序会在加载时进行验证,然后在调用路径中到达 LSM hook 时执行...
2021年3月18日 14:18
今天,我很高兴能谈一下我们的自主 DDoS (分布式拒绝服务(DDoS)) 防护系统。这个系统已部署到 Cloudflare 全球所有 200 多个数据中心,正在积极保护我们的所有客户,防御 3-7 层 (OSI 模型)上的 DDoS 攻击,无需人工干预。作为我们不计量 DDoS 防护承诺的一部分,我们不会因为客户受到 DDoS 攻击而增加对客户的收费。...
2019年5月18日 15:00
最近,在布拉格Linux网络会议Netdev 0x13上,我做了一个简短的演讲,题目是“Cloudflare上的Linux”。演讲最后主要是关于BPF(柏克莱封包过滤器)的。似乎,不管问题是什么——BPF都是答案。...
2018年4月12日 13:00
今天,我们将介绍Spectrum:一个可为任意基于TCP的协议提供DDoS保护、负载均衡和内容加速的Cloudflare新特性。...