Oracle SPARC M7 处理器提供了软件芯片化技术,借助此技术,软件可以更快更可靠地运行。其中一种软件芯片化功能是芯片保护内存 (Silicon Secured Memory, SSM),以前称为应用程序数据完整性 (Application Data Integrity, ADI),其电路系统会检测常见的内存访问错误,这些错误会导致运行时数据损坏。
错误的代码或对服务器内存的恶意攻击会导致这些错误。例如,众所周知缓冲区溢出是安全漏洞的一个主要来源。内存中数据库由于在内存中有重要的数据,因而会使应用程序更容易出现此类错误。
芯片保护内存技术通过向应用程序的内存指针及其指向的内存添加版本号,避免了在优化的生产代码中出现内存损坏情况。如果指针版本号与内容版本号不匹配,内存访问就会中止。芯片保护内存技术可以与采用系统级编程语言(例如 C 或 C++)编写的应用程序一起使用,这类应用程序更容易由于软件错误而出现内存损坏。
Oracle Developer Studio 12.5 包括 libdiscoverADI.so 库(也称为 discover ADI 库),该库提供了更新的 malloc() 库例程,这些库例程可确保为相邻数据结构提供不同的版本号。借助这些版本号,处理器的 SSM 技术可以检测内存访问错误,例如缓冲区溢出。内存结构释放后内存内容版本号会更改,以防止访问过时的指针。有关 discover 和 libdiscoverADI.so 捕获的错误的更多信息,请参见libdiscoverADI 库捕获的错误。
除了在生产环境中使用 SSM 检测潜在的内存损坏问题外,还可以在应用程序开发过程中使用该技术,从而确保在应用程序测试和认证过程中捕获此类错误。内存损坏错误是极难发现的,因为在损坏发生后要过很长时间应用程序才会遇到损坏的数据。discover 工具和 libdiscoverADI.so 库(属于 Oracle Developer Studio 开发工具套件的一部分)提供了更多的应用程序信息,利用这些信息可以更方便地查找和修复错误的代码。
discover ADI 库 libdiscoverADI.so 会报告那些导致无效内存访问的编程错误。可以通过以下两种方式使用该库:
通过使用 LD_PRELOAD_64 环境变量将 discover ADI 库预装入应用程序。此方法将以 ADI 模式运行应用程序中的所有 64 位二进制文件,例如,如果正常运行一个名为 server 的应用程序,命令将如下所示:
$ LD_PRELOAD_64=install-dir/lib/compilers/sparcv9/libdiscoverADI.so server
对特定二进制文件联合使用 ADI 模式和带 –i adi 选项的 discover 命令。
% discover -i adi a.out % a.out
缺省情况下在 a.out.html 文件中报告错误。有关 discover 报告的更多信息,请参见分析 discover 报告和输出选项。
libdiscoverADI.so 库会捕获以下错误:
数组越界访问 (ABR/ABW)
访问释放的内存 (FMR/FMW)
访问过时的指针(FMR/FMW 的一种特殊类型)
读取/写入未分配的内存 (UAR/UAW)
重复释放内存 (Double Free Memory, DFM)
有关上述每种错误类型的更多信息,请参见内存访问错误和警告。
有关完整示例,请参见使用 discover ADI 模式的示例。
以下选项确定使用 ADI 进行检测时,在 discover 报告中生成的信息的精确度和信息量。
将此标志设置为 on 时,discover ADI 库会报告错误的位置以及错误堆栈跟踪。此信息足以捕获错误,但不是始终都足以修复错误。有关如何更改此选项的运行时行为的信息,请参见SUNW_DISCOVER_OPTIONS 环境变量。
此标志还会生成在何处分配和释放了违规内存区域的信息。例如,输出可能会显示错误为 Array out of Bounds Access(数组越界访问)以及在何处分配了此数组。如果设置为 off,则不会报告分配和堆栈跟踪情况。缺省值为 on。
如果缓冲区溢出访问发生在缓冲区末尾之后或缓冲区开头之前,且距离缓冲区末尾或缓冲区开头很远。
如果 libdiscoverADI.so 达到了资源限制。在这种情况下,discover 可能记录了必要的分配堆栈跟踪,足以确定错误是否为缓冲区溢出。
如果将此标志设置为 off,ADI 将以非精确模式运行。在非精确模式下,会在确切指令执行后,再过几条指令(源代码行)捕获内存写入错误。要启用精确模式,请将此标志设置为 on,这是缺省值。
要获得更好的运行时性能,可以指定 –A off 或 –P off,或同时将这两个选项设置为 off。
企业应用程序通常可以管理自己的内存,而不使用 (libc) malloc(3C) 系统库。对于这些情况,libdiscoverADI.so 的正常用法(即插入 malloc())将无法捕获内存损坏。Oracle Developer Studio 提供了 API,所以在分配或释放内存区域时,企业应用程序可以通知 libdiscoverADI.so。libdiscoverADI.so 管理 SSM 版本化、信号处理和错误报告。这些 API 和 libdiscoverADI.so 仅适用于由 mmap(2) 或 shmget(2) 分配的内存。
提供的 API 如下:
分配器将即将超过的内存的一个指针和大小传递给请求内存的客户机。如果内存未启用 ADI,则库将使用 memcntl(2) 调用。返回一个版本化的指针,分配器必须将其传递给客户机。
分配器将传递一个指针和要释放的内存区域的大小。
分配器可以使用指针运算访问客户机内存的元数据。客户机内存通常已版本化,但是分配器元数据未版本化。在这种情况下,需要使用此 API。分配器将传递任意指针,并返回一个未版本化的等效指针。
有时,分配器会与版本化的指针失去联系。此 API 接受任意指针,并返回一个带有正确版本的等效指针。使用返回值解除任何引用都不会导致 ADI SEGV。
如果将内存区域重用于不同的目的,则需要清除 ADI 版本。例如,如果某个内存区域为了供分配器客户机使用而进行了版本化,而现在分配器将其用于元数据。此函数接受一个版本化或未版本化的指针和大小,将该区域的版本设置为 0,并返回一个未版本化的指针。
您的应用程序可能会管理自己的内存分配和释放列表,例如,在程序中分配大的内存块,然后对其进行细分。对于您所管理的内存,要了解如何使用 ADI 版本控制 API 捕获这类内存错误的信息,请参见 Using Application Data Integrity and Oracle Solaris Studio to Find and Fix Memory Access Errors。
要使用这些 API,请在源代码中包括以下头文件:
install-dir/lib/compilers/include/cc/discoverADI_API.h
然后,执行以下操作之一:
将源代码与 install-dir/lib/compilers/sparcV9/libadiplugin.so 关联
将环境变量 LD_PRELOAD_64 设置为 install-dir/lib/compilers/sparcV9/libadiplugin.so
运行以下命令:discover -i adi executable
下面是一个使用定制内存分配器的示例。
示例 1 使用定制内存分配器下面是一个头文件的示例:
% cat allocator.h #define GRANULARITY 32 void *mymalloc(size_t len); void myfree(void *ptr);
下面是一个使用定制内存分配器和 libdiscoverADI 库的源代码的示例:
% cat allocator.c #include <sys/mman.h> #include <stdio.h> #include <stdlib.h> #include <ucontext.h> #include <errno.h> #include <dlfcn.h> #include <unistd.h> #include <assert.h> #include "allocator.h" #include "discoverADI_API.h" #pragma init (setup_allocator) #pragma fini (takedown_allocator) #define MAX_ALLOCATIONS 1024 #define START_ADDRESS 0x200000000 uint64_t next_available_address = 0; size_t allocation_table[MAX_ALLOCATIONS/GRANULARITY]; static void setup_allocator() { // mmap with a specific address void *addr = mmap((void *) START_ADDRESS, MAX_ALLOCATIONS, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); if (addr == MAP_FAILED) { fprintf(stderr, "mmap failed {%d}\n", errno); exit(1); } next_available_address = (uint64_t) addr; } static void takedown_allocator() { if (munmap((void *) START_ADDRESS, MAX_ALLOCATIONS) != 0) { fprintf(stderr, "munmap failed {%d}\n", errno); exit(1); } } // Simple malloc void *mymalloc(size_t size) { void *vaddr = (void *) next_available_address; next_available_address += size; assert(next_available_address < (START_ADDRESS+MAX_ALLOCATIONS)); int index = ((uint64_t)vaddr-START_ADDRESS)/GRANULARITY; allocation_table[index] = size; // Tell libdiscoverADI.so about the allocation, get a versioned // pointer vaddr = adi_mark_malloc(vaddr, size); // Return the versioned pointer return vaddr; } // Simple free void myfree(void *ptr) { int index = ((uint64_t)ptr-START_ADDRESS)/GRANULARITY; size_t size = allocation_table[index]; // Tell libdiscoverADI.so about the free(). adi_mark_free(ptr, size); }
% cat simple.c #include <stdio.h> #include <stdlib.h> #include "allocator.h" int main() { // Allocate char *buf1 = (char *) mymalloc(GRANULARITY*2); buf1[0] = 'x'; // Read before free printf("Read buf1[0] before free: [0x%p] %c\n", buf1, buf1[0]); // Allocate char *buf2 = (char *) mymalloc(GRANULARITY*2); // Buf1fer overflow buf1 printf("Read buf1[%d] past end: [0x%p] %c\n", GRANULARITY*2, buf1+GRANULARITY*2, buf1[GRANULARITY*2]); // Free myfree(buf1); // Read after free printf("Read buf1[0] after free: [0x%p] %c\n", buf1, buf1[0]); return 0; }
要编译并运行此示例,请运行以下命令:
% cc –m64 –g –I install-dir/lib/compilers/include/cc allocator.c simple.c install-dir/lib/compilers/sparcv9/libadiplugin.so –o simple % ./simple
下面是生成的 HTML 报告:
discover 的 ADI 模式只能用于符合以下要求的 64 位应用程序:采用 SPARC M7 芯片,此芯片至少运行 Oracle Solaris 11.2.8 或 Oracle Solaris 11.3。
类似于针对内存检查的检测,如果 libdiscoverADI.so 的函数插入相同的分配函数,预装入的库可能发生冲突。有关更多信息,请参见使用预装入或审计的二进制文件不兼容。
使用 libdiscoverADI 检查代码的其他限制如下:
只提供堆检查。不提供堆栈检查、静态数组越界检查和泄露检测。
如果应用程序使用 64 位地址中未使用的位来存储元数据,则不适用于此类应用程序。某些 64 位应用程序可能使用 64 位地址中当前未使用的高位来存储元数据(例如,锁)。此类应用程序无法与 ADI 模式下的 discover 一起使用,因为该功能使用 64 位地址中的 4 个最高位存储版本信息。
如果应用程序的指针运算有关于堆地址的假设(例如两个连续分配之间的距离),则可能不适用于此类应用程序。
与 memcheck 模式(使用 –i memcheck 进行检测)不同,如果应用程序在可执行文件中重新定义了标准内存分配函数,则 ADI 模式将不捕获错误。如果应用程序在库中重新定义了标准内存分配函数,则 ADI 模式会起作用。
缓冲区溢出的分辨度是 64 字节。对于 64 字节对齐的分配,libdiscoverADI.so 将按 1 字节或更多字节捕获溢出。对于非 64 位对齐的分配,报告缓冲区溢出时可能会丢失几个字节。一般情况下,不捕获 1 至 63 字节的溢出,具体取决于分配的对齐方式以及 libdiscoverADI.so 在高速缓存行中放置分配的位置。
使用 –xipo=2 编译的二进制文件可能具有经过内存优化的代码,这些代码处理地址的方式可能导致误报 SSM 错误,因而也会由于陷阱处理而导致性能降级,但这种几率很小。
本节提供了一个存在数组越界错误的代码样例,这些错误将通过 ADI 模式的 discover 进行捕获和报告。
假设以下样例代码位于名为 testcode.c 的文件中。
#include <stdio.h> #include <stdlib.h> int main() { char *arr1 = (char*)malloc(sizeof(char)*STRSZ); char *arr2 = (char*)malloc(sizeof(char)*STRSZ); // Buffer overflow due to using "<=" instead of "<" for (int i=0; i <= STRSZ; i++) arr1[i] = arr2[i]; // ABR/ABW free(arr1); free(arr2); char *arr3 = (char*)malloc(sizeof(char)*STRSZ); arr3[0] = arr2[0]; // FMR arr2[0] = arr3[3]; // FMWarr3[1] = arr1[1]; // Possible stale pointer/FMR free(arr2); // Double Free return 0; }
您使用以下命令生成测试代码:
$ cc testcode.c -g -m64
要使用 ADI 模式执行此样例应用程序,请使用以下命令:
$ discover -w - -i adi -o a.out.adi a.out $ ./a.out.adi
此命令在 discover 报告中生成以下输出。有关读取和理解这些报告的更多信息,请参见分析 discover 报告。
ERROR 1 (ABR): reading memory beyond array bounds at address 0x3fffffff7d47e080 {memory: v8}: main() + 0x48 <a.c:13> 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) 13:=> arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16: free(arr2); was allocated at (0x3fffffff7d47e040, 64 bytes): main() + 0x1c <a.c:9> 6: { 7: 8: char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9:=> char *arr2 = (char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) ERROR 2 (ABW): writing to memory beyond array bounds at address 0x2fffffff7d47e040 {memory: v3}: main() + 0x54 <a.c:13> 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) 13:=> arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16: free(arr2); was allocated at (0x2fffffff7d47e000, 64 bytes): main() + 0x8 <a.c:8> 5: int main() 6: { 7: 8:=> char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9: char *arr2 =(char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" ERROR 3 (FMR): reading from freed memory at address 0x3fffffff7d47e040 {memory: v10}: main() + 0xa0 <a.c:20> 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: 20:=> arr3[0] = arr2[0]; // FMR 21: arr2[0] = arr3[3]; // FMW 22: arr3[1] = arr1[1]; // Possible stale pointer/FMR 23: was allocated at (0x3fffffff7d47e040, 64 bytes): main() + 0x1c <a.c:9> 6: { 7: 8: char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9:=> char *arr2 = (char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) freed at (0x3fffffff7d47e040, 64 bytes): main() + 0x80 <a.c:16> 13: arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16:=> free(arr2); 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: ERROR 4 (FMW): writing to freed memory at address 0x3fffffff7d47e040 {memory: v10}: main() + 0xb8 <a.c:21> 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: 20: arr3[0] = arr2[0]; // FMR 21:=> arr2[0] = arr3[3]; // FMW 22: arr3[1] = arr1[1]; // Possible stalepointer/FMR 23: 24: free(arr2); // Double Free was allocated at (0x3fffffff7d47e040, 64 bytes): main() + 0x1c <a.c:9> 6: { 7: 8: char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9:=> char *arr2 =(char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) freed at (0x3fffffff7d47e040, 64 bytes): main() + 0x80 <a.c:16> 13: arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16:=> free(arr2); 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: ERROR 5 (FMR): reading from freed memory at address 0x2fffffff7d47e001 {memory: v3}: main() + 0xc0 <a.c:22> 19: 20: arr3[0] = arr2[0]; // FMR 21: arr2[0] = arr3[3]; // FMW 22:=> arr3[1] = arr1[1]; // Possible stale pointer/FMR 23: 24: free(arr2); // Double Free 25: was allocated at (0x2fffffff7d47e000, 64 bytes): main() + 0x8 <a.c:8> 5: int main() 6: { 7: 8:=> char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9: char *arr2 = (char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" freed at (0x2fffffff7d47e000, 64 bytes): main() + 0x74 <a.c:15> 12: for (int i=0; i <= STRSZ; i++) 13: arr1[i] = arr2[i]; //ABR/ABW 14: 15:=> free(arr1); 16: free(arr2); 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); ERROR 6 (DFM): double freeing memory at address 0x3fffffff7d47e040 {memory: v10}: main() + 0xd0 <a.c:24> 21: arr2[0] = arr3[3]; // FMW 22: arr3[1] = arr1[1]; // Possible stale pointer/FMR 23: 24:=> free(arr2); // Double Free 25: 26: return 0; 27: } was allocated at (0x3fffffff7d47e040, 64 bytes): main() + 0x1c <a.c:9> 6: { 7: 8: char *arr1 = (char*)malloc(sizeof(char)*STRSZ); 9:=> char *arr2 = (char*)malloc(sizeof(char)*STRSZ); 10: 11: // Buffer overflow due to using "<=" instead of "<" 12: for (int i=0; i <= STRSZ; i++) freed at (0x3fffffff7d47e040, 64 bytes): main() + 0x80 <a.c:16> 13: arr1[i] = arr2[i]; // ABR/ABW 14: 15: free(arr1); 16:=> free(arr2); 17: 18: char *arr3 = (char*)malloc(sizeof(char)*STRSZ); 19: DISCOVER SUMMARY: unique errors : 6 (6 total)