Oracle DB is trying to solve a performance overhead problem it has been
facing for the past 10 years and using this patch series, we can fix this
issue.
Oracle DB runs on a large scale with 100000s of short lived processes,
starting up and exiting quickly. A process monitoring DB daemon which
tracks and cleans up after processes that have died without a proper exit
needs notifications only when a process died with a non-zero exit code
(which should be rare).
Due to the pmon architecture, which is distributed, each process is
independent and has minimal interaction with pmon. Hence fd based
solutions to track a process's spawning and exit cannot be used. Pmon
needs to detect the abnormal death of a process so it can cleanup after.
Currently it resorts to checking /proc every few seconds. Other methods
we tried like using system call to reduce the above overhead were not
accepted upstream.
With this change, we add event based filtering to proc connector module
so that DB can only listen to the events it is interested in. A new
event type PROC_EVENT_NONZERO_EXIT is added, which is only sent by kernel
to a listening application when any process exiting has a non-zero exit
status.
This change will give Oracle DB substantial performance savings - it takes
50ms to scan about 8K PIDs in /proc, about 500ms for 100K PIDs. DB does
this check every 3 secs, so over an hour we save 10secs for 100K PIDs.
With this, a client can register to listen for only exit or fork or a mix or
all of the events. This greatly enhances performance - currently, we
need to listen to all events, and there are 9 different types of events.
For eg. handling 3 types of events - 8K-forks + 8K-exits + 8K-execs takes
200ms, whereas handling 2 types - 8K-forks + 8K-exits takes about 150ms,
and handling just one type - 8K exits takes about 70ms.
Measuring the time using pidfds for monitoring 8K process exits took 4
times longer - 200ms, as compared to 70ms using only exit notifications
of proc connector. Hence, we cannot use pidfd for our use case.
This kind of a new event could also be useful to other applications like
Google's lmkd daemon, which needs a killed process's exit notification.
This patch series is organized as follows -
Patch 1 : Needed for patch 3 to work.
Patch 2 : Needed for patch 3 to work.
Patch 3 : Fixes some bugs in proc connector, details in the patch.
Patch 4 : Adds event based filtering for performance enhancements.
Patch 5 : Allow non-root users access to proc connector events.
Patch 6 : Selftest code for proc connector.
v6->v7 changes:
- Incorporated Liam Howlett's comments on v6
- Incorporated Kalesh Anakkur Purayil's comments
v5->v6 changes:
- Incorporated Liam Howlett's comments
- Removed FILTER define from proc_filter.c and added a "-f" run-time
option to run new filter code.
- Made proc_filter.c a selftest in tools/testing/selftests/connector
v4->v5 changes:
- Change the cover letter
- Fix a small issue in proc_filter.c
v3->v4 changes:
- Fix comments by Jakub Kicinski to incorporate root access changes
within bind call of connector
v2->v3 changes:
- Fix comments by Jakub Kicinski to separate netlink (patch 2) (after
layering) from connector fixes (patch 3).
- Minor fixes suggested by Jakub.
- Add new multicast group level permissions check at netlink layer.
Split this into netlink & connector layers (patches 6 & 7)
v1->v2 changes:
- Fix comments by Jakub Kicinski to keep layering within netlink and
update kdocs.
- Move non-root users access patch last in series so remaining patches
can go in first.
v->v1 changes:
- Changed commit log in patch 4 as suggested by Christian Brauner
- Changed patch 4 to make more fine grained access to non-root users
- Fixed warning in cn_proc.c,
Reported-by: kernel test robot <[email protected]>
- Fixed some existing warnings in cn_proc.c
Anjali Kulkarni (6):
netlink: Reverse the patch which removed filtering
netlink: Add new netlink_release function
connector/cn_proc: Add filtering to fix some bugs
connector/cn_proc: Performance improvements
connector/cn_proc: Allow non-root users access
connector/cn_proc: Selftest for proc connector
drivers/connector/cn_proc.c | 112 ++++++-
drivers/connector/connector.c | 40 ++-
drivers/w1/w1_netlink.c | 6 +-
include/linux/connector.h | 8 +-
include/linux/netlink.h | 6 +
include/uapi/linux/cn_proc.h | 62 +++-
net/netlink/af_netlink.c | 33 +-
net/netlink/af_netlink.h | 4 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/connector/Makefile | 6 +
.../testing/selftests/connector/proc_filter.c | 310 ++++++++++++++++++
11 files changed, 545 insertions(+), 43 deletions(-)
create mode 100644 tools/testing/selftests/connector/Makefile
create mode 100644 tools/testing/selftests/connector/proc_filter.c
--
2.41.0
This patch adds the capability to filter messages sent by the proc
connector on the event type supplied in the message from the client
to the connector. The client can register to listen for an event type
given in struct proc_input.
This event based filteting will greatly enhance performance - handling
8K exits takes about 70ms, whereas 8K-forks + 8K-exits takes about 150ms
& handling 8K-forks + 8K-exits + 8K-execs takes 200ms. There are currently
9 different types of events, and we need to listen to all of them. Also,
measuring the time using pidfds for monitoring 8K process exits took
much longer - 200ms, as compared to 70ms using only exit notifications of
proc connector.
We also add a new event type - PROC_EVENT_NONZERO_EXIT, which is
only sent by kernel to a listening application when any process exiting,
has a non-zero exit status. This will help the clients like Oracle DB,
where a monitoring process wants notfications for non-zero process exits
so it can cleanup after them.
This kind of a new event could also be useful to other applications like
Google's lmkd daemon, which needs a killed process's exit notification.
The patch takes care that existing clients using old mechanism of not
sending the event type work without any changes.
cn_filter function checks to see if the event type being notified via
proc connector matches the event type requested by client, before
sending(matches) or dropping(does not match) a packet.
Signed-off-by: Anjali Kulkarni <[email protected]>
---
drivers/connector/cn_proc.c | 62 ++++++++++++++++++++++++++++++++----
include/uapi/linux/cn_proc.h | 19 +++++++++++
2 files changed, 75 insertions(+), 6 deletions(-)
diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
index 1ba288ed2bf7..dfc84d44f804 100644
--- a/drivers/connector/cn_proc.c
+++ b/drivers/connector/cn_proc.c
@@ -50,21 +50,45 @@ static DEFINE_PER_CPU(struct local_event, local_event) = {
static int cn_filter(struct sock *dsk, struct sk_buff *skb, void *data)
{
+ __u32 what, exit_code, *ptr;
enum proc_cn_mcast_op mc_op;
+ uintptr_t val;
- if (!dsk)
+ if (!dsk || !data)
return 0;
+ ptr = (__u32 *)data;
+ what = *ptr++;
+ exit_code = *ptr;
+ val = ((struct proc_input *)(dsk->sk_user_data))->event_type;
mc_op = ((struct proc_input *)(dsk->sk_user_data))->mcast_op;
if (mc_op == PROC_CN_MCAST_IGNORE)
return 1;
- return 0;
+ if ((__u32)val == PROC_EVENT_ALL)
+ return 0;
+
+ /*
+ * Drop packet if we have to report only non-zero exit status
+ * (PROC_EVENT_NONZERO_EXIT) and exit status is 0
+ */
+ if (((__u32)val & PROC_EVENT_NONZERO_EXIT) &&
+ (what == PROC_EVENT_EXIT)) {
+ if (exit_code)
+ return 0;
+ }
+
+ if ((__u32)val & what)
+ return 0;
+
+ return 1;
}
static inline void send_msg(struct cn_msg *msg)
{
+ __u32 filter_data[2];
+
local_lock(&local_event.lock);
msg->seq = __this_cpu_inc_return(local_event.count) - 1;
@@ -76,8 +100,16 @@ static inline void send_msg(struct cn_msg *msg)
*
* If cn_netlink_send() fails, the data is not sent.
*/
+ filter_data[0] = ((struct proc_event *)msg->data)->what;
+ if (filter_data[0] == PROC_EVENT_EXIT) {
+ filter_data[1] =
+ ((struct proc_event *)msg->data)->event_data.exit.exit_code;
+ } else {
+ filter_data[1] = 0;
+ }
+
cn_netlink_send_mult(msg, msg->len, 0, CN_IDX_PROC, GFP_NOWAIT,
- cn_filter, NULL);
+ cn_filter, (void *)filter_data);
local_unlock(&local_event.lock);
}
@@ -357,12 +389,15 @@ static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack)
/**
* cn_proc_mcast_ctl
- * @data: message sent from userspace via the connector
+ * @msg: message sent from userspace via the connector
+ * @nsp: NETLINK_CB of the client's socket buffer
*/
static void cn_proc_mcast_ctl(struct cn_msg *msg,
struct netlink_skb_parms *nsp)
{
enum proc_cn_mcast_op mc_op = 0, prev_mc_op = 0;
+ struct proc_input *pinput = NULL;
+ enum proc_cn_event ev_type = 0;
int err = 0, initial = 0;
struct sock *sk = NULL;
@@ -381,10 +416,21 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
goto out;
}
- if (msg->len == sizeof(mc_op))
+ if (msg->len == sizeof(*pinput)) {
+ pinput = (struct proc_input *)msg->data;
+ mc_op = pinput->mcast_op;
+ ev_type = pinput->event_type;
+ } else if (msg->len == sizeof(mc_op)) {
mc_op = *((enum proc_cn_mcast_op *)msg->data);
- else
+ ev_type = PROC_EVENT_ALL;
+ } else {
return;
+ }
+
+ ev_type = valid_event((enum proc_cn_event)ev_type);
+
+ if (ev_type == PROC_EVENT_NONE)
+ ev_type = PROC_EVENT_ALL;
if (nsp->sk) {
sk = nsp->sk;
@@ -400,6 +446,8 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
prev_mc_op =
((struct proc_input *)(sk->sk_user_data))->mcast_op;
}
+ ((struct proc_input *)(sk->sk_user_data))->event_type =
+ ev_type;
((struct proc_input *)(sk->sk_user_data))->mcast_op = mc_op;
}
@@ -411,6 +459,8 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
case PROC_CN_MCAST_IGNORE:
if (!initial && (prev_mc_op != PROC_CN_MCAST_IGNORE))
atomic_dec(&proc_event_num_listeners);
+ ((struct proc_input *)(sk->sk_user_data))->event_type =
+ PROC_EVENT_NONE;
break;
default:
err = EINVAL;
diff --git a/include/uapi/linux/cn_proc.h b/include/uapi/linux/cn_proc.h
index 6a06fb424313..f2afb7cc4926 100644
--- a/include/uapi/linux/cn_proc.h
+++ b/include/uapi/linux/cn_proc.h
@@ -30,6 +30,15 @@ enum proc_cn_mcast_op {
PROC_CN_MCAST_IGNORE = 2
};
+#define PROC_EVENT_ALL (PROC_EVENT_FORK | PROC_EVENT_EXEC | PROC_EVENT_UID | \
+ PROC_EVENT_GID | PROC_EVENT_SID | PROC_EVENT_PTRACE | \
+ PROC_EVENT_COMM | PROC_EVENT_NONZERO_EXIT | \
+ PROC_EVENT_COREDUMP | PROC_EVENT_EXIT)
+
+/*
+ * If you add an entry in proc_cn_event, make sure you add it in
+ * PROC_EVENT_ALL above as well.
+ */
enum proc_cn_event {
/* Use successive bits so the enums can be used to record
* sets of events as well
@@ -45,15 +54,25 @@ enum proc_cn_event {
/* "next" should be 0x00000400 */
/* "last" is the last process event: exit,
* while "next to last" is coredumping event
+ * before that is report only if process dies
+ * with non-zero exit status
*/
+ PROC_EVENT_NONZERO_EXIT = 0x20000000,
PROC_EVENT_COREDUMP = 0x40000000,
PROC_EVENT_EXIT = 0x80000000
};
struct proc_input {
enum proc_cn_mcast_op mcast_op;
+ enum proc_cn_event event_type;
};
+static inline enum proc_cn_event valid_event(enum proc_cn_event ev_type)
+{
+ ev_type &= PROC_EVENT_ALL;
+ return ev_type;
+}
+
/*
* From the user's point of view, the process
* ID is the thread group ID and thread ID is the internal
--
2.41.0
There were a couple of reasons for not allowing non-root users access
initially - one is there was some point no proper receive buffer
management in place for netlink multicast. But that should be long
fixed. See link below for more context.
Second is that some of the messages may contain data that is root only. But
this should be handled with a finer granularity, which is being done at the
protocol layer. The only problematic protocols are nf_queue and the
firewall netlink. Hence, this restriction for non-root access was relaxed
for NETLINK_ROUTE initially:
https://lore.kernel.org/all/[email protected]/
This restriction has also been removed for following protocols:
NETLINK_KOBJECT_UEVENT, NETLINK_AUDIT, NETLINK_SOCK_DIAG,
NETLINK_GENERIC, NETLINK_SELINUX.
Since process connector messages are not sensitive (process fork, exit
notifications etc.), and anyone can read /proc data, we can allow non-root
access here. However, since process event notification is not the only
consumer of NETLINK_CONNECTOR, we can make this change even more
fine grained than the protocol level, by checking for multicast group
within the protocol.
Allow non-root access for NETLINK_CONNECTOR via NL_CFG_F_NONROOT_RECV
but add new bind function cn_bind(), which allows non-root access only
for CN_IDX_PROC multicast group.
Signed-off-by: Anjali Kulkarni <[email protected]>
---
drivers/connector/cn_proc.c | 7 -------
drivers/connector/connector.c | 19 +++++++++++++++++++
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
index dfc84d44f804..bb1fa5d66cf9 100644
--- a/drivers/connector/cn_proc.c
+++ b/drivers/connector/cn_proc.c
@@ -410,12 +410,6 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
!task_is_in_init_pid_ns(current))
return;
- /* Can only change if privileged. */
- if (!__netlink_ns_capable(nsp, &init_user_ns, CAP_NET_ADMIN)) {
- err = EPERM;
- goto out;
- }
-
if (msg->len == sizeof(*pinput)) {
pinput = (struct proc_input *)msg->data;
mc_op = pinput->mcast_op;
@@ -467,7 +461,6 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
break;
}
-out:
cn_proc_ack(err, msg->seq, msg->ack);
}
diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c
index d1179df2b0ba..7f7b94f616a6 100644
--- a/drivers/connector/connector.c
+++ b/drivers/connector/connector.c
@@ -166,6 +166,23 @@ static int cn_call_callback(struct sk_buff *skb)
return err;
}
+/*
+ * Allow non-root access for NETLINK_CONNECTOR family having CN_IDX_PROC
+ * multicast group.
+ */
+static int cn_bind(struct net *net, int group)
+{
+ unsigned long groups = (unsigned long) group;
+
+ if (ns_capable(net->user_ns, CAP_NET_ADMIN))
+ return 0;
+
+ if (test_bit(CN_IDX_PROC - 1, &groups))
+ return 0;
+
+ return -EPERM;
+}
+
static void cn_release(struct sock *sk, unsigned long *groups)
{
if (groups && test_bit(CN_IDX_PROC - 1, groups)) {
@@ -261,6 +278,8 @@ static int cn_init(void)
struct netlink_kernel_cfg cfg = {
.groups = CN_NETLINK_USERS + 0xf,
.input = cn_rx_skb,
+ .flags = NL_CFG_F_NONROOT_RECV,
+ .bind = cn_bind,
.release = cn_release,
};
--
2.41.0
Run as ./proc_filter -f to run new filter code. Run without "-f" to run
usual proc connector code without the new filtering code.
Signed-off-by: Anjali Kulkarni <[email protected]>
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/connector/Makefile | 6 +
.../testing/selftests/connector/proc_filter.c | 310 ++++++++++++++++++
3 files changed, 317 insertions(+)
create mode 100644 tools/testing/selftests/connector/Makefile
create mode 100644 tools/testing/selftests/connector/proc_filter.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 90a62cf75008..7c9673951f9a 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -7,6 +7,7 @@ TARGETS += breakpoints
TARGETS += capabilities
TARGETS += cgroup
TARGETS += clone3
+TARGETS += connector
TARGETS += core
TARGETS += cpufreq
TARGETS += cpu-hotplug
diff --git a/tools/testing/selftests/connector/Makefile b/tools/testing/selftests/connector/Makefile
new file mode 100644
index 000000000000..21c9f3a973a0
--- /dev/null
+++ b/tools/testing/selftests/connector/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -Wall
+
+TEST_GEN_PROGS = proc_filter
+
+include ../lib.mk
diff --git a/tools/testing/selftests/connector/proc_filter.c b/tools/testing/selftests/connector/proc_filter.c
new file mode 100644
index 000000000000..4fe8c6763fd8
--- /dev/null
+++ b/tools/testing/selftests/connector/proc_filter.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <sys/types.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/connector.h>
+#include <linux/cn_proc.h>
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <strings.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+
+#include "../kselftest.h"
+
+#define NL_MESSAGE_SIZE (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \
+ sizeof(struct proc_input))
+#define NL_MESSAGE_SIZE_NF (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \
+ sizeof(int))
+
+#define MAX_EVENTS 1
+
+volatile static int interrupted;
+static int nl_sock, ret_errno, tcount;
+static struct epoll_event evn;
+
+static int filter;
+
+#ifdef ENABLE_PRINTS
+#define Printf printf
+#else
+#define Printf ksft_print_msg
+#endif
+
+int send_message(void *pinp)
+{
+ char buff[NL_MESSAGE_SIZE];
+ struct nlmsghdr *hdr;
+ struct cn_msg *msg;
+
+ hdr = (struct nlmsghdr *)buff;
+ if (filter)
+ hdr->nlmsg_len = NL_MESSAGE_SIZE;
+ else
+ hdr->nlmsg_len = NL_MESSAGE_SIZE_NF;
+ hdr->nlmsg_type = NLMSG_DONE;
+ hdr->nlmsg_flags = 0;
+ hdr->nlmsg_seq = 0;
+ hdr->nlmsg_pid = getpid();
+
+ msg = (struct cn_msg *)NLMSG_DATA(hdr);
+ msg->id.idx = CN_IDX_PROC;
+ msg->id.val = CN_VAL_PROC;
+ msg->seq = 0;
+ msg->ack = 0;
+ msg->flags = 0;
+
+ if (filter) {
+ msg->len = sizeof(struct proc_input);
+ ((struct proc_input *)msg->data)->mcast_op =
+ ((struct proc_input *)pinp)->mcast_op;
+ ((struct proc_input *)msg->data)->event_type =
+ ((struct proc_input *)pinp)->event_type;
+ } else {
+ msg->len = sizeof(int);
+ *(int *)msg->data = *(enum proc_cn_mcast_op *)pinp;
+ }
+
+ if (send(nl_sock, hdr, hdr->nlmsg_len, 0) == -1) {
+ ret_errno = errno;
+ perror("send failed");
+ return -3;
+ }
+ return 0;
+}
+
+int register_proc_netlink(int *efd, void *input)
+{
+ struct sockaddr_nl sa_nl;
+ int err = 0, epoll_fd;
+
+ nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
+
+ if (nl_sock == -1) {
+ ret_errno = errno;
+ perror("socket failed");
+ return -1;
+ }
+
+ bzero(&sa_nl, sizeof(sa_nl));
+ sa_nl.nl_family = AF_NETLINK;
+ sa_nl.nl_groups = CN_IDX_PROC;
+ sa_nl.nl_pid = getpid();
+
+ if (bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl)) == -1) {
+ ret_errno = errno;
+ perror("bind failed");
+ return -2;
+ }
+
+ epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+ if (epoll_fd < 0) {
+ ret_errno = errno;
+ perror("epoll_create1 failed");
+ return -2;
+ }
+
+ err = send_message(input);
+
+ if (err < 0)
+ return err;
+
+ evn.events = EPOLLIN;
+ evn.data.fd = nl_sock;
+ if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, nl_sock, &evn) < 0) {
+ ret_errno = errno;
+ perror("epoll_ctl failed");
+ return -3;
+ }
+ *efd = epoll_fd;
+ return 0;
+}
+
+static void sigint(int sig)
+{
+ interrupted = 1;
+}
+
+int handle_packet(char *buff, int fd, struct proc_event *event)
+{
+ struct nlmsghdr *hdr;
+
+ hdr = (struct nlmsghdr *)buff;
+
+ if (hdr->nlmsg_type == NLMSG_ERROR) {
+ perror("NLMSG_ERROR error\n");
+ return -3;
+ } else if (hdr->nlmsg_type == NLMSG_DONE) {
+ event = (struct proc_event *)
+ ((struct cn_msg *)NLMSG_DATA(hdr))->data;
+ tcount++;
+ switch (event->what) {
+ case PROC_EVENT_EXIT:
+ Printf("Exit process %d (tgid %d) with code %d, signal %d\n",
+ event->event_data.exit.process_pid,
+ event->event_data.exit.process_tgid,
+ event->event_data.exit.exit_code,
+ event->event_data.exit.exit_signal);
+ break;
+ case PROC_EVENT_FORK:
+ Printf("Fork process %d (tgid %d), parent %d (tgid %d)\n",
+ event->event_data.fork.child_pid,
+ event->event_data.fork.child_tgid,
+ event->event_data.fork.parent_pid,
+ event->event_data.fork.parent_tgid);
+ break;
+ case PROC_EVENT_EXEC:
+ Printf("Exec process %d (tgid %d)\n",
+ event->event_data.exec.process_pid,
+ event->event_data.exec.process_tgid);
+ break;
+ case PROC_EVENT_UID:
+ Printf("UID process %d (tgid %d) uid %d euid %d\n",
+ event->event_data.id.process_pid,
+ event->event_data.id.process_tgid,
+ event->event_data.id.r.ruid,
+ event->event_data.id.e.euid);
+ break;
+ case PROC_EVENT_GID:
+ Printf("GID process %d (tgid %d) gid %d egid %d\n",
+ event->event_data.id.process_pid,
+ event->event_data.id.process_tgid,
+ event->event_data.id.r.rgid,
+ event->event_data.id.e.egid);
+ break;
+ case PROC_EVENT_SID:
+ Printf("SID process %d (tgid %d)\n",
+ event->event_data.sid.process_pid,
+ event->event_data.sid.process_tgid);
+ break;
+ case PROC_EVENT_PTRACE:
+ Printf("Ptrace process %d (tgid %d), Tracer %d (tgid %d)\n",
+ event->event_data.ptrace.process_pid,
+ event->event_data.ptrace.process_tgid,
+ event->event_data.ptrace.tracer_pid,
+ event->event_data.ptrace.tracer_tgid);
+ break;
+ case PROC_EVENT_COMM:
+ Printf("Comm process %d (tgid %d) comm %s\n",
+ event->event_data.comm.process_pid,
+ event->event_data.comm.process_tgid,
+ event->event_data.comm.comm);
+ break;
+ case PROC_EVENT_COREDUMP:
+ Printf("Coredump process %d (tgid %d) parent %d, (tgid %d)\n",
+ event->event_data.coredump.process_pid,
+ event->event_data.coredump.process_tgid,
+ event->event_data.coredump.parent_pid,
+ event->event_data.coredump.parent_tgid);
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+int handle_events(int epoll_fd, struct proc_event *pev)
+{
+ char buff[CONNECTOR_MAX_MSG_SIZE];
+ struct epoll_event ev[MAX_EVENTS];
+ int i, event_count = 0, err = 0;
+
+ event_count = epoll_wait(epoll_fd, ev, MAX_EVENTS, -1);
+ if (event_count < 0) {
+ ret_errno = errno;
+ if (ret_errno != EINTR)
+ perror("epoll_wait failed");
+ return -3;
+ }
+ for (i = 0; i < event_count; i++) {
+ if (!(ev[i].events & EPOLLIN))
+ continue;
+ if (recv(ev[i].data.fd, buff, sizeof(buff), 0) == -1) {
+ ret_errno = errno;
+ perror("recv failed");
+ return -3;
+ }
+ err = handle_packet(buff, ev[i].data.fd, pev);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int epoll_fd, err;
+ struct proc_event proc_ev;
+ struct proc_input input;
+
+ signal(SIGINT, sigint);
+
+ if (argc > 2) {
+ printf("Expected 0(assume no-filter) or 1 argument(-f)\n");
+ exit(1);
+ }
+
+ if (argc == 2) {
+ if (strcmp(argv[1], "-f") == 0) {
+ filter = 1;
+ } else {
+ printf("Valid option : -f (for filter feature)\n");
+ exit(1);
+ }
+ }
+
+ if (filter) {
+ input.event_type = PROC_EVENT_NONZERO_EXIT;
+ input.mcast_op = PROC_CN_MCAST_LISTEN;
+ err = register_proc_netlink(&epoll_fd, (void*)&input);
+ } else {
+ enum proc_cn_mcast_op op = PROC_CN_MCAST_LISTEN;
+ err = register_proc_netlink(&epoll_fd, (void*)&op);
+ }
+
+ if (err < 0) {
+ if (err == -2)
+ close(nl_sock);
+ if (err == -3) {
+ close(nl_sock);
+ close(epoll_fd);
+ }
+ exit(1);
+ }
+
+ while (!interrupted) {
+ err = handle_events(epoll_fd, &proc_ev);
+ if (err < 0) {
+ if (ret_errno == EINTR)
+ continue;
+ if (err == -2)
+ close(nl_sock);
+ if (err == -3) {
+ close(nl_sock);
+ close(epoll_fd);
+ }
+ exit(1);
+ }
+ }
+
+ if (filter) {
+ input.mcast_op = PROC_CN_MCAST_IGNORE;
+ send_message((void*)&input);
+ } else {
+ enum proc_cn_mcast_op op = PROC_CN_MCAST_IGNORE;
+ send_message((void*)&op);
+ }
+
+ close(epoll_fd);
+ close(nl_sock);
+
+ printf("Done total count: %d\n", tcount);
+ exit(0);
+}
--
2.41.0
* Anjali Kulkarni <[email protected]> [691231 23:00]:
> There were a couple of reasons for not allowing non-root users access
> initially - one is there was some point no proper receive buffer
> management in place for netlink multicast. But that should be long
> fixed. See link below for more context.
>
> Second is that some of the messages may contain data that is root only. But
> this should be handled with a finer granularity, which is being done at the
> protocol layer. The only problematic protocols are nf_queue and the
> firewall netlink. Hence, this restriction for non-root access was relaxed
> for NETLINK_ROUTE initially:
> https://lore.kernel.org/all/[email protected]/
>
> This restriction has also been removed for following protocols:
> NETLINK_KOBJECT_UEVENT, NETLINK_AUDIT, NETLINK_SOCK_DIAG,
> NETLINK_GENERIC, NETLINK_SELINUX.
>
> Since process connector messages are not sensitive (process fork, exit
> notifications etc.), and anyone can read /proc data, we can allow non-root
> access here. However, since process event notification is not the only
> consumer of NETLINK_CONNECTOR, we can make this change even more
> fine grained than the protocol level, by checking for multicast group
> within the protocol.
>
> Allow non-root access for NETLINK_CONNECTOR via NL_CFG_F_NONROOT_RECV
> but add new bind function cn_bind(), which allows non-root access only
> for CN_IDX_PROC multicast group.
>
> Signed-off-by: Anjali Kulkarni <[email protected]>
> ---
> drivers/connector/cn_proc.c | 7 -------
> drivers/connector/connector.c | 19 +++++++++++++++++++
> 2 files changed, 19 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
> index dfc84d44f804..bb1fa5d66cf9 100644
> --- a/drivers/connector/cn_proc.c
> +++ b/drivers/connector/cn_proc.c
> @@ -410,12 +410,6 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
> !task_is_in_init_pid_ns(current))
> return;
>
> - /* Can only change if privileged. */
> - if (!__netlink_ns_capable(nsp, &init_user_ns, CAP_NET_ADMIN)) {
> - err = EPERM;
> - goto out;
> - }
> -
> if (msg->len == sizeof(*pinput)) {
> pinput = (struct proc_input *)msg->data;
> mc_op = pinput->mcast_op;
> @@ -467,7 +461,6 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
> break;
> }
>
> -out:
This label is still in use from your changes in patch 3.
> cn_proc_ack(err, msg->seq, msg->ack);
> }
>
> diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c
> index d1179df2b0ba..7f7b94f616a6 100644
> --- a/drivers/connector/connector.c
> +++ b/drivers/connector/connector.c
> @@ -166,6 +166,23 @@ static int cn_call_callback(struct sk_buff *skb)
> return err;
> }
>
> +/*
> + * Allow non-root access for NETLINK_CONNECTOR family having CN_IDX_PROC
> + * multicast group.
> + */
> +static int cn_bind(struct net *net, int group)
> +{
> + unsigned long groups = (unsigned long) group;
> +
> + if (ns_capable(net->user_ns, CAP_NET_ADMIN))
> + return 0;
> +
> + if (test_bit(CN_IDX_PROC - 1, &groups))
> + return 0;
> +
> + return -EPERM;
> +}
> +
> static void cn_release(struct sock *sk, unsigned long *groups)
> {
> if (groups && test_bit(CN_IDX_PROC - 1, groups)) {
> @@ -261,6 +278,8 @@ static int cn_init(void)
> struct netlink_kernel_cfg cfg = {
> .groups = CN_NETLINK_USERS + 0xf,
> .input = cn_rx_skb,
> + .flags = NL_CFG_F_NONROOT_RECV,
> + .bind = cn_bind,
> .release = cn_release,
> };
>
> --
> 2.41.0
>
> On Jul 6, 2023, at 1:16 PM, Liam Howlett <[email protected]> wrote:
>
> * Anjali Kulkarni <[email protected]> [691231 23:00]:
>> There were a couple of reasons for not allowing non-root users access
>> initially - one is there was some point no proper receive buffer
>> management in place for netlink multicast. But that should be long
>> fixed. See link below for more context.
>>
>> Second is that some of the messages may contain data that is root only. But
>> this should be handled with a finer granularity, which is being done at the
>> protocol layer. The only problematic protocols are nf_queue and the
>> firewall netlink. Hence, this restriction for non-root access was relaxed
>> for NETLINK_ROUTE initially:
>> https://lore.kernel.org/all/[email protected]/
>>
>> This restriction has also been removed for following protocols:
>> NETLINK_KOBJECT_UEVENT, NETLINK_AUDIT, NETLINK_SOCK_DIAG,
>> NETLINK_GENERIC, NETLINK_SELINUX.
>>
>> Since process connector messages are not sensitive (process fork, exit
>> notifications etc.), and anyone can read /proc data, we can allow non-root
>> access here. However, since process event notification is not the only
>> consumer of NETLINK_CONNECTOR, we can make this change even more
>> fine grained than the protocol level, by checking for multicast group
>> within the protocol.
>>
>> Allow non-root access for NETLINK_CONNECTOR via NL_CFG_F_NONROOT_RECV
>> but add new bind function cn_bind(), which allows non-root access only
>> for CN_IDX_PROC multicast group.
>>
>> Signed-off-by: Anjali Kulkarni <[email protected]>
>> ---
>> drivers/connector/cn_proc.c | 7 -------
>> drivers/connector/connector.c | 19 +++++++++++++++++++
>> 2 files changed, 19 insertions(+), 7 deletions(-)
>>
>> diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
>> index dfc84d44f804..bb1fa5d66cf9 100644
>> --- a/drivers/connector/cn_proc.c
>> +++ b/drivers/connector/cn_proc.c
>> @@ -410,12 +410,6 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
>> !task_is_in_init_pid_ns(current))
>> return;
>>
>> - /* Can only change if privileged. */
>> - if (!__netlink_ns_capable(nsp, &init_user_ns, CAP_NET_ADMIN)) {
>> - err = EPERM;
>> - goto out;
>> - }
>> -
>> if (msg->len == sizeof(*pinput)) {
>> pinput = (struct proc_input *)msg->data;
>> mc_op = pinput->mcast_op;
>> @@ -467,7 +461,6 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
>> break;
>> }
>>
>> -out:
>
> This label is still in use from your changes in patch 3.
Yes, will send updated patch with this fixed.
>
>> cn_proc_ack(err, msg->seq, msg->ack);
>> }
>>
>> diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c
>> index d1179df2b0ba..7f7b94f616a6 100644
>> --- a/drivers/connector/connector.c
>> +++ b/drivers/connector/connector.c
>> @@ -166,6 +166,23 @@ static int cn_call_callback(struct sk_buff *skb)
>> return err;
>> }
>>
>> +/*
>> + * Allow non-root access for NETLINK_CONNECTOR family having CN_IDX_PROC
>> + * multicast group.
>> + */
>> +static int cn_bind(struct net *net, int group)
>> +{
>> + unsigned long groups = (unsigned long) group;
>> +
>> + if (ns_capable(net->user_ns, CAP_NET_ADMIN))
>> + return 0;
>> +
>> + if (test_bit(CN_IDX_PROC - 1, &groups))
>> + return 0;
>> +
>> + return -EPERM;
>> +}
>> +
>> static void cn_release(struct sock *sk, unsigned long *groups)
>> {
>> if (groups && test_bit(CN_IDX_PROC - 1, groups)) {
>> @@ -261,6 +278,8 @@ static int cn_init(void)
>> struct netlink_kernel_cfg cfg = {
>> .groups = CN_NETLINK_USERS + 0xf,
>> .input = cn_rx_skb,
>> + .flags = NL_CFG_F_NONROOT_RECV,
>> + .bind = cn_bind,
>> .release = cn_release,
>> };
>>
>> --
>> 2.41.0
* Anjali Kulkarni <[email protected]> [691231 23:00]:
> Run as ./proc_filter -f to run new filter code. Run without "-f" to run
> usual proc connector code without the new filtering code.
>
> Signed-off-by: Anjali Kulkarni <[email protected]>
Reviewed-by: Liam R. Howlett <[email protected]>
> ---
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/connector/Makefile | 6 +
> .../testing/selftests/connector/proc_filter.c | 310 ++++++++++++++++++
> 3 files changed, 317 insertions(+)
> create mode 100644 tools/testing/selftests/connector/Makefile
> create mode 100644 tools/testing/selftests/connector/proc_filter.c
>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 90a62cf75008..7c9673951f9a 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -7,6 +7,7 @@ TARGETS += breakpoints
> TARGETS += capabilities
> TARGETS += cgroup
> TARGETS += clone3
> +TARGETS += connector
> TARGETS += core
> TARGETS += cpufreq
> TARGETS += cpu-hotplug
> diff --git a/tools/testing/selftests/connector/Makefile b/tools/testing/selftests/connector/Makefile
> new file mode 100644
> index 000000000000..21c9f3a973a0
> --- /dev/null
> +++ b/tools/testing/selftests/connector/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0
> +CFLAGS += -Wall
> +
> +TEST_GEN_PROGS = proc_filter
> +
> +include ../lib.mk
> diff --git a/tools/testing/selftests/connector/proc_filter.c b/tools/testing/selftests/connector/proc_filter.c
> new file mode 100644
> index 000000000000..4fe8c6763fd8
> --- /dev/null
> +++ b/tools/testing/selftests/connector/proc_filter.c
> @@ -0,0 +1,310 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <sys/types.h>
> +#include <sys/epoll.h>
> +#include <sys/socket.h>
> +#include <linux/netlink.h>
> +#include <linux/connector.h>
> +#include <linux/cn_proc.h>
> +
> +#include <stddef.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <strings.h>
> +#include <errno.h>
> +#include <signal.h>
> +#include <string.h>
> +
> +#include "../kselftest.h"
> +
> +#define NL_MESSAGE_SIZE (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \
> + sizeof(struct proc_input))
> +#define NL_MESSAGE_SIZE_NF (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \
> + sizeof(int))
> +
> +#define MAX_EVENTS 1
> +
> +volatile static int interrupted;
> +static int nl_sock, ret_errno, tcount;
> +static struct epoll_event evn;
> +
> +static int filter;
> +
> +#ifdef ENABLE_PRINTS
> +#define Printf printf
> +#else
> +#define Printf ksft_print_msg
> +#endif
> +
> +int send_message(void *pinp)
> +{
> + char buff[NL_MESSAGE_SIZE];
> + struct nlmsghdr *hdr;
> + struct cn_msg *msg;
> +
> + hdr = (struct nlmsghdr *)buff;
> + if (filter)
> + hdr->nlmsg_len = NL_MESSAGE_SIZE;
> + else
> + hdr->nlmsg_len = NL_MESSAGE_SIZE_NF;
> + hdr->nlmsg_type = NLMSG_DONE;
> + hdr->nlmsg_flags = 0;
> + hdr->nlmsg_seq = 0;
> + hdr->nlmsg_pid = getpid();
> +
> + msg = (struct cn_msg *)NLMSG_DATA(hdr);
> + msg->id.idx = CN_IDX_PROC;
> + msg->id.val = CN_VAL_PROC;
> + msg->seq = 0;
> + msg->ack = 0;
> + msg->flags = 0;
> +
> + if (filter) {
> + msg->len = sizeof(struct proc_input);
> + ((struct proc_input *)msg->data)->mcast_op =
> + ((struct proc_input *)pinp)->mcast_op;
> + ((struct proc_input *)msg->data)->event_type =
> + ((struct proc_input *)pinp)->event_type;
> + } else {
> + msg->len = sizeof(int);
> + *(int *)msg->data = *(enum proc_cn_mcast_op *)pinp;
> + }
> +
> + if (send(nl_sock, hdr, hdr->nlmsg_len, 0) == -1) {
> + ret_errno = errno;
> + perror("send failed");
> + return -3;
> + }
> + return 0;
> +}
> +
> +int register_proc_netlink(int *efd, void *input)
> +{
> + struct sockaddr_nl sa_nl;
> + int err = 0, epoll_fd;
> +
> + nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
> +
> + if (nl_sock == -1) {
> + ret_errno = errno;
> + perror("socket failed");
> + return -1;
> + }
> +
> + bzero(&sa_nl, sizeof(sa_nl));
> + sa_nl.nl_family = AF_NETLINK;
> + sa_nl.nl_groups = CN_IDX_PROC;
> + sa_nl.nl_pid = getpid();
> +
> + if (bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl)) == -1) {
> + ret_errno = errno;
> + perror("bind failed");
> + return -2;
> + }
> +
> + epoll_fd = epoll_create1(EPOLL_CLOEXEC);
> + if (epoll_fd < 0) {
> + ret_errno = errno;
> + perror("epoll_create1 failed");
> + return -2;
> + }
> +
> + err = send_message(input);
> +
> + if (err < 0)
> + return err;
> +
> + evn.events = EPOLLIN;
> + evn.data.fd = nl_sock;
> + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, nl_sock, &evn) < 0) {
> + ret_errno = errno;
> + perror("epoll_ctl failed");
> + return -3;
> + }
> + *efd = epoll_fd;
> + return 0;
> +}
> +
> +static void sigint(int sig)
> +{
> + interrupted = 1;
> +}
> +
> +int handle_packet(char *buff, int fd, struct proc_event *event)
> +{
> + struct nlmsghdr *hdr;
> +
> + hdr = (struct nlmsghdr *)buff;
> +
> + if (hdr->nlmsg_type == NLMSG_ERROR) {
> + perror("NLMSG_ERROR error\n");
> + return -3;
> + } else if (hdr->nlmsg_type == NLMSG_DONE) {
> + event = (struct proc_event *)
> + ((struct cn_msg *)NLMSG_DATA(hdr))->data;
> + tcount++;
> + switch (event->what) {
> + case PROC_EVENT_EXIT:
> + Printf("Exit process %d (tgid %d) with code %d, signal %d\n",
> + event->event_data.exit.process_pid,
> + event->event_data.exit.process_tgid,
> + event->event_data.exit.exit_code,
> + event->event_data.exit.exit_signal);
> + break;
> + case PROC_EVENT_FORK:
> + Printf("Fork process %d (tgid %d), parent %d (tgid %d)\n",
> + event->event_data.fork.child_pid,
> + event->event_data.fork.child_tgid,
> + event->event_data.fork.parent_pid,
> + event->event_data.fork.parent_tgid);
> + break;
> + case PROC_EVENT_EXEC:
> + Printf("Exec process %d (tgid %d)\n",
> + event->event_data.exec.process_pid,
> + event->event_data.exec.process_tgid);
> + break;
> + case PROC_EVENT_UID:
> + Printf("UID process %d (tgid %d) uid %d euid %d\n",
> + event->event_data.id.process_pid,
> + event->event_data.id.process_tgid,
> + event->event_data.id.r.ruid,
> + event->event_data.id.e.euid);
> + break;
> + case PROC_EVENT_GID:
> + Printf("GID process %d (tgid %d) gid %d egid %d\n",
> + event->event_data.id.process_pid,
> + event->event_data.id.process_tgid,
> + event->event_data.id.r.rgid,
> + event->event_data.id.e.egid);
> + break;
> + case PROC_EVENT_SID:
> + Printf("SID process %d (tgid %d)\n",
> + event->event_data.sid.process_pid,
> + event->event_data.sid.process_tgid);
> + break;
> + case PROC_EVENT_PTRACE:
> + Printf("Ptrace process %d (tgid %d), Tracer %d (tgid %d)\n",
> + event->event_data.ptrace.process_pid,
> + event->event_data.ptrace.process_tgid,
> + event->event_data.ptrace.tracer_pid,
> + event->event_data.ptrace.tracer_tgid);
> + break;
> + case PROC_EVENT_COMM:
> + Printf("Comm process %d (tgid %d) comm %s\n",
> + event->event_data.comm.process_pid,
> + event->event_data.comm.process_tgid,
> + event->event_data.comm.comm);
> + break;
> + case PROC_EVENT_COREDUMP:
> + Printf("Coredump process %d (tgid %d) parent %d, (tgid %d)\n",
> + event->event_data.coredump.process_pid,
> + event->event_data.coredump.process_tgid,
> + event->event_data.coredump.parent_pid,
> + event->event_data.coredump.parent_tgid);
> + break;
> + default:
> + break;
> + }
> + }
> + return 0;
> +}
> +
> +int handle_events(int epoll_fd, struct proc_event *pev)
> +{
> + char buff[CONNECTOR_MAX_MSG_SIZE];
> + struct epoll_event ev[MAX_EVENTS];
> + int i, event_count = 0, err = 0;
> +
> + event_count = epoll_wait(epoll_fd, ev, MAX_EVENTS, -1);
> + if (event_count < 0) {
> + ret_errno = errno;
> + if (ret_errno != EINTR)
> + perror("epoll_wait failed");
> + return -3;
> + }
> + for (i = 0; i < event_count; i++) {
> + if (!(ev[i].events & EPOLLIN))
> + continue;
> + if (recv(ev[i].data.fd, buff, sizeof(buff), 0) == -1) {
> + ret_errno = errno;
> + perror("recv failed");
> + return -3;
> + }
> + err = handle_packet(buff, ev[i].data.fd, pev);
> + if (err < 0)
> + return err;
> + }
> + return 0;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + int epoll_fd, err;
> + struct proc_event proc_ev;
> + struct proc_input input;
> +
> + signal(SIGINT, sigint);
> +
> + if (argc > 2) {
> + printf("Expected 0(assume no-filter) or 1 argument(-f)\n");
> + exit(1);
> + }
> +
> + if (argc == 2) {
> + if (strcmp(argv[1], "-f") == 0) {
> + filter = 1;
> + } else {
> + printf("Valid option : -f (for filter feature)\n");
> + exit(1);
> + }
> + }
> +
> + if (filter) {
> + input.event_type = PROC_EVENT_NONZERO_EXIT;
> + input.mcast_op = PROC_CN_MCAST_LISTEN;
> + err = register_proc_netlink(&epoll_fd, (void*)&input);
> + } else {
> + enum proc_cn_mcast_op op = PROC_CN_MCAST_LISTEN;
> + err = register_proc_netlink(&epoll_fd, (void*)&op);
> + }
> +
> + if (err < 0) {
> + if (err == -2)
> + close(nl_sock);
> + if (err == -3) {
> + close(nl_sock);
> + close(epoll_fd);
> + }
> + exit(1);
> + }
> +
> + while (!interrupted) {
> + err = handle_events(epoll_fd, &proc_ev);
> + if (err < 0) {
> + if (ret_errno == EINTR)
> + continue;
> + if (err == -2)
> + close(nl_sock);
> + if (err == -3) {
> + close(nl_sock);
> + close(epoll_fd);
> + }
> + exit(1);
> + }
> + }
> +
> + if (filter) {
> + input.mcast_op = PROC_CN_MCAST_IGNORE;
> + send_message((void*)&input);
> + } else {
> + enum proc_cn_mcast_op op = PROC_CN_MCAST_IGNORE;
> + send_message((void*)&op);
> + }
> +
> + close(epoll_fd);
> + close(nl_sock);
> +
> + printf("Done total count: %d\n", tcount);
> + exit(0);
> +}
> --
> 2.41.0
>
* Anjali Kulkarni <[email protected]> [691231 23:00]:
> This patch adds the capability to filter messages sent by the proc
> connector on the event type supplied in the message from the client
> to the connector. The client can register to listen for an event type
> given in struct proc_input.
>
> This event based filteting will greatly enhance performance - handling
> 8K exits takes about 70ms, whereas 8K-forks + 8K-exits takes about 150ms
> & handling 8K-forks + 8K-exits + 8K-execs takes 200ms. There are currently
> 9 different types of events, and we need to listen to all of them. Also,
> measuring the time using pidfds for monitoring 8K process exits took
> much longer - 200ms, as compared to 70ms using only exit notifications of
> proc connector.
>
> We also add a new event type - PROC_EVENT_NONZERO_EXIT, which is
> only sent by kernel to a listening application when any process exiting,
> has a non-zero exit status. This will help the clients like Oracle DB,
> where a monitoring process wants notfications for non-zero process exits
> so it can cleanup after them.
>
> This kind of a new event could also be useful to other applications like
> Google's lmkd daemon, which needs a killed process's exit notification.
>
> The patch takes care that existing clients using old mechanism of not
> sending the event type work without any changes.
>
> cn_filter function checks to see if the event type being notified via
> proc connector matches the event type requested by client, before
> sending(matches) or dropping(does not match) a packet.
>
> Signed-off-by: Anjali Kulkarni <[email protected]>
Reviewed-by: Liam R. Howlett <[email protected]>
> ---
> drivers/connector/cn_proc.c | 62 ++++++++++++++++++++++++++++++++----
> include/uapi/linux/cn_proc.h | 19 +++++++++++
> 2 files changed, 75 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
> index 1ba288ed2bf7..dfc84d44f804 100644
> --- a/drivers/connector/cn_proc.c
> +++ b/drivers/connector/cn_proc.c
> @@ -50,21 +50,45 @@ static DEFINE_PER_CPU(struct local_event, local_event) = {
>
> static int cn_filter(struct sock *dsk, struct sk_buff *skb, void *data)
> {
> + __u32 what, exit_code, *ptr;
> enum proc_cn_mcast_op mc_op;
> + uintptr_t val;
>
> - if (!dsk)
> + if (!dsk || !data)
> return 0;
>
> + ptr = (__u32 *)data;
> + what = *ptr++;
> + exit_code = *ptr;
> + val = ((struct proc_input *)(dsk->sk_user_data))->event_type;
> mc_op = ((struct proc_input *)(dsk->sk_user_data))->mcast_op;
>
> if (mc_op == PROC_CN_MCAST_IGNORE)
> return 1;
>
> - return 0;
> + if ((__u32)val == PROC_EVENT_ALL)
> + return 0;
> +
> + /*
> + * Drop packet if we have to report only non-zero exit status
> + * (PROC_EVENT_NONZERO_EXIT) and exit status is 0
> + */
> + if (((__u32)val & PROC_EVENT_NONZERO_EXIT) &&
> + (what == PROC_EVENT_EXIT)) {
> + if (exit_code)
> + return 0;
> + }
> +
> + if ((__u32)val & what)
> + return 0;
> +
> + return 1;
> }
>
> static inline void send_msg(struct cn_msg *msg)
> {
> + __u32 filter_data[2];
> +
> local_lock(&local_event.lock);
>
> msg->seq = __this_cpu_inc_return(local_event.count) - 1;
> @@ -76,8 +100,16 @@ static inline void send_msg(struct cn_msg *msg)
> *
> * If cn_netlink_send() fails, the data is not sent.
> */
> + filter_data[0] = ((struct proc_event *)msg->data)->what;
> + if (filter_data[0] == PROC_EVENT_EXIT) {
> + filter_data[1] =
> + ((struct proc_event *)msg->data)->event_data.exit.exit_code;
> + } else {
> + filter_data[1] = 0;
> + }
> +
> cn_netlink_send_mult(msg, msg->len, 0, CN_IDX_PROC, GFP_NOWAIT,
> - cn_filter, NULL);
> + cn_filter, (void *)filter_data);
>
> local_unlock(&local_event.lock);
> }
> @@ -357,12 +389,15 @@ static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack)
>
> /**
> * cn_proc_mcast_ctl
> - * @data: message sent from userspace via the connector
> + * @msg: message sent from userspace via the connector
> + * @nsp: NETLINK_CB of the client's socket buffer
> */
> static void cn_proc_mcast_ctl(struct cn_msg *msg,
> struct netlink_skb_parms *nsp)
> {
> enum proc_cn_mcast_op mc_op = 0, prev_mc_op = 0;
> + struct proc_input *pinput = NULL;
> + enum proc_cn_event ev_type = 0;
> int err = 0, initial = 0;
> struct sock *sk = NULL;
>
> @@ -381,10 +416,21 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
> goto out;
> }
>
> - if (msg->len == sizeof(mc_op))
> + if (msg->len == sizeof(*pinput)) {
> + pinput = (struct proc_input *)msg->data;
> + mc_op = pinput->mcast_op;
> + ev_type = pinput->event_type;
> + } else if (msg->len == sizeof(mc_op)) {
> mc_op = *((enum proc_cn_mcast_op *)msg->data);
> - else
> + ev_type = PROC_EVENT_ALL;
> + } else {
> return;
> + }
> +
> + ev_type = valid_event((enum proc_cn_event)ev_type);
> +
> + if (ev_type == PROC_EVENT_NONE)
> + ev_type = PROC_EVENT_ALL;
>
> if (nsp->sk) {
> sk = nsp->sk;
> @@ -400,6 +446,8 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
> prev_mc_op =
> ((struct proc_input *)(sk->sk_user_data))->mcast_op;
> }
> + ((struct proc_input *)(sk->sk_user_data))->event_type =
> + ev_type;
> ((struct proc_input *)(sk->sk_user_data))->mcast_op = mc_op;
> }
>
> @@ -411,6 +459,8 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
> case PROC_CN_MCAST_IGNORE:
> if (!initial && (prev_mc_op != PROC_CN_MCAST_IGNORE))
> atomic_dec(&proc_event_num_listeners);
> + ((struct proc_input *)(sk->sk_user_data))->event_type =
> + PROC_EVENT_NONE;
> break;
> default:
> err = EINVAL;
> diff --git a/include/uapi/linux/cn_proc.h b/include/uapi/linux/cn_proc.h
> index 6a06fb424313..f2afb7cc4926 100644
> --- a/include/uapi/linux/cn_proc.h
> +++ b/include/uapi/linux/cn_proc.h
> @@ -30,6 +30,15 @@ enum proc_cn_mcast_op {
> PROC_CN_MCAST_IGNORE = 2
> };
>
> +#define PROC_EVENT_ALL (PROC_EVENT_FORK | PROC_EVENT_EXEC | PROC_EVENT_UID | \
> + PROC_EVENT_GID | PROC_EVENT_SID | PROC_EVENT_PTRACE | \
> + PROC_EVENT_COMM | PROC_EVENT_NONZERO_EXIT | \
> + PROC_EVENT_COREDUMP | PROC_EVENT_EXIT)
> +
> +/*
> + * If you add an entry in proc_cn_event, make sure you add it in
> + * PROC_EVENT_ALL above as well.
> + */
> enum proc_cn_event {
> /* Use successive bits so the enums can be used to record
> * sets of events as well
> @@ -45,15 +54,25 @@ enum proc_cn_event {
> /* "next" should be 0x00000400 */
> /* "last" is the last process event: exit,
> * while "next to last" is coredumping event
> + * before that is report only if process dies
> + * with non-zero exit status
> */
> + PROC_EVENT_NONZERO_EXIT = 0x20000000,
> PROC_EVENT_COREDUMP = 0x40000000,
> PROC_EVENT_EXIT = 0x80000000
> };
>
> struct proc_input {
> enum proc_cn_mcast_op mcast_op;
> + enum proc_cn_event event_type;
> };
>
> +static inline enum proc_cn_event valid_event(enum proc_cn_event ev_type)
> +{
> + ev_type &= PROC_EVENT_ALL;
> + return ev_type;
> +}
> +
> /*
> * From the user's point of view, the process
> * ID is the thread group ID and thread ID is the internal
> --
> 2.41.0
>