1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * connector.c |
4 | * |
5 | * 2004+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> |
6 | * All rights reserved. |
7 | */ |
8 | |
9 | #include <linux/compiler.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/list.h> |
13 | #include <linux/skbuff.h> |
14 | #include <net/netlink.h> |
15 | #include <linux/moduleparam.h> |
16 | #include <linux/connector.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/mutex.h> |
19 | #include <linux/proc_fs.h> |
20 | #include <linux/spinlock.h> |
21 | |
22 | #include <net/sock.h> |
23 | |
24 | MODULE_LICENSE("GPL" ); |
25 | MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>" ); |
26 | MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector." ); |
27 | MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_CONNECTOR); |
28 | |
29 | static struct cn_dev cdev; |
30 | |
31 | static int cn_already_initialized; |
32 | |
33 | /* |
34 | * Sends mult (multiple) cn_msg at a time. |
35 | * |
36 | * msg->seq and msg->ack are used to determine message genealogy. |
37 | * When someone sends message it puts there locally unique sequence |
38 | * and random acknowledge numbers. Sequence number may be copied into |
39 | * nlmsghdr->nlmsg_seq too. |
40 | * |
41 | * Sequence number is incremented with each message to be sent. |
42 | * |
43 | * If we expect a reply to our message then the sequence number in |
44 | * received message MUST be the same as in original message, and |
45 | * acknowledge number MUST be the same + 1. |
46 | * |
47 | * If we receive a message and its sequence number is not equal to the |
48 | * one we are expecting then it is a new message. |
49 | * |
50 | * If we receive a message and its sequence number is the same as one |
51 | * we are expecting but it's acknowledgement number is not equal to |
52 | * the acknowledgement number in the original message + 1, then it is |
53 | * a new message. |
54 | * |
55 | * If msg->len != len, then additional cn_msg messages are expected following |
56 | * the first msg. |
57 | * |
58 | * The message is sent to, the portid if given, the group if given, both if |
59 | * both, or if both are zero then the group is looked up and sent there. |
60 | */ |
61 | int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, |
62 | gfp_t gfp_mask, |
63 | int (*filter)(struct sock *dsk, struct sk_buff *skb, void *data), |
64 | void *filter_data) |
65 | { |
66 | struct cn_callback_entry *__cbq; |
67 | unsigned int size; |
68 | struct sk_buff *skb; |
69 | struct nlmsghdr *nlh; |
70 | struct cn_msg *data; |
71 | struct cn_dev *dev = &cdev; |
72 | u32 group = 0; |
73 | int found = 0; |
74 | |
75 | if (portid || __group) { |
76 | group = __group; |
77 | } else { |
78 | spin_lock_bh(lock: &dev->cbdev->queue_lock); |
79 | list_for_each_entry(__cbq, &dev->cbdev->queue_list, |
80 | callback_entry) { |
81 | if (cn_cb_equal(&__cbq->id.id, &msg->id)) { |
82 | found = 1; |
83 | group = __cbq->group; |
84 | break; |
85 | } |
86 | } |
87 | spin_unlock_bh(lock: &dev->cbdev->queue_lock); |
88 | |
89 | if (!found) |
90 | return -ENODEV; |
91 | } |
92 | |
93 | if (!portid && !netlink_has_listeners(sk: dev->nls, group)) |
94 | return -ESRCH; |
95 | |
96 | size = sizeof(*msg) + len; |
97 | |
98 | skb = nlmsg_new(payload: size, flags: gfp_mask); |
99 | if (!skb) |
100 | return -ENOMEM; |
101 | |
102 | nlh = nlmsg_put(skb, portid: 0, seq: msg->seq, NLMSG_DONE, payload: size, flags: 0); |
103 | if (!nlh) { |
104 | kfree_skb(skb); |
105 | return -EMSGSIZE; |
106 | } |
107 | |
108 | data = nlmsg_data(nlh); |
109 | |
110 | memcpy(data, msg, size); |
111 | |
112 | NETLINK_CB(skb).dst_group = group; |
113 | |
114 | if (group) |
115 | return netlink_broadcast_filtered(ssk: dev->nls, skb, portid, group, |
116 | allocation: gfp_mask, filter, |
117 | filter_data: (void *)filter_data); |
118 | return netlink_unicast(ssk: dev->nls, skb, portid, |
119 | nonblock: !gfpflags_allow_blocking(gfp_flags: gfp_mask)); |
120 | } |
121 | EXPORT_SYMBOL_GPL(cn_netlink_send_mult); |
122 | |
123 | /* same as cn_netlink_send_mult except msg->len is used for len */ |
124 | int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, |
125 | gfp_t gfp_mask) |
126 | { |
127 | return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask, |
128 | NULL, NULL); |
129 | } |
130 | EXPORT_SYMBOL_GPL(cn_netlink_send); |
131 | |
132 | /* |
133 | * Callback helper - queues work and setup destructor for given data. |
134 | */ |
135 | static int cn_call_callback(struct sk_buff *skb) |
136 | { |
137 | struct nlmsghdr *nlh; |
138 | struct cn_callback_entry *i, *cbq = NULL; |
139 | struct cn_dev *dev = &cdev; |
140 | struct cn_msg *msg = nlmsg_data(nlh: nlmsg_hdr(skb)); |
141 | struct netlink_skb_parms *nsp = &NETLINK_CB(skb); |
142 | int err = -ENODEV; |
143 | |
144 | /* verify msg->len is within skb */ |
145 | nlh = nlmsg_hdr(skb); |
146 | if (nlh->nlmsg_len < NLMSG_HDRLEN + sizeof(struct cn_msg) + msg->len) |
147 | return -EINVAL; |
148 | |
149 | spin_lock_bh(lock: &dev->cbdev->queue_lock); |
150 | list_for_each_entry(i, &dev->cbdev->queue_list, callback_entry) { |
151 | if (cn_cb_equal(&i->id.id, &msg->id)) { |
152 | refcount_inc(r: &i->refcnt); |
153 | cbq = i; |
154 | break; |
155 | } |
156 | } |
157 | spin_unlock_bh(lock: &dev->cbdev->queue_lock); |
158 | |
159 | if (cbq != NULL) { |
160 | cbq->callback(msg, nsp); |
161 | kfree_skb(skb); |
162 | cn_queue_release_callback(cbq); |
163 | err = 0; |
164 | } |
165 | |
166 | return err; |
167 | } |
168 | |
169 | /* |
170 | * Allow non-root access for NETLINK_CONNECTOR family having CN_IDX_PROC |
171 | * multicast group. |
172 | */ |
173 | static int cn_bind(struct net *net, int group) |
174 | { |
175 | unsigned long groups = (unsigned long) group; |
176 | |
177 | if (ns_capable(ns: net->user_ns, CAP_NET_ADMIN)) |
178 | return 0; |
179 | |
180 | if (test_bit(CN_IDX_PROC - 1, &groups)) |
181 | return 0; |
182 | |
183 | return -EPERM; |
184 | } |
185 | |
186 | static void cn_release(struct sock *sk, unsigned long *groups) |
187 | { |
188 | if (groups && test_bit(CN_IDX_PROC - 1, groups)) { |
189 | kfree(objp: sk->sk_user_data); |
190 | sk->sk_user_data = NULL; |
191 | } |
192 | } |
193 | |
194 | /* |
195 | * Main netlink receiving function. |
196 | * |
197 | * It checks skb, netlink header and msg sizes, and calls callback helper. |
198 | */ |
199 | static void cn_rx_skb(struct sk_buff *skb) |
200 | { |
201 | struct nlmsghdr *nlh; |
202 | int len, err; |
203 | |
204 | if (skb->len >= NLMSG_HDRLEN) { |
205 | nlh = nlmsg_hdr(skb); |
206 | len = nlmsg_len(nlh); |
207 | |
208 | if (len < (int)sizeof(struct cn_msg) || |
209 | skb->len < nlh->nlmsg_len || |
210 | len > CONNECTOR_MAX_MSG_SIZE) |
211 | return; |
212 | |
213 | err = cn_call_callback(skb: skb_get(skb)); |
214 | if (err < 0) |
215 | kfree_skb(skb); |
216 | } |
217 | } |
218 | |
219 | /* |
220 | * Callback add routing - adds callback with given ID and name. |
221 | * If there is registered callback with the same ID it will not be added. |
222 | * |
223 | * May sleep. |
224 | */ |
225 | int cn_add_callback(const struct cb_id *id, const char *name, |
226 | void (*callback)(struct cn_msg *, |
227 | struct netlink_skb_parms *)) |
228 | { |
229 | struct cn_dev *dev = &cdev; |
230 | |
231 | if (!cn_already_initialized) |
232 | return -EAGAIN; |
233 | |
234 | return cn_queue_add_callback(dev: dev->cbdev, name, id, callback); |
235 | } |
236 | EXPORT_SYMBOL_GPL(cn_add_callback); |
237 | |
238 | /* |
239 | * Callback remove routing - removes callback |
240 | * with given ID. |
241 | * If there is no registered callback with given |
242 | * ID nothing happens. |
243 | * |
244 | * May sleep while waiting for reference counter to become zero. |
245 | */ |
246 | void cn_del_callback(const struct cb_id *id) |
247 | { |
248 | struct cn_dev *dev = &cdev; |
249 | |
250 | cn_queue_del_callback(dev: dev->cbdev, id); |
251 | } |
252 | EXPORT_SYMBOL_GPL(cn_del_callback); |
253 | |
254 | static int __maybe_unused cn_proc_show(struct seq_file *m, void *v) |
255 | { |
256 | struct cn_queue_dev *dev = cdev.cbdev; |
257 | struct cn_callback_entry *cbq; |
258 | |
259 | seq_printf(m, fmt: "Name ID\n" ); |
260 | |
261 | spin_lock_bh(lock: &dev->queue_lock); |
262 | |
263 | list_for_each_entry(cbq, &dev->queue_list, callback_entry) { |
264 | seq_printf(m, fmt: "%-15s %u:%u\n" , |
265 | cbq->id.name, |
266 | cbq->id.id.idx, |
267 | cbq->id.id.val); |
268 | } |
269 | |
270 | spin_unlock_bh(lock: &dev->queue_lock); |
271 | |
272 | return 0; |
273 | } |
274 | |
275 | static int cn_init(void) |
276 | { |
277 | struct cn_dev *dev = &cdev; |
278 | struct netlink_kernel_cfg cfg = { |
279 | .groups = CN_NETLINK_USERS + 0xf, |
280 | .input = cn_rx_skb, |
281 | .flags = NL_CFG_F_NONROOT_RECV, |
282 | .bind = cn_bind, |
283 | .release = cn_release, |
284 | }; |
285 | |
286 | dev->nls = netlink_kernel_create(net: &init_net, NETLINK_CONNECTOR, cfg: &cfg); |
287 | if (!dev->nls) |
288 | return -EIO; |
289 | |
290 | dev->cbdev = cn_queue_alloc_dev(name: "cqueue" , dev->nls); |
291 | if (!dev->cbdev) { |
292 | netlink_kernel_release(sk: dev->nls); |
293 | return -EINVAL; |
294 | } |
295 | |
296 | cn_already_initialized = 1; |
297 | |
298 | proc_create_single("connector" , S_IRUGO, init_net.proc_net, cn_proc_show); |
299 | |
300 | return 0; |
301 | } |
302 | |
303 | static void cn_fini(void) |
304 | { |
305 | struct cn_dev *dev = &cdev; |
306 | |
307 | cn_already_initialized = 0; |
308 | |
309 | remove_proc_entry("connector" , init_net.proc_net); |
310 | |
311 | cn_queue_free_dev(dev: dev->cbdev); |
312 | netlink_kernel_release(sk: dev->nls); |
313 | } |
314 | |
315 | subsys_initcall(cn_init); |
316 | module_exit(cn_fini); |
317 | |