[PATCH 5/5] connector: Create connector per namespace

From: Matt Bennett
Date: Wed Jul 01 2020 - 20:27:57 EST


Move to storing the connector instance per network namespace. In doing
so the ability to use the connector functionality outside the default
namespace is now available.

Signed-off-by: Matt Bennett <matt.bennett@xxxxxxxxxxxxxxxxxxx>
---
drivers/connector/cn_proc.c | 49 ++++++----
drivers/connector/connector.c | 171 ++++++++++++++++++++++++++++------
drivers/hv/hv_fcopy.c | 1 +
include/linux/connector.h | 14 ++-
include/net/net_namespace.h | 4 +
kernel/exit.c | 2 +-
6 files changed, 190 insertions(+), 51 deletions(-)

diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
index 9202be177a30..661d921fd146 100644
--- a/drivers/connector/cn_proc.c
+++ b/drivers/connector/cn_proc.c
@@ -17,6 +17,7 @@
#include <linux/atomic.h>
#include <linux/pid_namespace.h>
#include <net/net_namespace.h>
+#include <linux/netlink.h>

#include <linux/cn_proc.h>
#include <linux/local_lock.h>
@@ -37,7 +38,6 @@ static inline struct cn_msg *buffer_to_cn_msg(__u8 *buffer)
return (struct cn_msg *)(buffer + 4);
}

-static atomic_t proc_event_num_listeners = ATOMIC_INIT(0);
static struct cb_id cn_proc_event_id = { CN_IDX_PROC, CN_VAL_PROC };

/* local_event.count is used as the sequence number of the netlink message */
@@ -51,6 +51,9 @@ static DEFINE_PER_CPU(struct local_event, local_event) = {

static inline void send_msg(struct cn_msg *msg)
{
+ int ret = 0;
+ struct net *net = current->nsproxy->net_ns;
+
local_lock(&local_event.lock);

msg->seq = __this_cpu_inc_return(local_event.count) - 1;
@@ -62,7 +65,9 @@ static inline void send_msg(struct cn_msg *msg)
*
* If cn_netlink_send() fails, the data is not sent.
*/
- cn_netlink_send(&init_net, msg, 0, CN_IDX_PROC, GFP_NOWAIT);
+ ret = cn_netlink_send(net, msg, 0, CN_IDX_PROC, GFP_NOWAIT);
+ if (ret == -ESRCH && netlink_has_listeners(net->cdev.nls, CN_IDX_PROC) == 0)
+ atomic_set(&(net->cdev.proc_event_num_listeners), 0);

local_unlock(&local_event.lock);
}
@@ -73,8 +78,9 @@ void proc_fork_connector(struct task_struct *task)
struct proc_event *ev;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
struct task_struct *parent;
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -102,8 +108,9 @@ void proc_exec_connector(struct task_struct *task)
struct cn_msg *msg;
struct proc_event *ev;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -127,8 +134,9 @@ void proc_id_connector(struct task_struct *task, int which_id)
struct proc_event *ev;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
const struct cred *cred;
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -164,8 +172,9 @@ void proc_sid_connector(struct task_struct *task)
struct cn_msg *msg;
struct proc_event *ev;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -188,8 +197,9 @@ void proc_ptrace_connector(struct task_struct *task, int ptrace_id)
struct cn_msg *msg;
struct proc_event *ev;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -220,8 +230,9 @@ void proc_comm_connector(struct task_struct *task)
struct cn_msg *msg;
struct proc_event *ev;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -246,8 +257,9 @@ void proc_coredump_connector(struct task_struct *task)
struct proc_event *ev;
struct task_struct *parent;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -279,8 +291,9 @@ void proc_exit_connector(struct task_struct *task)
struct proc_event *ev;
struct task_struct *parent;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -321,8 +334,9 @@ static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack)
struct cn_msg *msg;
struct proc_event *ev;
__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);
+ struct net *net = current->nsproxy->net_ns;

- if (atomic_read(&proc_event_num_listeners) < 1)
+ if (atomic_read(&(net->cdev.proc_event_num_listeners)) < 1)
return;

msg = buffer_to_cn_msg(buffer);
@@ -353,13 +367,10 @@ static void cn_proc_mcast_ctl(struct net *net, struct cn_msg *msg,
if (msg->len != sizeof(*mc_op))
return;

- /*
- * Events are reported with respect to the initial pid
- * and user namespaces so ignore requestors from
- * other namespaces.
+ /*
+ * Events are reported with respect to network namespaces.
*/
- if ((current_user_ns() != &init_user_ns) ||
- (task_active_pid_ns(current) != &init_pid_ns))
+ if (current->nsproxy->net_ns != net)
return;

/* Can only change if privileged. */
@@ -371,10 +382,10 @@ static void cn_proc_mcast_ctl(struct net *net, struct cn_msg *msg,
mc_op = (enum proc_cn_mcast_op *)msg->data;
switch (*mc_op) {
case PROC_CN_MCAST_LISTEN:
- atomic_inc(&proc_event_num_listeners);
+ atomic_inc(&(net->cdev.proc_event_num_listeners));
break;
case PROC_CN_MCAST_IGNORE:
- atomic_dec(&proc_event_num_listeners);
+ atomic_dec(&(net->cdev.proc_event_num_listeners));
break;
default:
err = EINVAL;
diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c
index 82fcaa4d8be3..30efcf39751f 100644
--- a/drivers/connector/connector.c
+++ b/drivers/connector/connector.c
@@ -26,9 +26,7 @@ MODULE_AUTHOR("Evgeniy Polyakov <zbr@xxxxxxxxxxx>");
MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector.");
MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_CONNECTOR);

-static struct cn_dev cdev;
-
-static int cn_already_initialized;
+static DEFINE_MUTEX(cn_mutex);

/*
* Sends mult (multiple) cn_msg at a time.
@@ -66,10 +64,13 @@ int cn_netlink_send_mult(struct net *net, struct cn_msg *msg, u16 len,
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct cn_msg *data;
- struct cn_dev *dev = &cdev;
+ struct cn_dev *dev = &(net->cdev);
u32 group = 0;
int found = 0;

+ if (!msg || len < 0)
+ return -EINVAL;
+
if (portid || __group) {
group = __group;
} else {
@@ -133,7 +134,7 @@ static int cn_call_callback(struct net *net, struct sk_buff *skb)
{
struct nlmsghdr *nlh;
struct cn_callback_entry *i, *cbq = NULL;
- struct cn_dev *dev = &cdev;
+ struct cn_dev *dev = &net->cdev;
struct cn_msg *msg = nlmsg_data(nlmsg_hdr(skb));
struct netlink_skb_parms *nsp = &NETLINK_CB(skb);
int err = -ENODEV;
@@ -168,7 +169,7 @@ static int cn_call_callback(struct net *net, struct sk_buff *skb)
*
* It checks skb, netlink header and msg sizes, and calls callback helper.
*/
-static void cn_rx_skb(struct sk_buff *skb)
+static void __cn_rx_skb(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
int len, err;
@@ -190,6 +191,13 @@ static void cn_rx_skb(struct sk_buff *skb)
}
}

+static void cn_rx_skb(struct sk_buff *skb)
+{
+ mutex_lock(&cn_mutex);
+ __cn_rx_skb(skb);
+ mutex_unlock(&cn_mutex);
+}
+
/*
* Callback add routing - adds callback with given ID and name.
* If there is registered callback with the same ID it will not be added.
@@ -200,20 +208,47 @@ int cn_add_callback(struct cb_id *id, const char *name,
void (*callback)(struct net *, struct cn_msg *,
struct netlink_skb_parms *))
{
- int err;
- struct cn_dev *dev = &cdev;
-
- if (!cn_already_initialized)
- return -EAGAIN;
+ int err = -EINVAL;
+ struct net *net = NULL;
+ struct cn_dev *dev = NULL;

- err = cn_queue_add_callback(dev->cbdev, name, id, callback);
- if (err)
+ if (!id || !name || !callback)
return err;

- return 0;
+ down_read(&net_rwsem);
+ for_each_net(net) {
+ dev = &net->cdev;
+ err = cn_queue_add_callback(dev->cbdev, name, id, callback);
+ if (err)
+ break;
+ }
+
+ if (err) {
+ for_each_net(net) {
+ dev = &net->cdev;
+ cn_queue_del_callback(dev->cbdev, id);
+ }
+ }
+ up_read(&net_rwsem);
+
+ return err;
}
EXPORT_SYMBOL_GPL(cn_add_callback);

+int cn_add_callback_one(struct net *net, struct cb_id *id, const char *name,
+ void (*callback)(struct net *, struct cn_msg *,
+ struct netlink_skb_parms *))
+{
+ struct cn_dev *dev = NULL;
+
+ if (!net || !id || !name || !callback)
+ return -EINVAL;
+
+ dev = &(net->cdev);
+ return cn_queue_add_callback(dev->cbdev, name, id, callback);
+}
+EXPORT_SYMBOL_GPL(cn_add_callback_one);
+
/*
* Callback remove routing - removes callback
* with given ID.
@@ -224,15 +259,25 @@ EXPORT_SYMBOL_GPL(cn_add_callback);
*/
void cn_del_callback(struct cb_id *id)
{
- struct cn_dev *dev = &cdev;
+ struct net *net = NULL;
+ struct cn_dev *dev = NULL;
+
+ if (!id)
+ return;

- cn_queue_del_callback(dev->cbdev, id);
+ down_read(&net_rwsem);
+ for_each_net(net) {
+ dev = &net->cdev;
+ cn_queue_del_callback(dev->cbdev, id);
+ }
+ up_read(&net_rwsem);
}
EXPORT_SYMBOL_GPL(cn_del_callback);

static int __maybe_unused cn_proc_show(struct seq_file *m, void *v)
{
- struct cn_queue_dev *dev = cdev.cbdev;
+ struct net *net = seq_file_single_net(m);
+ struct cn_queue_dev *dev = net->cdev.cbdev;
struct cn_callback_entry *cbq;

seq_printf(m, "Name ID\n");
@@ -251,15 +296,62 @@ static int __maybe_unused cn_proc_show(struct seq_file *m, void *v)
return 0;
}

-static int cn_init(void)
+static int init_cn_net(struct net *net)
+{
+ int ret = 0;
+ struct cn_dev *init_dev = &(init_net.cdev);
+ struct cn_queue_dev *cbdev = init_dev->cbdev;
+
+ struct cn_callback_entry *cbq = NULL;
+ struct cn_callback_entry_ex *cbq_ex = NULL;
+ struct cn_callback_entry_ex *tmp = NULL;
+ LIST_HEAD(head);
+
+ if (!net)
+ return -EINVAL;
+
+ spin_lock_bh(&cbdev->queue_lock);
+ list_for_each_entry(cbq, &cbdev->queue_list, callback_entry) {
+ cbq_ex = kmalloc(sizeof(*cbq_ex), GFP_ATOMIC);
+ if (!cbq_ex) {
+ ret = -ENOMEM;
+ break;
+ }
+ INIT_LIST_HEAD(&(cbq_ex->list));
+
+ memcpy(&cbq_ex->id, &(cbq->id.id), sizeof(struct cb_id));
+ memcpy(cbq_ex->name, &(cbq->id.name), CN_CBQ_NAMELEN);
+ cbq_ex->callback = cbq->callback;
+
+ list_add_tail(&(cbq_ex->list), &head);
+ }
+ spin_unlock_bh(&cbdev->queue_lock);
+
+ if (ret < 0) {
+ list_for_each_entry_safe(cbq_ex, tmp, &head, list) {
+ kfree(cbq_ex);
+ }
+ } else {
+ list_for_each_entry_safe(cbq_ex, tmp, &head, list) {
+ cn_add_callback_one(net, &(cbq_ex->id), cbq_ex->name,
+ cbq_ex->callback);
+ kfree(cbq_ex);
+ }
+ }
+
+ return ret;
+}
+
+static int __net_init cn_init(struct net *net)
{
- struct cn_dev *dev = &cdev;
+ int ret = 0;
+ struct cn_dev *dev = &net->cdev;
struct netlink_kernel_cfg cfg = {
.groups = CN_NETLINK_USERS + 0xf,
.input = cn_rx_skb,
};

- dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR, &cfg);
+ dev->nls = netlink_kernel_create(net, NETLINK_CONNECTOR, &cfg);
if (!dev->nls)
return -EIO;

@@ -268,25 +360,44 @@ static int cn_init(void)
netlink_kernel_release(dev->nls);
return -EINVAL;
}
+ atomic_set(&(dev->proc_event_num_listeners), 0);

- cn_already_initialized = 1;
+ ret = init_cn_net(net);
+ if (ret < 0) {
+ cn_queue_free_dev(dev->cbdev);
+ netlink_kernel_release(dev->nls);
+ return ret;
+ }

- proc_create_single("connector", S_IRUGO, init_net.proc_net, cn_proc_show);
+ proc_create_net_single("connector", 0444, net->proc_net, cn_proc_show,
+ NULL);

return 0;
}

-static void cn_fini(void)
+static void __net_exit cn_fini(struct net *net)
{
- struct cn_dev *dev = &cdev;
-
- cn_already_initialized = 0;
-
- remove_proc_entry("connector", init_net.proc_net);
+ struct cn_dev *dev = &net->cdev;

+ remove_proc_entry("connector", net->proc_net);
cn_queue_free_dev(dev->cbdev);
netlink_kernel_release(dev->nls);
}

-subsys_initcall(cn_init);
-module_exit(cn_fini);
+static struct pernet_operations cn_netlink_net_ops = {
+ .init = cn_init,
+ .exit = cn_fini,
+};
+
+static int __init connector_init(void)
+{
+ return register_pernet_subsys(&cn_netlink_net_ops);
+}
+
+static void __exit connector_exit(void)
+{
+ unregister_pernet_subsys(&cn_netlink_net_ops);
+}
+
+subsys_initcall(connector_init);
+module_exit(connector_exit);
diff --git a/drivers/hv/hv_fcopy.c b/drivers/hv/hv_fcopy.c
index 5040d7e0cd9e..a7151296af5c 100644
--- a/drivers/hv/hv_fcopy.c
+++ b/drivers/hv/hv_fcopy.c
@@ -13,6 +13,7 @@
#include <linux/workqueue.h>
#include <linux/hyperv.h>
#include <linux/sched.h>
+#include <linux/slab.h>
#include <asm/hyperv-tlfs.h>

#include "hyperv_vmbus.h"
diff --git a/include/linux/connector.h b/include/linux/connector.h
index 8e9385eb18f8..17febd6946ce 100644
--- a/include/linux/connector.h
+++ b/include/linux/connector.h
@@ -14,11 +14,14 @@
#include <linux/list.h>
#include <linux/workqueue.h>

-#include <net/sock.h>
#include <uapi/linux/connector.h>

#define CN_CBQ_NAMELEN 32

+struct net;
+struct sock;
+struct netlink_skb_parms;
+
struct cn_queue_dev {
atomic_t refcnt;
unsigned char name[CN_CBQ_NAMELEN];
@@ -46,11 +49,20 @@ struct cn_callback_entry {
u32 seq, group;
};

+struct cn_callback_entry_ex {
+ struct list_head list;
+ struct cb_id id;
+ unsigned char name[CN_CBQ_NAMELEN];
+ void (*callback)(struct net *net, struct cn_msg *cn_msg,
+ struct netlink_skb_parms *nsp);
+};
+
struct cn_dev {
struct cb_id id;

u32 seq, groups;
struct sock *nls;
+ atomic_t proc_event_num_listeners;

struct cn_queue_dev *cbdev;
};
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 2ee5901bec7a..312972fb2dcc 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -38,6 +38,7 @@
#include <linux/idr.h>
#include <linux/skbuff.h>
#include <linux/notifier.h>
+#include <linux/connector.h>

struct user_namespace;
struct proc_dir_entry;
@@ -187,6 +188,9 @@ struct net {
#endif
#if IS_ENABLED(CONFIG_CRYPTO_USER)
struct sock *crypto_nlsk;
+#endif
+#if IS_ENABLED(CONFIG_CONNECTOR)
+ struct cn_dev cdev;
#endif
struct sock *diag_nlsk;
} __randomize_layout;
diff --git a/kernel/exit.c b/kernel/exit.c
index 727150f28103..976fd6032024 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -788,6 +788,7 @@ void __noreturn do_exit(long code)

tsk->exit_code = code;
taskstats_exit(tsk, group_dead);
+ proc_exit_connector(tsk);

exit_mm();

@@ -824,7 +825,6 @@ void __noreturn do_exit(long code)

exit_tasks_rcu_start();
exit_notify(tsk, group_dead);
- proc_exit_connector(tsk);
mpol_put_task_policy(tsk);
#ifdef CONFIG_FUTEX
if (unlikely(current->pi_state_cache))
--
2.27.0