libadiplugin - APIs for use by custom memory allocators to detect memory corruptions with Silicon Secured Memory (SSM)
#include <adiplugin.h>
Enterprise applications can often manage their own memory and do not use the system (libc) malloc(3C) library. For these cases, the normal usage of Oracle Solaris libadimalloc(3LIB), which interposes on malloc(), free(), etc will not be able to catch memory corruptions. Oracle Developer Studio provides APIs so the enterprise applications can tell libadiplugin.so when a memory area is allocated or freed. libadiplugin.so manages the SSM versioning. The system will issue a SEGV when a corruption is detected. These APIs and libadiplugin.so only work for memory allocated by mmap(2) or shmget(2). These are idential to the APIs provided with the discover ADI tool, but are designed for use in production systems.
The following APIs are provided:
The user allocator passes a pointer ptr and the size size of the memory about to be returned to the client requesting memory. If the memory does not have ADI enabled, the library uses a memcntl(2) call. An SSM versioned pointer is returned, which the allocator must pass to the client.
The allocator passes a pointer ptr and the size size to the memory area about to be freed.
Allocators can use pointer arithmetic to access a client memory's meta data. Client memory will be SSM versioned, but allocator meta data might not. This API should be used before any pointer arithmietic or comparison is done. The allocator passes any pointer ptr and returns an equivalent unversioned pointer.
Sometimes allocators lose track of versioned pointers. This API takes in any pointer ptr, and returns an equivalent pointer with the correct version on it. Any dereference with the return value will not cause an ADI SEGV.
If a memory area is being reused for a different purpose, the ADI version needs to be cleared. For example, if a memory area was versioned for use by an allocator client, and is now being used by the allocator for meta-data. This function takes in a versioned or unversioned pointer ptr and size size, sets the version for that area to zero, and returns an unversioned pointer.
Your application might manage its own memory allocation and free lists, for example by allocating large chunks of memory and subdividing it in your program. See Using Application Data Integrity and Oracle Solaris Studio to Find and Fix Memory Access Errors (https://community.oracle.com/docs/DOC-912448) for information about how you can use the ADI versioning APIs to catch errors with your managed memory.
To use these APIs, include the following header file in your sources:
#include <adiplugin.h>
Then, do one of the following:
Link your sources with install-dir/lib/compilers/sparcv9/libadiplugin.so
Set the environment variable LD_PRELOAD_64 to install-dir/lib/compilers/sparcV9/libadiplugin.so
The following is an example of using custom memory allocators.
Example 1 Using Custom Memory AllocatorsThe following is an example header file:
% cat allocator.h #define GRANULARITY 32 void *mymalloc(size_t len); void myfree(void *ptr);
The following is example source code, using custom memory allocators and the libadiplugin library:
% 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 "adiplugin.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 libadiplugin.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 libadiplugin.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; }
To compile and run this example:
% cc –m64 –g simple.c allocator.c install-dir/lib/compilers/sparcv9/libadiplugin.so –o simple % ./simple
You can use libadiplugin.so only with 64-bit applications on a SPARC M7 chip running at least Oracle Solaris 11.2.8 or Oracle Solaris 11.3.
Similar to instrumenting for memory checking, preloaded libraries might conflict if functions of libadiplugin.so interpose on the same allocation functions.
Other limitations for checking your code with .so include the following:
Only heap-checking is available. There is no stack checking, no static array-out-of-bounds checking, and no leak detection.
Does not work with applications which use the unused bits in 64-bit addresses for storing meta data. Some 64-bit applications might use the currently unused high bits in 64-bit addresses for storing meta-data, for instance, locks. Such applications will not work with libadiplugin.so because the feature works by using the 4 highest bits in the 64-bit address to store version information.
Might not work for applications that do pointer arithmetic with assumptions about heap addresses, for example, the distance between two successive allocations.
Resolution for buffer overflow is 64 bytes. For allocations that are 64-byte-aligned, libadiplugin.so will catch any overflow by 1 byte or more. For allocations that are not aligned at 64 bytes, it might miss the buffer overflow by a few bytes. In general, overflow by 1 to 63 bytes might not be caught depending on the alignment of the allocation and where libadiplugin.so places the allocation in the cache line.
Memory allocations that are not 64-byte aligned or not multiples of 64-bytes will not be SSM versioned. This could lead to memory corruptions not being detected.
There is a slight chance that binaries compiled with –xipo=2 might have a memory-optimized code that manipulates addresses in a way that will lead to false positive SSM errors and as a result also lead to performance degradation due to trap handling.
libadimalloc(3LIB), Oracle Developer Studio 12.6: Overview, Oracle Developer Studio 12.6: Discover and Uncover User’s Guide, discover(1)