Linux KASAN使用与实现原理

news/2024/5/20 10:50:31

一、KASAN工具使用

KASAN工具:Kernel Address SANitizer(KASAN)是一种动态内存安全错误检测工具,主要功能是检查内存越界访问和使用已释放内存的问题。

1.1 KASAN宏控开关

KASAN有三种模式:1.通用KASAN;2.基于软件标签的KASAN;3.基于硬件标签的KASAN

宏控开关

说明

通用KASAN

CONFIG_KASAN_GENERIC

启用通用KASAN,类似于用户空 间的ASan。这种模式在许多CPU架构上都被支持,但它有明显的性能和内存开销。

软件标签KASAN

CONFIG_KASAN_SW_TAGS

类似于用户空间HWASan,这种模式只支持arm64,但其适度的内存开销允许在内存受限的设备上用真实的工作负载进行测试。

硬件标签KASAN

CONFIG_KASAN_HW_TAGS

现场内存错误检测器或作为安全缓解的模式,这种模式只在支持MTE(内存标签扩展)的arm64 CPU上工作,但它的内存和性能开销很低,因此可以在生产中使用。

注:ARMv8.5-A 以及更高版本的 ARM 架构支持 MTE 功能,可以通过检查 CPUID 寄存器中的宏观架构标识符 (MIDR_EL1) 来确定 CPU 是否支持 MTE

打开宏控开关就可以使能KASAN.

1.2 编译器依赖

软件KASAN模式使用编译时工具在每个内存访问之前插入有效性检查,因此需要一个 提供支持的编译器版本。基于硬件标签的模式依靠硬件来执行这些检查,但仍然需要一个支持内存标签指令的编译器版本。

编译器依赖

通用KASAN

需要GCC 8.3.0版本或更高版本,或者内核支持的任何Clang版本。

软件标签KASAN

需要GCC 11+或者内核支持的任何Clang版本。

硬件标签KASAN

KASAN需要GCC 10+或Clang 12+。

1.3 检测的内存类型

检测的内存类型

通用KASAN

支持在所有的slab、page_alloc、vmap、vmalloc、堆栈和全局内存中查找错误。

软件标签KASAN

支持slab、page_alloc、vmalloc和堆栈内存。

硬件标签KASAN

支持slab、page_alloc和不可执行的vmalloc内存。

注:对于slab,通用KASAN和软KASAN模式都支持SLUB和SLAB分配器,而基于硬件标签的 KASAN只支持SLUB。

二、KASAN实现原理

2.1 linux kernel内存分配器

kernel中存在多种内存分配器,常用的包括Buddy、SLUB 、SLAB 、CMA、SLOB等,而KASAN基于内存分配器实现,因此需要对内存分配器做一些了解。

内存分配器

适用场景

接口函数

SLUB

是当前 Linux 内核中最常用的内存分配器。它适用于大多数通用的内存分配需求,具有较好的性能和灵活性。因此,对于大多数情况下的内存分配操作,SLUB 是首选的分配器。

1)kmalloc(size, flags): 分配指定大小的内存块。size 参数表示要分配的内存大小,flags 参数用于指定内存分配的标志。

2)kmem_cache_alloc(cache, flags): 在给定的缓存 cache 中分配一个对象。

3)kcalloc(n, size, flags): 分配 n 个元素,并将所分配的内存区域初始化为 0。

SLAB

相比SLUB分配器,SLAB 分配器对于某些特殊场景,如需要对齐要求、追踪统计信息等的内存分配操作,可能会更为合适。

1)kmem_cache_alloc(cache, flags): 在给定的缓存 cache 中分配一个对象。

2)kmalloc(size, flags): 分配指定大小的内存块。size 参数表示要分配的内存大小,flags 参数用于指定内存分配的标志。

Buddy

适用于需要页级别连续内存分配的情况,例如页面缓存、物理页面映射等。它将物理内存划分为固定大小的块,并提供对连续块的分配和释放。

alloc_pages(gfp_mask, order): 分配连续的页面。gfp_mask 参数表示内存分配的标志,order 参数表示要分配的页面数量的对数。

CMA

用于为需要大块连续内存的设备驱动程序提供内存分配支持,如 DMA 操作。

dma_alloc_coherent(dev, size, dma_handle, flag): 在 CMA 区域分配一块连续的内存。dev 参数是设备结构体指针,size 参数表示分配的内存大小,dma_handle 是 DMA 地址映射的输出参数,flag 是内存分配的标志。

SLOB

SLOB 分配器适用于嵌入式系统和资源受限环境中,它实现简单且占用较大的内存管理。因此,在资源非常有限的系统中,可以考虑使用 SLOB 分配器。

slob_alloc(size, align, boundary, usage): 分配指定大小的内存块。size 参数表示要分配的内存大小,align 参数表示内存对齐的要求,boundary 参数表示内存分配的边界,usage 是用于标识用途的标志。

2.2 kasan实现原理

kasan可以检测栈内存、堆内存的异常。

栈内存:全局变量、局部变量

堆内存:通过内存分配器(buddy、slub等)进行堆内存申请与释放的时候调用kasan的相关函数,对shadow memory做标记及检测。

2.2.1 kasan如何检测

检测原理:假设内存是从地址8~15一共8 bytes。对应的shadow memory值为5,假如现在访问11(11&7=3 3<5)地址,那么就是可以访问,假如想要访问地址13(13&7=5 5>=5),那么就不能访问,就检测出了问题。

 相关源码如下:

 static __always_inline bool memory_is_poisoned_1(unsigned long addr)
{/* 将地址转换成影子内存(每8byte有对应的1byte影子内存,后面分析具体映射关系) */s8 shadow_value = *(s8 *)kasan_mem_to_shadow((void *)addr);/* 如果shadow_value不为0,比如说是负数或者1-7的值,
那么就需要进行判断,看看对应要访问的字节的影子内存对应是否能够访问 */
if (unlikely(shadow_value)) {/* KASAN_SHADOW_MASK 的值为7 */
#define KASAN_SHADOW_MASK       (KASAN_SHADOW_SCALE_SIZE - 1)/* 这里把虚拟地址 &7 目的就是为了看访问的地址(实际上已经是地址+size)是否大于剩余可访问的字节数,注意这里就是kasan的最根本的原理 */s8 last_accessible_byte = addr & KASAN_SHADOW_MASK;return unlikely(last_accessible_byte >= shadow_value);}/* shadow 值为 0,8个字节都能被访问,其中一个字节肯定能访问,返回false说明kasan没有检测出问题 */return false;
}static __always_inline unsigned long bytes_is_nonzero(const u8 *start,size_t size)
{
while (size) {
/* 这里如果对应的影子地址的值非0,就需要进行权限的判断了 */if (unlikely(*start))return (unsigned long)start; start++;size--;}return 0;
}static __always_inline unsigned long memory_is_nonzero(const void *start, const void *end)
{unsigned int words;unsigned long ret;unsigned int prefix = (unsigned long)start % 8;if (end - start <= 16)return bytes_is_nonzero(start, end - start);/* 如果影子地址差了16个以上(对应16*8=128 即size大于128) */if (prefix) {prefix = 8 - prefix;
/* 将start按8对齐,先把未对齐的前 prefix 长度权限先校验 */ret = bytes_is_nonzero(start, prefix);if (unlikely(ret))return ret;start += prefix; /* start补齐成8的倍数 */}/* 在计算end到start有多少个8字节影子地址(即对应words倍的128长度的实际内存) */words = (end - start) / 8;while (words) {if (unlikely(*(u64 *)start))return bytes_is_nonzero(start, 8);/* 再次进行权限判断,如果有一个不为0,则说明有问题 */start += 8;words--;}/* 最后,将剩余长度的影子地址进行权限判断,同样的有一个不为0,就可能有问题 */return bytes_is_nonzero(start, (end - start) % 8);
}static __always_inline bool memory_is_poisoned_n(unsigned long addr,size_t size)
{unsigned long ret;/* 判断 内存对应的影子内存中,起始和结束 shadow 值是否都为 0 注意:这里影子内存起始就是直接转换来的,而结束比较有意思,找的永远是对应地址对应长度的影子地址的下一个影子地址 */ret = memory_is_nonzero(kasan_mem_to_shadow((void *)addr),kasan_mem_to_shadow((void *)addr + size - 1) + 1);/* 根据前面的判断,如果ret不为0(可能的值为负数或1-7),就说明内存权限可能有问题,需要进一步判断 */
if (unlikely(ret)) {/* 只判断起始地址,连续size长度的最后一字节所在影子内存所在位置的权限值 */unsigned long last_byte = addr + size - 1;s8 *last_shadow = (s8 *)kasan_mem_to_shadow((void *)last_byte);/* 如果ret!=last_shadow 可能是因为在连续的内存检测过程中,就已经检测到了一个非法权限,那么肯定就是有问题的 *//* ||后面的检测方案和 memory_is_poisoned_1 实现是相同的 */if (unlikely(ret != (unsigned long)last_shadow ||((long)(last_byte & KASAN_SHADOW_MASK) >= *last_shadow)))return true;}return false;
}

以上是kasan基于影子内存和对应权限值是如何检测出问题的原理。

kasan是如何维护和标记影子内存所对应的权限值的(详见2.2.2)?以及,kasan的影子内存是如何及映射的(详见2.2.3)?

2.2.2 影子内存标记

内存与释放内存时调用kasan的相关函数。

2.2.2.1 buddy内存分配器检测

Buddy 系统在 free 和 alloc 的时间点上插入了权限设置,所以 buddy 能检测出 use-after-free 类型的错误。调用的函数为:kasan_alloc_pages和kasan_free_pages

alloc 调用流程:

free调用流程:

/** 该函数会为从“addr”开始的“size”字节的阴影内存添加tag。内存地址应该对齐到KASAN_SHADOW_SCALE_SIZE */
void kasan_poison_shadow(const void *address, size_t size, u8 value)
{void *shadow_start, *shadow_end;address = reset_tag(address); //实际就是调用了__tag_resetshadow_start = kasan_mem_to_shadow(address);shadow_end = kasan_mem_to_shadow(address + size);/* 将影子内存中对应的权限值设置成value,对于alloc来说,实际上就是设置成0 对于free来说,实际上就是设置了0xFF*/__memset(shadow_start, value, shadow_end - shadow_start);
}void kasan_unpoison_shadow(const void *address, size_t size)
{u8 tag = get_tag(address); //实际上就是调用了__tag_get,也就是tag = 0 [CONFIG_KASAN_SW_TAGS 没开】address = reset_tag(address);kasan_poison_shadow(address, size, tag);/* 如果size不是8的倍数,对最后一个影子内存的权限值设置为当前 siez & 7 的大小 *//* 对于buddy,申请的都是整页,不会走下面,这个函数slab也会调用,会走到底下 */if (size & KASAN_SHADOW_MASK) {u8 *shadow = (u8 *)kasan_mem_to_shadow(address + size);if (IS_ENABLED(CONFIG_KASAN_SW_TAGS))*shadow = tag; /* 这里走不进来 */else*shadow = size & KASAN_SHADOW_MASK;}
}void kasan_alloc_pages(struct page *page, unsigned int order)
{u8 tag;unsigned long i;if (unlikely(PageHighMem(page)))return;tag = random_tag();for (i = 0; i < (1 << order); i++)page_kasan_tag_set(page + i, tag);/* 在这里设置对应影子内存的值 */kasan_unpoison_shadow(page_address(page), PAGE_SIZE << order);
}void kasan_free_pages(struct page *page, unsigned int order)
{
#define KASAN_FREE_PAGE         0xFFif (likely(!PageHighMem(page)))kasan_poison_shadow(page_address(page),PAGE_SIZE << order,KASAN_FREE_PAGE);
}

 

2.2.2.2 SLUB内存分配器检测

TBD

2.2.2.3 全局变量检测

开启kasan后,编译器会自动识别全局变量,进行初始化,最终调用__asan_register_globals()

/* The layout of struct dictated by compiler */
struct kasan_global {const void *beg;        /* Address of the beginning of the global variable. */size_t size;            /* Size of the global variable. */size_t size_with_redzone;   /* Size of the variable + size of the red zone. 32 bytes aligned */const void *name;const void *module_name;    /* Name of the module where the global variable is declared. */unsigned long has_dynamic_init; /* This needed for C++ */
#if KASAN_ABI_VERSION >= 4struct kasan_source_location *location;
#endif
#if KASAN_ABI_VERSION >= 5char *odr_indicator;
#endif
};static void register_global(struct kasan_global *global)
{/* 按照全局变量的大小向8取整,假设size=4,那么对齐大小为8 */size_t aligned_size = round_up(global->size, KASAN_SHADOW_SCALE_SIZE);/* 全局量的起始地址+大小 设置初值,那么这里就是设置0x4 */
kasan_unpoison_shadow(global->beg, global->size);#define KASAN_GLOBAL_REDZONE 0xF9/* 对地址+对齐为起始地址  假设地址=0,那么这里起始地址就是8,到红区结束填充0Xf9 */kasan_poison_shadow(global->beg + aligned_size,global->size_with_redzone - aligned_size,KASAN_GLOBAL_REDZONE);
}void __asan_register_globals(struct kasan_global *globals, size_t size)
{int i;for (i = 0; i < size; i++)register_global(&globals[i]);
}
EXPORT_SYMBOL(__asan_register_globals);

 

2.2.2.4 栈内存检测

TBD

2.2.2.5 影子内存标记总结

类型

影子内存标记

检测类型

buddy

初始化:0

释放:0xff

use_after_free

slub

TBD

TBD

global

初始化:0xf9

global-out-of-bounds

stack

TBD

TBD

2.2.3 影子内存映射

TBD


http://www.mrgr.cn/p/01373343

相关文章

实验8-1tensorboard可视化+实验8-2tensorboard案例

版本python3.7 tensorflow版本为tensorflow-gpu版本2.6 实验8-1tensorboard可视化运行结果:代码:import tensorflow as tf# 创建默认图 graph = tf.compat.v1.get_default_graph()# 定义命名空间 with graph.as_default():with tf.name_scope(input):# fetch:就是同时运行多…

科技论文网站:中国科技论文在线

文章目录 1. Intro2. Main3. Cons Evaluation彩蛋&#xff1a;科学素质 这是作者最后一次发 这种类型的宣传 科普文章 1. Intro 中国科技论文在线是经教育部批准&#xff0c;由教育部科技发展中心主办&#xff0c; 利用现代信息技术手段&#xff0c;打破传统出版物的概念&…

虚方法

若一个实例方法声明前带有virtual关键字,那么这个方法就是虚方法。虚方法与非虚方法的最大不同是,虚方法的实现可以由派生类所取代,这种取代是通过方法的重写实现的(以后再讲)虚方法的特点:虚方法前不允许有static,abstract,或override修饰符虚方法不能是私有的,因此不能…

【Vue】如何使用Webpack实现打包操作

一、Webpack介绍 Webpack最主要的作用就是打包操作&#xff0c;由两个核心部分构成分别是“出口”与“入口”。wbepack是现在比较热门的打包工具了&#xff0c;它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。说的直白一点&#xff0c;通过webpac…

RPC(远程过程调用)详解

一、RPC是什么 RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。 二、RPC需要解决的问题 1、Call ID映射 我们怎么告诉远…

【基础算法总结】双指针算法二

双指针 1.有效三角形的个数2.和为S的两个数字3.和为S的两个数字4.四数之和 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.有效三角形的个数…

前端工程化Vue使用Node.js设置国内高速npm镜像源(踩坑记录版)

前端工程化Vue使用Node.js设置国内高速npm镜像源&#xff08;踩坑记录版&#xff09; 此篇仅为踩坑记录&#xff0c;并未成功更换高速镜像源&#xff0c;实际解决方法见文末跳转链接。 1.自身源镜像 自身镜像源创建Vue项目下载速度感人 2.更改镜像源 2.1 通过命令行配置 前提…

京东web端h5st—4.7逆向分析

声明 本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 目标网站 aHR0cHM6Ly93d3cuamQuY29tLw== 分析流程了解h5st 看了sha256相关加密算法逻辑b…

API和微服务设计的优化方式有哪些?

在构建响应迅速、用户体验良好的应用程序中&#xff0c;API性能的优化至关重要。在构建高性能的API时&#xff0c;采取综合策略是至关重要的。通过采用一系列策略&#xff0c;我们可以确保API在处理请求时高效运行&#xff0c;提供流畅的服务。 一、API和微服务设计的优化可以…

XYCTF2024-web-wp

怎么全是傻逼绕过题。 不想评价,就随便打着玩,除了最后一道java反序列化搞心态,其他的ak了:简单题不想说,http注意一下代理是用Via就行,warmup直接:http://xyctf.top:37034/?val1=240610708&val2=QNKCDZO&md5=0e215962017&XYCTF=240610708&XY=24061070…

深入mysql索引

1. 索引 索引是对数据库表中一列或多列的值进行排序的一种结构。 MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。 简单类比…

平衡树炫酷科技

炫酷平衡树科技标题是吸引你点进来的 Case 1 LCT 的 access 操作应该可以实现某种区间覆盖的操作(听同学讲的,具体的还待讨论) Case 2 众所周知,可并堆中左偏树合并是这样写的(记 \(dist\) 为 \(rd\)): merge(A, B)if !A: return Bif !B: return Aif B.val > A.valsw…

杰发科技AC7840——ADC简介(1)_双路ADC同时使用

0. 简介 1. 特性 2. 双路ADC Sample里面没有双路的&#xff0c;以为那个规则组只有一个通道&#xff0c;看了外设寄存器才发现&#xff0c;原来他的通道是双路的。 注意1: ADC硬件引脚的配置 注意2: 规则组长度设置和 RSEQ序列号和CH通道号组合应该就对应了转换顺序&#xff0…

UniAD:以规划为导向的端到端自动驾驶

文章链接 这个文章是CVPR2023 Best Paper https://arxiv.org/pdf/2212.10156 提出背景 以往的自动驾驶多数是为不同的任务场景设计部署单独的模型&#xff0c;这样子组成的系统会很复杂如图a。 图b这是多任务共享一个主干&#xff0c;但还是要分离训练&#xff0c;而且不是…

ChatGPT有记忆了?!持久记忆(Memory)功能详细解读和教程!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

如何使得 单个项目有与其他项目 不一样的对齐方式

今天修改项目的样式&#xff0c;有这么个问题&#xff0c;便记录下。 align-self属性允许单个项目有与其他项目不一样的对齐方式&#xff0c;可覆盖align-items属性。默认值为auto&#xff0c;表示继承父元素的align-items属性&#xff0c;如果没有父元素&#xff0c;则等同于…

如何解决升级IntelliJ IDEA 2024后 打开项目就自动闪退关闭问题的终极指南

title: “&#x1f42f; 解决升级IntelliJ IDEA 2024后项目自动关闭的终极指南” date: 2024-04-23 author: 猫头虎 profile: CSDN 文章目录 title: "&#x1f42f; 解决升级IntelliJ IDEA 2024后项目自动关闭的终极指南" date: 2024-04-23 author: 猫头虎 profile: …

萌面匣超萌的小金刚

一款非常精致的金属机箱来了这款机箱支持的主板:畅网微控 P5 N100、N200、N305 V3版本的开发版 特点:卖家定制了背板+定制万兆网卡的转接板说说优点:贵 这款6盘的机箱是2.5英寸的小硬盘机箱 散热不错,也不怕化了 戴尔的硬盘架很专业 支持万兆 带wifi的小机器 适合长期使用,…

Games 101: 旋转矩阵

旋转矩阵 本文主要介绍了旋转矩阵的推导,分为两种方式:旋转坐标 旋转坐标轴 以下坐标系都是右手坐标系旋转坐标 已知坐标点\(A(x_a,y_a)\), 旋转\(\theta\)角后变为坐标点\(B(x_b,y_b)\),求解旋转矩阵.\[{\large \begin{align*} \begin{split} x_a &=r_a \cdot cos(\alp…

ROS学习-启动服务端错误debug

ros2 run examples_rclpy_minimal_service service 输入这个命令用于运行服务节点,这个服务的功能是将两个数字相加,给定a,b两个数,返回sum也就是ab之和。 报错: 2024-04-27 13:11:39.105 [RTPS_TRANSPORT_SHM Error] Failed init_port fastrtps_port7412:open_and_lock_f…