xref: /freebsd/contrib/libfido2/src/netlink.c (revision d4eeb02986980bf33dd56c41ceb9fc5f180c0d47)
1 /*
2  * Copyright (c) 2020 Yubico AB. All rights reserved.
3  * Use of this source code is governed by a BSD-style
4  * license that can be found in the LICENSE file.
5  */
6 
7 #include <sys/socket.h>
8 
9 #include <linux/genetlink.h>
10 #include <linux/netlink.h>
11 #include <linux/nfc.h>
12 
13 #include <errno.h>
14 #include <limits.h>
15 
16 #include "fido.h"
17 #include "netlink.h"
18 
19 #ifdef FIDO_FUZZ
20 static ssize_t (*fuzz_read)(int, void *, size_t);
21 static ssize_t (*fuzz_write)(int, const void *, size_t);
22 # define READ	fuzz_read
23 # define WRITE	fuzz_write
24 #else
25 # define READ	read
26 # define WRITE	write
27 #endif
28 
29 #ifndef SOL_NETLINK
30 #define SOL_NETLINK	270
31 #endif
32 
33 /* XXX avoid signed NLA_ALIGNTO */
34 #undef NLA_HDRLEN
35 #define NLA_HDRLEN	NLMSG_ALIGN(sizeof(struct nlattr))
36 
37 typedef struct nlmsgbuf {
38 	size_t         siz; /* alloc size */
39 	size_t         len; /* of payload */
40 	unsigned char *ptr; /* in payload */
41 	union {
42 		struct nlmsghdr   nlmsg;
43 		char              buf[NLMSG_HDRLEN]; /* align */
44 	}              u;
45 	unsigned char  payload[];
46 } nlmsgbuf_t;
47 
48 typedef struct genlmsgbuf {
49 	union {
50 		struct genlmsghdr genl;
51 		char              buf[GENL_HDRLEN];  /* align */
52 	}              u;
53 } genlmsgbuf_t;
54 
55 typedef struct nlamsgbuf {
56 	size_t         siz; /* alloc size */
57 	size_t         len; /* of payload */
58 	unsigned char *ptr; /* in payload */
59 	union {
60 		struct nlattr     nla;
61 		char              buf[NLA_HDRLEN];   /* align */
62 	}              u;
63 	unsigned char  payload[];
64 } nlamsgbuf_t;
65 
66 typedef struct nl_family {
67 	uint16_t id;
68 	uint32_t mcastgrp;
69 } nl_family_t;
70 
71 typedef struct nl_poll {
72 	uint32_t     dev;
73 	unsigned int eventcnt;
74 } nl_poll_t;
75 
76 typedef struct nl_target {
77 	int       found;
78 	uint32_t *value;
79 } nl_target_t;
80 
81 static const void *
82 nlmsg_ptr(const nlmsgbuf_t *m)
83 {
84 	return (&m->u.nlmsg);
85 }
86 
87 static size_t
88 nlmsg_len(const nlmsgbuf_t *m)
89 {
90 	return (m->u.nlmsg.nlmsg_len);
91 }
92 
93 static uint16_t
94 nlmsg_type(const nlmsgbuf_t *m)
95 {
96 	return (m->u.nlmsg.nlmsg_type);
97 }
98 
99 static nlmsgbuf_t *
100 nlmsg_new(uint16_t type, uint16_t flags, size_t len)
101 {
102 	nlmsgbuf_t *m;
103 	size_t siz;
104 
105 	if (len > SIZE_MAX - sizeof(*m) ||
106 	    (siz = sizeof(*m) + len) > UINT16_MAX ||
107 	    (m = calloc(1, siz)) == NULL)
108 		return (NULL);
109 
110 	m->siz = siz;
111 	m->len = len;
112 	m->ptr = m->payload;
113 	m->u.nlmsg.nlmsg_type = type;
114 	m->u.nlmsg.nlmsg_flags = NLM_F_REQUEST | flags;
115 	m->u.nlmsg.nlmsg_len = NLMSG_HDRLEN;
116 
117 	return (m);
118 }
119 
120 static nlamsgbuf_t *
121 nla_from_buf(const unsigned char **ptr, size_t *len)
122 {
123 	nlamsgbuf_t h, *a;
124 	size_t nlalen, skip;
125 
126 	if (*len < sizeof(h.u))
127 		return (NULL);
128 
129 	memset(&h, 0, sizeof(h));
130 	memcpy(&h.u, *ptr, sizeof(h.u));
131 
132 	if ((nlalen = h.u.nla.nla_len) < sizeof(h.u) || nlalen > *len ||
133 	    nlalen - sizeof(h.u) > UINT16_MAX ||
134 	    nlalen > SIZE_MAX - sizeof(*a) ||
135 	    (skip = NLMSG_ALIGN(nlalen)) > *len ||
136 	    (a = calloc(1, sizeof(*a) + nlalen - sizeof(h.u))) == NULL)
137 		return (NULL);
138 
139 	memcpy(&a->u, *ptr, nlalen);
140 	a->siz = sizeof(*a) + nlalen - sizeof(h.u);
141 	a->ptr = a->payload;
142 	a->len = nlalen - sizeof(h.u);
143 	*ptr += skip;
144 	*len -= skip;
145 
146 	return (a);
147 }
148 
149 static nlamsgbuf_t *
150 nla_getattr(nlamsgbuf_t *a)
151 {
152 	return (nla_from_buf((void *)&a->ptr, &a->len));
153 }
154 
155 static uint16_t
156 nla_type(const nlamsgbuf_t *a)
157 {
158 	return (a->u.nla.nla_type);
159 }
160 
161 static nlamsgbuf_t *
162 nlmsg_getattr(nlmsgbuf_t *m)
163 {
164 	return (nla_from_buf((void *)&m->ptr, &m->len));
165 }
166 
167 static int
168 nla_read(nlamsgbuf_t *a, void *buf, size_t cnt)
169 {
170 	if (cnt > a->u.nla.nla_len ||
171 	    fido_buf_read((void *)&a->ptr, &a->len, buf, cnt) < 0)
172 		return (-1);
173 
174 	a->u.nla.nla_len = (uint16_t)(a->u.nla.nla_len - cnt);
175 
176 	return (0);
177 }
178 
179 static nlmsgbuf_t *
180 nlmsg_from_buf(const unsigned char **ptr, size_t *len)
181 {
182 	nlmsgbuf_t h, *m;
183 	size_t msglen, skip;
184 
185 	if (*len < sizeof(h.u))
186 		return (NULL);
187 
188 	memset(&h, 0, sizeof(h));
189 	memcpy(&h.u, *ptr, sizeof(h.u));
190 
191 	if ((msglen = h.u.nlmsg.nlmsg_len) < sizeof(h.u) || msglen > *len ||
192 	    msglen - sizeof(h.u) > UINT16_MAX ||
193 	    (skip = NLMSG_ALIGN(msglen)) > *len ||
194 	    (m = nlmsg_new(0, 0, msglen - sizeof(h.u))) == NULL)
195 		return (NULL);
196 
197 	memcpy(&m->u, *ptr, msglen);
198 	*ptr += skip;
199 	*len -= skip;
200 
201 	return (m);
202 }
203 
204 static int
205 nlmsg_read(nlmsgbuf_t *m, void *buf, size_t cnt)
206 {
207 	if (cnt > m->u.nlmsg.nlmsg_len ||
208 	    fido_buf_read((void *)&m->ptr, &m->len, buf, cnt) < 0)
209 		return (-1);
210 
211 	m->u.nlmsg.nlmsg_len = (uint32_t)(m->u.nlmsg.nlmsg_len - cnt);
212 
213 	return (0);
214 }
215 
216 static int
217 nlmsg_write(nlmsgbuf_t *m, const void *buf, size_t cnt)
218 {
219 	if (cnt > UINT32_MAX - m->u.nlmsg.nlmsg_len ||
220 	    fido_buf_write(&m->ptr, &m->len, buf, cnt) < 0)
221 		return (-1);
222 
223 	m->u.nlmsg.nlmsg_len = (uint32_t)(m->u.nlmsg.nlmsg_len + cnt);
224 
225 	return (0);
226 }
227 
228 static int
229 nlmsg_set_genl(nlmsgbuf_t *m, uint8_t cmd)
230 {
231 	genlmsgbuf_t g;
232 
233 	memset(&g, 0, sizeof(g));
234 	g.u.genl.cmd = cmd;
235 	g.u.genl.version = NFC_GENL_VERSION;
236 
237 	return (nlmsg_write(m, &g, sizeof(g)));
238 }
239 
240 static int
241 nlmsg_get_genl(nlmsgbuf_t *m, uint8_t cmd)
242 {
243 	genlmsgbuf_t g;
244 
245 	memset(&g, 0, sizeof(g));
246 
247 	if (nlmsg_read(m, &g, sizeof(g)) < 0 || g.u.genl.cmd != cmd)
248 		return (-1);
249 
250 	return (0);
251 }
252 
253 static int
254 nlmsg_get_status(nlmsgbuf_t *m)
255 {
256 	int status;
257 
258 	if (nlmsg_read(m, &status, sizeof(status)) < 0 || status == INT_MIN)
259 		return (-1);
260 	if (status < 0)
261 		status = -status;
262 
263 	return (status);
264 }
265 
266 static int
267 nlmsg_setattr(nlmsgbuf_t *m, uint16_t type, const void *ptr, size_t len)
268 {
269 	int r;
270 	char *padding;
271 	size_t skip;
272 	nlamsgbuf_t a;
273 
274 	if ((skip = NLMSG_ALIGN(len)) > UINT16_MAX - sizeof(a.u) ||
275 	    skip < len || (padding = calloc(1, skip - len)) == NULL)
276 		return (-1);
277 
278 	memset(&a, 0, sizeof(a));
279 	a.u.nla.nla_type = type;
280 	a.u.nla.nla_len = (uint16_t)(len + sizeof(a.u));
281 	r = nlmsg_write(m, &a.u, sizeof(a.u)) < 0 ||
282 	    nlmsg_write(m, ptr, len) < 0 ||
283 	    nlmsg_write(m, padding, skip - len) < 0 ? -1 : 0;
284 
285 	free(padding);
286 
287 	return (r);
288 }
289 
290 static int
291 nlmsg_set_u16(nlmsgbuf_t *m, uint16_t type, uint16_t val)
292 {
293 	return (nlmsg_setattr(m, type, &val, sizeof(val)));
294 }
295 
296 static int
297 nlmsg_set_u32(nlmsgbuf_t *m, uint16_t type, uint32_t val)
298 {
299 	return (nlmsg_setattr(m, type, &val, sizeof(val)));
300 }
301 
302 static int
303 nlmsg_set_str(nlmsgbuf_t *m, uint16_t type, const char *val)
304 {
305 	return (nlmsg_setattr(m, type, val, strlen(val) + 1));
306 }
307 
308 static int
309 nla_get_u16(nlamsgbuf_t *a, uint16_t *v)
310 {
311 	return (nla_read(a, v, sizeof(*v)));
312 }
313 
314 static int
315 nla_get_u32(nlamsgbuf_t *a, uint32_t *v)
316 {
317 	return (nla_read(a, v, sizeof(*v)));
318 }
319 
320 static char *
321 nla_get_str(nlamsgbuf_t *a)
322 {
323 	size_t n;
324 	char *s = NULL;
325 
326 	if ((n = a->len) < 1 || a->ptr[n - 1] != '\0' ||
327 	    (s = calloc(1, n)) == NULL || nla_read(a, s, n) < 0) {
328 		free(s);
329 		return (NULL);
330 	}
331 	s[n - 1] = '\0';
332 
333 	return (s);
334 }
335 
336 static int
337 nlmsg_tx(int fd, const nlmsgbuf_t *m)
338 {
339 	ssize_t r;
340 
341 	if ((r = WRITE(fd, nlmsg_ptr(m), nlmsg_len(m))) == -1) {
342 		fido_log_error(errno, "%s: write", __func__);
343 		return (-1);
344 	}
345 	if (r < 0 || (size_t)r != nlmsg_len(m)) {
346 		fido_log_debug("%s: %zd != %zu", __func__, r, nlmsg_len(m));
347 		return (-1);
348 	}
349 	fido_log_xxd(nlmsg_ptr(m), nlmsg_len(m), "%s", __func__);
350 
351 	return (0);
352 }
353 
354 static ssize_t
355 nlmsg_rx(int fd, unsigned char *ptr, size_t len, int ms)
356 {
357 	ssize_t r;
358 
359 	if (len > SSIZE_MAX) {
360 		fido_log_debug("%s: len", __func__);
361 		return (-1);
362 	}
363 	if (fido_hid_unix_wait(fd, ms, NULL) < 0) {
364 		fido_log_debug("%s: fido_hid_unix_wait", __func__);
365 		return (-1);
366 	}
367 	if ((r = READ(fd, ptr, len)) == -1) {
368 		fido_log_error(errno, "%s: read %zd", __func__, r);
369 		return (-1);
370 	}
371 	fido_log_xxd(ptr, (size_t)r, "%s", __func__);
372 
373 	return (r);
374 }
375 
376 static int
377 nlmsg_iter(nlmsgbuf_t *m, void *arg, int (*parser)(nlamsgbuf_t *, void *))
378 {
379 	nlamsgbuf_t *a;
380 	int r;
381 
382 	while ((a = nlmsg_getattr(m)) != NULL) {
383 		r = parser(a, arg);
384 		free(a);
385 		if (r < 0) {
386 			fido_log_debug("%s: parser", __func__);
387 			return (-1);
388 		}
389 	}
390 
391 	return (0);
392 }
393 
394 static int
395 nla_iter(nlamsgbuf_t *g, void *arg, int (*parser)(nlamsgbuf_t *, void *))
396 {
397 	nlamsgbuf_t *a;
398 	int r;
399 
400 	while ((a = nla_getattr(g)) != NULL) {
401 		r = parser(a, arg);
402 		free(a);
403 		if (r < 0) {
404 			fido_log_debug("%s: parser", __func__);
405 			return (-1);
406 		}
407 	}
408 
409 	return (0);
410 }
411 
412 static int
413 nl_parse_reply(const uint8_t *blob, size_t blob_len, uint16_t msg_type,
414     uint8_t genl_cmd, void *arg, int (*parser)(nlamsgbuf_t *, void *))
415 {
416 	nlmsgbuf_t *m;
417 	int r;
418 
419 	while (blob_len) {
420 		if ((m = nlmsg_from_buf(&blob, &blob_len)) == NULL) {
421 			fido_log_debug("%s: nlmsg", __func__);
422 			return (-1);
423 		}
424 		if (nlmsg_type(m) == NLMSG_ERROR) {
425 			r = nlmsg_get_status(m);
426 			free(m);
427 			return (r);
428 		}
429 		if (nlmsg_type(m) != msg_type ||
430 		    nlmsg_get_genl(m, genl_cmd) < 0) {
431 			fido_log_debug("%s: skipping", __func__);
432 			free(m);
433 			continue;
434 		}
435 		if (parser != NULL && nlmsg_iter(m, arg, parser) < 0) {
436 			fido_log_debug("%s: nlmsg_iter", __func__);
437 			free(m);
438 			return (-1);
439 		}
440 		free(m);
441 	}
442 
443 	return (0);
444 }
445 
446 static int
447 parse_mcastgrp(nlamsgbuf_t *a, void *arg)
448 {
449 	nl_family_t *family = arg;
450 	char *name;
451 
452 	switch (nla_type(a)) {
453 	case CTRL_ATTR_MCAST_GRP_NAME:
454 		if ((name = nla_get_str(a)) == NULL ||
455 		    strcmp(name, NFC_GENL_MCAST_EVENT_NAME) != 0) {
456 			free(name);
457 			return (-1); /* XXX skip? */
458 		}
459 		free(name);
460 		return (0);
461 	case CTRL_ATTR_MCAST_GRP_ID:
462 		if (family->mcastgrp)
463 			break;
464 		if (nla_get_u32(a, &family->mcastgrp) < 0) {
465 			fido_log_debug("%s: group", __func__);
466 			return (-1);
467 		}
468 		return (0);
469 	}
470 
471 	fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a));
472 
473 	return (0);
474 }
475 
476 static int
477 parse_mcastgrps(nlamsgbuf_t *a, void *arg)
478 {
479 	return (nla_iter(a, arg, parse_mcastgrp));
480 }
481 
482 static int
483 parse_family(nlamsgbuf_t *a, void *arg)
484 {
485 	nl_family_t *family = arg;
486 
487 	switch (nla_type(a)) {
488 	case CTRL_ATTR_FAMILY_ID:
489 		if (family->id)
490 			break;
491 		if (nla_get_u16(a, &family->id) < 0) {
492 			fido_log_debug("%s: id", __func__);
493 			return (-1);
494 		}
495 		return (0);
496 	case CTRL_ATTR_MCAST_GROUPS:
497 		return (nla_iter(a, family, parse_mcastgrps));
498 	}
499 
500 	fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a));
501 
502 	return (0);
503 }
504 
505 static int
506 nl_get_nfc_family(int fd, uint16_t *type, uint32_t *mcastgrp)
507 {
508 	nlmsgbuf_t *m;
509 	uint8_t reply[512];
510 	nl_family_t family;
511 	ssize_t r;
512 	int ok;
513 
514 	if ((m = nlmsg_new(GENL_ID_CTRL, 0, 64)) == NULL ||
515 	    nlmsg_set_genl(m, CTRL_CMD_GETFAMILY) < 0 ||
516 	    nlmsg_set_u16(m, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL) < 0 ||
517 	    nlmsg_set_str(m, CTRL_ATTR_FAMILY_NAME, NFC_GENL_NAME) < 0 ||
518 	    nlmsg_tx(fd, m) < 0) {
519 		free(m);
520 		return (-1);
521 	}
522 	free(m);
523 	memset(&family, 0, sizeof(family));
524 	if ((r = nlmsg_rx(fd, reply, sizeof(reply), -1)) < 0) {
525 		fido_log_debug("%s: nlmsg_rx", __func__);
526 		return (-1);
527 	}
528 	if ((ok = nl_parse_reply(reply, (size_t)r, GENL_ID_CTRL,
529 	    CTRL_CMD_NEWFAMILY, &family, parse_family)) != 0) {
530 		fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
531 		return (-1);
532 	}
533 	if (family.id == 0 || family.mcastgrp == 0) {
534 		fido_log_debug("%s: missing attr", __func__);
535 		return (-1);
536 	}
537 	*type = family.id;
538 	*mcastgrp = family.mcastgrp;
539 
540 	return (0);
541 }
542 
543 static int
544 parse_target(nlamsgbuf_t *a, void *arg)
545 {
546 	nl_target_t *t = arg;
547 
548 	if (t->found || nla_type(a) != NFC_ATTR_TARGET_INDEX) {
549 		fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a));
550 		return (0);
551 	}
552 	if (nla_get_u32(a, t->value) < 0) {
553 		fido_log_debug("%s: target", __func__);
554 		return (-1);
555 	}
556 	t->found = 1;
557 
558 	return (0);
559 }
560 
561 int
562 fido_nl_power_nfc(fido_nl_t *nl, uint32_t dev)
563 {
564 	nlmsgbuf_t *m;
565 	uint8_t reply[512];
566 	ssize_t r;
567 	int ok;
568 
569 	if ((m = nlmsg_new(nl->nfc_type, NLM_F_ACK, 64)) == NULL ||
570 	    nlmsg_set_genl(m, NFC_CMD_DEV_UP) < 0 ||
571 	    nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 ||
572 	    nlmsg_tx(nl->fd, m) < 0) {
573 		free(m);
574 		return (-1);
575 	}
576 	free(m);
577 	if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1)) < 0) {
578 		fido_log_debug("%s: nlmsg_rx", __func__);
579 		return (-1);
580 	}
581 	if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type,
582 	    NFC_CMD_DEV_UP, NULL, NULL)) != 0 && ok != EALREADY) {
583 		fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
584 		return (-1);
585 	}
586 
587 	return (0);
588 }
589 
590 static int
591 nl_nfc_poll(fido_nl_t *nl, uint32_t dev)
592 {
593 	nlmsgbuf_t *m;
594 	uint8_t reply[512];
595 	ssize_t r;
596 	int ok;
597 
598 	if ((m = nlmsg_new(nl->nfc_type, NLM_F_ACK, 64)) == NULL ||
599 	    nlmsg_set_genl(m, NFC_CMD_START_POLL) < 0 ||
600 	    nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 ||
601 	    nlmsg_set_u32(m, NFC_ATTR_PROTOCOLS, NFC_PROTO_ISO14443_MASK) < 0 ||
602 	    nlmsg_tx(nl->fd, m) < 0) {
603 		free(m);
604 		return (-1);
605 	}
606 	free(m);
607 	if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1)) < 0) {
608 		fido_log_debug("%s: nlmsg_rx", __func__);
609 		return (-1);
610 	}
611 	if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type,
612 	    NFC_CMD_START_POLL, NULL, NULL)) != 0) {
613 		fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
614 		return (-1);
615 	}
616 
617 	return (0);
618 }
619 
620 static int
621 nl_dump_nfc_target(fido_nl_t *nl, uint32_t dev, uint32_t *target, int ms)
622 {
623 	nlmsgbuf_t *m;
624 	nl_target_t t;
625 	uint8_t reply[512];
626 	ssize_t r;
627 	int ok;
628 
629 	if ((m = nlmsg_new(nl->nfc_type, NLM_F_DUMP, 64)) == NULL ||
630 	    nlmsg_set_genl(m, NFC_CMD_GET_TARGET) < 0 ||
631 	    nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 ||
632 	    nlmsg_tx(nl->fd, m) < 0) {
633 		free(m);
634 		return (-1);
635 	}
636 	free(m);
637 	if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), ms)) < 0) {
638 		fido_log_debug("%s: nlmsg_rx", __func__);
639 		return (-1);
640 	}
641 	memset(&t, 0, sizeof(t));
642 	t.value = target;
643 	if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type,
644 	    NFC_CMD_GET_TARGET, &t, parse_target)) != 0) {
645 		fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
646 		return (-1);
647 	}
648 	if (!t.found) {
649 		fido_log_debug("%s: target not found", __func__);
650 		return (-1);
651 	}
652 
653 	return (0);
654 }
655 
656 static int
657 parse_nfc_event(nlamsgbuf_t *a, void *arg)
658 {
659 	nl_poll_t *ctx = arg;
660 	uint32_t dev;
661 
662 	if (nla_type(a) != NFC_ATTR_DEVICE_INDEX) {
663 		fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a));
664 		return (0);
665 	}
666 	if (nla_get_u32(a, &dev) < 0) {
667 		fido_log_debug("%s: dev", __func__);
668 		return (-1);
669 	}
670 	if (dev == ctx->dev)
671 		ctx->eventcnt++;
672 	else
673 		fido_log_debug("%s: ignoring dev 0x%x", __func__, dev);
674 
675 	return (0);
676 }
677 
678 int
679 fido_nl_get_nfc_target(fido_nl_t *nl, uint32_t dev, uint32_t *target)
680 {
681 	uint8_t reply[512];
682 	nl_poll_t ctx;
683 	ssize_t r;
684 	int ok;
685 
686 	if (nl_nfc_poll(nl, dev) < 0) {
687 		fido_log_debug("%s: nl_nfc_poll", __func__);
688 		return (-1);
689 	}
690 #ifndef FIDO_FUZZ
691 	if (setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
692 	    &nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)) == -1) {
693 		fido_log_error(errno, "%s: setsockopt add", __func__);
694 		return (-1);
695 	}
696 #endif
697 	r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1);
698 #ifndef FIDO_FUZZ
699 	if (setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP,
700 	    &nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)) == -1) {
701 		fido_log_error(errno, "%s: setsockopt drop", __func__);
702 		return (-1);
703 	}
704 #endif
705 	if (r < 0) {
706 		fido_log_debug("%s: nlmsg_rx", __func__);
707 		return (-1);
708 	}
709 	memset(&ctx, 0, sizeof(ctx));
710 	ctx.dev = dev;
711 	if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type,
712 	    NFC_EVENT_TARGETS_FOUND, &ctx, parse_nfc_event)) != 0) {
713 		fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
714 		return (-1);
715 	}
716 	if (ctx.eventcnt == 0) {
717 		fido_log_debug("%s: dev 0x%x not observed", __func__, dev);
718 		return (-1);
719 	}
720 	if (nl_dump_nfc_target(nl, dev, target, -1) < 0) {
721 		fido_log_debug("%s: nl_dump_nfc_target", __func__);
722 		return (-1);
723 	}
724 
725 	return (0);
726 }
727 
728 void
729 fido_nl_free(fido_nl_t **nlp)
730 {
731 	fido_nl_t *nl;
732 
733 	if (nlp == NULL || (nl = *nlp) == NULL)
734 		return;
735 	if (nl->fd != -1 && close(nl->fd) == -1)
736 		fido_log_error(errno, "%s: close", __func__);
737 
738 	free(nl);
739 	*nlp = NULL;
740 }
741 
742 fido_nl_t *
743 fido_nl_new(void)
744 {
745 	fido_nl_t *nl;
746 	int ok = -1;
747 
748 	if ((nl = calloc(1, sizeof(*nl))) == NULL)
749 		return (NULL);
750 	if ((nl->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC,
751 	    NETLINK_GENERIC)) == -1) {
752 		fido_log_error(errno, "%s: socket", __func__);
753 		goto fail;
754 	}
755 	nl->saddr.nl_family = AF_NETLINK;
756 	if (bind(nl->fd, (struct sockaddr *)&nl->saddr,
757 	    sizeof(nl->saddr)) == -1) {
758 		fido_log_error(errno, "%s: bind", __func__);
759 		goto fail;
760 	}
761 	if (nl_get_nfc_family(nl->fd, &nl->nfc_type, &nl->nfc_mcastgrp) < 0) {
762 		fido_log_debug("%s: nl_get_nfc_family", __func__);
763 		goto fail;
764 	}
765 
766 	ok = 0;
767 fail:
768 	if (ok < 0)
769 		fido_nl_free(&nl);
770 
771 	return (nl);
772 }
773 
774 #ifdef FIDO_FUZZ
775 void
776 set_netlink_io_functions(ssize_t (*read_f)(int, void *, size_t),
777     ssize_t (*write_f)(int, const void *, size_t))
778 {
779 	fuzz_read = read_f;
780 	fuzz_write = write_f;
781 }
782 #endif
783