1# LMS调测 2 3 4## 基本概念 5 6LMS全称为Lite Memory Sanitizer,是一种实时检测内存操作合法性的调测工具。LMS能够实时检测缓冲区溢出(buffer overflow),释放后使用(use after free) 和重复释放(double free), 在异常发生的第一时间通知操作系统,结合backtrace等定位手段,能准确定位到产生内存问题的代码行,极大提升内存问题定位效率。 7 8OpenHarmony LiteOS-A内核的LMS模块提供下面几种功能: 9 10- 支持多内存池检测; 11 12- 支持LOS_MemAlloc、LOS_MemAllocAlign、LOS_MemRealloc申请出的内存检测; 13 14- 支持安全函数的访问检测(默认开启); 15 16- 支持libc 高频函数的访问检测,包括:memset、memcpy、memmove、strcat、strcpy、strncat、strncpy。 17 18 19## 运行机制 20 21LMS使用影子内存映射标记系统内存的状态,一共可标记为三个状态:可读写,不可读写,已释放。影子内存存放在内存池的尾部。 22 23- 内存从堆上申请后,会将数据区的影子内存设置为“可读写”状态,并将头节点区的影子内存设置为“不可读写”状态。 24 25- 内存在堆上被释放时,会将被释放内存的影子内存设置为“已释放”状态。 26 27- 编译代码时,会在代码中的读写指令前插入检测函数,对地址的合法性进行检验。主要是检测访问内存的影子内存的状态值,若检测到影子内存为不可读写,则会报溢出错误;若检测到影子内存为已释放,则会报释放后使用错误。 28 29- 在内存释放时,会检测被释放地址的影子内存状态值,若检测到影子内存非可读写,则会报重复释放错误。 30 31 32## 接口说明 33 34 35### 内核态 36 37OpenHarmony LiteOS-A内核的LMS模块提供下面几种功能,接口详细信息可以查看[API](https://gitee.com/openharmony/kernel_liteos_a/blob/master/kernel/include/los_lms.h)参考。 38 39 **表1** LMS模块接口说明 40 41| 功能分类 | 接口名 | 描述 | 42| -------- | -------- | -------- | 43| 添加指定内存池被检测 | LOS_LmsCheckPoolAdd | 将指定内存池的地址范围添加到LMS的内存检测链表上,当访问的地址在链表范围内时,LMS才进行合法性校验;且LOS_MemInit接口会调用该接口,默认将初始化的内存池挂入到检测链表中。 | 44| 删除指定内存池不被检测 | LOS_LmsCheckPoolDel | 不检测指定内存池地址范围内的合法性校验。 | 45| 使能指定内存段锁保护 | LOS_LmsAddrProtect | 为某段内存地址上锁,设置为不可读写,一旦访问则报错。 | 46| 去能指定内存段锁保护 | LOS_LmsAddrDisableProtect | 为某段内存地址解锁,设置为可读写。 | 47 48 49### 用户态 50 51用户态仅提供LMS检测库,不提供对外接口。 52 53 54## 开发指导 55 56 57### 内核态开发流程 58 59开启LMS调测的典型流程如下: 60 611. 配置LMS模块相关宏。 62 配置LMS控制宏LOSCFG_KERNEL_LMS,默认关,在kernel/liteos_a目录下执行 make update_config命令配置"Kernel->Enable Lite Memory Sanitizer"中打开YES: 63 64 | 宏 | menuconfig选项 | 含义 | 取值 | 65 | -------- | -------- | -------- | -------- | 66 | LOSCFG_KERNEL_LMS | Enable Lms Feature | Lms模块的裁剪开关 | YES/NO | 67 | LOSCFG_LMS_MAX_RECORD_POOL_NUM | Lms check pool max num | LMS支持的检测内存池最大个数 | INT | 68 | LOSCFG_LMS_LOAD_CHECK | Enable lms read check | LMS内存读检测的裁剪开关 | YES/NO | 69 | LOSCFG_LMS_STORE_CHECK | Enable lms write check | LMS内存写检测的裁剪开关 | YES/NO | 70 | LOSCFG_LMS_CHECK_STRICT | Enable lms strict check, byte-by-byte | LMS内存逐字节严格检测的裁剪开关 | YES/NO | 71 722. 在被检测模块的编译脚本中,修改编译选项。 73 增加LMS检测编译选项-fsanitize=kernel-address。为避免编译器优化,增加-O0编译选项。 74 75 gcc与clang编译选项存在差异,参照如下示例: 76 77 ``` 78 if ("$ohos_build_compiler_specified" == "gcc") { 79 cflags_c = [ 80 "-O0", 81 "-fsanitize=kernel-address", 82 ] 83 } else { 84 cflags_c = [ 85 "-O0", 86 "-fsanitize=kernel-address", 87 "-mllvm", 88 "-asan-instrumentation-with-call-threshold=0", 89 "-mllvm", 90 "-asan-stack=0", 91 "-mllvm", 92 "-asan-globals=0", 93 ] 94 } 95 ``` 96 973. 重新编译,查看串口输出。如果检测到内存问题,会输出检测结果。 98 99 100#### 内核态编程实例 101 102本实例实现如下功能: 103 1041. 创建一个用于Lms测试的任务。 105 1062. 构造内存溢出错误和释放后使用错误。 107 1083. 添加-fsanitize=kernel-address后编译执行,观察输出结果。 109 110 111#### 内核态示例代码 112 113 该示例代码的测试函数可以加在 kernel /liteos_a/testsuites /kernel /src /osTest.c 中的 TestTaskEntry 中进行测试。 114 实例代码如下: 115 116```c 117#define PAGE_SIZE (0x1000U) 118#define INDEX_MAX 20 119UINT32 g_lmsTestTaskId; 120char g_testLmsPool[2 * PAGE_SIZE]; 121STATIC VOID testPoolInit(void) 122{ 123 UINT32 ret = LOS_MemInit(g_testLmsPool, 2 * PAGE_SIZE); 124 if (ret != 0) { 125 PRINT_ERR("%s failed, ret = 0x%x\n", __FUNCTION__, ret); 126 return; 127 } 128} 129static VOID LmsTestOsmallocOverflow(VOID) 130{ 131 PRINTK("\n######%s start ######\n", __FUNCTION__); 132 UINT32 i; 133 CHAR *str = (CHAR *)LOS_MemAlloc(g_testLmsPool, INDEX_MAX); 134 PRINTK("str[%2d]=0x%2x ", INDEX_MAX, str[INDEX_MAX]); /* trigger heap overflow at str[INDEX_MAX] */ 135 PRINTK("\n######%s stop ######\n", __FUNCTION__); 136} 137static VOID LmsTestUseAfterFree(VOID) 138{ 139 PRINTK("\n######%s start ######\n", __FUNCTION__); 140 UINT32 i; 141 CHAR *str = (CHAR *)LOS_MemAlloc(g_testLmsPool, INDEX_MAX); 142 (VOID)LOS_MemFree(g_testLmsPool, str); 143 PRINTK("str[%2d]=0x%2x ", 0, str[0]); /* trigger use after free at str[0] */ 144 PRINTK("\n######%s stop ######\n", __FUNCTION__); 145} 146VOID LmsTestCaseTask(VOID) 147{ 148 testPoolInit(); 149 LmsTestOsmallocOverflow(); 150 LmsTestUseAfterFree(); 151} 152UINT32 Example_Lms_test(VOID) 153{ 154 UINT32 ret; 155 TSK_INIT_PARAM_S lmsTestTask; 156 /* 创建用于lms测试的任务 */ 157 memset(&lmsTestTask, 0, sizeof(TSK_INIT_PARAM_S)); 158 lmsTestTask.pfnTaskEntry = (TSK_ENTRY_FUNC)LmsTestCaseTask; 159 lmsTestTask.pcName = "TestLmsTsk"; /* 测试任务名称 */ 160 lmsTestTask.uwStackSize = 0x800; // 0x800: lms test task stack size 161 lmsTestTask.usTaskPrio = 5; // 5: lms test task priority 162 lmsTestTask.uwResved = LOS_TASK_STATUS_DETACHED; 163 ret = LOS_TaskCreate(&g_lmsTestTaskId, &lmsTestTask); 164 if (ret != LOS_OK) { 165 PRINT_ERR("LmsTestTask create failed .\n"); 166 return LOS_NOK; 167 } 168 return LOS_OK; 169} 170LOS_MODULE_INIT(Example_Lms_test, LOS_INIT_LEVEL_KMOD_EXTENDED); 171``` 172 173 174#### 内核态结果验证 175 176 输出结果如下: 177 178``` 179######LmsTestOsmallocOverflow start ###### 180[ERR][KProcess:LmsTestCaseTask]* Kernel Address Sanitizer Error Detected Start * 181[ERR][KProcess:LmsTestCaseTask]Heap buffer overflow error detected 182[ERR][KProcess:LmsTestCaseTask]Illegal READ address at: [0x4157a3c8] 183[ERR][KProcess:LmsTestCaseTask]Shadow memory address: [0x4157be3c : 4] Shadow memory value: [2] 184OsBackTrace fp = 0x402c0f88 185runTask->taskName = LmsTestCaseTask 186runTask->taskID = 2 187***backtrace begin*** 188traceback fp fixed, trace using fp = 0x402c0fd0 189traceback 0 -- lr = 0x400655a4 fp = 0x402c0ff8 190traceback 1 -- lr = 0x40065754 fp = 0x402c1010 191traceback 2 -- lr = 0x40044bd0 fp = 0x402c1038 192traceback 3 -- lr = 0x40004e14 fp = 0xcacacaca 193[LMS] Dump info around address [0x4157a3c8]: 194 [0x4157a3a0]: 00 00 00 00 00 00 00 00 | [0x4157be3a | 0]: 1 1 195 [0x4157a3a8]: ba dc cd ab 00 00 00 00 | [0x4157be3a | 4]: 2 2 196 [0x4157a3b0]: 20 00 00 80 00 00 00 00 | [0x4157be3b | 0]: 2 0 197 [0x4157a3b8]: 00 00 00 00 00 00 00 00 | [0x4157be3b | 4]: 0 0 198 [0x4157a3c0]: 00 00 00 00 00 00 00 00 | [0x4157be3c | 0]: 0 0 199 [0x4157a3c8]: [ba] dc cd ab a8 a3 57 41 | [0x4157be3c | 4]: [2] 2 200 [0x4157a3d0]: 2c 1a 00 00 00 00 00 00 | [0x4157be3d | 0]: 2 3 201 [0x4157a3d8]: 00 00 00 00 00 00 00 00 | [0x4157be3d | 4]: 3 3 202 [0x4157a3e0]: 00 00 00 00 00 00 00 00 | [0x4157be3e | 0]: 3 3 203 [0x4157a3e8]: 00 00 00 00 00 00 00 00 | [0x4157be3e | 4]: 3 3 204 [0x4157a3f0]: 00 00 00 00 00 00 00 00 | [0x4157be3f | 0]: 3 3 205[ERR][KProcess:LmsTestCaseTask]* Kernel Address Sanitizer Error Detected End * 206str[20]=0xffffffba 207######LmsTestOsmallocOverflow stop ###### 208###### LmsTestUseAfterFree start ###### 209[ERR][KProcess:LmsTestCaseTask]* Kernel Address Sanitizer Error Detected Start * 210[ERR][KProcess:LmsTestCaseTask]Use after free error detected 211[ERR][KProcess:LmsTestCaseTask]Illegal READ address at: [0x4157a3d4] 212[ERR][KProcess:LmsTestCaseTask]Shadow memory address: [0x4157be3d : 2] Shadow memory value: [3] 213OsBackTrace fp = 0x402c0f90 214runTask->taskName = LmsTestCaseTask 215runTask->taskID = 2 216***backtrace begin*** 217traceback fp fixed, trace using fp = 0x402c0fd8 218traceback 0 -- lr = 0x40065680 fp = 0x402c0ff8 219traceback 1 -- lr = 0x40065758 fp = 0x402c1010 220traceback 2 -- lr = 0x40044bd0 fp = 0x402c1038 221traceback 3 -- lr = 0x40004e14 fp = 0xcacacaca 222[LMS] Dump info around address [0x4157a3d4]: 223 [0x4157a3a8]: ba dc cd ab 00 00 00 00 | [0x4157be3a | 4]: 2 2 224 [0x4157a3b0]: 20 00 00 80 00 00 00 00 | [0x4157be3b | 0]: 2 0 225 [0x4157a3b8]: 00 00 00 00 00 00 00 00 | [0x4157be3b | 4]: 0 0 226 [0x4157a3c0]: 00 00 00 00 00 00 00 00 | [0x4157be3c | 0]: 0 0 227 [0x4157a3c8]: ba dc cd ab a8 a3 57 41 | [0x4157be3c | 4]: 2 2 228 [0x4157a3d0]: 2c 1a 00 00 [00] 00 00 00 | [0x4157be3d | 0]: 2 [3] 229 [0x4157a3d8]: 00 00 00 00 00 00 00 00 | [0x4157be3d | 4]: 3 3 230 [0x4157a3e0]: 00 00 00 00 00 00 00 00 | [0x4157be3e | 0]: 3 3 231 [0x4157a3e8]: ba dc cd ab c8 a3 57 41 | [0x4157be3e | 4]: 2 2 232 [0x4157a3f0]: 0c 1a 00 00 00 00 00 00 | [0x4157be3f | 0]: 2 3 233 [0x4157a3f8]: 00 00 00 00 00 00 00 00 | [0x4157be3f | 4]: 3 3 234[ERR][KProcess:LmsTestCaseTask]* Kernel Address Sanitizer Error Detected End * 235str[ 0]=0x 0 236######LmsTestUseAfterFree stop ###### 237``` 238 239输出的关键信息包括: 240 241- 错误类型: 242 - Heap buffer overflow堆内存越界 243 - Use after free 释放后使用 244 245- 错误操作: 246 - Illegal Read非法读 247 - Illegal Write非法写 248 - Illegal Double free重复释放 249 250- 上下文: 251 - 当前任务信息(taskName, taskId) 252 - 回溯栈(backtrace) 253 254- 出错地址的内存信息: 255 - 内存的值、及对应影子内存的值 256 - 内存地址:内存值| [影子内存地址 | 影子内存字节内偏移]:影子内存值 257 - 影子内存值:0(可读可写)、3(已释放)、2(红区)、1(填充值)。 258 259 260### 用户态开发流程 261 262在待检测的app编译脚本中,添加如下参数即可, 完整示例可参见 [/kernel/liteos_a/apps/lms/BUILD.gn](https://gitee.com/openharmony/kernel_liteos_a/blob/master/apps/lms/BUILD.gn)。 263 264 265``` 266if ("$ohos_build_compiler_specified" == "gcc") { 267 cflags_c = [ 268 "-O0", 269 "-fsanitize=kernel-address", 270 "-funwind-tables", 271 "-fasynchronous-unwind-tables", 272 ] 273 } else { 274 cflags_c = [ 275 "-O0", 276 "-fsanitize=kernel-address", 277 "-mllvm", 278 "-asan-instrumentation-with-call-threshold=0", 279 "-mllvm", 280 "-asan-stack=0", 281 "-mllvm", 282 "-asan-globals=0", 283 "-funwind-tables", 284 "-fasynchronous-unwind-tables", 285 ] 286 } 287 ldflags = [ 288 "-rdynamic", 289 "-lunwind", 290 "-lusrlms", 291 "-Wl,--wrap=realloc", 292 "-Wl,--wrap=calloc", 293 "-Wl,--wrap=malloc", 294 "-Wl,--wrap=free", 295 "-Wl,--wrap=valloc", 296 "-Wl,--wrap=aligned_alloc", 297 "-Wl,--wrap=memset", 298 "-Wl,--wrap=memcpy", 299 "-Wl,--wrap=memmove", 300 "-Wl,--wrap=strcpy", 301 "-Wl,--wrap=strcat", 302 ] 303 deps = [ "//kernel/liteos_a/kernel/extended/lms/usr:usrlmslib" ] 304``` 305 306 307#### 用户态编程实例 308 309本实例实现如下功能: 310 3111. 构造内存溢出错误和释放后使用错误。 312 3132. 添加对应编译选项后,重新编译执行。 314 315 316#### 用户态示例代码 317 318 实例代码如下: 319 320```c 321static void BufWriteTest(void *buf, int start, int end) 322{ 323 for (int i = start; i <= end; i++) { 324 ((char *)buf)[i] = 'a'; 325 } 326} 327static void BufReadTest(void *buf, int start, int end) 328{ 329 char tmp; 330 for (int i = start; i <= end; i++) { 331 tmp = ((char *)buf)[i]; 332 } 333} 334static void LmsMallocTest(void) 335{ 336 printf("\n-------- LmsMallocTest Start --------\n"); 337 char *buf = (char *)malloc(16); // 16: buffer size for test 338 BufReadTest(buf, -1, 16); 339 free(buf); 340 printf("\n-------- LmsMallocTest End --------\n"); 341} 342static void LmsFreeTest(void) 343{ 344 printf("\n-------- LmsFreeTest Start --------\n"); 345 char *buf = (char *)malloc(16); // 16: buffer size for test 346 free(buf); 347 BufReadTest(buf, 1, 1); 348 free(buf); 349 printf("\n-------- LmsFreeTest End --------\n"); 350} 351int main(int argc, char * const * argv) 352{ 353 printf("\n############### Lms Test start ###############\n"); 354 char *tmp = (char *)malloc(5000); // 5000: temp buffer size 355 LmsMallocTest(); 356 LmsFreeTest(); 357 printf("\n############### Lms Test End ###############\n"); 358} 359``` 360 361 362#### 用户态结果验证 363 364 输出结果如下: 365 366``` 367* Lite Memory Sanitizer Error Detected * 368Heap buffer overflow error detected! 369Illegal READ address at: [0x1f8b3edf] 370Shadow memory address: [0x3d34d3ed : 6] Shadow memory value: [2] 371Accessible heap addr 0 372Heap red zone 2 373Heap freed buffer 3 374Dump info around address [0x1f8b3edf]: 375 [0x1f8b3eb8]: 74 55 8b 1f 74 55 8b 1f | [0x3d34d3eb | 4]: 0 0 376 [0x1f8b3ec0]: f8 9c 8b 1f 00 00 00 00 | [0x3d34d3ec | 0]: 0 0 377 [0x1f8b3ec8]: 00 00 00 00 9c fc fc fc | [0x3d34d3ec | 4]: 0 0 378 [0x1f8b3ed0]: 21 00 00 00 41 00 00 00 | [0x3d34d3ed | 0]: 0 0 379 [0x1f8b3ed8]: 60 55 8b 1f 60 55 8b [1f]| [0x3d34d3ed | 4]: 2 [2] 380 [0x1f8b3ee0]: 50 4e 0b 00 00 00 00 00 | [0x3d34d3ee | 0]: 0 0 381 [0x1f8b3ee8]: 09 00 00 00 00 00 00 00 | [0x3d34d3ee | 4]: 0 0 382 [0x1f8b3ef0]: 00 00 00 00 08 03 09 00 | [0x3d34d3ef | 0]: 2 2 383 [0x1f8b3ef8]: 00 00 00 00 00 00 00 00 | [0x3d34d3ef | 4]: 2 2 384* Lite Memory Sanitizer Error Detected End * 385Backtrace() returned 5 addresses 386 #01: <LMS_ReportError+0x284>[0x4d6c] -> ./sample_usr_lms 387 #02: <(null)+0x2004074>[0x4074] -> ./sample_usr_lms 388 #03: <(null)+0x2003714>[0x3714] -> ./sample_usr_lms 389 #04: <main+0x40>[0x363c] -> ./sample_usr_lms 390 #05: <(null)+0x1f856f30>[0x56f30] -> /lib/libc.so 391-------- LMS_malloc_test End -------- 392* Lite Memory Sanitizer Error Detected * 393Use after free error detected! 394Illegal Double free address at: [0x1f8b3ee0] 395Shadow memory address: [0x3d34d3ee : 0] Shadow memory value: [3] 396Accessible heap addr 0 397Heap red zone 2 398Heap freed buffer 3 399Dump info around address [0x1f8b3ee0]: 400 [0x1f8b3ec0]: f8 9c 8b 1f 00 00 00 00 | [0x3d34d3ec | 0]: 0 0 401 [0x1f8b3ec8]: 00 00 00 00 fc fd fc fc | [0x3d34d3ec | 4]: 0 0 402 [0x1f8b3ed0]: 21 00 00 00 20 01 00 00 | [0x3d34d3ed | 0]: 0 0 403 [0x1f8b3ed8]: 60 55 8b 1f 60 55 8b 1f | [0x3d34d3ed | 4]: 2 2 404 [0x1f8b3ee0]: [20] 60 9a 1f 40 61 9a 1f | [0x3d34d3ee | 0]: [3] 3 405 [0x1f8b3ee8]: 60 62 9a 1f 80 63 9a 1f | [0x3d34d3ee | 4]: 3 3 406 [0x1f8b3ef0]: 20 40 8b 1f 20 20 8b 1f | [0x3d34d3ef | 0]: 3 3 407 [0x1f8b3ef8]: 00 00 00 00 00 00 00 00 | [0x3d34d3ef | 4]: 3 3 408 [0x1f8b3f00]: 00 00 00 00 00 00 00 00 | [0x3d34d3f0 | 0]: 3 3 409* Lite Memory Sanitizer Error Detected End * 410Backtrace() returned 5 addresses 411 #01: <LMS_ReportError+0x284>[0x4d6c] -> ./sample_usr_lms 412 #02: <LMS_free+0xcc>[0x5548] -> ./sample_usr_lms 413 #03: <(null)+0x2003fc4>[0x3fc4] -> ./sample_usr_lms 414 #04: <main+0x68>[0x3664] -> ./sample_usr_lms 415 #05: <(null)+0x1f856f30>[0x56f30] -> /lib/libc.so 416-------- LMS_free_test End -------- 417``` 418 419输出的Backtrace中包含地址所在的文件名,用户需查找对应文件中地址对应的代码行号。 420