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: Li Li <dualli@google.com>
Signed-off-by: Carlos Llamas <cmllamas@google.com>
Link: https://lore.kernel.org/r/20250727182932.2499194-4-cmllamas@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Li Li 2025-07-27 18:29:06 +00:00 committed by Greg Kroah-Hartman
parent 5cd0645b43
commit 63740349eb
8 changed files with 265 additions and 5 deletions

View File

@ -0,0 +1,93 @@
# 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

View File

@ -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

View File

@ -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,

View File

@ -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/

View File

@ -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);

View File

@ -0,0 +1,31 @@
// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/binder.yaml */
/* YNL-GEN kernel source */
#include <net/netlink.h>
#include <net/genetlink.h>
#include "binder_netlink.h"
#include <uapi/linux/android/binder_netlink.h>
/* Ops table for binder */
static const struct genl_split_ops binder_nl_ops[] = {
};
static const struct genl_multicast_group binder_nl_mcgrps[] = {
[BINDER_NLGRP_REPORT] = { "report", },
};
struct genl_family binder_nl_family __ro_after_init = {
.name = BINDER_FAMILY_NAME,
.version = BINDER_FAMILY_VERSION,
.netnsok = true,
.parallel_ops = true,
.module = THIS_MODULE,
.split_ops = binder_nl_ops,
.n_split_ops = ARRAY_SIZE(binder_nl_ops),
.mcgrps = binder_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(binder_nl_mcgrps),
};

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/binder.yaml */
/* YNL-GEN kernel header */
#ifndef _LINUX_BINDER_GEN_H
#define _LINUX_BINDER_GEN_H
#include <net/netlink.h>
#include <net/genetlink.h>
#include <uapi/linux/android/binder_netlink.h>
enum {
BINDER_NLGRP_REPORT,
};
extern struct genl_family binder_nl_family;
#endif /* _LINUX_BINDER_GEN_H */

View File

@ -0,0 +1,37 @@
/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/binder.yaml */
/* YNL-GEN uapi header */
#ifndef _UAPI_LINUX_ANDROID_BINDER_NETLINK_H
#define _UAPI_LINUX_ANDROID_BINDER_NETLINK_H
#define BINDER_FAMILY_NAME "binder"
#define BINDER_FAMILY_VERSION 1
enum {
BINDER_A_REPORT_ERROR = 1,
BINDER_A_REPORT_CONTEXT,
BINDER_A_REPORT_FROM_PID,
BINDER_A_REPORT_FROM_TID,
BINDER_A_REPORT_TO_PID,
BINDER_A_REPORT_TO_TID,
BINDER_A_REPORT_IS_REPLY,
BINDER_A_REPORT_FLAGS,
BINDER_A_REPORT_CODE,
BINDER_A_REPORT_DATA_SIZE,
__BINDER_A_REPORT_MAX,
BINDER_A_REPORT_MAX = (__BINDER_A_REPORT_MAX - 1)
};
enum {
BINDER_CMD_REPORT = 1,
__BINDER_CMD_MAX,
BINDER_CMD_MAX = (__BINDER_CMD_MAX - 1)
};
#define BINDER_MCGRP_REPORT "report"
#endif /* _UAPI_LINUX_ANDROID_BINDER_NETLINK_H */