Oracle SPARC M7 处理器提供了软件芯片化技术,借助此技术,软件可以更快更可靠地运行。其中一种软件芯片化功能是芯片保护内存 (Silicon Secured Memory, SSM),以前称为应用程序数据完整性 (Application Data Integrity, ADI),其电路系统会检测常见的内存访问错误,这些错误会导致运行时数据损坏。
错误的代码或对服务器内存的恶意攻击会导致这些错误。例如,众所周知缓冲区溢出是安全漏洞的一个主要来源。内存中数据库由于在内存中有重要的数据,因而会使应用程序更容易出现此类错误。
芯片保护内存技术通过向应用程序的内存指针及其指向的内存添加版本号,避免了在优化的生产代码中出现内存损坏情况。如果指针版本号与内容版本号不匹配,内存访问就会中止。芯片保护内存技术可以与采用系统级编程语言(例如 C 或 C++)编写的应用程序一起使用,这类应用程序更容易由于软件错误而出现内存损坏。
Oracle Solaris Studio 12.4 4/15 平台特定增强 (Platform Specific Enhancement, PSE) 包括 libdiscoverADI.so 库(也称为 discover ADI 库),该库提供了更新的 malloc() 库例程,这些库例程可确保为相邻数据结构提供不同的版本号。借助这些版本号,处理器的 SSM 技术可以检测缓冲区溢出问题。内存结构释放后内存内容版本号会更改,以防止访问过时的指针。有关 discover 和 libdiscoverADI.so 捕获的错误的更多信息,请参见libdiscoverADI 捕获的错误。
除了在生产环境中使用芯片保护内存技术检测潜在的内存损坏问题外,还可以在应用程序开发过程中使用该技术,从而确保在应用程序测试和认证过程中捕获此类错误。内存损坏错误是极难发现的,因为在损坏发生后要过很长时间应用程序才会遇到损坏的数据。discover 工具和 libdiscoverADI.so 库(属于 Oracle Solaris Studio 开发工具套件的一部分)提供了更多的应用程序信息,利用这些信息可以更方便地查找和修复错误的代码。
discover ADI 库 libdiscoverADI 会报告那些导致无效内存访问的编程错误。可以通过以下两种方式使用该库:
通过使用 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)
有关上述每种错误类型的更多信息,请参见内存访问错误和警告。
您的应用程序可能会管理自己的内存分配和释放列表,例如,在程序中分配大的内存块,然后对其进行细分。对于您所管理的内存,要了解如何使用 ADI 版本控制 API 捕获这类内存错误的信息,请参见Using Application Data Integrity and Oracle Solaris Studio to Find and Fix Memory Access Errors。
有关完整示例,请参见使用 discover ADI 模式的示例。
以下选项确定使用 ADI 模式进行检测时,在 discover 报告中生成的信息的精确度和数量。
将此标志设置为 on 时,discover ADI 库会报告错误的位置以及错误堆栈跟踪。此信息足以捕获错误,但不是始终都足以修复错误。此标志还会生成在何处分配和释放了违规内存区域的信息。例如,输出可能会显示错误为 Array out of Bounds Access(数组越界访问)以及在何处分配了此数组。如果设置为 off,则不会报告分配和堆栈跟踪情况。缺省值为 on。
如果缓冲区溢出访问发生在缓冲区末尾之后或缓冲区开头之前,且距离缓冲区末尾或缓冲区开头很远。
如果 libdiscoverADI.so 达到了资源限制。在这种情况下,discover 可能记录了必要的分配堆栈跟踪,足以确定错误是否为缓冲区溢出。
如果将此标志设置为 off,ADI 将以非精确模式运行。在非精确模式下,会在确切指令执行后,再过几条指令(源代码行)捕获内存写入错误。要启用精确模式,请将此标志设置为 on,这是缺省值。
要获得更好的运行时性能,可以指定 –A off 或 –P off,或同时将这两个选项设置为 off。
discover 的 ADI 模式只能用于符合以下要求的 64 位应用程序:采用 SPARC M7 芯片,此芯片至少运行 Oracle Solaris 11.2.8 或 Oracle Solaris 11.3,且系统安装了 Oracle Solaris Studio 12.4 4/15 平台特定增强 (Platform Specific Enhancement, PSE)。
类似于针对内存检查的检测,如果 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 编译的二进制文件可能具有经过内存优化的代码,这些代码处理地址的方式可能导致误报 ADI 错误,并由于陷阱处理而导致性能降级,但这种几率很小。
本节提供了一个存在数组越界错误的代码样例,这些错误将通过 ADI 模式的 discover 进行捕获和报告。
假设以下样例代码位于名为 testcode.c 的文件中。
#include <stdio.h> #include <stdlib.h> int main() { char *x = (char*)malloc(512); int *y = (int*)malloc(20*sizeof(int)); char *z = (char*)malloc(64); x[-14] = 0; y[-10] = 0; z[-4] = 0; x[16] = 0; y[20] = 0; z[64] = 0; x[20] = 0; y[26] = 0; z[120] = 0; }
您使用以下命令生成测试代码:
$ cc testcode.c -g -m64
要使用 ADI 模式执行此样例应用程序,请使用以下命令:
$ discover -w - -i adi -o a.out.adi a.out $ ./a.out.adi
此命令在 discover 报告中生成以下输出。有关读取和理解这些报告的更多信息,请参见分析 discover 报告。
ERROR 1 (ABW): writing to memory beyond array bounds at address 0x2fffffff7e877ff2: main() + 0x3c <test-abrw.c:10> 7: int *y = (int*)malloc(20*sizeof(int)); 8: char *z = (char*)malloc(64); 9: 10:=> x[-14] = 0; 11: y[-10] = 0; 12: z[-4] = 0; 13: x[16] = 0; _start() + 0x108 was allocated at (512 bytes): main() + 0x8 <test-abrw.c:6> 3: 4: int main() { 5: 6:=> char *x = (char*)malloc(512); 7: int *y = (int*)malloc(20*sizeof(int)); 8: char *z = (char*)malloc(64); 9: _start() + 0x108 ERROR 2 (ABW): writing to memory beyond array bounds at address 0x2fffffff7e873ffc: main() + 0x50 <test-abrw.c:12> 9: 10: x[-14] = 0; 11: y[-10] = 0; 12:=> z[-4] = 0; 13: x[16] = 0; 14: y[20] = 0; 15: z[64] = 0; _start() + 0x108 was allocated at (64 bytes): main() + 0x28 <test-abrw.c:8> 5: 6: char *x = (char*)malloc(512); 7: int *y = (int*)malloc(20*sizeof(int)); 8:=> char *z = (char*)malloc(64); 9: 10: x[-14] = 0; 11: y[-10] = 0; _start() + 0x108 ERROR 3 (ABW): writing to memory beyond array bounds at address 0x2fffffff7e876080: main() + 0x64 <test-abrw.c:14> 11: y[-10] = 0; 12: z[-4] = 0; 13: x[16] = 0; 14:=> y[20] = 0; 15: z[64] = 0; 16: x[20] = 0; 17: y[26] = 0; _start() + 0x108 was allocated at (128 bytes): main() + 0x18 <test-abrw.c:7> 4: int main() { 5: 6: char *x = (char*)malloc(512); 7:=> int *y = (int*)malloc(20*sizeof(int)); 8: char *z = (char*)malloc(64); 9: 10: x[-14] = 0; _start() + 0x108 ERROR 4 (ABW): writing to memory beyond array bounds at address 0x2fffffff7e874040: main() + 0x70 <test-abrw.c:15> 12: z[-4] = 0; 13: x[16] = 0; 14: y[20] = 0; 15:=> z[64] = 0; 16: x[20] = 0; 17: y[26] = 0; 18: z[120] = 0; _start() + 0x108 was allocated at (64 bytes): main() + 0x28 <test-abrw.c:8> 5: 6: char *x = (char*)malloc(512); 7: int *y = (int*)malloc(20*sizeof(int)); 8:=> char *z = (char*)malloc(64); 9: 10: x[-14] = 0; 11: y[-10] = 0; _start() + 0x108 ERROR 5 (ABW): writing to memory beyond array bounds at address 0x2fffffff7e876098: main() + 0x84 <test-abrw.c:17> 14: y[20] = 0; 15: z[64] = 0; 16: x[20] = 0; 17:=> y[26] = 0; 18: z[120] = 0; 19: 20: } _start() + 0x108 was allocated at (128 bytes): main() + 0x18 <test-abrw.c:7> 4: int main() { 5: 6: char *x = (char*)malloc(512); 7:=> int *y = (int*)malloc(20*sizeof(int)); 8: char *z = (char*)malloc(64); 9: 10: x[-14] = 0; _start() + 0x108 ERROR 6 (ABW): writing to memory beyond array bounds at address 0x2fffffff7e874078: main() + 0x90 <test-abrw.c:18> 15: z[64] = 0; 16: x[20] = 0; 17: y[26] = 0; 18:=> z[120] = 0; 19: 20: } 21: _start() + 0x108 was allocated at (64 bytes): main() + 0x28 <test-abrw.c:8> 5: 6: char *x = (char*)malloc(512); 7: int *y = (int*)malloc(20*sizeof(int)); 8:=> char *z = (char*)malloc(64); 9: 10: x[-14] = 0; 11: y[-10] = 0; _start() + 0x108 DISCOVER SUMMARY: unique errors : 6 (6 total)