Source code analysis of Binder native layer: sm's receiving and processing of data

Keywords: SELinux Android Permission denied MediaPlayer

We start with the main function of servicemanager

main and binder & loop

//\frameworks\native\cmds\servicemanager\service_manager.c
int main()
{
    struct binder_state *bs;

    bs = binder_open(128*1024);
    if (!bs) {
        ALOGE("failed to open binder driver\n");
        return -1;
    }

    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
//..., selinux related code, skipping
    binder_loop(bs, svcmgr_handler);

    return 0;
}

In the main function of servicemanager, the Binder driver is turned on first, then you can make yourself the default manager of the environment, and then you can enter the Binder menu loop. Note that the svcmgr? Handler function is passed in as a parameter to the Binder? Loop.

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];

    bwr.write_size = 0;//write size is 0, so it will not write
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
//It seems that the handling of the protocol by binder ﹣ thread ﹣ write is just to set the state of the thread. It's not important. Skip
    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint32_t));
//Enter cycle
    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;

        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//Read data

        if (res < 0) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }
		//Give it to binder Gu parse
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == 0) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}

In the binder loop, bwr's write size is 0, and read size > 0. That is to say, the function constantly reads the request in the loop, and then submits it to the binder parse for processing.

In the ioctl function of the driver, since read_size > 0 and write_size=0, the bind_thread_read function will be entered.

binder_thread_read

static int binder_thread_read(struct binder_proc *proc,
			      struct binder_thread *thread,
			      binder_uintptr_t binder_buffer, size_t size,
			      binder_size_t *consumed, int non_block)
{
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	void __user *ptr = buffer + *consumed;//That is, read buf in binder loop
	void __user *end = buffer + size;

	int ret = 0;
	int wait_for_proc_work;

	if (*consumed == 0) {
		if (put_user(BR_NOOP, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
	}
retry:
	binder_inner_proc_lock(proc);
	wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
	binder_inner_proc_unlock(proc);

	thread->looper |= BINDER_LOOPER_STATE_WAITING;
//When transaction stack is empty and todo list is empty, wait for proc work is true
	trace_binder_wait_for_work(wait_for_proc_work,
				   !!thread->transaction_stack,
				   !binder_worklist_empty(proc, &thread->todo));
//The transaction has been put into the todo list. If wait for proc work is false, the if will not be entered
	if (wait_for_proc_work) {
		if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
					BINDER_LOOPER_STATE_ENTERED))) {
			binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n",
				proc->pid, thread->pid, thread->looper);
		//Into sleep, waiting to wake up
			wait_event_interruptible(binder_user_error_wait,
						 binder_stop_on_user_error < 2);
		}
		binder_restore_priority(current, proc->default_priority);
	}
	if (non_block) {
		if (!binder_has_work(thread, wait_for_proc_work))
			ret = -EAGAIN;
	} else {
		ret = binder_wait_for_work(thread, wait_for_proc_work);
	}
	//Set thread loop state
	thread->looper &= ~BINDER_LOOPER_STATE_WAITING;

	if (ret)
		return ret;

In the last blog post, we know that the driver has put a transaction into the todo list, so we will not go to sleep at this time, and continue to read.

	while(1) {
		uint32_t cmd;
		struct binder_transaction_data tr;
		struct binder_work *w = NULL;
		struct list_head *list = NULL;
		struct binder_transaction *t = NULL;
		struct binder_thread *t_from;

		binder_inner_proc_lock(proc);
		//Get todo queue
		if (!binder_worklist_empty_ilocked(&thread->todo))
			list = &thread->todo;
		else if (!binder_worklist_empty_ilocked(&proc->todo) &&
			   wait_for_proc_work)
			list = &proc->todo;
		else {
			binder_inner_proc_unlock(proc);

			/* no data added */
			if (ptr - buffer == 4 && !thread->looper_need_return)
				goto retry;
			break;
		}
		w = binder_dequeue_work_head_ilocked(list);
		if (binder_worklist_empty_ilocked(&thread->todo))
			thread->process_todo = false;
//Handle according to the type of binder work. Here we only focus on how it was put into the sending thread before processing
//tcomplete of type binder work transaction complete and
//Binder work of type transaction
		switch (w->type) {
		case BINDER_WORK_TRANSACTION: {
			binder_inner_proc_unlock(proc);
//Put the address of t - > work into the queue in the binder ﹣ thread ﹣ write. Here, recover the address of t - > work according to the address of work
			t = container_of(w, struct binder_transaction, work);
		} break;
//Because in ioctl, it is written first and then read, when the sending process is completed, it will enter
//The branch of bind ﹣ thread ﹣ read processes the tcomplete transaction put into the todo queue when it was written,
//Instead of waiting for ioctl to be called again.
		case BINDER_WORK_TRANSACTION_COMPLETE: {
			binder_inner_proc_unlock(proc);
			cmd = BR_TRANSACTION_COMPLETE;//
			if (put_user(cmd, (uint32_t __user *)ptr))//Writing the cmd of Br? Transaction? Compile in prt (mIn) indicates that the transmission is successful.
				return -EFAULT;
			ptr += sizeof(uint32_t);

			binder_stat_br(proc, thread, cmd);
			binder_debug(BINDER_DEBUG_TRANSACTION_COMPLETE,
				     "%d:%d BR_TRANSACTION_COMPLETE\n",
				     proc->pid, thread->pid);
			kfree(w);
			binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
		} break;
//...

In the switch case, when dealing with binder ﹣ work ﹣ transfer, only a simple recovery of t is made, and the processing of t is put behind:

		if (!t)
			continue;

		BUG_ON(t->buffer == NULL);
		if (t->buffer->target_node) {
		//Get the bind node corresponding to sm
			struct binder_node *target_node = t->buffer->target_node;
			struct binder_priority node_prio;
		//According to the analysis of the previous post, we know that ptr and cookie point to service class
			tr.target.ptr = target_node->ptr;
			tr.cookie =  target_node->cookie;//Note that cookie s are assigned here
			node_prio.sched_policy = target_node->sched_policy;
			node_prio.prio = target_node->min_priority;
			binder_transaction_priority(current, t, node_prio,
						    target_node->inherit_rt);
			cmd = BR_TRANSACTION;//Note that this is Br? Transaction, not BC
		} else {
			tr.target.ptr = 0;
			tr.cookie = 0;
			cmd = BR_REPLY;
		}
		tr.code = t->code;
		tr.flags = t->flags;
		tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);

		t_from = binder_get_txn_from(t);
		if (t_from) {
			struct task_struct *sender = t_from->proc->tsk;

			tr.sender_pid = task_tgid_nr_ns(sender,
							task_active_pid_ns(current));
		} else {
			tr.sender_pid = 0;
		}

		tr.data_size = t->buffer->data_size;
		tr.offsets_size = t->buffer->offsets_size;
		//Convert kernel address to user space address
		tr.data.ptr.buffer = (binder_uintptr_t)
			((uintptr_t)t->buffer->data +
			binder_alloc_get_user_buffer_offset(&proc->alloc));
		tr.data.ptr.offsets = tr.data.ptr.buffer +
					ALIGN(t->buffer->data_size,
					    sizeof(void *));
	//Write protocol to read buf
		if (put_user(cmd, (uint32_t __user *)ptr)) {
			if (t_from)
				binder_thread_dec_tmpref(t_from);

			binder_cleanup_transaction(t, "put_user failed",
						   BR_FAILED_REPLY);

			return -EFAULT;
		}
		ptr += sizeof(uint32_t);
		//Write bind transaction data tr to read buf
		if (copy_to_user(ptr, &tr, sizeof(tr))) {
			if (t_from)
				binder_thread_dec_tmpref(t_from);

			binder_cleanup_transaction(t, "copy_to_user failed",
						   BR_FAILED_REPLY);

			return -EFAULT;
		}
		ptr += sizeof(tr);
		//...

		if (t_from)
			binder_thread_dec_tmpref(t_from);
		t->buffer->allow_user_free = 1;
		if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
			binder_inner_proc_lock(thread->proc);
			//Backup transaction? Stack
			t->to_parent = thread->transaction_stack;
			t->to_thread = thread;
			thread->transaction_stack = t;//Transaction "stack" of this thread is equal to t
			binder_inner_proc_unlock(thread->proc);
		} else {
			binder_free_transaction(t);
		}
		break;
	}

The above code is very simple. According to t, we reconstruct the bind_transaction_data TR, and then write it into the read_buf in the previous bind_loop. Note that the transmitted data has been copied to the communication space allocated to the process in the bind \ thread \ write, so the value of tr.data.ptr.buffer is directly equal to the address of the communication space (converted to the address of the user space), thus completing the data cross program transmission.

In addition, note that one of the assignment statements tr.cookie = trace_node.cookie, and the reference of the service corresponding to the target service node is stored in tr.

The next code is to update bwr.consumed. After the update, the bind? Thread? Read is finished.

binder_parse

After ioctl is called, the binder loop will enter the binder parse function.

//res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
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) {
//Read cmd first.
        uint32_t cmd = *(uint32_t *) ptr;//Read br "transaction
        ptr += sizeof(uint32_t);
        switch(cmd) {
//...
        case BR_TRANSACTION: {
//As in waitforResponse, when dealing with the TRANSACTION and REPLY protocols, the following data
//It is forced to be of type bind_transaction_data, except that the variable name here is txn, and
//The variable name in waitForResponse is tr
            struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
            if ((end - ptr) < sizeof(*txn)) {
                ALOGE("parse: txn too small!\n");
                return -1;
            }
            binder_dump_txn(txn);
            if (func) {//func is the function svcmgr? Handler
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;//Note that reply is of type binder ﹣ IO, not Parcel
                int res;

                bio_init(&reply, rdata, sizeof(rdata), 4);//Initialize reply
                bio_init_from_txn(&msg, txn);//See below
                res = func(bs, txn, &msg, &reply);//Give to svcmgr ﹐ handler for processing
                if (txn->flags & TF_ONE_WAY) {//If flags is 0, the if will not be entered
                    binder_free_buffer(bs, txn->data.ptr.buffer);
                } else {//Enter the else
                //Send reply
                    binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
                }
            }
            ptr += sizeof(*txn);
            break;
        }
//...
}

Bind? IO type and bio? Init? From? TxN

When processing the br transaction protocol, the read-out bin transaction TxN is used to initialize the bin IO MSG. Let's look at the initialization function and the bin IO data structure.

struct binder_io
{
    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;
};

There are two areas in binderio, data area and off area. Where data0/offs0 is the starting address of the region, and data / off is the current address. Among them, off records the relevant information of obj.

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 code is very simple, that is to extract the data part of txn and discard the part of txn used for data transmission, such as pid, uid, code, etc.

svcmgr_handler

After getting two binders, the binders enter the key function to handle the client request, that is, the svcmgr handler of sm

//res = func(bs, txn, &msg, &reply);
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;

    //ALOGI("target=%p code=%d pid=%d uid=%d\n",
    //      (void*) txn->target.ptr, txn->code, txn->sender_pid, txn->sender_euid);
//Check whether the target is correct
    if (txn->target.ptr != BINDER_SERVICE_MANAGER)
        return -1;

    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);
//Check whether the target service name is android.io.IServiceManager. Service name is the first in the end of source code analysis
//Interface token put into Parcel
    s = bio_get_string16(msg, &len);
    if (s == NULL) {
        return -1;
    }

    if ((len != (sizeof(svcmgr_id) / 2)) ||
        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
        fprintf(stderr,"invalid id %s\n", str8(s, len));
        return -1;
    }

    if (sehandle && selinux_status_updated() > 0) {
        struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
        if (tmp_sehandle) {
            selabel_close(sehandle);
            sehandle = tmp_sehandle;
        }
    }
//Start processing of formal entry request
    switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
//...
    case SVC_MGR_ADD_SERVICE:
//...
    case SVC_MGR_LIST_SERVICES: {
//...
    default:
        ALOGE("unknown code %d\n", txn->code);
        return -1;
    }

    bio_put_uint32(reply, 0);
    return 0;
}

Four kinds of requests are processed. At the end of source code analysis I, we know that the code value passed in is add ﹣ service ﹣ transaction, which corresponds to SVC ﹣ Mgr ﹣ add ﹣ service above in name.

Process SVC Mgr add service request

    case SVC_MGR_ADD_SERVICE:
    //Read the string. In source code analysis 1, the second one written to Parcel is String16("media.player")
    //Service name
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
        handle = bio_get_ref(msg);
        allow_isolated = bio_get_uint32(msg) ? 1 : 0;
        if (do_add_service(bs, s, len, handle, txn->sender_euid,
            allow_isolated, txn->sender_pid))
            return -1;
        break;

Gets the service name and handle value and then calls do_add_service. Let's see how handle is obtained first.

Bio get ref and bio get obj

uint32_t bio_get_ref(struct binder_io *bio)
{
    struct flat_binder_object *obj;

    obj = _bio_get_obj(bio);//Read obj
    if (!obj)
        return 0;
	//In source code analysis 4, we know that the type of obj has been changed to
	//BINDER_TYPE_HANDLE
    if (obj->type == BINDER_TYPE_HANDLE)
        return obj->handle;

    return 0;//Otherwise, return 0
}

static struct flat_binder_object *_bio_get_obj(struct binder_io *bio)
{
    size_t n;
    size_t off = bio->data - bio->data0;

    /* TODO: be smarter about this? */
    //If the offs et information of an off area is equal to the current position, an obj is read from the current position
    for (n = 0; n < bio->offs_avail; n++) {
        if (bio->offs[n] == off)
            return bio_get(bio, sizeof(struct flat_binder_object));
    }

    bio->data_avail = 0;
    bio->flags |= BIO_F_OVERFLOW;
    return NULL;
}

In fact, it is to recover the flat bin object from the offers area (equivalent to bin_transactiondata.data.ptr.offs ets and Parcel.mObjects), and then return its handle value.

As we know in the previous blog, the handle value is the key value of binder ref in the red black tree of binder Proc of sm. Through this handle value, we can find the corresponding binder ref from the binder Proc of sm, and then find the binder node of MediaPlayerService, as shown in the figure:

do_add_service

After obtaining the handle value, it is handed over to do add service for processing.

//do_add_service(bs, s, len, handle, txn->sender_euid,allow_isolated, txn->sender_pid)
int do_add_service(struct binder_state *bs,
                   const uint16_t *s, size_t len,
                   uint32_t handle, uid_t uid, int allow_isolated,
                   pid_t spid)
{
    struct svcinfo *si;

    //ALOGI("add_service('%s',%x,%s) uid=%d\n", str8(s, len), handle,
    //        allow_isolated ? "allow_isolated" : "!allow_isolated", uid);

    if (!handle || (len == 0) || (len > 127))
        return -1;

    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;
    }
//Find whether the service exists in svclist
    si = find_svc(s, len);
    if (si) {//If service exists in svclist
    //If there is a handle value, that is, the old binder ref of the service exists in the binder proc, it will be discarded
        if (si->handle) {
            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",
                 str8(s, len), handle, uid);
            svcinfo_death(bs, si);//Release the reference to the corresponding service of the handle
        }
        si->handle = handle;//Update handle value
    } else {//If the service does not exist in svclist
    	//Assign a new servie and insert svclist
        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;//Record the information of the binder ref key
        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->next = svclist;//Insert svclist
        svclist = si;
    }

    binder_acquire(bs, handle);//Add a reference to the corresponding service of the handle
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}

This code involves some protocols, which can only be seen clearly by combining the processing of the protocol in binder? Thread? Write and executecommand.

When the service to be added already exists, svcinfo · death() will be called to send a data with the protocol of BC · release and the data of the old handle value to Binder.

At the end of the function, a call will be made to the Binder driver to send a data with the protocol of BC acquire and the data of the new handle value to the Binder driver.

Let's see how these protocols are handled in the Binder driver.

Bind > thread > write processing of BC > release, BC > acquire

		switch (cmd) {
		case BC_INCREFS:
		case BC_ACQUIRE:
		case BC_RELEASE:
		case BC_DECREFS: {
			uint32_t target;
			const char *debug_string;
			bool strong = cmd == BC_ACQUIRE || cmd == BC_RELEASE;
			bool increment = cmd == BC_INCREFS || cmd == BC_ACQUIRE;
			struct binder_ref_data rdata;
		//Read the handle value
			if (get_user(target, (uint32_t __user *)ptr))
				return -EFAULT;

			ptr += sizeof(uint32_t);
			ret = -1;
			if (increment && !target) {//target(handle value) is not 0, will not enter this if
				struct binder_node *ctx_mgr_node;
				mutex_lock(&context->context_mgr_node_lock);
				ctx_mgr_node = context->binder_context_mgr_node;
				if (ctx_mgr_node)
					ret = binder_inc_ref_for_node(
							proc, ctx_mgr_node,
							strong, NULL, &rdata);
				mutex_unlock(&context->context_mgr_node_lock);
			}
			if (ret)//If ret is not 0, the if will be entered
				ret = binder_update_ref_for_handle(
						proc, target, increment, strong,
						&rdata);
			if (!ret && rdata.desc != target) {
				binder_user_error("%d:%d tried to acquire reference to desc %d, got %d instead\n",
					proc->pid, thread->pid,
					target, rdata.desc);
			}

The handle value is read out and passed to the binder update ref for handle processing. Note that the proc is the Proc of servicemanager.

Binder? Update? Ref? For? Handle and binder? Ref types

static int binder_update_ref_for_handle(struct binder_proc *proc,
		uint32_t desc, bool increment, bool strong,
		struct binder_ref_data *rdata)
{//desc stands for handle value
	int ret = 0;
	struct binder_ref *ref;
	bool delete_ref = false;

	binder_proc_lock(proc);
	//According to the handle value, find the corresponding binder ref in the red black tree of proc
	ref = binder_get_ref_olocked(proc, desc, strong);
	if (!ref) {
		ret = -EINVAL;
		goto err_no_ref;
	}
	if (increment)
		ret = binder_inc_ref_olocked(ref, strong, NULL);//Add reference
	else
		delete_ref = binder_dec_ref_olocked(ref, strong);//Reduce citation

	if (rdata)
		*rdata = ref->data;
	binder_proc_unlock(proc);

	if (delete_ref)
		binder_free_ref(ref);
	return ret;

err_no_ref:
	binder_proc_unlock(proc);
	return ret;
}

Find the reference corresponding to the handle from the binder ﹣ Proc of the servicemanager process, and then increase or decrease the reference count according to the parameter value.

The code of bind ﹣ Inc ﹣ ref ﹣ locked has been read in the previous blog. To sum up, add bind ﹣ ref - > data.strong, and when the value is 0, add the corresponding bind ﹣ node - > internal ﹣ strong ﹣ refs

When it is reduced to 0, it will also reduce the number of binders > nodes > data.strong.

summary

So far, the service manager has completed the registration of mediaplayer service. In fact, it is to create a binder [node] in the service process, and then create a binder [ref] in the sm process to point to the binder [node] of the service. The handle value is the keyword of the binder [ref], which is used to find the binder [ref] of the service in the red black tree of the binder [ref] of sm.

Remember that in the previous blog, when the service process communicates with the sm process, it directly obtains the binding node of sm from the environment variable. When the binder ref is created, sm can get the binder ref of the service according to the handle value, and then get the binder node, which also has the ability to send communication data to the service process.

In the next blog, we will analyze how sm gives the information of the server to the client to enable the client to communicate with the server when the client queries the service from sm. With this in mind, the bottom implementation of Binder is basically over.

Published 8 original articles · praised 0 · visited 432
Private letter follow

Posted by x_maras on Sun, 02 Feb 2020 09:46:08 -0800