From nobody Fri Jun 12 17:16:03 2026 Received: from mail-qv1-f54.google.com (mail-qv1-f54.google.com [209.85.219.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6E9DA4D98E3 for ; Wed, 13 May 2026 17:53:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778694814; cv=none; b=bFrx+7GvsMwVHS3Usk2zQcvTL2AB3eIPha44Fnf4kHyA0lFX0831miFd6pNCveuxmLkXrb8vxTfNv/HtNvsC4Eu0Id2pRb4JlVoRoUtTUi3Ri13vQyrprjqc5ROI9U6wkEE5ix34msQoIwxJ3WnIGHVkgSJQUF8I2nyRihnVY24= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778694814; c=relaxed/simple; bh=EQ4MYxskE0lYtC8XPV/NocVxlYCWxw9+uUFSKNZhNpM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nPl6PP4EqLWAQDQwr4tJ+1gkVUqkFoHJ8lJjGGyW+Ps2XGEBPnBxM9dJ4DnYbLo1hdEvdKbluXhcCZ9YTcF7SmknsByjGk3YjryZmpk2qInukYu6H8+1S3gNQrmNppaN12oqO2nDbjo0IzN2yQd7oaFQnmjHGPVRDaGsynOs5Ko= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MudqVIS4; arc=none smtp.client-ip=209.85.219.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MudqVIS4" Received: by mail-qv1-f54.google.com with SMTP id 6a1803df08f44-8b81586dff3so81621276d6.1 for ; Wed, 13 May 2026 10:53:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778694809; x=1779299609; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=5ayiddnyLyvk5j8vikm+y7Z1odGVhjZ1CBk7OhZOJKg=; b=MudqVIS4/mF7LlQ/QH1Zw0rLQ8X4SLJ3djO/+ryidarHfdiNuuRSlJ5TeMmiY1Y7qa T+libQKQ+ApHnrM/KCRb8bkVeb271DV1eCup3DtLnHmqmtqeDeLU42EGS7B8YjABIkKD FxgOLe2mHevjXaLaabOXp0BtuatAF3jkffzoJE83OLq7CKvVU5gxZX+tXoty8Zdlyf/y XGDdg8Rb4OxCq5tI3gfZlZ6tjfPZ6LuztsdHqgwf33MItmyKW7kenhBEYfO8yDRNP6TJ qrATCDxhtCuxi+tMg7TocEUO/BgpmjNaJ6tfBIqjc8TsSgcC2QPFbujxOhgOLpC/Q7ui w0cw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778694809; x=1779299609; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=5ayiddnyLyvk5j8vikm+y7Z1odGVhjZ1CBk7OhZOJKg=; b=ZmA4rm4NrvOnFDcjKCcMv46Exr1v8vNGzkrHHHvlmiRtA2rW2B3AngeYLz38qUCc1p Y1mzomnsTBRorkwR/HYbtn4kdqRcgoerXgOF3l6KvtI175SBBKsOO+l1ebKhI+kMMA8S +FqvPT4RQGPc/YFq6JXBfgFD2YkkuA0Cot84z5KBlXjNCd510lZJ4Fnq5Uh49FejyYNU HtLUKLocoB3i/hr/tNUh5E+tXHOO+MuHsWRR+BzAeQ0Scw0qPUEHWO5hpoATPrS1GeSL L6jgkKKlx6XDZRsjSPd+Aa8ecm+GtzmhBBEopMQj1BwmTaguZnwfppY0eWlI+S6DiyUQ 6F/Q== X-Gm-Message-State: AOJu0YwTVF6TCXLn8dJ7OZsZAJIepzS/000+2iwUTzTFUEyovgMlE0sC 937jx3zJhC1vRCdduFDKwidO0l/+3sShz1DtVGvkHu8NFffAiSsmo5q4 X-Gm-Gg: Acq92OEKw2cfOO/iIBGcCzpBtg7Nbl7lirJMCttyxrG32zlu3ntxvoA96k56Bc/iG4S h28/rIL9JCqxeez2V8XokRwm1d6elcfHHdP/jfHnv7LOXTz9AysLkdzSR7KhHU4u7511dwHP8ca A3xw2kpIsFrbpjcKGXzPDtbUWv/7jq9Ogt2fn8HevXBhfB26Apmp97cwIkSd/gEw1kOJR4IyZ+K ld/7dCsMwOcRDaV3BhCek2EQyJiOXTspuWVu41VqeebJpyuSP8vgC7MMmV8LjKj2tQAllYyyBbl ZaDInEjE1JdHp6rXZH1w1O2uA4xtjQgKLlFe6BolcVMF7xqPxwokqmOCEZsGyiEY6BeVFi/jwP0 Kjlv5czo9nRjqG0SCGBYsztVj0Vh/zHzWsKI4NU/C5cea/ADlWYMI2xmhQi6/qDbM1RrpxrFWmC DD/0t3O1VGOKzKnG0qc+BZcbCkKyqcbeelBLqK/bst7eXIAMgQqeKU2RhYY7iSFKGdi0/HduOvV ye/xBgMiuae3CGB+UdIxhBgm8fqVrZo6n8DXBGXiXJs2yKJi2FpVQ== X-Received: by 2002:a0c:f119:0:b0:8ac:a689:34ce with SMTP id 6a1803df08f44-8c7bdc83746mr54170006d6.45.1778694809391; Wed, 13 May 2026 10:53:29 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c90bf6720asm2036946d6.39.2026.05.13.10.53.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 13 May 2026 10:53:28 -0700 (PDT) From: Michael Bommarito To: Bernard Metzler , Jason Gunthorpe , Leon Romanovsky , linux-rdma@vger.kernel.org Cc: linux-kernel@vger.kernel.org Subject: [PATCH 1/2] RDMA/siw: reject MPA FPDU length underflow before signed receive math Date: Wed, 13 May 2026 13:53:24 -0400 Message-ID: <20260513175325.2042630-2-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260513175325.2042630-1-michael.bommarito@gmail.com> References: <20260513175325.2042630-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A malicious connected siw peer can send an iWARP FPDU whose MPA length field (c_hdr->mpa_len, 16 bit big-endian, peer-controlled) is smaller than the fixed DDP/RDMAP header for the announced opcode. Soft-iWARP parses the full header in siw_get_hdr() based on iwarp_pktinfo[opcode] .hdr_len, but never compares mpa_len against that header length. siw_tcp_rx_data() then derives srx->fpdu_part_rem =3D be16_to_cpu(mpa_len) - fpdu_part_rcvd + MPA_HDR_SIZE; where fpdu_part_rcvd equals iwarp_pktinfo[opcode].hdr_len at this point. For a tagged WRITE (hdr_len 16, MPA_HDR_SIZE 2) the smallest on-wire mpa_len of 0 yields fpdu_part_rem =3D -14, and any mpa_len below hdr_len - MPA_HDR_SIZE underflows to a negative int. The signed value then flows into siw_proc_write()/siw_proc_rresp() as bytes =3D min(srx->fpdu_part_rem, srx->skb_new); is handed to siw_check_mem() as an int len (whose interval check addr + len > mem->va + mem->len is satisfied for a valid base when len is negative), and reaches siw_rx_data() -> siw_rx_kva() / siw_rx_umem() -> skb_copy_bits() as a signed copy length. The header copy branch in skb_copy_bits() promotes that to size_t, producing a multi-gigabyte read. KASAN under a KUnit harness that drives the real kernel TCP receive path -- a loopback AF_INET socketpair, the malformed FPDU written via kernel_sendmsg, sk_data_ready firing in softirq, tcp_read_sock dispatching to siw_tcp_rx_data -- reports: BUG: KASAN: use-after-free in skb_copy_bits+0x284/0x480 Read of size 4294967295 at addr ffff888... Call Trace: skb_copy_bits siw_rx_kva siw_rx_data siw_check_mem siw_proc_write siw_tcp_rx_data __tcp_read_sock siw_qp_llp_data_ready tcp_data_ready tcp_data_queue Add the missing invariant at the earliest point where the peer header is fully assembled. iwarp_pktinfo[*].hdr_len - MPA_HDR_SIZE is exactly the value the siw transmitter uses as the minimum mpa_len for each opcode (drivers/infiniband/sw/siw/siw_qp.c:33), so this matches the protocol contract. Out-of-range FPDUs terminate the connection with TERM_ERROR_LAYER_LLP / LLP_ETYPE_MPA / LLP_ECODE_FPDU_START -- which is RFC 5044 Section 8 error code 3 ("Marker and ULPDU Length fields do not agree on the start of an FPDU"), the correct framing-error class for this inconsistency. Fixes: 8b6a361b8c48 ("rdma/siw: receive path") Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-7 Acked-by: Bernard Metzler --- See cover letter for full root cause, series rationale, and test summary. [2/2] adds the KUnit regression harness used to validate this fix. drivers/infiniband/sw/siw/siw_qp_rx.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/infiniband/sw/siw/siw_qp_rx.c b/drivers/infiniband/sw/= siw/siw_qp_rx.c index e8a88b378d51..34d03584160c 100644 --- a/drivers/infiniband/sw/siw/siw_qp_rx.c +++ b/drivers/infiniband/sw/siw/siw_qp_rx.c @@ -1081,6 +1081,21 @@ static int siw_get_hdr(struct siw_rx_stream *srx) return -EAGAIN; } =20 + /* + * Peer-controlled mpa_len must not underflow srx->fpdu_part_rem + * in siw_tcp_rx_data(); a negative value flows as a signed copy + * length into siw_check_mem() and skb_copy_bits(). + */ + if (unlikely(be16_to_cpu(c_hdr->mpa_len) + MPA_HDR_SIZE < + iwarp_pktinfo[opcode].hdr_len)) { + pr_warn_ratelimited("siw: short mpa_len %u for opcode %u (hdr_len %u)\n", + be16_to_cpu(c_hdr->mpa_len), opcode, + iwarp_pktinfo[opcode].hdr_len); + siw_init_terminate(rx_qp(srx), TERM_ERROR_LAYER_LLP, + LLP_ETYPE_MPA, LLP_ECODE_FPDU_START, 0); + return -EINVAL; + } + /* * DDP/RDMAP header receive completed. Check if the current * DDP segment starts a new RDMAP message or continues a previously --=20 2.53.0 From nobody Fri Jun 12 17:16:03 2026 Received: from mail-qt1-f181.google.com (mail-qt1-f181.google.com [209.85.160.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 776E64C6F15 for ; Wed, 13 May 2026 17:53:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778694813; cv=none; b=W9EhjmUvDR1QsvUcEhpderFrDkjqBsld2uP4VrClOsXqyslaMvvXxsOYaxX26H+cH35Vhrdbwt50y49f2oFJHU3yssi4owmDWNf0EqwY5/iIrfgxEWqf3/FVKHA9s4lUu5g4NLPfbt6tc4wgRzVJoCeETQGWLvfifnVs+kRV/c0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778694813; c=relaxed/simple; bh=zNu1dPnVUp8Za6/qZk1i2eXcs0j69iChQl4hWpE2ByQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=t/TZRIAp0JRHC50bIfgnbdq+J3Wc0PO4T2PV213ERq0halwwdPIbKbVJFZgWa4Rii5ZIJ8MdvgZM0j36OKiUn41yVBHs3m4cOsEdDi1bhZqwF9tDncH+7xqTgUui3qBUT/NP6zxLv8eFxjg8k/1YZk+I95a0sCqwJcI6iOR4FVg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=VIcAPyg8; arc=none smtp.client-ip=209.85.160.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="VIcAPyg8" Received: by mail-qt1-f181.google.com with SMTP id d75a77b69052e-514ae601e01so41684151cf.1 for ; Wed, 13 May 2026 10:53:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778694810; x=1779299610; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=VxFjpCjJA3p0RNQiO7dqfCUMLP5o0eD7dzNq+c/0a0w=; b=VIcAPyg8SmosR7d/vqegtKR9UKxQ3PoGyOoFtJBgi5bk85zFDRVx02nlE0czOFAvfa 7j8YvriVE3eSAKY6JkMxRad4Mu3Nb2xAAsyoBI+lkOKxu0BQgTWq89/RnrRlaR/LuXdx wrBWk1crGtquuFLDtMmUoeh+qTQ0k4tU/PmVY73bszP6ViADAclfyXt38JjJVIDrEh0Z gyZUE3Y2k8lwCXK+1JlHyygZeqDVaIZ+IICrOJuW5WafA1FtfWD0mAtsZGfM5WLgWKyD I0soExJtnfZp4vVUrgMJ7dHKqwEscwtrBqgN+aaJYxjj82DgL4hAAkSukyNLmQuRqgYl ulEw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778694810; x=1779299610; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=VxFjpCjJA3p0RNQiO7dqfCUMLP5o0eD7dzNq+c/0a0w=; b=Kti1HJN9P2e+woIvvF79w4anxWFgEWHn64inIi7Tap9IS8okDT8xevwLy2C4w/Jrj+ 3Qxri2qnQ8LzqX3pn8PD2b9cmJF4a6maPlcV/MyondvLCh3H2bt89tZNQemnvYkl3Uri rLGPYblPxHOYkeE/YZ72/Sj2qhhNqBRP/hkNcvLKYJJ2E7cBN3PTToMCga5q+DeyV5Xk Z3NLtUafUKNYd/DatUvCwXHmZiT1echy9cHBWCDKHCteUie6FvYSbusSz3r5DqilIqDj t6NfQ4mfYk3V4Vk4BMgeHT7M23LW8VExTTJn2DfPthyc9dnBy47rL0859d5MRpuoRoz+ JVXA== X-Gm-Message-State: AOJu0YxJ5caFTheNOf7P+iM0k1Qj+bg2BUG/WkRF2tSCGo6WO1znTNTF 3euDzaQte1sSlBQ3kUFxihHFbtQBuQHS1fLQ1nLXN9S2YWZWyLIiNLnA X-Gm-Gg: Acq92OHOSqV8+xjOw4gwIWLZ16RIlskUaJSquxQb77+VnJ3Yj5XNRhUnXJgqq7pI+tL XVwxLUfGzHqKsJzNifZsqA8Y814NNz/UlNi7R1nnFBUz9BSwdTH04tefdYCSTI/fP9ITp0eH8mI vM9bvuzk9k97xe0NOFvuhsqDwDGAUjkj7Lp921/80ejM/AQhHDhkMXYVGP4VzTcXGhjC+JvUBWu Prr8sEJV28t8Sn3vTReXerQUjBz/5xqCmGveLv7f3USvct//DjPSIHp1+l549CZcOajZ9TOjo8+ RO8y9E/tOUWN0PxPE4T9fXtVBPOa5r9ogN23g1am4SgOR9UbImNu+0z84yrKdkByfn6cTgWmojt CpbBhwbmaDbAtm7CZIKXqeTA9dn/4h4EiduRuCpQvhr0LuAmgPywXMeZ5dCvye0qhQnj3tJgY1M 3w7FWeraFTctz2WiqqbHj+5A4kbC+FHokSsSiCOhmmi5vXnnWA2ylIJ6Xnj1ZxPQKwimB4TceTB ukNSXqiXhs1NZfZ6E4dTzBYbL1p3US+zDu6tG14HeM= X-Received: by 2002:a05:622a:2611:b0:50f:de06:45e2 with SMTP id d75a77b69052e-5162fefff50mr53939611cf.31.1778694810400; Wed, 13 May 2026 10:53:30 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c90bf6720asm2036946d6.39.2026.05.13.10.53.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 13 May 2026 10:53:29 -0700 (PDT) From: Michael Bommarito To: Bernard Metzler , Jason Gunthorpe , Leon Romanovsky , linux-rdma@vger.kernel.org Cc: linux-kernel@vger.kernel.org Subject: [PATCH 2/2] RDMA/siw: add KUnit tests for MPA receive parsing Date: Wed, 13 May 2026 13:53:25 -0400 Message-ID: <20260513175325.2042630-3-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260513175325.2042630-1-michael.bommarito@gmail.com> References: <20260513175325.2042630-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a KUnit suite (CONFIG_SIW_MPA_RX_KUNIT_TEST) that exercises the real siw_tcp_rx_data() path with three cases covering the MPA length validation added in the previous patch: - siw_mpa_write_underflow_rejected Constructs an sk_buff carrying a tagged RDMA WRITE FPDU whose mpa_len is one below iwarp_pktinfo[opcode].hdr_len - MPA_HDR_SIZE. Registers a REMOTE_WRITE MR in mem_xa so the WRITE path would otherwise reach siw_proc_write(), and calls siw_tcp_rx_data() directly. Asserts the FPDU is rejected with TERM(LLP/MPA/FPDU_START) and rx_suspend =3D 1. - siw_mpa_write_minimum_valid_accepted Regression control with mpa_len =3D hdr_len - MPA_HDR_SIZE (the smallest legal value, i.e. a zero-length WRITE). Asserts the new check does not fire: no terminate, rx_stream not suspended. - siw_mpa_write_underflow_rejected_live_socket Opens a loopback AF_INET socketpair via sock_create_kern(), attaches a struct siw_cep as sk_user_data so sk_to_qp() resolves to the test QP, and installs siw_qp_llp_data_ready as sk_data_ready on the victim socket. Writes the malformed FPDU via kernel_sendmsg from the attacker side. The kernel TCP stack delivers, sk_data_ready fires in softirq, and tcp_read_sock dispatches to siw_tcp_rx_data the same way a remote peer would. Asserts the same terminate state as the first case. The third case is the design driver: it confirms the bug-fix codepath fires from a real softirq RX entry, not just a synthetic direct call. On a stock siw tree the same harness reproduces the KASAN slab-out-of-bounds / use-after-free in skb_copy_bits. Bringing siw's loopback netdev up (case 3 binds 127.0.0.1) is done inline via dev_change_flags() under rtnl_lock since the KUnit environment does not run init scripts. Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-7 --- drivers/infiniband/sw/siw/Kconfig | 18 + drivers/infiniband/sw/siw/Makefile | 2 + drivers/infiniband/sw/siw/siw_mpa_rx_kunit.c | 349 +++++++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 drivers/infiniband/sw/siw/siw_mpa_rx_kunit.c diff --git a/drivers/infiniband/sw/siw/Kconfig b/drivers/infiniband/sw/siw/= Kconfig index 186f182b80e7..b137f5920271 100644 --- a/drivers/infiniband/sw/siw/Kconfig +++ b/drivers/infiniband/sw/siw/Kconfig @@ -18,3 +18,21 @@ config RDMA_SIW space verbs API, libibverbs. To implement RDMA over TCP/IP, the driver further interfaces with the Linux in-kernel TCP socket layer. + +config SIW_MPA_RX_KUNIT_TEST + bool "KUnit tests for Soft-iWARP MPA receive parsing" if !KUNIT_ALL_TESTS + depends on KUNIT && RDMA_SIW + default KUNIT_ALL_TESTS + help + Build KUnit regression tests for the Soft-iWARP MPA receive + state machine. The tests cover the MPA length consistency + check in siw_get_hdr(): malformed FPDUs whose mpa_len is + below the opcode's fixed DDP/RDMAP header must be rejected + with TERM(LLP/MPA/FPDU_START); the minimum-valid mpa_len + (zero-length WRITE) must still be accepted. One case drives + the real kernel TCP receive path via a loopback socketpair + so the same softirq sk_data_ready -> tcp_read_sock -> + siw_tcp_rx_data chain a remote peer would exercise is + covered. + + If unsure, say N. diff --git a/drivers/infiniband/sw/siw/Makefile b/drivers/infiniband/sw/siw= /Makefile index f5f7e3867889..09d4c90d8758 100644 --- a/drivers/infiniband/sw/siw/Makefile +++ b/drivers/infiniband/sw/siw/Makefile @@ -9,3 +9,5 @@ siw-y :=3D \ siw_qp_tx.o \ siw_qp_rx.o \ siw_verbs.o + +siw-$(CONFIG_SIW_MPA_RX_KUNIT_TEST) +=3D siw_mpa_rx_kunit.o diff --git a/drivers/infiniband/sw/siw/siw_mpa_rx_kunit.c b/drivers/infinib= and/sw/siw/siw_mpa_rx_kunit.c new file mode 100644 index 000000000000..204b3213b840 --- /dev/null +++ b/drivers/infiniband/sw/siw/siw_mpa_rx_kunit.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit harness for siw MPA receive-state length validation. + * + * Internal to the SIW_MPA_LEN_UNDERFLOW_RX_COPY disclosure validation tre= e. + * Not part of the upstream patch. + * + * case 1: short mpa_len triggers the new siw_get_hdr() check via direct + * siw_tcp_rx_data() call with a constructed sk_buff + * - expects: TERM(LLP/MPA/FPDU_START), rx_suspend=3D1 + * - under stock siw: KASAN slab-out-of-bounds in skb_copy_bits() + * - under patched siw: no splat, terminate state set + * + * case 2: minimum-valid mpa_len control (constructed sk_buff) + * - mpa_len =3D hdr_len - MPA_HDR_SIZE -> fpdu_part_rem =3D 0 + * so siw_proc_write() takes the zero-length WRITE short path + * and returns 0 without calling skb_copy_bits(). + * - expects: no TERM, state machine progressed normally + * + * case 3: real loopback TCP socketpair (the "live two-node" analog) + * - opens AF_INET TCP sockets in-kernel via sock_create_kern() + * - binds/listens on 127.0.0.1:0, connects, accepts + * - installs siw_qp_llp_data_ready on the victim socket and + * attaches a struct siw_cep so sk_to_qp() resolves to our qp + * - writes the malformed FPDU bytes via kernel_sendmsg on the + * attacker socket + * - the kernel TCP stack delivers, sk_data_ready fires, and + * siw_qp_llp_data_ready -> tcp_read_sock -> siw_tcp_rx_data + * runs in the normal kernel receive path + * - expects: TERM(LLP/MPA/FPDU_START) on the qp + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "siw.h" +#include "siw_cm.h" +#include "siw_mem.h" + +static void siw_kunit_kfree_skb(void *skb) +{ + kfree_skb(skb); +} + +struct siw_mpa_rx_ctx { + struct siw_device *sdev; + struct siw_qp *qp; + struct siw_mem *mem; + void *target; + u32 stag; +}; + +static void siw_mpa_rx_setup(struct kunit *test, struct siw_mpa_rx_ctx *c) +{ + void *xa_ret; + + c->stag =3D 0x00000100; + + c->sdev =3D kunit_kzalloc(test, sizeof(*c->sdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, c->sdev); + xa_init_flags(&c->sdev->mem_xa, XA_FLAGS_ALLOC1); + + c->qp =3D kunit_kzalloc(test, sizeof(*c->qp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, c->qp); + c->qp->sdev =3D c->sdev; + c->qp->pd =3D kunit_kzalloc(test, sizeof(*c->qp->pd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, c->qp->pd); + c->qp->rx_stream.state =3D SIW_GET_HDR; + + c->target =3D kunit_kzalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, c->target); + + c->mem =3D kunit_kzalloc(test, sizeof(*c->mem), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, c->mem); + kref_init(&c->mem->ref); + c->mem->sdev =3D c->sdev; + c->mem->stag =3D c->stag; + c->mem->stag_valid =3D 1; + c->mem->va =3D (u64)(uintptr_t)c->target; + c->mem->len =3D 64; + c->mem->pd =3D c->qp->pd; + c->mem->perms =3D IB_ACCESS_REMOTE_WRITE; + + xa_ret =3D xa_store(&c->sdev->mem_xa, c->stag >> 8, c->mem, GFP_KERNEL); + KUNIT_ASSERT_FALSE(test, xa_is_err(xa_ret)); +} + +static void siw_mpa_rx_run(struct kunit *test, struct siw_mpa_rx_ctx *c, + u16 mpa_len_val) +{ + struct iwarp_rdma_write write =3D { }; + struct sk_buff *skb; + read_descriptor_t rd_desc =3D { }; + u8 payload[sizeof(write) + 1]; + + write.ctrl.mpa_len =3D cpu_to_be16(mpa_len_val); + write.ctrl.ddp_rdmap_ctrl =3D DDP_FLAG_TAGGED | DDP_FLAG_LAST | + cpu_to_be16(DDP_VERSION << 8) | + cpu_to_be16(RDMAP_VERSION << 6) | + cpu_to_be16(RDMAP_RDMA_WRITE); + write.sink_stag =3D cpu_to_be32(c->stag); + write.sink_to =3D cpu_to_be64((u64)(uintptr_t)c->target); + + memcpy(payload, &write, sizeof(write)); + payload[sizeof(write)] =3D 0x41; + + skb =3D alloc_skb(sizeof(payload), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, skb); + skb_put_data(skb, payload, sizeof(payload)); + kunit_add_action_or_reset(test, siw_kunit_kfree_skb, skb); + + rd_desc.arg.data =3D c->qp; + rd_desc.count =3D sizeof(payload); + + siw_tcp_rx_data(&rd_desc, skb, 0, sizeof(payload)); +} + +static void siw_mpa_write_underflow_rejected(struct kunit *test) +{ + struct siw_mpa_rx_ctx c; + + siw_mpa_rx_setup(test, &c); + + /* mpa_len one byte short of the WRITE hdr_len - MPA_HDR_SIZE floor. */ + siw_mpa_rx_run(test, &c, + sizeof(struct iwarp_rdma_write) - MPA_HDR_SIZE - 1); + + KUNIT_EXPECT_EQ(test, (int)c.qp->term_info.valid, 1); + KUNIT_EXPECT_EQ(test, (int)c.qp->term_info.layer, + (int)TERM_ERROR_LAYER_LLP); + KUNIT_EXPECT_EQ(test, (int)c.qp->term_info.etype, + (int)LLP_ETYPE_MPA); + KUNIT_EXPECT_EQ(test, (int)c.qp->term_info.ecode, + (int)LLP_ECODE_FPDU_START); + KUNIT_EXPECT_EQ(test, (int)c.qp->rx_stream.rx_suspend, 1); +} + +static void siw_mpa_write_minimum_valid_accepted(struct kunit *test) +{ + struct siw_mpa_rx_ctx c; + + siw_mpa_rx_setup(test, &c); + + /* + * mpa_len =3D=3D hdr_len - MPA_HDR_SIZE is the smallest legal value; + * it yields fpdu_part_rem =3D 0, i.e. a zero-length WRITE. The new + * length check in siw_get_hdr() must accept this. + */ + siw_mpa_rx_run(test, &c, + sizeof(struct iwarp_rdma_write) - MPA_HDR_SIZE); + + KUNIT_EXPECT_EQ(test, (int)c.qp->term_info.valid, 0); + KUNIT_EXPECT_EQ(test, (int)c.qp->rx_stream.rx_suspend, 0); +} + +static int siw_mpa_rx_bring_up_lo(struct kunit *test) +{ + struct net_device *lo; + int rv; + + rtnl_lock(); + lo =3D __dev_get_by_name(&init_net, "lo"); + KUNIT_ASSERT_NOT_NULL(test, lo); + if (!(lo->flags & IFF_UP)) + rv =3D dev_change_flags(lo, lo->flags | IFF_UP, NULL); + else + rv =3D 0; + rtnl_unlock(); + KUNIT_ASSERT_EQ(test, rv, 0); + return 0; +} + +static int siw_mpa_rx_make_pair(struct kunit *test, struct socket **listen, + struct socket **server, struct socket **client) +{ + struct sockaddr_in addr =3D { .sin_family =3D AF_INET, }; + struct sockaddr_in bound =3D { }; + struct socket *l =3D NULL, *s =3D NULL, *c =3D NULL; + int rv; + + siw_mpa_rx_bring_up_lo(test); + + rv =3D sock_create_kern(&init_net, AF_INET, SOCK_STREAM, IPPROTO_TCP, &l); + KUNIT_ASSERT_EQ(test, rv, 0); + + addr.sin_addr.s_addr =3D htonl(INADDR_LOOPBACK); + addr.sin_port =3D 0; + rv =3D kernel_bind(l, (struct sockaddr_unsized *)&addr, sizeof(addr)); + KUNIT_ASSERT_EQ(test, rv, 0); + + rv =3D l->ops->getname(l, (struct sockaddr *)&bound, 0); + KUNIT_ASSERT_GT(test, rv, 0); + + rv =3D kernel_listen(l, 1); + KUNIT_ASSERT_EQ(test, rv, 0); + + rv =3D sock_create_kern(&init_net, AF_INET, SOCK_STREAM, IPPROTO_TCP, &c); + KUNIT_ASSERT_EQ(test, rv, 0); + + rv =3D kernel_connect(c, (struct sockaddr_unsized *)&bound, + sizeof(bound), 0); + KUNIT_ASSERT_EQ(test, rv, 0); + + rv =3D kernel_accept(l, &s, 0); + KUNIT_ASSERT_EQ(test, rv, 0); + + *listen =3D l; + *server =3D s; + *client =3D c; + return 0; +} + +static void siw_mpa_write_underflow_rejected_live_socket(struct kunit *tes= t) +{ + struct siw_device *sdev; + struct siw_qp *qp; + struct siw_cep *cep; + struct siw_mem *mem; + struct socket *listen_sock =3D NULL, *server_sock =3D NULL, *client_sock = =3D NULL; + struct iwarp_rdma_write write =3D { }; + struct kvec iov; + struct msghdr msg =3D { }; + void *xa_ret, *target; + u8 payload[sizeof(write) + 1]; + u32 stag =3D 0x00000100; + int rv, i; + + sdev =3D kunit_kzalloc(test, sizeof(*sdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sdev); + xa_init_flags(&sdev->mem_xa, XA_FLAGS_ALLOC1); + + qp =3D kunit_kzalloc(test, sizeof(*qp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, qp); + qp->sdev =3D sdev; + qp->pd =3D kunit_kzalloc(test, sizeof(*qp->pd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, qp->pd); + qp->rx_stream.state =3D SIW_GET_HDR; + init_rwsem(&qp->state_lock); + qp->attrs.state =3D SIW_QP_STATE_RTS; + qp->cep =3D NULL; + + /* Register a valid REMOTE_WRITE memory object. On unpatched siw + * this is what lets the negative-length copy reach skb_copy_bits; + * without an MR the STag lookup in siw_proc_write() returns NULL + * and the WRITE is terminated before the underflow primitive fires. + * With this patch in place, the new siw_get_hdr() check rejects + * the FPDU BEFORE STag lookup, so the MR is unused. We keep it in + * the test so unpatched-kernel reruns also exercise the full path. + */ + target =3D kunit_kzalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, target); + mem =3D kunit_kzalloc(test, sizeof(*mem), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mem); + kref_init(&mem->ref); + mem->sdev =3D sdev; + mem->stag =3D stag; + mem->stag_valid =3D 1; + mem->va =3D (u64)(uintptr_t)target; + mem->len =3D 64; + mem->pd =3D qp->pd; + mem->perms =3D IB_ACCESS_REMOTE_WRITE; + xa_ret =3D xa_store(&sdev->mem_xa, stag >> 8, mem, GFP_KERNEL); + KUNIT_ASSERT_FALSE(test, xa_is_err(xa_ret)); + + /* siw_qp_llp_data_ready dereferences sk_user_data as siw_cep. */ + cep =3D kunit_kzalloc(test, sizeof(*cep), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cep); + cep->qp =3D qp; + spin_lock_init(&cep->lock); + kref_init(&cep->ref); + + rv =3D siw_mpa_rx_make_pair(test, &listen_sock, &server_sock, &client_soc= k); + KUNIT_ASSERT_EQ(test, rv, 0); + + write_lock_bh(&server_sock->sk->sk_callback_lock); + server_sock->sk->sk_user_data =3D cep; + server_sock->sk->sk_data_ready =3D siw_qp_llp_data_ready; + qp->attrs.sk =3D server_sock; + write_unlock_bh(&server_sock->sk->sk_callback_lock); + + write.ctrl.mpa_len =3D + cpu_to_be16(sizeof(write) - MPA_HDR_SIZE - 1); + write.ctrl.ddp_rdmap_ctrl =3D DDP_FLAG_TAGGED | DDP_FLAG_LAST | + cpu_to_be16(DDP_VERSION << 8) | + cpu_to_be16(RDMAP_VERSION << 6) | + cpu_to_be16(RDMAP_RDMA_WRITE); + write.sink_stag =3D cpu_to_be32(stag); + write.sink_to =3D cpu_to_be64((u64)(uintptr_t)target); + + memcpy(payload, &write, sizeof(write)); + payload[sizeof(write)] =3D 0x41; + + iov.iov_base =3D payload; + iov.iov_len =3D sizeof(payload); + rv =3D kernel_sendmsg(client_sock, &msg, &iov, 1, sizeof(payload)); + KUNIT_ASSERT_EQ(test, rv, (int)sizeof(payload)); + + /* Wait for TCP to deliver bytes and sk_data_ready to fire. */ + for (i =3D 0; i < 200; i++) { + if (qp->term_info.valid) + break; + msleep(20); + } + + KUNIT_EXPECT_EQ(test, (int)qp->term_info.valid, 1); + KUNIT_EXPECT_EQ(test, (int)qp->term_info.layer, + (int)TERM_ERROR_LAYER_LLP); + KUNIT_EXPECT_EQ(test, (int)qp->term_info.etype, + (int)LLP_ETYPE_MPA); + KUNIT_EXPECT_EQ(test, (int)qp->term_info.ecode, + (int)LLP_ECODE_FPDU_START); + KUNIT_EXPECT_EQ(test, (int)qp->rx_stream.rx_suspend, 1); + + /* Detach our handler before tearing down sockets so the TCP stack + * cannot call into freed kunit memory after the test. + */ + write_lock_bh(&server_sock->sk->sk_callback_lock); + server_sock->sk->sk_user_data =3D NULL; + server_sock->sk->sk_data_ready =3D sock_def_readable; + write_unlock_bh(&server_sock->sk->sk_callback_lock); + + sock_release(client_sock); + sock_release(server_sock); + sock_release(listen_sock); +} + +static struct kunit_case siw_mpa_rx_cases[] =3D { + KUNIT_CASE(siw_mpa_write_underflow_rejected), + KUNIT_CASE(siw_mpa_write_minimum_valid_accepted), + KUNIT_CASE(siw_mpa_write_underflow_rejected_live_socket), + { } +}; + +static struct kunit_suite siw_mpa_rx_suite =3D { + .name =3D "siw_mpa_rx", + .test_cases =3D siw_mpa_rx_cases, +}; + +kunit_test_suite(siw_mpa_rx_suite); --=20 2.53.0