Event handling of servicemanager

stay Start process of servicemanager In, you can see that servicemanager will enter the binder_loop() to start processing events after all preparations are ready. This note focuses on how to handle these core events.

Command resolution: bind_parse()

First of all, the command should be parsed and different actions should be performed according to different commands. From the following code, there are not too many commands supported by servicemanager. Here, we only focus on BR < transaction.

int binder_parse(struct binder_state *bs, struct binder_io *bio, uintptr_t ptr, size_t size, binder_handler func)
{
    int r = 1;
    uintptr_t end = ptr + (uintptr_t) size;

    while (ptr < end) {
		// Command (4 bytes)
        uint32_t cmd = *(uint32_t *) ptr;
        ptr += sizeof(uint32_t);

        switch(cmd) {
        case BR_NOOP:
        case BR_TRANSACTION_COMPLETE:
        case BR_INCREFS:
        case BR_ACQUIRE:
        case BR_RELEASE:
        case BR_DECREFS:
        case BR_TRANSACTION:
			// IPC calls from the client. The call information is in the binder transaction data
            struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
            if ((end - ptr) < sizeof(*txn)) {
                ALOGE("parse: txn too small!\n");
                return -1;
            }
            if (func) {
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;
                int res;
				// Initialize reply (point reply.data to rdata), and you may need to send a response to the caller later
                bio_init(&reply, rdata, sizeof(rdata), 4);
				// Initialize msg according to txn, and then use msg to parse the data passed by the driver
                bio_init_from_txn(&msg, txn);
				// func is svcmgr_handler(), which handles IPC calls
                res = func(bs, txn, &msg, &reply);
				// No response needs to be sent for an IPC call of type oneway
                if (txn->flags & TF_ONE_WAY) {
                    binder_free_buffer(bs, txn->data.ptr.buffer);
                } else {
                    binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
                }
            }
			// Continue with next
            ptr += sizeof(*txn);
            break;
        case BR_REPLY:
        case BR_DEAD_BINDER:
        case BR_FAILED_REPLY:
        case BR_DEAD_REPLY:
        default:
        }
    }
    return r;
}

Initialize message resolution

void bio_init_from_txn(struct binder_io *bio, struct binder_transaction_data *txn)
{
    bio->data = bio->data0 = (char *)(intptr_t)txn->data.ptr.buffer;
    bio->offs = bio->offs0 = (binder_size_t *)(intptr_t)txn->data.ptr.offsets;
    bio->data_avail = txn->data_size;
    bio->offs_avail = txn->offsets_size / sizeof(size_t);
    bio->flags = BIO_F_SHARED;
}

The first part of the message carried by the transaction type command is struct binder transaction data, which indicates what transaction it is:

struct binder_transaction_data {
  // Target indicates who should handle the IPC call. In particular, when target.handle=0, it indicates that servicemanager should handle it
  union {
    __u32 handle;
    binder_uintptr_t ptr;
  } target;
  binder_uintptr_t cookie;
  // Opcode, representing what to do
  __u32 code;
  __u32 flags;
  // Caller's identity
  pid_t sender_pid;
  uid_t sender_euid;
  // Data part interpreted according to opcode
  binder_size_t data_size;
  binder_size_t offsets_size;
  // Point to the data area carried by the transaction, and use the following struct binder IO to operate the data area
  union {
    struct {
      binder_uintptr_t buffer;
      binder_uintptr_t offsets;
    } ptr;
    __u8 buf[8];
  } data;
};

In addition, in order to facilitate the parsing and filling of buffer parameters, servicemanager also defines struct binder_io.

struct binder_io
{
	// During processing, data will gradually move, but data0 always points to the beginning of the message
    char *data;            /* pointer to read/write from */
    binder_size_t *offs;   /* array of offsets */
    size_t data_avail;     /* bytes available in data buffer */
    size_t offs_avail;     /* entries available in offsets array */

    char *data0;           /* start of data buffer */
    binder_size_t *offs0;  /* start of offsets buffer */
    uint32_t flags;
    uint32_t unused;
};

Transaction command processing: br < transaction

int svcmgr_handler(struct binder_state *bs, struct binder_transaction_data *txn,
	struct binder_io *msg, struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;
    uint32_t strict_policy;
    int allow_isolated;
    uint32_t dumpsys_priority;

	// Judge whether it is the message sent to yourself (ptr is 0, i.e. handle is 0)
    if (txn->target.ptr != BINDER_SERVICE_MANAGER)
        return -1;
	// PING operation is used to keep alive and return to success directly
    if (txn->code == PING_TRANSACTION)
        return 0;

    // Equivalent to Parcel::enforceInterface(), reading the RPC
    // header with the strict mode policy mask and the interface name.
    // Note that we ignore the strict_policy and don't propagate it
    // further (since we do no outbound RPCs anyway).
    strict_policy = bio_get_uint32(msg);
    // Resolves the name of servicemanager, format: number of characters in the string (4 bytes) + string (2 bytes per character)
    s = bio_get_string16(msg, &len);
    if (s == NULL) {
        return -1;
    }
	// Verify the name of the binder interface. The caller must be specified as "android.os.IServiceManger"
    if ((len != (sizeof(svcmgr_id) / 2)) || memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
        fprintf(stderr,"invalid id %s\n", str8(s, len));
        return -1;
    }
	// SELinux related, ignored
...
	// According to the operation code, determine what the client needs servicemanager to do,
	// In fact, the only core business that Service manager can do is to query and add services
    switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE: // Access services
		// Get the name of the service to find
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
		// Lookup service
        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
        if (!handle)
            break;
		// Record the found service handle to reply and return
        bio_put_ref(reply, handle);
        return 0;
    case SVC_MGR_ADD_SERVICE:// Add service, i.e. register service
		// Get the name of the service to add
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
		// Get the service handle (the binder driver has been allocated, here it is only for reading)
        handle = bio_get_ref(msg);
        allow_isolated = bio_get_uint32(msg) ? 1 : 0;
        dumpsys_priority = bio_get_uint32(msg);
		// Add service
        if (do_add_service(bs, s, len, handle, txn->sender_euid, allow_isolated, dumpsys_priority, txn->sender_pid))
            return -1;
        break;
    case SVC_MGR_LIST_SERVICES: { // List all registered services
        uint32_t n = bio_get_uint32(msg);
        uint32_t req_dumpsys_priority = bio_get_uint32(msg);
		// Check the identity of the caller
        if (!svc_can_list(txn->sender_pid, txn->sender_euid)) {
            ALOGE("list_service() uid=%d - PERMISSION DENIED\n", txn->sender_euid);
            return -1;
        }
        si = svclist;
        // walk through the list of services n times skipping services that
        // do not support the requested priority
        while (si) {
            if (si->dumpsys_priority & req_dumpsys_priority) {
                if (n == 0) break;
                n--;
            }
            si = si->next;
        }
        if (si) {
            bio_put_string16(reply, si->name);
            return 0;
        }
        return -1;
    }
    default:
        ALOGE("unknown code %d\n", txn->code);
        return -1;
    }
    bio_put_uint32(reply, 0);
    return 0;
}

Registration service

As mentioned above, when registering a service, the caller first needs to specify the name of the service to be registered. servicemanager calls do  add  service() to add a service. As for assigning a handle to a service, it is actually done by the binder driver. servicemanager only obtains the handle from the data passed by the driver.

int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle,
	uid_t uid, int allow_isolated, uint32_t dumpsys_priority, pid_t spid)
{
    struct svcinfo *si;

	// The service handle cannot be 0, and the service name must be provided, and the length cannot exceed 127 characters
    if (!handle || (len == 0) || (len > 127))
        return -1;
	// Check if the caller has permission to register the service
    if (!svc_can_register(s, len, spid, uid)) {
        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n", str8(s, len), handle, uid);
        return -1;
    }
	// Check whether there is a service with the same name by using the service name as the key
    si = find_svc(s, len);
    if (si) {
		// For a repeatedly registered service, its service handle is updated
        if (si->handle) {
            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n", str8(s, len), handle, uid);
            svcinfo_death(bs, si);
        }
        si->handle = handle;
    } else {
		// Assign the svcinfo object to hold the registered service information (including the service name at the end)
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if (!si) {
            ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n", str8(s, len), handle, uid);
            return -1;
        }
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->dumpsys_priority = dumpsys_priority;
		// Insert at the beginning of the service list
        si->next = svclist;
        svclist = si;
    }
	// Increase the reference count to the service through the binder driver
    binder_acquire(bs, handle);
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}

As mentioned above, servicemanager corresponds to a struct svcinfo structure for each registered service, in which the information that servicemanager needs to save is saved, and servicemanager organizes all registered services into the global single chain table svclist.

// Each registered service is abstracted into a svcinfo object in servicemanger
struct svcinfo
{
    struct svcinfo *next;
	// Service handle, which should be provided by each service provider and unique in the service provider process
    uint32_t handle;
	// Service destruction information
    struct binder_death death;
    int allow_isolated;
	// Each service can specify a dumpsys priority to control whether dumpsys can dump to the service
    uint32_t dumpsys_priority;
	// Service name information
    size_t len;
    uint16_t name[0];
};

The rest of the lookup service and enumeration service implementations are not covered in detail.

Published 119 original articles, won praise 12, visited 70000+
Private letter follow

Tags: Permission denied Android SELinux REST

Posted on Sun, 16 Feb 2020 03:26:51 -0800 by m5638829