In API 27 , Google added SharedMemory class so applications can create and use shared memory using asmem (/dev/ashmem). Today, less than 1% of mobile devices work with API 27 (Android 8.1) so It is useless. In this post I will show you how to work with ashmem, creating and using shared memory between 2 untrusted applications (applications from the Play store) and it is working for API 15 and up (Android 4)

To support this , we will create an Android library, Server application and Client Application , we will use JNI to create and map ashmem objects using native C++ code and we will use the binder to pass the file descriptors between processes.

If you don’t know how to create a service and use it in Android Application read this first

Start with creating a new Android Application, add C++ support, choose API 15 and keep other defaults

Add a new Module -> Android Library

Add a new aidl file:

// ISharedMem.aidl package com.example.sharedmemlib; import android.os.ParcelFileDescriptor; interface ISharedMem { ParcelFileDescriptor OpenSharedMem(String name, int size, boolean create); } 1 2 3 4 5 6 7 8 // ISharedMem.aidl package com . example . sharedmemlib ; import android . os . ParcelFileDescriptor ; interface ISharedMem { ParcelFileDescriptor OpenSharedMem ( String name , int size , boolean create ) ; }

The type ParcelFileDescriptor is used to pass the file object from one process to another.

The Server Application

The Server application host a service to create and return a file descriptor for the shared memory. It is also contains an Activity for testing the shared memory.

To create or open a shared memory we need to pass name and size. We create a simple class to handle it using native code.

The Java code:

public class ShmLib { static { System.loadLibrary("native-lib"); } private static HashMap<String,Integer> memAreas = new HashMap<>(); public static int OpenSharedMem(String name, int size, boolean create) { Integer i = memAreas.get(name); if (create && i != null) return -1; if (i == null){ i = new Integer(getFD(name, size)); memAreas.put(name, i); } return i.intValue(); } public static int setValue(String name, int pos, int val){ Integer fd = memAreas.get(name); if(fd != null) return setVal(fd.intValue(),pos,val); return -1; } public static int getValue(String name, int pos ){ Integer fd = memAreas.get(name); if(fd != null) return getVal(fd.intValue(),pos); return -1; } private static native int setVal(int fd,int pos, int val); private static native int getVal(int fd,int pos); private static native int getFD(String name , int size); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class ShmLib { static { System . loadLibrary ( "native-lib" ) ; } private static HashMap < String , Integer > memAreas = new HashMap <> ( ) ; public static int OpenSharedMem ( String name , int size , boolean create ) { Integer i = memAreas . get ( name ) ; if ( create && i != null ) return - 1 ; if ( i == null ) { i = new Integer ( getFD ( name , size ) ) ; memAreas . put ( name , i ) ; } return i . intValue ( ) ; } public static int setValue ( String name , int pos , int val ) { Integer fd = memAreas . get ( name ) ; if ( fd != null ) return setVal ( fd . intValue ( ) , pos , val ) ; return - 1 ; } public static int getValue ( String name , int pos ) { Integer fd = memAreas . get ( name ) ; if ( fd != null ) return getVal ( fd . intValue ( ) , pos ) ; return - 1 ; } private static native int setVal ( int fd , int pos , int val ) ; private static native int getVal ( int fd , int pos ) ; private static native int getFD ( String name , int size ) ; }

We create an HashMap to store all shared memory objects (the file descriptors) by name. We write 3 native C++ functions to create the shared memory , set a value and get a value

The C++ code

struct memArea{ int *map; int fd; int size; }; struct memArea maps[10]; int num = 0; static jint getFD(JNIEnv *env, jclass cl, jstring path,jint size) { const char *name = env->GetStringUTFChars(path,NULL); jint fd = open("/dev/ashmem",O_RDWR); ioctl(fd,ASHMEM_SET_NAME,name); ioctl(fd,ASHMEM_SET_SIZE,size); maps[num].size = size; maps[num].fd = fd; maps[num++].map = (int *)mmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); env->ReleaseStringUTFChars(path,name); return fd; } static jint setNum(JNIEnv *env, jclass cl,jint fd, jint pos,jint num) { for(int i = 0; i < num; i++) { if(maps[i].fd == fd) { if(pos < (maps[i].size/ sizeof(int))) { maps[i].map[pos] = num; return 0; } return -1; } } return -1; } static jint getNum(JNIEnv *env, jclass cl,jint fd, jint pos) { for(int i = 0; i < num; i++) { if(maps[i].fd == fd) { if(pos < (maps[i].size/ sizeof(int))) { return maps[i].map[pos]; } return -1; } } return -1; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 struct memArea { int * map ; int fd ; int size ; } ; struct memArea maps [ 10 ] ; int num = 0 ; static jint getFD ( JNIEnv * env , jclass cl , jstring path , jint size ) { const char * name = env -> GetStringUTFChars ( path , NULL ) ; jint fd = open ( "/dev/ashmem" , O_RDWR ) ; ioctl ( fd , ASHMEM_SET_NAME , name ) ; ioctl ( fd , ASHMEM_SET_SIZE , size ) ; maps [ num ] . size = size ; maps [ num ] . fd = fd ; maps [ num ++ ] . map = ( int * ) mmap ( 0 , size , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0 ) ; env -> ReleaseStringUTFChars ( path , name ) ; return fd ; } static jint setNum ( JNIEnv * env , jclass cl , jint fd , jint pos , jint num ) { for ( int i = 0 ; i < num ; i ++ ) { if ( maps [ i ] . fd == fd ) { if ( pos < ( maps [ i ] . size / sizeof ( int ) ) ) { maps [ i ] . map [ pos ] = num ; return 0 ; } return - 1 ; } } return - 1 ; } static jint getNum ( JNIEnv * env , jclass cl , jint fd , jint pos ) { for ( int i = 0 ; i < num ; i ++ ) { if ( maps [ i ] . fd == fd ) { if ( pos < ( maps [ i ] . size / sizeof ( int ) ) ) { return maps [ i ] . map [ pos ] ; } return - 1 ; } } return - 1 ; }

On the server application you can use the above code to create and use shared memory

ShmLib.OpenSharedMem("sh1",1000,true); ShmLib.setValue("sh1",10,200); //sh1[10] = 200 int v = ShmLib.getValue("sh1",10); 1 2 3 ShmLib . OpenSharedMem ( "sh1" , 1000 , true ) ; ShmLib . setValue ( "sh1" , 10 , 200 ) ; //sh1[10] = 200 int v = ShmLib . getValue ( "sh1" , 10 ) ;

Creating The Service

To create the service we need to implement the interface defined by the aidl file.

public class SharedMemImp extends ISharedMem.Stub { @Override public ParcelFileDescriptor OpenSharedMem(String name, int size, boolean create) throws RemoteException { int fd = ShmLib.OpenSharedMem(name,size,create); try { return ParcelFileDescriptor.fromFd(fd); } catch (IOException e) { e.printStackTrace(); } return null; } } 1 2 3 4 5 6 7 8 9 10 11 12 public class SharedMemImp extends ISharedMem . Stub { @Override public ParcelFileDescriptor OpenSharedMem ( String name , int size , boolean create ) throws RemoteException { int fd = ShmLib . OpenSharedMem ( name , size , create ) ; try { return ParcelFileDescriptor . fromFd ( fd ) ; } catch ( IOException e ) { e . printStackTrace ( ) ; } return null ; } }

Using ParcelFileDescriptor we can pass any file descriptor we have to another process using the binder. It can be a file, device, socket, IPC object etc.

Add a class for a service:

public class ShmService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return new SharedMemImp(); } } 1 2 3 4 5 6 7 public class ShmService extends Service { @Nullable @Override public IBinder onBind ( Intent intent ) { return new SharedMemImp ( ) ; } }

Add update the manifest file:

<service android:name=".ShmService"> <intent-filter> <action android:name="com.example.developer.testashmem.ShmService"/> </intent-filter> </service> 1 2 3 4 5 < service android : name = ".ShmService" > < intent - filter > < action android : name = "com.example.developer.testashmem.ShmService" / > < / intent - filter > < / service >

Add Activity for testing

The code simply set and get values in the shared memory

Writing A Client Application

First we need to bind the service and get the file descriptor of the shared memory object:

bindService(new Intent("com.example.developer.testashmem.ShmService") .setPackage("com.example.developer.testashmem"), this,BIND_AUTO_CREATE); ... public void onServiceConnected(ComponentName componentName, IBinder iBinder) { ShmMemService = ISharedMem.Stub.asInterface(iBinder); try { ParcelFileDescriptor p = ShmMemService.OpenSharedMem("sh1", 1000, false); int fd = p.getFd() } catch (RemoteException e) { e.printStackTrace(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 bindService ( new Intent ( "com.example.developer.testashmem.ShmService" ) . setPackage ( "com.example.developer.testashmem" ) , this , BIND_AUTO_CREATE ) ; . . . public void onServiceConnected ( ComponentName componentName , IBinder iBinder ) { ShmMemService = ISharedMem . Stub . asInterface ( iBinder ) ; try { ParcelFileDescriptor p = ShmMemService . OpenSharedMem ( "sh1" , 1000 , false ) ; int fd = p . getFd ( ) } catch ( RemoteException e ) { e . printStackTrace ( ) ; } }

Now we need to write a native code to use mmap and access the shared memory. We create a simple JNI wrapper class:

public class ShmClientLib { static { System.loadLibrary("client-lib"); } public static native int setVal(int pos, int val); public static native int getVal(int pos); public static native void setMap(int fd , int size); } 1 2 3 4 5 6 7 8 9 10 public class ShmClientLib { static { System . loadLibrary ( "client-lib" ) ; } public static native int setVal ( int pos , int val ) ; public static native int getVal ( int pos ) ; public static native void setMap ( int fd , int size ) ; }

And the C++ code

#include <jni.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/mman.h> int *map ; int size; static void setmap(JNIEnv *env, jclass cl, jint fd, jint sz) { size = sz; map = (int *)mmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); } static jint setNum(JNIEnv *env, jclass cl, jint pos,jint num) { if(pos < (size / sizeof(int))) { map[pos] = num; return 0; } return -1; } static jint getNum(JNIEnv *env, jclass cl, jint pos) { if(pos < (size / sizeof(int))) { return map[pos]; } return -1; } static JNINativeMethod method_table[] = { { "setVal", "(II)I", (void *) setNum }, { "getVal", "(I)I", (void *) getNum }, { "setMap", "(II)V", (void *)setmap } }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } else { jclass clazz = env->FindClass("com/example/testashmemclient/ShmClientLib"); if (clazz) { jint ret = env->RegisterNatives(clazz, method_table, sizeof(method_table) / sizeof(method_table[0])); env->DeleteLocalRef(clazz); return ret == 0 ? JNI_VERSION_1_6 : JNI_ERR; } else { return JNI_ERR; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <jni.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/mman.h> int * map ; int size ; static void setmap ( JNIEnv * env , jclass cl , jint fd , jint sz ) { size = sz ; map = ( int * ) mmap ( 0 , size , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0 ) ; } static jint setNum ( JNIEnv * env , jclass cl , jint pos , jint num ) { if ( pos < ( size / sizeof ( int ) ) ) { map [ pos ] = num ; return 0 ; } return - 1 ; } static jint getNum ( JNIEnv * env , jclass cl , jint pos ) { if ( pos < ( size / sizeof ( int ) ) ) { return map [ pos ] ; } return - 1 ; } static JNINativeMethod method_table [ ] = { { "setVal" , "(II)I" , ( void * ) setNum } , { "getVal" , "(I)I" , ( void * ) getNum } , { "setMap" , "(II)V" , ( void * ) setmap } } ; extern "C" jint JNI_OnLoad ( JavaVM * vm , void * reserved ) { JNIEnv * env ; if ( vm -> GetEnv ( reinterpret_cast < void * * > ( & env ) , JNI_VERSION_1_6 ) != JNI_OK ) { return JNI_ERR ; } else { jclass clazz = env -> FindClass ( "com/example/testashmemclient/ShmClientLib" ) ; if ( clazz ) { jint ret = env -> RegisterNatives ( clazz , method_table , sizeof ( method_table ) / sizeof ( method_table [ 0 ] ) ) ; env -> DeleteLocalRef ( clazz ) ; return ret == 0 ? JNI_VERSION_1_6 : JNI_ERR ; } else { return JNI_ERR ; } } }

Now we can use setMap to mmap the memory and setVal/getVal to access it

You can download / clone the full code from my github

Stay Updated, Sign up to our List: