linux 内存检测工具 kfence 详解

版本基于:

Linux-5.10

 

约定:

PAGE_SIZE:4K

内存架构:UMA

 

0. 前言

本文 kfence 之外的代码版本是基于 Linux5.10,最近需要将 kfence 移植到 Linux5.10 中,本文借此机会将 kfence 机制详细地记录一下。

kfence,全称为 Kernel Electric-Fence,是 Linux5.12 版本新引入的内存使用错误检测机制。

kfence 基本原理非常简单,它创建了自己的专有检测内存池 kfence_pool。然后在 data page 的两边加上 fence page 电子栅栏,利用 MMU 的特性把 fence page 设置为不可访问。如果对 data page 的访问越过 page 边界,就会立刻触发异常。

检测的内存错误有:

  • OOB:out-of-bounds access,访问越界;
  • UAF:use-after-free,释放再使用;
  • invalid free,无效释放;

现在,kfence 检测的内存错误类型不如 KASAN 多,但,kfence 设计的目的:

  • be enabled in production kernels,在产品内核中使能
  • has near zero performance overhead,接近 0 性能开销

kfence 机制依赖 slab 和kmalloc 机制,熟悉这两个机制能更好理解 kfence。 

1. kfence 依赖的config

//当使用 arm64时,该config会被默认select,详细看arch/arm64/Kconfig
CONFIG_HAVE_ARCH_KFENCE//kfence 机制的核心config,需要手动配置,下面所有的config都依赖它
CONFIG_KFENCE------------------ 下面所有config都依赖CONFIG_KFENCE-----------//依赖CONFIG_JUMP_LABEL
//用以启动静态key功能,主要是来优化性能,每次读取kfence_allocation_gate的值是否为0来进行判断,这样的性能
//开销比较大
CONFIG_KFENCE_STATIC_KEYS//kfence pool的获取频率,默认为100ms
//  另外,该config可以设置为0,表示禁用 kfence功能
CONFIG_KFENCE_SAMPLE_INTERVAL//kfence pool中共支持多少个OBJECTS,默认为255,从1~65535之间取值
//  一个kfence object需要申请两个pages
CONFIG_KFENCE_NUM_OBJECTS//stress tesing of fault handling and error reporting, default 0
CONFIG_KFENCE_STRESS_TEST_FAULTS//依赖CONFIG_TRACEPOINTS && CONFIG_KUNIT,用以启动kfence的测试用例
CONFIG_KFENCE_KUNIT_TEST

 

1. kfence 原理

2. kfence中的重要数据结构

2.1 __kfence_pool

char *__kfence_pool __ro_after_init;
EXPORT_SYMBOL(__kfence_pool); /* Export for test modules. */

kfence 中有个专门的内存池,在 memblock移交 buddy之前从 memblock 中申请的一块内存。

内存的首地址保存在全局变量 __kfence_pool 中。

 

这里来看下内存池的大小:

include/linux/kfence.h#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS + 1) * 2 * PAGE_SIZE)

每个 object 会占用 2 pages,一个page 用于 object 自身,另一个page 用作guard page

kfence pool 会在 CONFIG_KFENCE_NUM_OBJECTS 的基础上多申请 2 个pages,即kfence pool 的page0 和 page1。page0 大部分是没用的,仅仅作为一个扩展的guard page。多加上page1 方便简化metadata 索引地址映射。

下面弄一个图方便理解 kfence_pool

  • page0 和 page1 就是上面所述的多出来的两个 pages;
  • 其他的pages 是 CONFIG_KFENCE_NUM_OBJECTS * 2,每个object 拥有两个pages,第一个为object 本身,第二个为 fence page;
  • kfence 定义一个全局变量 kfence_metadata 数组,数组的长度为CONFIG_KFENCE_NUM_OBJECT,里面管理所有的objects,包括obj当前状态,内存地址等信息;
  • kfence pool 中可用的 metadata 会被存放在链表 kfence_freelist 中;
  • 从图上可以看到,每一个object page 都会被两个 guard page 包裹了;

2.2 kfence_sample_interval

static unsigned long kfence_sample_interval __read_mostly = CONFIG_KFENCE_SAMPLE_INTERVAL;

该变量用以存储 kfence 的采样间隔,默认使用的是 CONFIG_KFENCE_SAMPLE_INTERVAL 的值。当然,内核中还提供内存参数的方式进行配置:

static const struct kernel_param_ops sample_interval_param_ops = {.set = param_set_sample_interval,.get = param_get_sample_interval,
};
module_param_cb(sample_interval, &sample_interval_param_ops, &kfence_sample_interval, 0600);

通过set、get 指定内核参数 kfence.sample_interval 的配置和获取,单位为毫秒。

可以通过设施 kfence.sample_interval=0 来禁用 kfence 功能。

2.3 kfence_enabled

该变量表示kfence pool 初始化成功,kfence 进入正常运行中。

kfence 中一共有两个地方会将 kfence_enables 设为false。

第一个地方:

mm/kfence/core.c#define KFENCE_WARN_ON(cond)                                                   \({                                                                     \const bool __cond = WARN_ON(cond);                             \if (unlikely(__cond))                                          \WRITE_ONCE(kfence_enabled, false);                     \__cond;                                                        \})

在kfence 中很多地方需要确定重要条件不能为 false,通过 KFENCE_WARN_ON() 进行check,如果condition 为false,则将 kfence_enabled 设为 false。

第二个地方:

param_set_sample_interval() 调用时,如果采样间隔设为0,则表示 kfence 功能关闭。

2.4 kfence_metadata

这是一个 struct kfence_metadata 的全局变量。用以管理所有 kfence objects:

mm/kfence/kfence.hstruct kfence_metadata {struct list_head list;		//kfence_metadata为kfence_freelist中的一个节点struct rcu_head rcu_head;	//delayed freeing 使用//每个kfence_metadata带有一个自旋锁,用以保护data一致性//我们不能将同一个metadata从freelist 中抓取两次,也不能对同一个metadata进行__kfence_alloc() 多次raw_spinlock_t lock;//object 的当前状态,默认为UNUSEDenum kfence_object_state state;//对象的基地址,都是按照页对齐的unsigned long addr;/** The size of the original allocation.*/size_t size;//最后一次从对象中分配内存的kmem_cache//如果没有申请或kmem_cache被销毁,则该值为NULLstruct kmem_cache *cache;//记录发生异常的地址unsigned long unprotected_page;/* 分配或释放的栈信息 */struct kfence_track alloc_track;struct kfence_track free_track;
};

2.5 kfence_freelist

/* Freelist with available objects. */
static struct list_head kfence_freelist = LIST_HEAD_INIT(kfence_freelist);
static DEFINE_RAW_SPINLOCK(kfence_freelist_lock); /* Lock protecting freelist. */

用以管理所有的可用的kfence objeces

 

2.6  kfence_allocation_gate

这是个 atomic_t 变量,是kfence 定时开放分配的闸门,0 表示允许分配,非0表示不允许分配。

正常情况下,在 kfence_alloc() 进行内存分配的时候,会通过atomic_read() 读取该变量的值,如果为0,则表示允许分配,kfence 会进一步调用 __kfence_alloc() 函数。

当考虑到性能问题,内核启动了 static key 功能,即变量 kfence_allocation_key,详见下一小节。

2.7 kfence_allocation_key

这个是kfence 分配的static key,需要 CONFIG_KFENCE_STATIC_KEYS 使能。

#ifdef CONFIG_KFENCE_STATIC_KEYS
/* The static key to set up a KFENCE allocation. */
DEFINE_STATIC_KEY_FALSE(kfence_allocation_key);
#endif

这是一个 static_key_false key。

如果 CONFIG_KFENCE_STATIC_KEYS 使能,在 kfence_alloc() 的时候将不再判断 kfence_allocation_gate 的值,而是判断该key 的值。

3. kfence 初始化

init/main.cstatic void __init mm_init(void)
{...kfence_alloc_pool();report_meminit();mem_init();...
}

《buddy 初始化》一文中得知,mm_init() 函数开始将进行buddy 系统的内存初始化。而在函数 mem_init() 中会通过 free 操作,将内存一个页块一个页块的添加到 buddy 系统中。

而 kfence pool 是在 mem_init() 调用之前,从memblock 中分配出一段内存。

 

3.1 kfence_alloc_pool()

mm/kfence/core.cvoid __init kfence_alloc_pool(void)
{if (!kfence_sample_interval)return;__kfence_pool = memblock_alloc(KFENCE_POOL_SIZE, PAGE_SIZE);if (!__kfence_pool)pr_err("failed to allocate pool\n");
}

代码比较简单:

  • 确认 kfence_sample_interval 是否为0,如果为0 则表示kfence 为disabled;
  • 通过 memblock_alloc() 申请 KFENCE_POOL_SIZE 的空间,PAGE_SIZE 对齐;

3.2 kfence_init()

该函数被放置在 start_kernel() 函数比较靠后的位置,此时buddy初始化、slab初始化、workqueue 初始化等已经完成。

mm/kfence/core.cvoid __init kfence_init(void)
{/* Setting kfence_sample_interval to 0 on boot disables KFENCE. */if (!kfence_sample_interval)return;if (!kfence_init_pool()) {pr_err("%s failed\n", __func__);return;}WRITE_ONCE(kfence_enabled, true);queue_delayed_work(system_unbound_wq, &kfence_timer, 0);pr_info("initialized - using %lu bytes for %d objects at 0x%p-0x%p\n", KFENCE_POOL_SIZE,CONFIG_KFENCE_NUM_OBJECTS, (void *)__kfence_pool,(void *)(__kfence_pool + KFENCE_POOL_SIZE));
}
  • 同样,当采样间隔设为0,即 kfence_sample_interval 为0 时,关闭kfence;
  • 调用 kfence_init_pool() 对kfence pool 进行初始化;
  • 变量 kfence_enabled  设为 true,表示 kfence 功能正常,可以正常工作;
  • 创建工作队列 kfence_timer,并添加到 system_unbound_wq 中,注意这里延迟为0,即立刻执行 kfence_timer;

注意最后打印的信息,在kfence pool 初始化结束,会从dmesg 中看到如下log:

<6>[    0.000000] kfence: initialized - using 2097152 bytes for 255 objects at 0x(____ptrval____)-0x(____ptrval____)

系统中申请了 255 个 objects,共使用 2M 的内存空间。

3.2.1 kfence_init_pool()

mm/kfence/core.cstatic bool __init kfence_init_pool(void)
{unsigned long addr = (unsigned long)__kfence_pool;struct page *pages;int i;//确认 __kfence_pool已经申请成功,kfence_alloc_pool()会从memblock中申请if (!__kfence_pool)return false;//对于 arm64架构,该函数直接返回true//对于 x86架构,会通过lookup_address()检查__kfence_pool是否映射到物理地址了if (!arch_kfence_init_pool())goto err;//获取映射好的pages,从vmemmap 中查找pages = virt_to_page(addr);//配置kfence pool中的page,将其打上slab页的标记for (i = 0; i < KFENCE_POOL_SIZE / PAGE_SIZE; i++) {if (!i || (i % 2))  //第0页和奇数页跳过,即配置偶数页continue;//确认pages不是复合页if (WARN_ON(compound_head(&pages[i]) != &pages[i]))goto err;__SetPageSlab(&pages[i]);}//将kfence pool的前两个页面设为guard pages//主要是清除对应 pte项的present位,这样当CPU访问前两页就会触发缺页异常,就会进入kfence处理流程for (i = 0; i < 2; i++) {if (unlikely(!kfence_protect(addr)))goto err;addr += PAGE_SIZE;}//遍历所有的kfence objects页面,kfence_metadata数组是专门对CONFIG_KFENCE_NUM_OBJECTS个对象的状态进行管理for (i = 0; i < CONFIG_KFENCE_NUM_OBJECTS; i++) {struct kfence_metadata *meta = &kfence_metadata[i];/* 初始化kfence metadata */INIT_LIST_HEAD(&meta->list);        //初始化kfence_metadata节点raw_spin_lock_init(&meta->lock);    //初始化spi lockmeta->state = KFENCE_OBJECT_UNUSED; //所有的起始状态是UNUSEDmeta->addr = addr;                  //保存该对象的page地址list_add_tail(&meta->list, &kfence_freelist);  //将可用的metadata添加到kfence_freelist尾部//保护每个object的右边区域的pageif (unlikely(!kfence_protect(addr + PAGE_SIZE)))goto err;addr += 2 * PAGE_SIZE; //跳到下一个对象}//kfence pool是一直活着的,从此时起永远不会被释放//之前在调用 memblock_alloc()时在 kmemleak中留有记录,这里要删除这部分记录,防止与后面调用//  kfence_alloc()分配时出现冲突kmemleak_free(__kfence_pool);return true;err:/** Only release unprotected pages, and do not try to go back and change* page attributes due to risk of failing to do so as well. If changing* page attributes for some pages fails, it is very likely that it also* fails for the first page, and therefore expect addr==__kfence_pool in* most failure cases.*/memblock_free_late(__pa(addr), KFENCE_POOL_SIZE - (addr - (unsigned long)__kfence_pool));__kfence_pool = NULL;return false;
}

3.2.2 kfence_timer

在上面 kfence_init_pool() 成功完成之后,kfence_init() 会进入下一步:创建周期性的工作队列。

queue_delayed_work(system_unbound_wq, &kfence_timer, 0);

注意最后一个参数为0,因为这里是kfence_init(),第一次执行 kfence_timer 会立即执行,之后的 kfence_timer 会有个 kfence_sample_interval 的延迟。

来看下 kfence_timer 的创建:

mm/kfence/core.cstatic DECLARE_DELAYED_WORK(kfence_timer, toggle_allocation_gate);

通过调用 DECLARE_DELAYED_WORK() 初始化一个延迟队列,toggle_allocation_gate() 为时间到达后的处理函数。

 

下面来看下 toggle_allocation_gate():

mm/kfence/core.cstatic void toggle_allocation_gate(struct work_struct *work)
{//首先确定kfence功能正常if (!READ_ONCE(kfence_enabled))return;//将 kfence_allocation_gate 设为0//  这是kfence内存池开启分配的标志,0表示开启,非0表示关闭//  这样保证每隔一段时间,最多只允许从kfence内存池分配一次内存atomic_set(&kfence_allocation_gate, 0);#ifdef CONFIG_KFENCE_STATIC_KEYS//使能static key,等到分配的发生static_branch_enable(&kfence_allocation_key);//内核发出 hung task警告的时间最短时间长度,为CONFIG_DEFAULT_HUNG_TASK_TIMEOUT的值if (sysctl_hung_task_timeout_secs) {//如果内存分配没有那么频繁,就有可能出现等待时间过长的问题,//  这里将等待超过时间设置为hung task警告时间的一半,//  这样,内核就不会因为处于D状态过长导致内核出现警告wait_event_idle_timeout(allocation_wait, atomic_read(&kfence_allocation_gate),sysctl_hung_task_timeout_secs * HZ / 2);} else {//如果hungtask检测时间为0,表示时间无限长,那么可以放心等待下去,直到有人从kfence中//  分配了内存,会将kfence_allocation_gate设为1,然后唤醒阻塞在allocation_wait里的任务wait_event_idle(allocation_wait, atomic_read(&kfence_allocation_gate));}/* 将static key关闭,保证不会进入 __kfence_alloc() */static_branch_disable(&kfence_allocation_key);
#endif//等待kfence_sample_interval,单位是毫秒,然后再次开启kfence内存池分配queue_delayed_work(system_unbound_wq, &kfence_timer,msecs_to_jiffies(kfence_sample_interval));
}

注意 static key 需要 CONFIG_KFNECE_STATIC_KEYS 使能。

这里使用 static key,主要是来优化性能,每次读取 kfence_allocation_gate 的值是否为0来进行判断,这样的性能开销比较大。

另外,在此次 toggle 执行完成后,会再次调用 queue_delayed_work() 进入下一次work,只不过有个 delay——kfence_sample_interval。

4. kfence 申请

kfence 申请的核心接口是 __kfence_alloc() 函数,系统中调用该函数有两个地方:

  • kmem_cache_alloc_bulk()
  • slab_alloc_node()

第一个函数只有在 io_alloc_req() 函数中调用,详见 fs/io_uring.c 

第二个函数如果只考虑 UMA 架构,起点只会是 slab_alloc() 函数,调用的地方有:

kmem_cache_alloc()
kmem_cache_alloc_trace()
__kmalloc()

函数的细节可以查看《slub 分配器之kmem_cache_alloc》《slub 分配器之kmalloc详解》

5. kfence 释放

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/169856.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不希望你的数据在云中?关闭iPhone或Mac上的iCloud

​如果你不想使用iCloud&#xff0c;可以很容易地从设备设置中选择退出并关闭它。当你禁用iCloud时&#xff0c;它会删除该设备对iCloud的访问&#xff0c;但不会删除苹果服务器上的任何数据。我们将在本文末尾向你展示如何做到这一点。 注销iCloud并完全禁用它 如果你根本不…

矢量图形编辑软件 illustrator 2023 mac 中文软件特点

illustrator 2023 mac是一款矢量图形编辑软件&#xff0c;用于创建和编辑排版、图标、标志、插图和其他类型的矢量图形。 illustrator 2023 mac软件特点 矢量图形&#xff1a;illustrator创建的图形是矢量图形&#xff0c;可以无限放大而不失真&#xff0c;这与像素图形编辑软…

idea 设置serlvet 类模板(快捷生成servlet类)

我的版本是idea2020.3.4&#xff0c;博客中有相应安装教程&#xff0c;其他版本设置类似&#xff1a; 1.选择文件-->设置 2.选择编辑器-->文件和代码模板-->其他 3.选择Web-->Servlet Annotated Class.java-->复制相应模板&#xff0c;下面顺便设置了注释模板 …

B-3:Web安全之综合渗透测试

B-3:Web安全之综合渗透测试 任务环境说明: 服务器场景:Server2104(关闭链接) 服务器场景用户名、密码:未知 1.通过URL访问http://靶机IP/1,对该页面进行渗透测试,将完成后返回的结果内容作为FLAG值提交; 通过访问IP/1,查看源代码发现flagishere,访问后发现什么也没…

Qt Creater 设计的登录注册界面 使用SQLite数据库

Qt Creater 设计的登录注册界面 使用SQLite数据库 案例截图 登录页面 注册页面 项目目录结构截图 代码 main.cpp #include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);MainWindow w;//第一个是…

Lua语言编写爬虫程序

以下是一个使用luasocket-http库和Lua语言编写的爬虫程序。此程序使用了https://www.duoip.cn/get_proxy的代码。 -- 引入所需的库 local http require("socket.http") local ltn12 require("ltn12") local json require("json") ​ -- 获取…

[moeCTF 2023] crypto

这个比赛从8月到10月&#xff0c;漫长又不分段。结束了以后前边的都基本上忘光了。还是分段提交的好点&#xff0c;有机会写写。不过反正也是新生赛&#xff0c;又不是新生只是打个热闹。 ezrot 厨子解决大部分问题 可可的新围墙 给了1个串 mt3_hsTal3yGnM_p3jocfFn3cp3_hFs…

Hadoop3教程(二十六):(生产调优篇)NameNode核心参数配置与回收站的启用

文章目录 &#xff08;143&#xff09;NameNode内存配置&#xff08;144&#xff09;NN心跳并发配置&#xff08;145&#xff09;开启回收站参考文献 &#xff08;143&#xff09;NameNode内存配置 每个文件块&#xff08;的元数据等&#xff09;在内存中大概 占用150byte&…

边缘计算发生了什么?

边缘计算(Edge computing)成为一种革命性工具&#xff0c;可以满足日益增长的实时数据处理需求。通过在网络边缘&#xff08;更靠近数据生成位置&#xff09;进行数据处理&#xff0c;边缘计算可显着减少延迟和带宽使用。 这是我们多年来一直被告知的故事&#xff0c;但随着生…

设计模式:组合模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

上一篇《模板模式》 下一篇《代理模式》 简介&#xff1a; 组合模式&#xff0c;它是一种用于处理树形结构、表示“部分-整体”层次结构的设计模式。它允许你将对象组合成树形结构&#xff0c;以表示部分…

【COMP329 LEC4 Locomotion and Kinematics】

Only for the Test 1 which include 4.2 4.3 4.4 Locomotion and Kinematics 运动和运动学 (4.2) Part 2: Wheeled Motion 1. Wheeled Robots a. 省略控制双腿需要的计算复杂度 b. 只限于easy terrain &#xff08;地形&#xff09; c. 不平坦uneven 不规则irregular 的地形需要…

STM32 PWM配置及呼吸灯

PWM的英文全称是"Pulse Width Modulation"&#xff0c;中文翻译为"脉冲宽度调制"。 在PWM中可以调节的其实只有两个东西&#xff0c;一个叫做可调周期&#xff08;调频率&#xff09;&#xff0c;另一个叫做占空比&#xff08;高电平/周期&#xff09;。 而…

SpringBoot整合XXL-JOB详解

❤️作者简介&#xff1a;2022新星计划第三季云原生与云计算赛道Top5&#x1f3c5;、华为云享专家&#x1f3c5;、云原生领域潜力新星&#x1f3c5; &#x1f49b;博客首页&#xff1a;C站个人主页&#x1f31e; &#x1f497;作者目的&#xff1a;如有错误请指正&#xff0c;将…

【Python】Windows跟随程序启动和关闭系统代理

前言 在日常使用计算机时&#xff0c;偶尔可能需要配置代理来访问特定的网络资源或进行网络调试。 当在使用mitmproxy 时候&#xff0c; 程序开始前&#xff0c;需要手动打开系统代理&#xff1b;程序解释后&#xff0c;需要手动关闭系统代理。 这些重复性且没有技术含量工作…

前端(二十三)——轮询和长轮询

&#x1f62b;博主&#xff1a;小猫娃来啦 &#x1f62b;文章核心&#xff1a;实现客户端与服务器实时通信的技术手段 文章目录 前言轮询技术轮询的概念轮询的实现原理轮询的优缺点轮询的使用场景 长轮询技术长轮询的概念长轮询的实现原理长轮询的优缺点长轮询的使用场景 轮询与…

CDC实时数据同步

一丶CDC实时数据同步介绍 CDC实时数据同步指的是Change Data Capture&#xff08;数据变更捕获&#xff09;技术在数据同步过程中的应用。CDC技术允许在数据源发生变化时&#xff0c;实时地捕获这些变化&#xff0c;并将其应用到目标系统中&#xff0c;从而保持数据的同步性。…

FreeRTOS 任务调度和任务的状态

目录 什么是任务调度&#xff1f; FreeRTOS的任务调度规则是怎样的&#xff1f; 抢占式调度运行过程​编辑 时间片调度运行过程​编辑 任务的状态 任务调度和任务的状态案例分析 什么是任务调度&#xff1f; 调度器就是使用相关的调度算法来决定当前需要执行的哪个任务。…

2 第一个Go程序

概述 在上一节的内容中&#xff0c;我们介绍了Go的前世今生&#xff0c;包括&#xff1a;Go的诞生、发展历程、特性和应用领域。从本节开始&#xff0c;我们将正式学习Go语言。Go语言是一种编译型语言&#xff0c;也就是说&#xff0c;Go语言在运行之前需要先进行编译&#xff…

tomcat的负载均衡、动静分离(nginx联动)

动静分离&#xff1a; 访问静态页面和动态页面分开 实现动态和静态页面负载均衡 实验5台虚拟机 一、动态负载均衡 3台虚拟机模拟&#xff1a; 代理服务器&#xff1a;30 tomcat动态页面&#xff1a;21、22 代理服务器&#xff1a; proxy_pass http://tomcat; proxy_set_h…

docker环境,ubuntu18.04安装VTK8.2和PCL1.9.1

下载源码和依赖库 首先下载源码VTK8.2: Download | VTK 下载PCL1.9.1链接&#xff1a;Releases PointCloudLibrary/pcl GitHub 下载好了以后&#xff0c;先安装PCL依赖 sudo apt-get update sudo apt-get install git build-essential linux-libc-dev sudo apt-get instal…