Sunday, June 14, 2015

UEvents in Android - from kernel events to notifications

In my current project, the system has to notify the user about a certain action that actually takes place inside the kernel (in this case, transfer of security keys). I don't want to get into too much details about the tasks, but let's consider that the kernel and the Dalvik are the only trustworthy components and the security keys are stored inside the kernel. I've also seen some pieces of code that helps to implement this, but I could not find a end-to-end solution.

[This post is a bit long, as it contains a lot of code and I tried to explain some details of it.]

Ok, let's proceed. This is the flow:

1) The kernel is asked to perform a certain syscall (transfer securely stored keys inside the kernel);
2) The kernel has to notify the user that some application has asked to do something suspicious, for example, to have access to some security keys;
3) The event is sent all the way to the userspace and a notification appears to the user (UI).

First, we have to generate one event in the kernel space (you can create a module and insert your code there). For this, we use an UEVENT. In addition, we can also add extra pieces of information into the event, for example strings, integers and so forth. The following code shows a simple UEVENT generated from the kernel space. It contains one integer field called "etype".

Some pieces of code were taken from the kernel itself (linux/samples/kobject/kset-example.c), and I made small changes to keep it even simpler. For more information, check the original one.

Initially, we need to create a kobject to embedded an attribute, for instance (https://www.kernel.org/doc/Documentation/kobject.txt). Once the kobject is registered, we broadcast it to other components of the system using kobject_uevent(). The files foo_kobject.c/h and foo_event.c/h contain code to broadcast an UEvent using a kobject.

foo_kobject.h (kernel code, for example, as part of your module)
#ifndef FOO_KOBJECT_
#define FOO_KOBJECT_

/* 
 * Partially copied from linux/samples/kobject/kset-example.c
 *
 * Released under the GPL version 2 only.
 */

/*
 * This is our "object" that we will create and register it with sysfs.
 */
struct foo_obj {
 struct kobject kobj;
 int foo;
};
#define to_foo_obj(x) container_of(x, struct foo_obj, kobj)

struct foo_obj *
create_foo_obj(const char *name);

int 
foo_kobj_init(void);

void
foo_kobj_exit(void);

#endif

foo_kobject.c (kernel code, for example, as part of your module)
/*
 * Partially copied from linux/samples/kobject/kset-example.c
 *
 * Released under the GPL version 2 only.
 *
 */

#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>

#include "foo_kobject.h"

/*
 * This module shows how to create a kset in sysfs called
 * /sys/kernel/foo
 * Then one kobject is created and assigned to this kset, "foo".  
 * In this kobject, one attribute is also
 * created and if an integer is written to these files, it can be later
 * read out of it.
 */

/* a custom attribute that works just for a struct foo_obj. */
struct foo_attribute {
 struct attribute attr;
 ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf);
 ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count);
};
#define to_foo_attr(x) container_of(x, struct foo_attribute, attr)

/*
 * The default show function that must be passed to sysfs.  This will be
 * called by sysfs for whenever a show function is called by the user on a
 * sysfs file associated with the kobjects we have registered.  We need to
 * transpose back from a "default" kobject to our custom struct foo_obj and
 * then call the show function for that specific object.
 */
static ssize_t foo_attr_show(struct kobject *kobj,
        struct attribute *attr,
        char *buf)
{
 struct foo_attribute *attribute;
 struct foo_obj *foo;

 attribute = to_foo_attr(attr);
 foo = to_foo_obj(kobj);

 if (!attribute->show)
  return -EIO;

 return attribute->show(foo, attribute, buf);
}

/*
 * Just like the default show function above, but this one is for when the
 * sysfs "store" is requested (when a value is written to a file.)
 */
static ssize_t foo_attr_store(struct kobject *kobj,
         struct attribute *attr,
         const char *buf, size_t len)
{
 struct foo_attribute *attribute;
 struct foo_obj *foo;

 attribute = to_foo_attr(attr);
 foo = to_foo_obj(kobj);

 if (!attribute->store)
  return -EIO;

 return attribute->store(foo, attribute, buf, len);
}

/* Our custom sysfs_ops that we will associate with our ktype later on */
static const struct sysfs_ops foo_sysfs_ops = {
 .show = foo_attr_show,
 .store = foo_attr_store,
};

/*
 * The release function for our object.  This is REQUIRED by the kernel to
 * have.  We free the memory held in our object here.
 *
 * NEVER try to get away with just a "blank" release function to try to be
 * smarter than the kernel.  Turns out, no one ever is...
 */
static void foo_release(struct kobject *kobj)
{
 struct foo_obj *foo;

 foo = to_foo_obj(kobj);
 kfree(foo);
}

/*
 * The "foo" file where the .foo variable is read from and written to.
 */
static ssize_t foo_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
   char *buf)
{
 return sprintf(buf, "%d\n", foo_obj->foo);
}

static ssize_t foo_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
    const char *buf, size_t count)
{
 sscanf(buf, "%du", &foo_obj->foo);
 return count;
}

static struct foo_attribute foo_attribute =
 __ATTR(foo, 0666, foo_show, foo_store);

/*
 * Create a group of attributes so that we can create and destroy them all
 * at once.
 */
static struct attribute *foo_default_attrs[] = {
 &foo_attribute.attr,
 NULL, /* need to NULL terminate the list of attributes */
};

/*
 * Our own ktype for our kobjects.  Here we specify our sysfs ops, the
 * release function, and the set of default attributes we want created
 * whenever a kobject of this type is registered with the kernel.
 */
static struct kobj_type foo_ktype = {
 .sysfs_ops = &foo_sysfs_ops,
 .release = foo_release,
 .default_attrs = foo_default_attrs,
};

static struct kset *example_kset;

struct foo_obj *create_foo_obj(const char *name)
{
 struct foo_obj *foo;
 int retval;

 /* allocate the memory for the whole object */
 foo = kzalloc(sizeof(*foo), GFP_KERNEL);
 if (!foo)
  return NULL;

 /*
  * As we have a kset for this kobject, we need to set it before calling
  * the kobject core.
         */
 foo->kobj.kset = example_kset;

 /*
  * Initialize and add the kobject to the kernel.  All the default files
  * will be created here.  As we have already specified a kset for this
  * kobject, we don't have to set a parent for the kobject, the kobject
  * will be placed beneath that kset automatically.
  */
 retval = kobject_init_and_add(&foo->kobj, &foo_ktype, NULL, "%s", name);
 if (retval) {
  kobject_put(&foo->kobj);
  return NULL;
 }

 /*
  * We are always responsible for sending the uevent that the kobject
  * was added to the system.
  */
 kobject_uevent(&foo->kobj, KOBJ_ADD);

 return foo;
}

static void destroy_foo_obj(struct foo_obj *foo)
{
 kobject_put(&foo->kobj);
}

int
foo_kobject_init(void)
{
 /*
  * Create a kset with the name of "kset_foo",
  * located under /sys/kernel/
  */
 example_kset = kset_create_and_add("kset_foo", NULL, kernel_kobj);
 if (!example_kset)
  return -ENOMEM;

}

void 
foo_kobject_exit(void)
{
 destroy_foo_obj(foo_kobj);
 kset_unregister(example_kset);
}

foo_uevent.h (kernel code, for example, as part of your module)
#ifndef FOO_UEVENT_
#define FOO_UEVENT_

enum FOO_event_type {
  FOO_GET = 1,
  FOO_SET
};

struct foo_event {
  enum foo_event_type etype;
};

int foo_init_events(void);

int foo_send_uevent(struct foo_event *fooe);

#endif


In the following code, we send an event string as part of our new UEvent. In our case, just the type of the event (FOO_GET or FOO_SET). foo_uevent.c (kernel code, for example, as part of your module)
#include <linux/kobject.h>
#include "foo_kobject.h"
#include "foo_uevent.h"

static struct foo_obj *foo_kobj;

int foo_init_events(void)
{
  int ret;

  ret = example_init();
  if (ret) 
  { 
    printk("error - could not create ksets\n");  
    goto foo_error;
  } 
 
  foo_kobj = create_foo_obj("foo");
  if (!foo_kobj)
  {
    printk("error - could not create kobj\n"); 
    goto foo_error;
  }
 
  return 0;

foo_error:
  return -EINVAL;
}

int foo_send_uevent(struct foo_event *pce)
{
  char event_string[20];
  char *envp[] = { event_string, NULL };

  if (!foo_kobj)
    return -EINVAL;

  snprintf(event_string, 20, "FOO_EVENT=%d", pce->etype);

  return kobject_uevent_env(&foo_kobj->kobj, KOBJ_CHANGE, envp);
}

So far, we've only made changes in the kernel level. But also, we need to listen to UEVENTs in the userspace. For that, I changed a little bit the Dalvik and added the following binder to listen to UEvents and send notifications, so that the user can see the event.

For more details on how to add a system service in Android, check this link. In this case, I added a Binder, as we are not providing any service (setting an integer, for instance). In our example, we simply broadcast events to the user interface.

To add the following Binder, create a file that contains the code into framework/base/services/java/com/android/server/. You also have to change the file Android.mk at framework/base/services/jni/Android.mk

...
LOCAL_SRC_FILES:= \
     com_android_server_VibratorService.cpp \
     com_android_server_location_GpsLocationProvider.cpp \
     com_android_server_connectivity_Vpn.cpp \
+    com_android_server_pm_PackageManagerService.cpp \
     onload.cpp
...

Also, don't forget to register the service (check the link that I mentioned before!), by changing the file framework/base/services/java/com/android/server/SystemServer.java

class ServerThread extends Thread {
...
         WifiP2pService wifiP2p = null;
         WifiService wifi = null;
         NsdService serviceDiscovery= null;
+        MyServiceBinder myService = null;
         IPackageManager pm = null;
         Context context = null;
         WindowManagerService wm = null;
...
         class ServerThread extends Thread {
...
             }

             try {
+                Slog.i(TAG, "My Service - Example");
+                myService = MyServiceBinder.create(context);
+                ServiceManager.addService(
+                        Context.MY_SERVICE, myService);
+            } catch (Throwable e) {
+                reportWtf("starting Privacy Capsules Service", e);
+            }
+
...

Finally, here is the Java code. This Binder simply listens to UEvents that contains the string "FOO_EVENT", which also embeds the integer as part of the UEvent. Then, we create a UI notification and we attach an Intent to the notification, so that when the user clicks on it, a Activity or a Dialog is launched.
...
public class MyServiceBinder extends Binder {

    private final int FOO_GET = 1;
    private final int FOO_SET = 2;

    private MyServiceWorkerThread mWorker;
    private MyServiceWorkerHandler mHandler;
    private Context mContext;
    private NotificationManager mNotificationManager;

    /*
     * We create an observer to listen to the UEvent we are interested in. 
     * See UEventObserver#startObserving(String). From the UEvent, we extract 
     * an existing integer field and we propagate create a notification to 
     * be seen by the user.
     */
    private final UEventObserver mUEventObserver = new UEventObserver() {
        @Override
        public void onUEvent(UEventObserver.UEvent event) {
            final int eventType = Integer.parseInt(event.get("FOO_EVENT"));
            sendMyServiceNotification(eventType);
        }
    };

    public MyServiceBinder(Context context) {
        super();
        mContext = context;
        mNotificationManager = (NotificationManager)mContext.getSystemService(
                               Context.NOTIFICATION_SERVICE);

         /* We have to specify a filter to receive only certain UEvents from the 
          * kernel. In this case, the UEVENT that we want to listen to contains 
          * the string "FOO_EVENT".
          */
        mUEventObserver.startObserving("FOO_EVENT=");

        mWorker = new MyServiceWorkerThread("MyServiceWorker");
        mWorker.start();
    }

    /*
     * Add potential initialization steps here
     */
    public static MyServiceBinder create(Context context) {
        MyServiceBinder service = new MyServiceBinder(context);
        return service;
    }
    
    /*
     * Sends notification to the user. In this case, we are create notifications. 
     * If the user clicks on the notification, we send an Intent so that an system 
     * application is launched. For example, if we are listening to notifications 
     * for USB UEvents, the USB Settings application can be launched when the 
     * notification is clicked.
     */
    private final void sendNotificationToUser(int type) {
        String notificationMessage = "";
        Resources r = mContext.getResources();
        switch(type) {
                case FOO_GET:
                // do something if the event type is A
                notificationMessage = "This is event type FOO_GET";
                break;
                case FOO_SET:
                // do something if the event type is B
                notificationMessage = "This is event type FOO_SET";      
                break;
                default:
                break;
        }  

        String notificationTitle = r.getString(R.string.my_service_notification_title);
        long notificationWhen = System.currentTimeMillis();
        int requestID = (int) notificationWhen;
 
        Intent intent = new Intent(Intent.ACTION_FOO_EVENT);
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP
                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        intent.putExtra(FooManager.EXTRA_ETYPE, type);
        intent.putExtra(FooManager.EXTRA_MESSAGE, notificationMessage);

        PendingIntent pi = PendingIntent.getActivityAsUser(mContext, requestID,
                                intent, 0, null, UserHandle.CURRENT);

        Notification notification = new Notification.Builder(mContext)
                        .setSmallIcon(R.drawable.indicator_input_error)
                        .setContentTitle(notificationTitle)
                        .setContentText(notificationMessage)
                        .setPriority(Notification.PRIORITY_HIGH)
                        .setDefaults(0)
                        .setWhen(notificationWhen)
                        .setOngoing(true)
                        .setContentIntent(pi)
                        .setAutoCancel(true)
                        .build();

        mNotificationManager.notifyAsUser(null,
                        R.string.my_service_notification_title,
                        notification, UserHandle.ALL);
    }


    /*
     * Runner for the 
     *
     */
    private void sendMyServiceNotification(final int type) {
        
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                sendNotificationToUser(type);
            }
        });

    }

    private class MyServiceWorkerThread extends Thread {

        public MyServiceWorkerThread(String name) {
            super(name);
        }

        public void run() {
            Looper.prepare();
            mHandler = new MyServiceWorkerHandler();
            Looper.loop();
        }

    }
 
    private class MyServiceWorkerHandler extends Handler {

        private static final int MESSAGE_SET = 0;

        @Override
        public void handleMessage(Message msg) {
            try {
                if (msg.what == MESSAGE_SET) {
                    Log.i(TAG, "set message received: " + msg.arg1);
                }
            } catch (Exception e) {
                // Log, don't crash!
                Log.e(TAG, "Exception in MyServiceWorkerHandler.handleMessage:", e);
            }
        }

    }
}

No comments: