본문 바로가기
Programming/Android Kernel,Native

Deep Dive into Android IPC/Binder Framework(2/2)

by 개Foot/Dog발?! 2014. 10. 21.

URL : https://thenewcircle.com/s/post/1340/Deep_Dive_Into_Binder_Presentation.htm#slide-15


.....


Sharing Memory via Binder

- Binder transactional data is copied among parties communicating - not ideal if we have a lot of data to send

    + In fact, binder imposes limits on how much data we can send via transactions


- If the data we want to share comes from a file, then we should just send the file descriptor instead

    + This is how we ask the media player to play an audio/video file for us - we just send it the FD


- If the data we want to send is located in memory, rather than trying to send all of it at once, we could send multiple but smaller chunks instead

    + Complicates our design


- Alternatively, we could take advantage of Android’s ashmem (Anonymous Shared Memory) facilities

    + Its Java wrapper android.os.MemoryFile is not meant for memory sharing from 3rd party apps

    + Drop to native (via JNI) and use ashmem directly?


- Native memory sharing implemented via frameworks/base/libs/binder/Parcel.cpp's:

    + void Parcel::writeBlob(size_t len, WritableBlob* outBlob)

    + status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob)


- This is roughly implemented as follows:

    Client

size_t len = 4096;
int fd = ashmem_create_region("Parcel Blob", len);
ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
void* ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
ashmem_set_prot_region(fd, PROT_READ);
writeFileDescriptor(fd, true);
// write into  ptr for len as desiredmunmap(ptr, len);
close(fd);

    Service

 

int fd = readFileDescriptor();
void* ptr = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
// read from ptr up to len as desiredmunmap(ptr, len);

# Removed error handling for brevity. Also, writeFileDescriptor(…) and readFileDescriptor(…) are provided by libbinder.


Limitations of Binder
- Binder supports a maximum of 15 binder threads per process
   frameworks/base/libs/binder/ProcessState.cpp

static int open_driver()
{
    int fd = open("/dev/binder", O_RDWR);
    if (fd >= 0) {size_t maxThreads = 15;
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);} else {}
    return fd;
}

    + Avoid blocking binder threads
    + If we need to perform a long-running task, it’s better to spawn our own thread

- Binder limits its transactional buffer to 1Mb per process across all concurrent transactions
    + If arguments/return values are too large to fit into this buffer, TransactionTooLargeException is thrown
    + Because this buffer is shared across all transactions in a given process, many moderately sized transactions could also exhaust its limit
    + When this exception is thrown, we don’t know whether we failed to send the request, or failed to receive the response
    + Keep transaction data small or use shared memory (ashmem)


Binder - Security

 - Binder does directly deal with "security" concerns, but it enables a "trusted" execution environment and DAC

- The binder driver allows only a single CONTEXT_MGR (i.e. servicemanager) to register:

    drivers/staging/android/binder.c:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{switch (cmd) {case BINDER_SET_CONTEXT_MGR:
    if (binder_context_mgr_node != NULL) {
      printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");
      ret = -EBUSY;
      goto err;
    }
    …
    binder_context_mgr_node = binder_new_node(proc, NULL, NULL);}
  …
…


- The servicemanager in turn only allows registrations from trusted UIDs (like system, radio, media, etc.):

    frameworks/base/cmds/servicemanager/service_manager.c:

static struct {
    unsigned uid;
    const char *name;
} allowed[] = {
#ifdef LVMX
    { AID_MEDIA, "com.lifevibes.mx.ipc" },
#endif
    { AID_MEDIA, "media.audio_flinger" },
    { AID_MEDIA, "media.player" },
    { AID_MEDIA, "media.camera" },
    { AID_MEDIA, "media.audio_policy" },
    { AID_DRM,   "drm.drmManager" },
    { AID_NFC,   "nfc" },
    { AID_RADIO, "radio.phone" },
    { AID_RADIO, "radio.sms" },
    { AID_RADIO, "radio.phonesubinfo" },
    { AID_RADIO, "radio.simphonebook" },
/* TODO: remove after phone services are updated: */
    { AID_RADIO, "phone" },
    { AID_RADIO, "sip" },
    { AID_RADIO, "isms" },
    { AID_RADIO, "iphonesubinfo" },
    { AID_RADIO, "simphonebook" },
};int svc_can_register(unsigned uid, uint16_t *name)
{
    unsigned n;

    if ((uid == 0) || (uid == AID_SYSTEM))
        return 1;

    for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
        if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
            return 1;

    return 0;
}int do_add_service(struct binder_state *bs,
                   uint16_t *s, unsigned len,
                   void *ptr, unsigned uid)
{if (!svc_can_register(uid, s)) {
        LOGE("add_service('%s',%p) uid=%d - PERMISSION DENIED\n",
             str8(s), ptr, uid);
        return -1;
    }}


- Each binder transaction caries in it the UID and PID of the sender, which we can easily access:

    + android.os.Binder.getCallingPid()

    + android.os.Binder.getCallingUid()


- Once we have the knowledge of the calling UID, we can easily resolve it the calling app via  PackageManager.getPackagesForUid(int uid)

- Once we have the knowledge of the calling app, we can easily check whether it holds a permission we want to enforce via PackageManager.getPackageInfo(String packageName, int flags) (with the PackageManager.GET_PERMISSIONS flag)

- But, much easier to do permission enforcement via:

    + Context.checkCallingOrSelfPermission(String permission), which returns PackageManager.PERMISSION_GRANTED if the calling process has been granted the permission or PackageManager.PERMISSION_DENIED otherwise

    + Context.enforceCallingPermission(String permission, String message) - to automatically throw SecurityException if the caller does not have the requested permission


- This is how many of the application framework services enforce their permissions

images/AndroidSecurityCallFlow.svg


- For example:

    frameworks/base/services/java/com/android/server/VibratorService.java:

package com.android.server;public class VibratorService extends IVibratorService.Stub {public void vibrate(long milliseconds, IBinder token) {
    if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
      != PackageManager.PERMISSION_GRANTED) {
      throw new SecurityException("Requires VIBRATE permission");
    }}}


    frameworks/base/services/java/com/android/server/LocationManagerService.java:

package com.android.server;public class LocationManagerService extends ILocationManager.Stub implements Runnable {private static final String ACCESS_FINE_LOCATION =
      android.Manifest.permission.ACCESS_FINE_LOCATION;
  private static final String ACCESS_COARSE_LOCATION =
      android.Manifest.permission.ACCESS_COARSE_LOCATION;private void checkPermissionsSafe(String provider) {
    if ((LocationManager.GPS_PROVIDER.equals(provider)
             || LocationManager.PASSIVE_PROVIDER.equals(provider))
        && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION)
            != PackageManager.PERMISSION_GRANTED)) {
        throw new SecurityException("Provider " + provider
                + " requires ACCESS_FINE_LOCATION permission");
    }
    if (LocationManager.NETWORK_PROVIDER.equals(provider)
        && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION)
            != PackageManager.PERMISSION_GRANTED)
        && (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION)
            != PackageManager.PERMISSION_GRANTED)) {
        throw new SecurityException("Provider " + provider
                + " requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission");
    }
  }private Location _getLastKnownLocationLocked(String provider) {
    checkPermissionsSafe(provider);}public Location getLastKnownLocation(String provider) {_getLastKnownLocationLocked(provider);}
}


Permissions by Example

- In this example, we are given two applications, FibonacciClient and FibonacciService

images/FibonacciClientServiceArchitecture.svg

    + These two apps communicate via Binder/IPC

    + Common files for these applications reside in a library project called FibonacciCommon


- The finished code for these applications (as Eclipse projects) is available:

    + As a ZIP archive: https://github.com/marakana/FibonacciBinderDemo/zipball/secured

    + By Git: git clone https://github.com/marakana/FibonacciBinderDemo.git -b secured



Static Permission Enforcement

- Here, we want to restrict access to the com.marakana.android.fibonacciservice.FibonacciService to applications (i.e. clients) that hold USE_FIBONACCI_SERVICE custom permission

    1. We start by by creating a custom permission group (making sure that we name-space it):

    FibonacciService/res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources><string name="fibonacci_permissions_group_label">Fibonacci Permissions</string></resources>


    FibonacciService/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest><permission-group
        android:name="com.marakana.android.fibonacciservice.FIBONACCI_PERMISSIONS"
        android:label="@string/fibonacci_permissions_group_label" /></manifest>

# This permission group is optional - as we could instead use one of the already provided groups


    2. Next, we create a custom permission (again, making sure that we name-space it), while taking advantage of our newly-created permission group:

    FibonacciService/res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources><string name="use_fibonacci_service_permission_label">use fibonacci service</string>
    <string name="use_fibonacci_service_permission_description">
      applications with this permissions get fibonacci results for free
    </string></resources>

    FibonacciService/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest><permission-group/>
    <permission
        android:name="com.marakana.android.fibonacciservice.USE_FIBONACCI_SERVICE"
        android:description="@string/use_fibonacci_service_permission_description"
        android:label="@string/use_fibonacci_service_permission_label"
        android:permissionGroup="com.marakana.android.fibonacciservice.FIBONACCI_PERMISSIONS"
        android:protectionLevel="dangerous" /></manifest>


    3. Now we can statically require the permission on our FibonacciService service:

    FibonacciService/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest><permission-group/>
    <permission/>
    <application>
        <service
            android:name=".FibonacciService"
            android:permission="com.marakana.android.fibonacciservice.USE_FIBONACCI_SERVICE" ></service>
    </application></manifest>


    4. If we now re-run the FibonacciService and re-run the FibonacciClient, we will notice that the client will fail to launch and adb logcat will show something like:

W/ActivityManager(   85): Permission Denial: Accessing service ComponentInfo{com.marakana.android.fibonacciservice/com.marakana.android.fibonacciservice.FibonacciService} from pid=540, uid=10043 requires com.marakana.android.fibonacciservice.USE_FIBONACCI_SERVICE

D/AndroidRuntime(  540): Shutting down VM

W/dalvikvm(  540): threadid=1: thread exiting with uncaught exception (group=0x409c01f8)

E/AndroidRuntime(  540): FATAL EXCEPTION: main

E/AndroidRuntime(  540): java.lang.RuntimeException: Unable to resume activity {com.marakana.android.fibonacciclient/com.marakana.android.fibonacciclient.FibonacciActivity}: java.lang.SecurityException: Not allowed to bind to service Intent { act=com.marakana.android.fibonaccicommon.IFibonacciService }

E/AndroidRuntime(  540):    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2444)

E/AndroidRuntime(  540):    at dalvik.system.NativeStart.main(Native Method)

E/AndroidRuntime(  540): Caused by: java.lang.SecurityException: Not allowed to bind to service Intent { act=com.marakana.android.fibonaccicommon.IFibonacciService }

E/AndroidRuntime(  540):    at android.app.ContextImpl.bindService(ContextImpl.java:1135)

E/AndroidRuntime(  540):    at android.content.ContextWrapper.bindService(ContextWrapper.java:370)

E/AndroidRuntime(  540):    at com.marakana.android.fibonacciclient.FibonacciActivity.onResume(FibonacciActivity.java:65)

W/ActivityManager(   85):   Force finishing activity com.marakana.android.fibonacciclient/.FibonacciActivity

 


    5. Finally, we can give FibonacciClient a fighting chance by allowing it to use the USE_FIBONACCI_SERVICE permission:

    FibonacciClient/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest><uses-permission android:name="com.marakana.android.fibonacciservice.USE_FIBONACCI_SERVICE"/></manifest>


    6. We can now observe that our client is again able to use the service


    7. In the Emulator, if we go to Home → Menu → Manage apps → Fibonacci Client → PERMISSIONS, we should see the Fibonacci Permissions group and under it, use fibonacci service permission


Dynamic Permission Enforcement

- Here, we want to restrict access to the com.marakana.android.fibonacciservice.IFibonacciServiceImpl's recursive operations (fibJR(long n) and fibNR(long n)) for n > 10 to applications (i.e. clients) that hold USE_SLOW_FIBONACCI_SERVICE custom permission

    1. Like before, we start off by creating a custom permission:

    FibonacciService/res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources><string name="use_slow_fibonacci_service_permission_label">
        use slow fibonacci service operations
   </string>
    <string name="use_slow_fibonacci_service_permission_description">
        applications with this permissions can melt the CPU and drain the battery
        by using slow fibonacci operations
    </string></resources>


    FibonacciService/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest><permission-group/>
    <permission/>
    <permission
        android:name="com.marakana.android.fibonacciservice.USE_SLOW_FIBONACCI_SERVICE"
        android:description="@string/use_slow_fibonacci_service_permission_description"
        android:label="@string/use_slow_fibonacci_service_permission_label"
        android:permissionGroup="com.marakana.android.fibonacciservice.FIBONACCI_PERMISSIONS"
        android:protectionLevel="dangerous" /></manifest>


    2. Next, we update our IFibonacciServiceImpl to enforce this permission dynamically - via a android.content.Context that get expect to get through the constructor:

    FibonacciService/src/com/marakana/android/fibonacciservice/IFibonacciServiceImpl.java:

package com.marakana.android.fibonacciservice;

import android.content.Context;public class IFibonacciServiceImpl extends IFibonacciService.Stub {private final Context context;

    public IFibonacciServiceImpl(Context context) {
        this.context = context;
    }

    private long checkN(long n) {
        if (n > 10) {
            this.context.enforceCallingOrSelfPermission(
                    Manifest.permission.USE_SLOW_FIBONACCI_SERVICE, "Go away!");
        }
        return n;
    }public long fibJR(long n) {return FibLib.fibJR(this.checkN(n));
    }public long fibNR(long n) {return FibLib.fibNR(this.checkN(n));
    }}


    3. We have to update FibonacciService to invoke the new IFibonacciServiceImpl's constructor:

    FibonacciService/src/com/marakana/android/fibonacciservice/FibonacciService.java:

public class FibonacciService extends Service {
    …
    @Override
    public void onCreate() {this.service = new IFibonacciServiceImpl(super.getApplicationContext());}}


    4. If we now re-run the FibonacciService and re-run the FibonacciClient for a recursive operation with n > 10, we will notice that the client will fail and adb logcat will show something like:

D/IFibonacciServiceImpl(  617): fib(15, RECURSIVE_NATIVE)

D/IFibonacciServiceImpl(  617): fibNR(15)

W/dalvikvm(  604): threadid=11: thread exiting with uncaught exception (group=0x409c01f8)

E/AndroidRuntime(  604): FATAL EXCEPTION: AsyncTask #1

E/AndroidRuntime(  604): java.lang.RuntimeException: An error occured while executing doInBackground()

E/AndroidRuntime(  604):    at java.lang.Thread.run(Thread.java:856)

E/AndroidRuntime(  604): Caused by: java.lang.SecurityException: Go away!: Neither user 10043 nor current process has com.marakana.android.fibonacciservice.USE_SLOW_FIBONACCI_SERVICE.

 


    5. Finally, we can allow FibonacciClient to melt our CPU and drain our battery by allowing it to use the USE_SLOW_FIBONACCI_SERVICE permission:

    FibonacciClient/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest><uses-permission android:name="com.marakana.android.fibonacciservice.USE_SLOW_FIBONACCI_SERVICE"/></manifest>


    6. We can now observe that our client is again able to use recursive fibonacci operations even for n > 10


    7. In the Emulator, if we go to Home → Menu → Manage apps → Fibonacci Client → PERMISSIONS → Fibonacci Permissions, we should see both use fibonacci service and use slow fibonacci service operations permissions


Other Binder Features

- Given a reference to an IBinder object, we can:

    + Ask whether the remote object is alive, via isBinderAlive() and pingBinder()

    + Ask to be notified of its death, via linkToDeath(IBinder.DeathRecipient recipient, int flags):

    frameworks/base/services/java/com/android/server/LocationManagerService.java:

public class LocationManagerService extends ILocationManager.Stub implements Runnable {private Receiver getReceiver(ILocationListener listener) {
    IBinder binder = listener.asBinder();
    Receiver receiver = mReceivers.get(binder);
    if (receiver == null) {
      receiver = new Receiver(listener);
      …
      receiver.getListener().asBinder().linkToDeath(receiver, 0);}
    return receiver;
  }

  private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished {
    final ILocationListener mListener;Receiver(ILocationListener listener) {
      mListener = listener;}public void binderDied() {removeUpdatesLocked(this);
    }}}


- Binder driver reports various stats on active/failed transactions via /proc/binder/

    + /proc/binder/failed_transaction_log

    + /proc/binder/state

    + /proc/binder/stats

    + /proc/binder/transaction_log

    + /proc/binder/transactions

    + /proc/binder/proc/<pid>


......



   Additional Resources