]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
io_uring/net: allow filtering on IORING_OP_CONNECT
authorShouvik Kar <auxcorelabs@gmail.com>
Tue, 12 May 2026 11:02:42 +0000 (16:32 +0530)
committerJens Axboe <axboe@kernel.dk>
Thu, 14 May 2026 13:23:01 +0000 (07:23 -0600)
This adds custom filtering for IORING_OP_CONNECT, where the target
family is always exposed, and (for AF_INET / AF_INET6) port and
address are exposed. port and v4_addr are in network byte order so
filter authors can compare against on-wire constants.

Skip population unless addr_len covers the populated fields, to
avoid leaking stale io_async_msghdr data on short connects.

Signed-off-by: Shouvik Kar <auxcorelabs@gmail.com>
Link: https://patch.msgid.link/20260512110242.26219-1-auxcorelabs@gmail.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
include/uapi/linux/io_uring/bpf_filter.h
io_uring/net.c
io_uring/net.h
io_uring/opdef.c

index 1b461d792a7b9f0f5f6cad570c119ec7aa376720..ce7d78ab13b30c27a396ccf98faa086736e39744 100644 (file)
@@ -27,6 +27,22 @@ struct io_uring_bpf_ctx {
                        __u64   mode;
                        __u64   resolve;
                } open;
+               /*
+                * For CONNECT: fields are populated only when addr_len covers
+                * them; unpopulated fields are zero from the caller-side memset
+                * in io_uring_populate_bpf_ctx(). port and v4_addr are network
+                * byte order. Filters may only issue BPF_LD|BPF_W|BPF_ABS at
+                * 4-byte aligned offsets; load + mask for sub-word fields.
+                */
+               struct {
+                       __u32   family; /* sa_family_t zero-extended */
+                       __be16  port;
+                       __u8    pad[2];
+                       union {
+                               __be32  v4_addr;
+                               __u8    v6_addr[16];
+                       };
+               } connect;
        };
 };
 
index 30cd22c0b934b97ce6e265756b24daca7d398361..cceb5c1409ca8c25db66d785ee246be12083ff2b 100644 (file)
@@ -1674,6 +1674,47 @@ void io_socket_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req)
        bctx->socket.protocol = sock->protocol;
 }
 
+void io_connect_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req)
+{
+       struct io_connect *conn = io_kiocb_to_cmd(req, struct io_connect);
+       struct io_async_msghdr *iomsg = req->async_data;
+       struct sockaddr_storage *ss = &iomsg->addr;
+
+       /*
+        * move_addr_to_kernel() skips the copy for addr_len == 0, so
+        * iomsg->addr may hold stale data from a prior CONNECT. Bail
+        * unless addr_len covers the family discriminator.
+        */
+       if (conn->addr_len < (int)sizeof(sa_family_t))
+               return;
+
+       bctx->connect.family = ss->ss_family;
+       switch (ss->ss_family) {
+       case AF_INET: {
+               struct sockaddr_in *sin = (struct sockaddr_in *)ss;
+
+               if (conn->addr_len < (int)sizeof(*sin))
+                       break;
+               bctx->connect.port = sin->sin_port;
+               bctx->connect.v4_addr = sin->sin_addr.s_addr;
+               break;
+       }
+       case AF_INET6: {
+               struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss;
+
+               if (conn->addr_len < (int)sizeof(*sin6))
+                       break;
+               bctx->connect.port = sin6->sin6_port;
+               memcpy(bctx->connect.v6_addr, &sin6->sin6_addr,
+                      sizeof(bctx->connect.v6_addr));
+               break;
+       }
+       default:
+               /* family is set; per-family fields stay zero - family-only filtering */
+               break;
+       }
+}
+
 int io_socket_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
 {
        struct io_socket *sock = io_kiocb_to_cmd(req, struct io_socket);
index d4d1ddce50e3a4f6d02c43960d1db4107a38ae32..51fda715d3c0b4a29c694aeba5bf4ace44dd3375 100644 (file)
@@ -46,6 +46,7 @@ int io_accept(struct io_kiocb *req, unsigned int issue_flags);
 int io_socket_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
 int io_socket(struct io_kiocb *req, unsigned int issue_flags);
 void io_socket_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req);
+void io_connect_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req);
 
 int io_connect_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
 int io_connect(struct io_kiocb *req, unsigned int issue_flags);
@@ -69,4 +70,10 @@ static inline void io_socket_bpf_populate(struct io_uring_bpf_ctx *bctx,
                                          struct io_kiocb *req)
 {
 }
+
+static inline void io_connect_bpf_populate(struct io_uring_bpf_ctx *bctx,
+                                          struct io_kiocb *req)
+{
+}
+
 #endif
index c3ef52b7081132fa01dec4d96f0615fdfe3b4348..8ea6bd274607ae8b6a8e2abccf95f1767ca6b891 100644 (file)
@@ -203,9 +203,11 @@ const struct io_issue_def io_issue_defs[] = {
                .unbound_nonreg_file    = 1,
                .pollout                = 1,
 #if defined(CONFIG_NET)
+               .filter_pdu_size        = sizeof_field(struct io_uring_bpf_ctx, connect),
                .async_size             = sizeof(struct io_async_msghdr),
                .prep                   = io_connect_prep,
                .issue                  = io_connect,
+               .filter_populate        = io_connect_bpf_populate,
 #else
                .prep                   = io_eopnotsupp_prep,
 #endif