From nobody Mon Feb 9 16:54:12 2026 Received: from mail-108-mta11.mxroute.com (mail-108-mta11.mxroute.com [136.175.108.11]) (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 1A8532745E for ; Sat, 31 Jan 2026 11:21:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=136.175.108.11 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769858507; cv=none; b=fvJMsq8GGTx6JvRODlxoEHz3FyQC1l9zVvDdKqQB5KCpngXHlISyC5I6yNTUnKbbJ533u6oQFImYglq+ctHIwiR/4DUAhpA1g+6Zn9PpPH1NUXdGjAf1kQsnTT6l96pF3ugM2toHYD4WUoCaJM+6oWsMbYwI4isQKgTS8KIQ9ZQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769858507; c=relaxed/simple; bh=4DBxnNLknnVZV4GeWc1dMyv5NkoYIUXdQ3oVwbXQPN0=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=cmDYUZDyyEc2oFhoiGnk2MKKp0u8fVl9Uq9Ou408BgolFgIZBO7tIDFSYNmYFFtpEZlpa8oH20U0aDyU7iywcE0KF9qc6WVz4BhYrIGm8VUmNa7BLj2b+ulwVmGbijbUCLgpyCfaeOhRDk9ypf6xLAHus87GB11X7W6/eA6Wz1w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=ry.rs; spf=pass smtp.mailfrom=ry.rs; dkim=pass (2048-bit key) header.d=ry.rs header.i=@ry.rs header.b=QaiGIsu/; arc=none smtp.client-ip=136.175.108.11 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=ry.rs Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ry.rs Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ry.rs header.i=@ry.rs header.b="QaiGIsu/" Received: from filter006.mxroute.com ([136.175.111.3] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta11.mxroute.com (ZoneMTA) with ESMTPSA id 19c13c4d05b0009140.008 for (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Sat, 31 Jan 2026 11:16:31 +0000 X-Zone-Loop: 0bffabeb24eeec225833a8c17138412c7a8d0b061297 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=ry.rs; s=x; h=Content-Transfer-Encoding:MIME-Version:Date:Subject:Cc:To:From:Sender: Reply-To:Content-Type:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References; bh=SWvtaEiT/41NAalPPWhdhfM93rTmPvj7juU4A2w+NMk=; b=QaiGIsu/AcVLE7nl7OY51sFaz9 QMLORznRNBDfaeMa2oSyQ+2C2IIT6Lo6CSllhbJOcrGPWkXUgrwNHMmHU3RMKh86WJCm9AFVzVupT SMseQJW+vvvioYCDU8PKJWVh/DxTaaYHgiaZG4lxCiGbTEieH9YEgM81cyfBhCVHD2m3nTmB0yWy8 RS3RSQcrbPis1wP2F00XpgY3e7wKBxCEXFKa18b8PiCXlvte7xkWKmwFfK7sHzGnJMlRpdQohql6O 61L5YH61yA0rqL8jZXC3JO094DnI3Ys36aJ5TAzLdymBS6MCQN5+3XDbgNwSHCJ3/72NA39V+h+H+ vRZ8J3pA==; From: Zijing Zhang To: dakr@kernel.org, acourbot@nvidia.com Cc: aliceryhl@google.com, airlied@gmail.com, simona@ffwll.ch, nouveau@lists.freedesktop.org, dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, Zijing Zhang Subject: [PATCH] gpu: nova-core: vbios: harden falcon pointer parsing Date: Sat, 31 Jan 2026 11:16:19 +0000 Message-ID: <20260131111619.1360414-1-zijing.zhang@ry.rs> 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 X-Authenticated-Id: zijing.zhang@ry.rs Content-Type: text/plain; charset="utf-8" Use checked arithmetic and slice get() to avoid overflow/underflow and out-= of-bounds access when parsing Falcon data pointers and FWSEC payloads. This also clarifies the error message for pointers that still fall within t= he PciAt image. Signed-off-by: Zijing Zhang --- drivers/gpu/nova-core/vbios.rs | 95 ++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/nova-core/vbios.rs b/drivers/gpu/nova-core/vbios.rs index 3e3fa5b72524..45bbc8894281 100644 --- a/drivers/gpu/nova-core/vbios.rs +++ b/drivers/gpu/nova-core/vbios.rs @@ -785,22 +785,31 @@ fn get_bit_token(&self, token_id: u8) -> Result { fn falcon_data_ptr(&self) -> Result { let token =3D self.get_bit_token(BIT_TOKEN_ID_FALCON_DATA)?; =20 - // Make sure we don't go out of bounds - if usize::from(token.data_offset) + 4 > self.base.data.len() { - return Err(EINVAL); - } - - // read the 4 bytes at the offset specified in the token let offset =3D usize::from(token.data_offset); - let bytes: [u8; 4] =3D self.base.data[offset..offset + 4].try_into= ().map_err(|_| { - dev_err!(self.base.dev, "Failed to convert data slice to array= \n"); - EINVAL - })?; + let end =3D offset.checked_add(4).ok_or(EINVAL)?; + + // Read the 4 bytes at the offset specified in the token. + let bytes: [u8; 4] =3D self + .base + .data + .get(offset..end) + .ok_or(EINVAL)? + .try_into() + .map_err(|_| { + dev_err!(self.base.dev, "Failed to convert data slice to a= rray\n"); + EINVAL + })?; =20 let data_ptr =3D u32::from_le_bytes(bytes); =20 - if (usize::from_safe_cast(data_ptr)) < self.base.data.len() { - dev_err!(self.base.dev, "Falcon data pointer out of bounds\n"); + // The BIT Falcon data pointer is expected to point outside the Pc= iAt image (into a later + // image in the ROM chain). Reject pointers that still fall within= this PciAt image, since + // downstream code will subtract `self.base.data.len()` from this = offset. + if usize::from_safe_cast(data_ptr) < self.base.data.len() { + dev_err!( + self.base.dev, + "Falcon data pointer points inside PciAt image\n" + ); return Err(EINVAL); } =20 @@ -921,14 +930,17 @@ fn setup_falcon_data( pci_at_image: &PciAtBiosImage, first_fwsec: &FwSecBiosBuilder, ) -> Result { - let mut offset =3D usize::from_safe_cast(pci_at_image.falcon_data_= ptr()?); + let data_ptr =3D pci_at_image.falcon_data_ptr()?; + let mut offset =3D usize::from_safe_cast(data_ptr); let mut pmu_in_first_fwsec =3D false; =20 // The falcon data pointer assumes that the PciAt and FWSEC images // are contiguous in memory. However, testing shows the EFI image = sits in // between them. So calculate the offset from the end of the PciAt= image // rather than the start of it. Compensate. - offset -=3D pci_at_image.base.data.len(); + offset =3D offset + .checked_sub(pci_at_image.base.data.len()) + .ok_or(EINVAL)?; =20 // The offset is now from the start of the first Fwsec image, howe= ver // the offset points to a location in the second Fwsec image. Since @@ -938,7 +950,9 @@ fn setup_falcon_data( if offset < first_fwsec.base.data.len() { pmu_in_first_fwsec =3D true; } else { - offset -=3D first_fwsec.base.data.len(); + offset =3D offset + .checked_sub(first_fwsec.base.data.len()) + .ok_or(EINVAL)?; } =20 self.falcon_data_offset =3D Some(offset); @@ -946,12 +960,16 @@ fn setup_falcon_data( if pmu_in_first_fwsec { self.pmu_lookup_table =3D Some(PmuLookupTable::new( &self.base.dev, - &first_fwsec.base.data[offset..], + first_fwsec.base.data.get(offset..).ok_or(EINVAL)?, )?); } else { + if offset >=3D self.base.data.len() { + dev_err!(self.base.dev, "Falcon data pointer out of bounds= \n"); + return Err(EINVAL); + } self.pmu_lookup_table =3D Some(PmuLookupTable::new( &self.base.dev, - &self.base.data[offset..], + self.base.data.get(offset..).ok_or(EINVAL)?, )?); } =20 @@ -963,12 +981,20 @@ fn setup_falcon_data( { Ok(entry) =3D> { let mut ucode_offset =3D usize::from_safe_cast(entry.data); - ucode_offset -=3D pci_at_image.base.data.len(); + ucode_offset =3D ucode_offset + .checked_sub(pci_at_image.base.data.len()) + .ok_or(EINVAL)?; if ucode_offset < first_fwsec.base.data.len() { dev_err!(self.base.dev, "Falcon Ucode offset not in se= cond Fwsec.\n"); return Err(EINVAL); } - ucode_offset -=3D first_fwsec.base.data.len(); + ucode_offset =3D ucode_offset + .checked_sub(first_fwsec.base.data.len()) + .ok_or(EINVAL)?; + if ucode_offset >=3D self.base.data.len() { + dev_err!(self.base.dev, "Falcon Ucode offset out of bo= unds.\n"); + return Err(EINVAL); + } self.falcon_ucode_offset =3D Some(ucode_offset); } Err(e) =3D> { @@ -1007,7 +1033,11 @@ pub(crate) fn header(&self) -> Result { let falcon_ucode_offset =3D self.falcon_ucode_offset; =20 // Read the first 4 bytes to get the version. - let hdr_bytes: [u8; 4] =3D self.base.data[falcon_ucode_offset..fal= con_ucode_offset + 4] + let hdr_bytes: [u8; 4] =3D self + .base + .data + .get(falcon_ucode_offset..falcon_ucode_offset.checked_add(4).o= k_or(EINVAL)?) + .ok_or(EINVAL)? .try_into() .map_err(|_| EINVAL)?; let hdr =3D u32::from_le_bytes(hdr_bytes); @@ -1039,13 +1069,20 @@ pub(crate) fn ucode(&self, desc: &FalconUCodeDesc) = -> Result<&[u8]> { let falcon_ucode_offset =3D self.falcon_ucode_offset; =20 // The ucode data follows the descriptor. - let ucode_data_offset =3D falcon_ucode_offset + desc.size(); - let size =3D usize::from_safe_cast(desc.imem_load_size() + desc.dm= em_load_size()); + let ucode_data_offset =3D falcon_ucode_offset + .checked_add(desc.size()) + .ok_or(ERANGE)?; + let size_u32 =3D desc + .imem_load_size() + .checked_add(desc.dmem_load_size()) + .ok_or(ERANGE)?; + let size =3D usize::from_safe_cast(size_u32); + let ucode_data_end =3D ucode_data_offset.checked_add(size).ok_or(E= RANGE)?; =20 // Get the data slice, checking bounds in a single operation. self.base .data - .get(ucode_data_offset..ucode_data_offset + size) + .get(ucode_data_offset..ucode_data_end) .ok_or(ERANGE) .inspect_err(|_| { dev_err!( @@ -1062,12 +1099,18 @@ pub(crate) fn sigs(&self, desc: &FalconUCodeDesc) -= > Result<&[Bcrt30Rsa3kSignatu FalconUCodeDesc::V3(_v3) =3D> core::mem::size_of::(), }; // The signatures data follows the descriptor. - let sigs_data_offset =3D self.falcon_ucode_offset + hdr_size; + let sigs_data_offset =3D self + .falcon_ucode_offset + .checked_add(hdr_size) + .ok_or(ERANGE)?; let sigs_count =3D usize::from(desc.signature_count()); - let sigs_size =3D sigs_count * core::mem::size_of::(); + let sigs_size =3D sigs_count + .checked_mul(core::mem::size_of::()) + .ok_or(ERANGE)?; =20 // Make sure the data is within bounds. - if sigs_data_offset + sigs_size > self.base.data.len() { + let end =3D sigs_data_offset.checked_add(sigs_size).ok_or(ERANGE)?; + if end > self.base.data.len() { dev_err!( self.base.dev, "fwsec signatures data not contained within BIOS bounds\n" --=20 2.52.0