Commit 63740349 authored by Li Li's avatar Li Li Committed by Greg Kroah-Hartman
Browse files

binder: introduce transaction reports via netlink



Introduce a generic netlink multicast event to report binder transaction
failures to userspace. This allows subscribers to monitor these events
and take appropriate actions, such as stopping a misbehaving application
that is spamming a service with huge amount of transactions.

The multicast event contains full details of the failed transactions,
including the sender/target PIDs, payload size and specific error code.
This interface is defined using a YAML spec, from which the UAPI and
kernel headers and source are auto-generated.

Signed-off-by: default avatarLi Li <dualli@google.com>
Signed-off-by: default avatarCarlos Llamas <cmllamas@google.com>
Link: https://lore.kernel.org/r/20250727182932.2499194-4-cmllamas@google.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 5cd0645b
Loading
Loading
Loading
Loading
+93 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
#
# Copyright 2025 Google LLC
#
---
name: binder
protocol: genetlink
uapi-header: linux/android/binder_netlink.h
doc: Binder interface over generic netlink

attribute-sets:
  -
    name: report
    doc: |
      Attributes included within a transaction failure report. The elements
      correspond directly with the specific transaction that failed, along
      with the error returned to the sender e.g. BR_DEAD_REPLY.

    attributes:
      -
        name: error
        type: u32
        doc: The enum binder_driver_return_protocol returned to the sender.
      -
        name: context
        type: string
        doc: The binder context where the transaction occurred.
      -
        name: from_pid
        type: u32
        doc: The PID of the sender process.
      -
        name: from_tid
        type: u32
        doc: The TID of the sender thread.
      -
        name: to_pid
        type: u32
        doc: |
          The PID of the recipient process. This attribute may not be present
          if the target could not be determined.
      -
        name: to_tid
        type: u32
        doc: |
          The TID of the recipient thread. This attribute may not be present
          if the target could not be determined.
      -
        name: is_reply
        type: flag
        doc: When present, indicates the failed transaction is a reply.
      -
        name: flags
        type: u32
        doc: The bitmask of enum transaction_flags from the transaction.
      -
        name: code
        type: u32
        doc: The application-defined code from the transaction.
      -
        name: data_size
        type: u32
        doc: The transaction payload size in bytes.

operations:
  list:
    -
      name: report
      doc: |
        A multicast event sent to userspace subscribers to notify them about
        binder transaction failures. The generated report provides the full
        details of the specific transaction that failed. The intention is for
        programs to monitor these events and react to the failures as needed.

      attribute-set: report
      mcgrp: report
      event:
        attributes:
          - error
          - context
          - from_pid
          - from_tid
          - to_pid
          - to_tid
          - is_reply
          - flags
          - code
          - data_size

mcast-groups:
  list:
    -
      name: report
+1 −0
Original line number Diff line number Diff line
@@ -1790,6 +1790,7 @@ M: Suren Baghdasaryan <surenb@google.com>
L:	linux-kernel@vger.kernel.org
S:	Supported
T:	git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
F:	Documentation/netlink/specs/binder.yaml
F:	drivers/android/
ANDROID GOLDFISH PIC DRIVER
+1 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ menu "Android"
config ANDROID_BINDER_IPC
	bool "Android Binder IPC Driver"
	depends on MMU
	depends on NET
	default n
	help
	  Binder is used in Android for both communication between processes,
+1 −1
Original line number Diff line number Diff line
@@ -2,5 +2,5 @@
ccflags-y += -I$(src)			# needed for trace events

obj-$(CONFIG_ANDROID_BINDERFS)		+= binderfs.o
obj-$(CONFIG_ANDROID_BINDER_IPC)	+= binder.o binder_alloc.o
obj-$(CONFIG_ANDROID_BINDER_IPC)	+= binder.o binder_alloc.o binder_netlink.o
obj-$(CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST)	+= tests/
+81 −4
Original line number Diff line number Diff line
@@ -74,6 +74,7 @@

#include <linux/cacheflush.h>

#include "binder_netlink.h"
#include "binder_internal.h"
#include "binder_trace.h"

@@ -2993,6 +2994,67 @@ static void binder_set_txn_from_error(struct binder_transaction *t, int id,
	binder_thread_dec_tmpref(from);
}

/**
 * binder_netlink_report() - report a transaction failure via netlink
 * @proc:	the binder proc sending the transaction
 * @t:		the binder transaction that failed
 * @data_size:	the user provided data size for the transaction
 * @error:	enum binder_driver_return_protocol returned to sender
 */
static void binder_netlink_report(struct binder_proc *proc,
				  struct binder_transaction *t,
				  u32 data_size,
				  u32 error)
{
	const char *context = proc->context->name;
	struct sk_buff *skb;
	void *hdr;

	if (!genl_has_listeners(&binder_nl_family, &init_net,
				BINDER_NLGRP_REPORT))
		return;

	skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
	if (!skb)
		return;

	hdr = genlmsg_put(skb, 0, 0, &binder_nl_family, 0, BINDER_CMD_REPORT);
	if (!hdr)
		goto free_skb;

	if (nla_put_u32(skb, BINDER_A_REPORT_ERROR, error) ||
	    nla_put_string(skb, BINDER_A_REPORT_CONTEXT, context) ||
	    nla_put_u32(skb, BINDER_A_REPORT_FROM_PID, t->from_pid) ||
	    nla_put_u32(skb, BINDER_A_REPORT_FROM_TID, t->from_tid))
		goto cancel_skb;

	if (t->to_proc &&
	    nla_put_u32(skb, BINDER_A_REPORT_TO_PID, t->to_proc->pid))
		goto cancel_skb;

	if (t->to_thread &&
	    nla_put_u32(skb, BINDER_A_REPORT_TO_TID, t->to_thread->pid))
		goto cancel_skb;

	if (t->is_reply && nla_put_flag(skb, BINDER_A_REPORT_IS_REPLY))
		goto cancel_skb;

	if (nla_put_u32(skb, BINDER_A_REPORT_FLAGS, t->flags) ||
	    nla_put_u32(skb, BINDER_A_REPORT_CODE, t->code) ||
	    nla_put_u32(skb, BINDER_A_REPORT_DATA_SIZE, data_size))
		goto cancel_skb;

	genlmsg_end(skb, hdr);
	genlmsg_multicast(&binder_nl_family, skb, 0, BINDER_NLGRP_REPORT,
			  GFP_KERNEL);
	return;

cancel_skb:
	genlmsg_cancel(skb, hdr);
free_skb:
	nlmsg_free(skb);
}

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
@@ -3679,10 +3741,13 @@ static void binder_transaction(struct binder_proc *proc,
		return_error_line = __LINE__;
		goto err_copy_data_failed;
	}
	if (t->buffer->oneway_spam_suspect)
	if (t->buffer->oneway_spam_suspect) {
		tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT;
	else
		binder_netlink_report(proc, t, tr->data_size,
				      BR_ONEWAY_SPAM_SUSPECT);
	} else {
		tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
	}

	if (reply) {
		binder_enqueue_thread_work(thread, tcomplete);
@@ -3730,8 +3795,11 @@ static void binder_transaction(struct binder_proc *proc,
		 * process and is put in a pending queue, waiting for the target
		 * process to be unfrozen.
		 */
		if (return_error == BR_TRANSACTION_PENDING_FROZEN)
		if (return_error == BR_TRANSACTION_PENDING_FROZEN) {
			tcomplete->type = BINDER_WORK_TRANSACTION_PENDING;
			binder_netlink_report(proc, t, tr->data_size,
					      return_error);
		}
		binder_enqueue_thread_work(thread, tcomplete);
		if (return_error &&
		    return_error != BR_TRANSACTION_PENDING_FROZEN)
@@ -3789,6 +3857,8 @@ static void binder_transaction(struct binder_proc *proc,
		binder_dec_node(target_node, 1, 0);
		binder_dec_node_tmpref(target_node);
	}

	binder_netlink_report(proc, t, tr->data_size, return_error);
	kfree(t);
	binder_stats_deleted(BINDER_STAT_TRANSACTION);
err_alloc_t_failed:
@@ -7059,12 +7129,19 @@ static int __init binder_init(void)
		}
	}

	ret = init_binderfs();
	ret = genl_register_family(&binder_nl_family);
	if (ret)
		goto err_init_binder_device_failed;

	ret = init_binderfs();
	if (ret)
		goto err_init_binderfs_failed;

	return ret;

err_init_binderfs_failed:
	genl_unregister_family(&binder_nl_family);

err_init_binder_device_failed:
	hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) {
		misc_deregister(&device->miscdev);
Loading