最近处理一个骨灰级历史残留问题,内核模块DPI的内存数据被无故关顾,导致系统的panic的问题,linux 内核版本3.18 x86_64,由于我们要精简系统,许多调试工具已经被阉割,SLAB_DEBUG, KASAN not support, 由于这部分数据主要是查询,在初始化后不会对其进行修改,所以想到一个办法初始化完DPI后,将其使用的内存页设置为只读,通过stack的信息找到元凶。

按照以上的分析总共分为以下步骤:

  1. 查找 虚拟地址的PTE
  2. 设置PTE的属性为只读
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gfp.h>
#include <linux/mm.h>MODULE_LICENSE("GPL");static void *address = NULL;static __init int test_init(void)
{int level;pte_t *ptep;struct page *page = alloc_pages(GFP_KERNEL, 1);if (unlikely(page == NULL)) {pr_err("alloc page err %p\n", page);return -1;}address = page_address(page);pr_info("lookup_address %p\n", address);// 1, lookup for PTEptep = lookup_address((unsigned long)address, &level);if (unlikely(ptep == NULL)) {pr_err("lookup_address %p\n", ptep);goto err;}if(level != PG_LEVEL_4K) {pr_err("level not 4K %d\n", level);goto err;}if (!pte_present(*ptep)) {pr_err("level not 4K %d\n", level);goto err;}// 2, set write protect flagset_pte(ptep, pte_wrprotect(*ptep));return 0;err:__free_page(page);return -1;
}static __exit void test_exit(void)
{// clear wrprotect flag// TODO ...pr_info("test exit\n");
}module_init(test_init);
module_exit(test_exit);

按照思路从业务中抽取功能代码,写了非常简单的一个测试用例,以为万事大吉,万万没有想到,理想很丰满,现实很骨感,事情总是不按照我们的预期执行,多次执行insmod test.ko,得到以下结果

[  659.486243] lookup_address ffff8800692e4000
[  659.486248] level not 4K 2
[  660.142577] lookup_address ffff880046436000
[  660.142582] level not 4K 2
[  660.530890] lookup_address ffff8800461a0000
[  660.530896] level not 4K 2
[  660.873884] lookup_address ffff88012369a000
[  660.873889] level not 4K 2

为什么level不是PG_LEVEL_4K,明明申请一页,level层级确是PG_LEVEL_2M,这样会将2M的内存空间设置为只读状态,为了查清这个问题,我们不得不梳理内存管理初始化流程:

start_kernel()|---->setup_arch(&command_line);||---->init_mem_mapping();||---->memory_map_top_down();||---->init_range_memory_mapping();||---->init_memory_mapping();
/** Setup the direct mapping of the physical memory at PAGE_OFFSET.* This runs before bootmem is initialized and gets pages directly from* the physical memory. To access them they are temporarily mapped.*/
unsigned long __init_refok init_memory_mapping(unsigned long start,unsigned long end)
{struct map_range mr[NR_RANGE_MR];unsigned long ret = 0;int nr_range, i;pr_info("init_memory_mapping: [mem %#010lx-%#010lx]\n",start, end - 1);memset(mr, 0, sizeof(mr));nr_range = split_mem_range(mr, 0, start, end);for (i = 0; i < nr_range; i++)ret = kernel_physical_mapping_init(mr[i].start, mr[i].end,mr[i].page_size_mask);add_pfn_range_mapped(start >> PAGE_SHIFT, ret >> PAGE_SHIFT);return ret >> PAGE_SHIFT;
}static int __meminit split_mem_range(struct map_range *mr, int nr_range,unsigned long start,unsigned long end)
{...... // 省略部分代码/* big page (2M) range */start_pfn = round_up(pfn, PFN_DOWN(PMD_SIZE));
#ifdef CONFIG_X86_32end_pfn = round_down(limit_pfn, PFN_DOWN(PMD_SIZE));
#else /* CONFIG_X86_64 */end_pfn = round_up(pfn, PFN_DOWN(PUD_SIZE));if (end_pfn > round_down(limit_pfn, PFN_DOWN(PMD_SIZE)))end_pfn = round_down(limit_pfn, PFN_DOWN(PMD_SIZE));
#endifif (start_pfn < end_pfn) {nr_range = save_mr(mr, nr_range, start_pfn, end_pfn,page_size_mask & (1<<PG_LEVEL_2M));pfn = end_pfn;}#ifdef CONFIG_X86_64/* big page (1G) range */start_pfn = round_up(pfn, PFN_DOWN(PUD_SIZE));end_pfn = round_down(limit_pfn, PFN_DOWN(PUD_SIZE));if (start_pfn < end_pfn) {nr_range = save_mr(mr, nr_range, start_pfn, end_pfn,page_size_mask &((1<<PG_LEVEL_2M)|(1<<PG_LEVEL_1G)));pfn = end_pfn;}/* tail is not big page (1G) alignment */start_pfn = round_up(pfn, PFN_DOWN(PMD_SIZE));end_pfn = round_down(limit_pfn, PFN_DOWN(PMD_SIZE));if (start_pfn < end_pfn) {nr_range = save_mr(mr, nr_range, start_pfn, end_pfn,page_size_mask & (1<<PG_LEVEL_2M));pfn = end_pfn;}
#endif...... // 省略部分代码
}

从split_mem_range() 可以看出,在做物理内存直接映射的时候,尽可能使用huge page去映射,这就解释了为什么我们申请的内存是PG_LEVEL_2M,理论上说应该也会出现PG_LEVEL_1G的大页,问题原因找到了,该怎么解决这个问题呢?此时想到了BPF功能,会将BPF字节码注入内核,为了安全它也会做BPF字节码的内存设置只读权限,肯定也会遇到我们同样的问题,RTFSC

sys_bpf()
|
|---->bpf_prog_load()||---->bpf_prog_select_runtime()||---->bpf_int_jit_compile()||---->set_memory_ro()||---->change_page_attr_clear()||---->__change_page_attr_set_clr()||---->__change_page_attr()||---->lookup_address_cpa()||---->split_large_page() /* ! PG_LEVEL_4K */

从上面代码流程可以看出,bpf() 系统调用最终会调用split_large_page() 来解决申请的大页的情况,x86平台封装了系列函数,至此我们修改我们的实现方式,采用set_memory_ro(),自作聪明的以为修改PTE属性,还是掉进的坑里。

/** The set_memory_* API can be used to change various attributes of a virtual* address range. The attributes include:* Cachability   : UnCached, WriteCombining, WriteBack* Executability : eXeutable, NoteXecutable* Read/Write    : ReadOnly, ReadWrite* Presence      : NotPresent* /
int set_memory_uc(unsigned long addr, int numpages);
int set_memory_wc(unsigned long addr, int numpages);
int set_memory_wb(unsigned long addr, int numpages);
int set_memory_x(unsigned long addr, int numpages);
int set_memory_nx(unsigned long addr, int numpages);
int set_memory_ro(unsigned long addr, int numpages);
int set_memory_rw(unsigned long addr, int numpages);
int set_memory_np(unsigned long addr, int numpages);
int set_memory_4k(unsigned long addr, int numpages);

学习的道路,永无止境,特别是内核学习,RTFSC!!!!

一个历史遗留问题,引发的linux内存管理的‘血案’相关推荐

  1. Linux内存管理:一个故事看懂CPU内存管理技术

    目录 8086 32位时代 虚拟内存 分页交换 现在 往期热门回顾 推荐阅读 还记得我吗,我是阿Q,CPU一号车间的那个阿Q. 今天忙里偷闲,来到厂里地址翻译部门转转,负责这项工作的小黑正忙得满头大汗 ...

  2. 一个历史遗留项目清理总结

    2015年时我接了一个历史遗留项目,给甲方做办公文档加解密的系统.该项目是2011年承建的,由于各种各样的问题,一直没有验收,项目成员换了好几批.公司要做历史遗留项目清理,所以点名让我接手该项目. 我 ...

  3. 一文掌握 Linux 内存管理

    作者:dengxuanshi,腾讯 IEG 后台开发工程师 以下源代码来自 linux-5.10.3 内核代码,主要以 x86-32 为例. Linux 内存管理是一个很复杂的"工程&quo ...

  4. Linux内存管理:ARM64体系结构与编程之cache(1)

    <Linux内存管理:ARM64体系结构与编程之cache(1)> <Linux内存管理:ARM64体系结构与编程之cache(2)> <ARM SMMU原理与IOMMU ...

  5. Linux内存管理:为什么 Linux 需要虚拟内存?为什么 Linux 默认页大小是 4KB?

    Table of Contents 为什么 Linux 需要虚拟内存? 缓存 内存管理 内存保护 总结 推荐阅读 为什么 Linux 默认页大小是 4KB? 页表项 碎片化 总结 推荐阅读 为什么 L ...

  6. Linux内存管理知识总结(一)

    以下源代码来自 linux-5.10.3 内核代码,主要以 x86-32 为例 Linux 内存管理是一个很复杂的"工程",它不仅仅是对物理内存的管理,也涉及到虚拟内存管理.内存交 ...

  7. Windows内存管理和linux内存管理

    windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或 ...

  8. Linux内存管理原理【转】

    转自:http://www.cnblogs.com/zhaoyl/p/3695517.html 本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址 ...

  9. Linux内存管理【转】

    转自:http://www.cnblogs.com/wuchanming/p/4360264.html 转载:http://www.kerneltravel.net/journal/v/mem.htm ...

最新文章

  1. linux-centos7-yum安装mcrypt**
  2. phpstorm 点击方法跳转 后 返回 原来的位置
  3. LINUX相关的镜像源网站大全,个人收集完整版!
  4. 怎么用PHP语句做出增改删查功能,PHP、MYSQLI实现简单的增、删、改、查功能(初学者)...
  5. php文件多上传文件,php文件上传(多文件上传)
  6. Linux下Weblogic创建域方法和步骤
  7. 百度地图3.2教程(2)公交查询
  8. python进阶04IO的同步异步,阻塞非阻塞
  9. 《YOLO系列原理实战笔记》高清.pdf
  10. word树状分支图_word绘制树形图
  11. C++笔试面试题 -- 带答案
  12. Tor配置:514 Authentication required
  13. CarSim仿真快速入门(十五)—CarSim传感器仿真之ADAS Sensor Objects (1)
  14. org.apache.commons.codec.binary.Base64包需要下载的jar包依赖
  15. Visio科学图形包免费下载
  16. mongodb数据备份与恢复
  17. 4、MyBatis + Log4j日志查看Sql参数、结果集元数据、Mapper代理开发、JDK的动态代理与CGLib代理
  18. malloc,calloc和realloc。
  19. 音频合成:TTS和歌声合成
  20. 疑难杂症篇(十)--Catia软件出现“没有合适的许可证来实现xx的请求”解决方案

热门文章

  1. nyist --- 组队赛(四)
  2. zigbee之SampleApp_ProcessEvent()
  3. 枚举是如何实现的?(枚举的线程安全性及序列化问题)
  4. sqlserver 跨服务器备份表
  5. php打印负载函数、Linux awk打印负载
  6. 20171115_Python学习五周三次课
  7. 7款高颜值HTML5播放器:让你的音乐有声有色
  8. JavaScript实用小技巧
  9. 使用Code First Migrations依据代码更新数据库结构
  10. webGL简单例子(klayge)