【futuretask 源码分析】【tomcat http源码】【源码如何交付】softirq 源码
1.浅析linux 内核 高精度定时器(hrtimer)实现机制(二)
2.Linux 中断( IRQ / softirq )基础:原理及内核实现
3.Nftables CVE-2023-0179提权利用
4.网卡数据包的接收(netif_rx)
浅析linux 内核 高精度定时器(hrtimer)实现机制(二)
分析linux内核高精度定时器(hrtimer)的实现机制时,首先介绍的是定时器的迁移过程switch_hrtimer_base。该函数会尝试选择一个新的hrtimer_cpu_base结构体,用于定时器的激活。get_target_base函数被用于挑选新的迁移位置,这个函数的futuretask 源码分析代码与分析低分辨率定时器层时的定时器迁移概念相似。timers_migration_enabled变量在切换到NO_HZ模式时变为True,退出NO_HZ模式时变为False,用于判断是否可以进行迁移。只有在切换到NO_HZ模式且定时器未绑定到特定CPU的情况下,才会进行迁移选择。get_nohz_timer_target函数会判断当前CPU是否处于空闲状态,如果不是,则返回当前CPU编号,如果是空闲,则会找到最近一个忙碌的处理器并返回其编号。所有条件不满足时,会直接返回传入的hrtimer_cpu_base结构体指针。
接下来分析hrtimer_callback_running函数,用于检查要迁移的定时器是否正是当前正在处理的定时器。hrtimer_check_target函数则用于检查定时器的到期时间是否早于要迁移到的CPU上即将到期的时间。如果高分辨率定时器的到期时间比目标CPU上的所有定时器到期时间还要早,并且目标CPU不是当前CPU,那么激活目标CPU会涉及到通知该CPU重新编程定时器,这通常不如直接在当前CPU上激活定时器来得简单。因此,如果迁移操作与实际激活操作没有关系,即使从get_target_base函数获得的base与定时器中指定的base相同,迁移操作也会进行。
在迁移过程中,内核会临时将定时器的hrtimer_clock_base结构体变量设置为全局变量migration_base的指针。这个全局变量仅用于在获得定时器所属CPU的hrtimer_cpu_base结构体变量时,通过判断base变量是否等于migration_base的指针来判断定时器是否正在迁移。这样的设计可以在未正式加锁之前过滤掉很多情况,从而提高速度。
文章福利提供Linux内核技术交流群,tomcat http源码包含学习书籍、视频资料,前名可额外获得价值的内核资料包(含视频教程、电子书、实战项目及代码)。
内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂
在低精度模式下,高分辨率定时器层通过普通(低分辨率)定时器层驱动。当Tick到来时,其处理函数会调用hrtimer_run_queues函数通知高分辨率定时器层。每次调用该函数时,都会判断是否可以切换到高精度模式。如果可以切换,会调用hrtimer_switch_to_hres完成切换并退出。如果不需要切换,则从时间维护层获得当前时间和各种偏移值,并设置到所有的hrtimer_clock_base结构体中。如果当前时间不早于softirq_expires_next变量的值,表示“软”定时器已到期,需要激活软中断处理程序。在软中断处理程序中,首先调用hrtimer_update_base函数更新当前时间,并在适当时候执行,处理到期的“软”定时器。该处理程序会遍历所有指定类型(“软”或“硬”)的到期定时器,判断定时器的“软”到期时间是否已到,处理到期定时器,并循环取下一个要到期的定时器。最后,会调用hrtimer_reprogram函数对底层定时事件设备进行重编程。
在高精度模式下,周期处理函数hrtimer_interrupt在定时事件设备到期后调用。处理过程包括激活HRTIMER_SOFTIRQ软中断处理程序,处理所有“软”定时器,并对底层定时事件设备进行重编程。重编程确保设备在到期后能正确触发中断,源码如何交付同时避免在一次中断中处理过多定时器,以防止超时。通过查找和设置到期时间时使用“硬”到期时间,而在处理定时器时使用“软”到期时间,内核能尽量减少中断调用,提高性能。
低精度模式切换到高精度模式的hrtimer_switch_to_hres函数通过调用tick_init_highres函数实现切换,将定时事件设备切换到单次触发模式,并设置中断处理函数为hrtimer_interrupt。一旦完成切换,底层定时事件设备将始终工作在单次触发模式。切换成功后,必须找到最近到期的定时器,并用其到期事件对定时事件设备进行重编程,确保设备能正确响应到期。
在高精度模式下,中断处理程序通过直接调用__hrtimer_run_queues函数处理所有“硬”定时器,并激活HRTIMER_SOFTIRQ软中断处理程序来处理所有“软”定时器。在高精度模式下,底层定时事件设备始终处于单次触发模式,因此在到期后必须进行重编程。如果编程失败,重试三次后,适当延迟到期事件后再次尝试编程,并强制执行。
使用实例展示了高精度定时器在实际应用中的精度,时间戳显示其定时精度可达到ms级别。
Linux 中断( IRQ / softirq )基础:原理及内核实现
中断(IRQ),尤其是软中断(softirq)的广泛用途之一是网络数据包的接收与发送,但其应用场景并非单一。本文将全面整理中断(IRQ)与软中断(softirq)的基础知识,这些内容与网络数据包处理虽无直接联系,但整理本文旨在更深入地理解网络数据包处理机制。
什么是中断?
CPU 通过时分复用处理多任务,其中包括硬件任务,手动编译源码如磁盘读写、键盘输入,以及软件任务,如网络数据包处理。CPU 在任何时刻只能执行一个任务。当某个硬件或软件任务当前未被执行,但希望CPU立即处理时,会向CPU发送中断请求——希望CPU暂停手头工作,优先服务“我”。中断以事件形式通知CPU,因此常看到“在XX条件下会触发XX中断事件”的表述。
中断分为两类:
管理中断的设备:Advanced Programmable Interrupt Controller(APIC)。
硬中断的中断处理流程
中断随时发生,处理流程如下:
Maskable and non-maskable
Maskable interrupts 在x_上可以通过sti/cli指令来屏蔽(关闭)和恢复:
在屏蔽期间,这种类型的中断不会触发新的中断事件。大部分IRQ都属于这种类型。例如,网卡的收发包硬件中断。
Non-maskable interrupts 不可屏蔽,因此属于更高优先级的类型。
问题:执行速度与逻辑复杂性之间的矛盾
IRQ处理器的两个特点如下:
存在内在矛盾。
解决方式:中断的推迟处理(deferred interrupt handling)
传统解决方式是将中断处理分为两部分:
这种方式称为中断的推迟处理或延后处理。现在已是一个通用术语,涵盖各种推迟执行中断处理的方式。中断分为两部分处理:
在Linux中,有三种推迟中断(deferred interrupts):
具体细节将在后续介绍。
软中断与软中断子系统
软中断是内核子系统的一部分:
每个CPU上会初始化一个ksoftirqd内核线程,负责处理各种类型的softirq中断事件;
使用cgroup ls或ps -ef都能看到:
软中断事件的handler提前注册到softirq子系统,注册方式为open_softirq(softirq_id, handler)
例如,注册网卡收发包(RX/TX)软中断处理函数:
软中断占用了CPU的总开销:可以使用top查看,第三行倒数第二个指标是系统的软中断开销(si字段):
Linux内核源码分析学习地址:ke.qq.com/course/...
文章福利小编推荐自己的Linux内核源码分析交流群:点击加入整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!
主处理
smpboot.c类似于事件驱动的循环,会调度ksoftirqd线程执行pending的文章连载源码软中断。ksoftirqd内部会进一步调用到__do_softirq,
避免软中断占用过多CPU
软中断的潜在影响:推迟执行的部分(如softirq)可能会占用较长时间,在这段时间内,用户空间线程只能等待。反映在top中,si占比。
不过softirq调度循环对此有所改进,通过budget机制来避免softirq占用过多CPU时间。
硬中断-软中断调用栈
softirq是一种推迟中断处理机制,将IRQ的大部分处理逻辑推迟在这里执行。有两条路径都会执行到softirq主处理逻辑__do_softirq():
1、CPU调度到ksoftirqd线程时,会执行到__do_softirq();
2、每次IRQ handler退出时:do_IRQ()->...
do_IRQ是内核中主要的IRQ处理方式。它执行结束时,会调用exiting_irq(),这会展开成irq_exit()。后者会检查是否pending有softirq,如果有,则唤醒:
进而会使CPU执行到__do_softirq。
软中断触发执行的步骤
总结,每个软中断会经过以下阶段:
以收包软中断为例,IRQ handler并不执行NAPI,只是触发它,在内部会执行到raiseNET_RX_SOFTIRQ;真正的执行在softirq,会调用网卡的poll()方法收包。IRQ handler中会调用napi_schedule(),然后启动NAPI poll()。
需要注意的是,虽然IRQ handler所做的工作很少,但处理这个包的softirq和IRQ在同一CPU上运行。这意味着,如果大量的包都放在同一个RX队列,虽然IRQ开销可能不多,但该CPU仍然会非常繁忙,都花费在softirq上。解决方式:RPS。它不会降低延迟,只是将包重新分配:RXQ->CPU。
三种推迟执行方式(softirq/tasklet/workqueue)
提到,Linux中的三种推迟中断执行方式:
其中:
前面已经看到,Linux在每个CPU上创建了一个ksoftirqd内核线程。
softirqs是在Linux内核编译时确定的,例如网络收包对应的NET_RX_SOFTIRQ软中断。因此是一种静态机制。如果想添加一种新softirq类型,需要修改并重新编译内核。
内部组织
内部由一个数组(或称为向量)管理,每个软中断号对应一个softirq handler。数组与注册:
在5.中所有类型的softirq:
也就是在cat /proc/softirqs看到的哪些。
触发(唤醒)softirq
以收包软中断为例,IRQ handler并不执行NAPI,只是触发它,在内部会执行到raiseNET_RX_SOFTIRQ;真正的执行在softirq,会调用网卡的poll()方法收包。IRQ handler中会调用napi_schedule(),然后启动NAPI poll()。
如果对内核源码有一定了解,会发现softirq使用非常有限,原因之一是它是静态编译的,依赖内置的ksoftirqd线程来调度内置的9种softirq。如果想添加一种新功能,就得修改并重新编译内核,开发成本很高。
实际上,实现推迟执行的更常用方式是tasklet。它构建在softirq机制之上,具体来说就是使用了两种softirq:
换句话说,tasklet是在运行时(runtime)创建和初始化的softirq,
内核软中断子系统初始化了两个per-cpu变量:
tasklet再执行针对list的循环:
tasklet在内核中的使用非常广泛。不过,后面又出现了第三种方式:workqueue。
这也是一种推迟执行机制,与tasklet有些相似,但有显著不同。
使用场景
简而言之,workqueue子系统提供了一个接口,通过该接口可以创建内核线程来处理从其他地方enqueue过来的任务。这些内核线程称为worker threads,内置的per-cpu worker threads:
结构体
kworker线程调度workqueues,原理与ksoftirqd线程调度softirqs类似。然而,我们可以为workqueue创建新的线程,而softirq则不行。
参考资料引用链接
[1]
中断与中断处理:0xax.gitbooks.io/linux-...
作者:赵亚楠 原文:arthurchiao.art/blog/li...来源:云原生实验室
Nftables CVE--提权利用
在分析了CVE---Nftables整型溢出漏洞的成因之后,本文接下来将深入探讨如何利用这一漏洞。首先,了解到payload_eval_copy_vlan函数中存在整型溢出问题,这使得我们可以将vlan头部结构拷贝至寄存器(NFT_REG_-NFT_REG_)中,而该变量位于栈上,因此可以覆盖到栈上的其他变量。
然而,观察源码后发现regs变量无法直接覆盖到返回地址。进一步分析后发现,jumpstack变量位于regs变量下方,因此可以利用整型溢出覆盖到jumpstack变量。接下来,我们关注nft_jumpstack结构体,该结构体在nft_do_chain函数中起作用,当状态寄存器被设置为JUMP条件时,程序将跳转至其他chain进行处理。保存完当前chain状态后,程序将跳转至目的chain,即存储在regs.verdict.chain中。
还原chain的过程涉及通过递减stackptr来取出存储在jumpstack变量中的chain、rule、lastrule,然后进行rule的解析。需要注意的是,在遍历rule时,循环条件为rule < lastrule。因此,在伪造lastrule时,需要确保其值大于rule,否则无法进入循环内部。
接着,观察nft_rule_dp结构体,发现其中包含用于存储nft_expr结构体指针的八个字节。通过篡改该指针,可以劫持程序流程。为了解决这一问题,作者从ctfiot.com/.html中学习了一个技巧:使用ptype /o struct xxx显示结构体信息与偏移。
构建流程如下:通过漏洞溢出至nft_jumpstack结构体,并修改rule变量指向可控内容的地址。同时,将lastrule值篡改为大于rule的值。接下来,在可控内容中伪造一个nft_rule_dp结构体,篡改第一个八个字节(填充位)和第二个八个字节(函数表指针)的值,使其分别指向可控内容的地址。之后,在该地址处伪造nft_expr,将ops变量指向所需执行的函数。
分析后得知,通过上述手段可以实现程序流程的劫持。接下来,需要考虑如何伪造上述结构体。在nft_payload_copy_vlan函数中,漏洞点在于将vlan头数据复制到寄存器中,由于vlan头地址低于寄存器地址,导致在复制完vlan头后会覆盖寄存器值。这里,我们可以通过控制NFT_REG_的值来实现对jumpstack结构体的篡改。
由于可以控制regs变量,首先泄露regs地址,然后在此基础上伪造rule并重新指向jumpstack。这里采用将last_rule设置为函数地址的技巧,以节省八个字节的空间。然而,仅控制八个字节的函数指针不足以构造完整的ROP链,因此需要使用栈迁移。栈迁移的目标是控制一段内存,选择regs作为目标。
在寻找合适的栈迁移gadget时,作者使用了vmlinux-to-elf工具提取符号表,然后利用ropper工具搜索gadget。但尝试后发现大部分gadget不可用。作者最后尝试使用objdump工具提取gadget,并通过搜索add rsp.*等指令找到栈迁移的gadget。进一步计算栈顶与regs函数地址的差值,找到相应的栈迁移gadget。
在提权方面,作者通过覆盖modprobe_path实现提权。选择合适的gadget将rdi设置为modprobe_path,rax设置为覆盖后的路径。然而,在返回到nf_hook_slow函数时,由于设置了rax值导致状态码无法正确设置,程序跳转至NF_DROP分支,导致内核异常。最终,作者在rbp中找到了一个do_softirq函数,并尝试将其作为返回地址。运行后发现程序能够正常返回到用户态。
最后,验证新用户是否已写入至/etc/passwd文件中,完成提权过程。完整exploit代码可参考github.com/h0pe-ay/Vuln.../poc.c。相关资料参考链接包括github.com/TurtleARM/CV...
网卡数据包的接收(netif_rx)
网卡数据包接收的核心操作主要围绕netif_rx和其相关的内部函数进行,如netif_rx_internal。这些函数的目的是处理网卡接收到的skb(Sk_buff,网络数据包)并将其存储起来,以供后续处理。
存储路径的关键在于netsoft_data结构,每个CPU都有自己的实例。当网卡收到新的skb时,它会被添加到netsoft_data结构中的input_pkt_queue队列。只有当input_pkt_queue为空时,才会触发napi_schedule_rps,这会启动一个软中断,以便重新激活中断处理流程。
进一步追踪,napi_schedule_rps通过一系列调用链最终将napi_struct添加到poll_list中,并触发NET_RX_SOFTIRQ软中断。然而,值得注意的是,虽然我们可以在驱动函数和netif_rx系列中找到与skb处理相关的部分,但并未直接找到将驱动的napi_struct添加到backlog(这里可能指的是input_pkt_queue)的具体步骤。
总结来说,网卡数据包接收的过程涉及数据包的接收、存储,以及通过napi机制管理软中断,但驱动中napi_struct如何精确地加入到backlog的具体实现细节并不明显。这可能需要深入驱动源码或者相关文档以获取更详细的解答。