From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-lf1-f46.google.com (mail-lf1-f46.google.com [209.85.167.46]) (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 9035D3AE18C for ; Tue, 4 Aug 2026 14:28:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.46 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785853749; cv=none; b=j6X8fMf0xWYqec5qb5acbxnkRh1GSz0YNTiu5PTDWDZ1nwnu5KARMxKkMAcdzouV6e9uErzatb/0eiQ8S9Jj/6JiNqFpX6HH2OL1iAlf9yT/lBOEsZlih2QTQggd4CX7UjftakoGwdCWa0xyuQR7N2S+6+LJj5b/zx/SzOqePTM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785853749; c=relaxed/simple; bh=Zzf4GBcaHRkdwdNLecVrXUEDo085F5X2K1Y6OucS1Rg=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=Oo5GYvCGhgip9vNE+mgmkFqsIBCeOOc1FJU96GzUCTuhAVxy0Hoky9fHzvuoCzc9HTteZA3+MJ4epYi8Qy5lNkLvKxWkxM2i9OubMap368wmpf6ZlGesIlPmFap6vZe+V6BY9wwTXmTVkactZsq0V1a7vXQghwn2oz53xWSkjfI= 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=casRjeyC; arc=none smtp.client-ip=209.85.167.46 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="casRjeyC" Received: by mail-lf1-f46.google.com with SMTP id 2adb3069b0e04-5b28c91fba5so1324421e87.1 for ; Tue, 04 Aug 2026 07:28:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785853733; x=1786458533; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=iRhJ2aYZNtm2kCJIVYQl1pK7YdCm7CzHtYjyN2QbDAU=; b=casRjeyClWFMlKltNYW2NaVYp0MWskTm87BXlt1dKvwmmw/4Huq0XXdmwNmyLyx8hY UMi/F6jW9SknOL8B6SgsXw7bzO6LLphRDLkypm4XnG/m+Oz0TA+3EFDrFZ81VIidoHlH oBsvAp42HbOIOlxtv1rAkT30up+L3vgF5KnFsjcZvkx1iQkpSMS2YTSRNSYcf0v8N5cU brm8XdyZoCKxwlEMvzQwE6ZGxVyWkuM55FElims4TK5qWZXHneiI2D/Buzw9xdGmSkf7 kYrQrlRKXzLWxS/OKY8Z5gFUSG14x5svrgGVbav6M2qf3h79q6G65KDtyVKf2L3HPir/ Gx4Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785853733; x=1786458533; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=iRhJ2aYZNtm2kCJIVYQl1pK7YdCm7CzHtYjyN2QbDAU=; b=ExcWmyN5koFsjI41tXzkg8SJU3zYeBEgqmDe2M8cc5tL7Ey3vLcSBCSkiERSxFMXjm Yuy+hXa2RMQ4zcQuyrSTMPScpSt86a6V6zDYMobKWWodjY3mN9OdYO2RKv4+L12wzGrE DsIluFOOpKnqPdWYdqLBaexFJ1xtowFAoG+GPMYh1uqdDbQ4F/ibUXKCI6BwScE+A60+ xHhlqK647glnk+ofa4MDtIctFgoJd73FB+4lmnclxhMBL7Yhx/qSSEh4OH4TWvQCGW7Y ahsxZ8jZVLHCj7O0EopNrhtwIpeUSs+prpyhFZDrxRizzvELw6sUgLrEmixdWuScQnGx s9Aw== X-Forwarded-Encrypted: i=1; AHgh+RqXxDhJsdoHBSHwlwSEQKvfD2ADw9BAzavfUJbEGW2qyua1OfwH6jBsJxlyctr6AxnjzrI7xNLcyvKHOMY=@vger.kernel.org X-Gm-Message-State: AOJu0Yw65pjKZaQ/qjyO9tXVFbkHAhWJHuOY/zB2sYJMExcQbQ9kJpf6 L/1lKpVGwRAeEE3OhyR7c27U8Era2zhY/ikO02bgrymp3O8mMuqsSJ8c X-Gm-Gg: AR+sD10nPoUDEMicd57RTOK76j0C6gaqn4qlZNAOMF1PqC8xsuKqrikAbz3CnVNP2UU zU7LeBsE/MySLvwaq8wIjPbk0PECDXfrhIcWCUnPPov9gErh6N1HHGBiFTx9l4Z3pzXjf6rDxz3 w01TGvOepkAW0reyPndz50g8f2zFAsKKlX8yTuypLO57I4MRICu31z3q+yKi71Zs17iOg0xtIeY XKNPoHcUjc+1wk2dhTeK7G7u7n5IIhfJ7vplAPq22n92SwgLJAUiAal9jyUYG7jz0qopsihJFrd YRmIqAog+PUjmrrLcmDE7s8dxVV+z3ELuCPAzWkRugf65kxweJv7qugEeyR2UwqJWGe74QsRPO/ 5dyb826wHeIlec6MPJcFmxxFQBmZevgVr4IyFLL7wlOd6vSvCUU73QFtm0UoiaAXr7tfz9HWFHU 1uNt3wOgaa8uJJyydvKfbpT4QOsvUHa1qswrZeLeK0D4sDEkv+PB1RrwWnFQ4xN77fZ/FQ2Mnda hPCjA== X-Received: by 2002:ac2:4e14:0:b0:5b0:1186:fdf with SMTP id 2adb3069b0e04-5b2f281c00emr918624e87.11.1785853733048; Tue, 04 Aug 2026 07:28:53 -0700 (PDT) Received: from NB-9797.corp.yadro.com ([89.207.88.244]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-5b2e2441e96sm2650558e87.42.2026.08.04.07.28.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 04 Aug 2026 07:28:52 -0700 (PDT) From: Ilya Khomyakov To: "Martin K . Petersen" Cc: "James E . J . Bottomley" , Sathya Prakash Veerichetty , Kashyap Desai , Sumit Saxena , Sreekanth Reddy , mpi3mr-linuxdrv.pdl@broadcom.com, linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org, Ilya Khomyakov Subject: [PATCH] scsi: mpi3mr: make SAS port PHY masks 64-bit safe Date: Tue, 4 Aug 2026 17:28:31 +0300 Message-ID: <20260804142831.4365-1-khomyakovilya@gmail.com> X-Mailer: git-send-email 2.43.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit This patch fixes 64-bit PHY-mask handling in the Broadcom MPI3 Storage Controller driver under drivers/scsi/mpi3mr/. struct mpi3mr_sas_port stores phy_mask as u64, but several paths construct the mask with the signed-int expression 1 << phy_id or 1 << i. The shift is evaluated as int before the result is converted to u64. The operation therefore has undefined behavior when the PHY identifier reaches the sign bit or width of int. The same code uses ffs() to find the lowest set bit, but ffs() accepts int and truncates bits 32 through 63. The issue was reproduced with UBSAN during SAS port creation: UBSAN: shift-out-of-bounds in mpi3mr_transport.c Workqueue: mpi3mr0_fwevt_wrkr mpi3mr_fwevt_worker mpi3mr_sas_port_add mpi3mr_update_links mpi3mr_report_tgtdev_to_sas_transport The reproduced topology contains a controller host node with 39 PHYs and an expander with 46 PHYs. Such a topology is sufficient to exercise PHY identifiers above 31 during normal discovery. Add a helper that validates the firmware PHY identifier and constructs the mask bit with BIT_ULL(). Add a separate helper that handles an empty mask and otherwise finds the lowest bit with __ffs64(). Use the helpers in the PHY add and remove paths, initial port construction, and reset-refresh port grouping. Also initialize lowest_phy when the first PHY is dynamically added to an empty port. The patch was tested in an out-of-tree mpi3mr 8.17.1.0.0 build. The driver successfully discovered a 39-PHY host node and a 46-PHY expander, created expander PHY objects through PHY 45, and completed device discovery without a shift-out-of-bounds or other UBSAN report. The boot test directly exercised initial high-PHY port construction. The same helpers are used in the add, remove, and reset-refresh paths to remove the identical 32-bit operations from those paths as well. Signed-off-by: Ilya Khomyakov --- drivers/scsi/mpi3mr/mpi3mr_transport.c | 58 ++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/drivers/scsi/mpi3mr/mpi3mr_transport.c b/drivers/scsi/mpi3mr/mpi3mr_transport.c index 240f67a..d6492dd 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_transport.c +++ b/drivers/scsi/mpi3mr/mpi3mr_transport.c @@ -11,6 +11,42 @@ #include "mpi3mr.h" +/** + * mpi3mr_sas_phy_bit - build a bit for a firmware PHY identifier + * @phy_id: Firmware PHY identifier to represent in a 64-bit port mask + * + * The port mask is a u64, so every shift must be performed in a 64-bit + * unsigned type. Reject identifiers that cannot be represented before the + * shift to avoid undefined behavior. + * + * Return: BIT_ULL(@phy_id) for a representable identifier, otherwise zero. + */ +static u64 mpi3mr_sas_phy_bit(u8 phy_id) +{ + if (WARN_ON_ONCE(phy_id >= sizeof(u64) * 8)) + return 0; + + return BIT_ULL(phy_id); +} + +/** + * mpi3mr_sas_port_lowest_phy - find the lowest PHY in a port mask + * @phy_mask: 64-bit bitmap of PHY identifiers assigned to the port + * + * Use a 64-bit find-first-set operation so PHY identifiers 32 through 63 + * are not truncated to int. Keep -1 as the empty-mask sentinel used by the + * surrounding port bookkeeping. + * + * Return: lowest set PHY identifier, or -1 when the mask is empty. + */ +static int mpi3mr_sas_port_lowest_phy(u64 phy_mask) +{ + if (!phy_mask) + return -1; + + return __ffs64(phy_mask); +} + /** * mpi3mr_post_transport_req - Issue transport requests and wait * @mrioc: Adapter instance reference @@ -610,10 +646,11 @@ static void mpi3mr_delete_sas_phy(struct mpi3mr_ioc *mrioc, mr_sas_port->num_phys--; if (host_node) { - mr_sas_port->phy_mask &= ~(1 << mr_sas_phy->phy_id); + mr_sas_port->phy_mask &= ~mpi3mr_sas_phy_bit(mr_sas_phy->phy_id); if (mr_sas_port->lowest_phy == mr_sas_phy->phy_id) - mr_sas_port->lowest_phy = ffs(mr_sas_port->phy_mask) - 1; + mr_sas_port->lowest_phy = + mpi3mr_sas_port_lowest_phy(mr_sas_port->phy_mask); } sas_port_delete_phy(mr_sas_port->port, mr_sas_phy->phy); mr_sas_phy->phy_belongs_to_port = 0; @@ -641,10 +678,12 @@ static void mpi3mr_add_sas_phy(struct mpi3mr_ioc *mrioc, list_add_tail(&mr_sas_phy->port_siblings, &mr_sas_port->phy_list); mr_sas_port->num_phys++; if (host_node) { - mr_sas_port->phy_mask |= (1 << mr_sas_phy->phy_id); + mr_sas_port->phy_mask |= mpi3mr_sas_phy_bit(mr_sas_phy->phy_id); - if (mr_sas_phy->phy_id < mr_sas_port->lowest_phy) - mr_sas_port->lowest_phy = ffs(mr_sas_port->phy_mask) - 1; + if (mr_sas_port->lowest_phy < 0 || + mr_sas_phy->phy_id < mr_sas_port->lowest_phy) + mr_sas_port->lowest_phy = + mpi3mr_sas_port_lowest_phy(mr_sas_port->phy_mask); } sas_port_add_phy(mr_sas_port->port, mr_sas_phy->phy); mr_sas_phy->phy_belongs_to_port = 1; @@ -1396,7 +1435,7 @@ static struct mpi3mr_sas_port *mpi3mr_sas_port_add(struct mpi3mr_ioc *mrioc, &mr_sas_port->phy_list); mr_sas_port->num_phys++; if (mr_sas_node->host_node) - mr_sas_port->phy_mask |= (1 << i); + mr_sas_port->phy_mask |= mpi3mr_sas_phy_bit(i); } if (!mr_sas_port->num_phys) { @@ -1406,7 +1445,8 @@ static struct mpi3mr_sas_port *mpi3mr_sas_port_add(struct mpi3mr_ioc *mrioc, } if (mr_sas_node->host_node) - mr_sas_port->lowest_phy = ffs(mr_sas_port->phy_mask) - 1; + mr_sas_port->lowest_phy = + mpi3mr_sas_port_lowest_phy(mr_sas_port->phy_mask); if (mr_sas_port->remote_identify.device_type == SAS_END_DEVICE) { tgtdev = mpi3mr_get_tgtdev_by_addr(mrioc, @@ -1738,7 +1778,7 @@ mpi3mr_refresh_sas_ports(struct mpi3mr_ioc *mrioc) found = 0; for (j = 0; j < host_port_count; j++) { if (h_port[j].handle == attached_handle) { - h_port[j].phy_mask |= (1 << i); + h_port[j].phy_mask |= mpi3mr_sas_phy_bit(i); found = 1; break; } @@ -1765,7 +1805,7 @@ mpi3mr_refresh_sas_ports(struct mpi3mr_ioc *mrioc) port_idx = host_port_count; h_port[port_idx].sas_address = le64_to_cpu(sasinf->sas_address); h_port[port_idx].handle = attached_handle; - h_port[port_idx].phy_mask = (1 << i); + h_port[port_idx].phy_mask = mpi3mr_sas_phy_bit(i); h_port[port_idx].iounit_port_id = sas_io_unit_pg0->phy_data[i].io_unit_port; h_port[port_idx].lowest_phy = sasinf->phy_num; h_port[port_idx].used = 0;