lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <9de086d3-e441-439f-9ff5-aa66a99643ba@nvidia.com>
Date: Thu, 5 Jun 2025 12:09:46 -0400
From: Joel Fernandes <joelagnelf@...dia.com>
To: Lyude Paul <lyude@...hat.com>, Alexandre Courbot <acourbot@...dia.com>,
 Miguel Ojeda <ojeda@...nel.org>, Alex Gaynor <alex.gaynor@...il.com>,
 Boqun Feng <boqun.feng@...il.com>, Gary Guo <gary@...yguo.net>,
 Björn Roy Baron <bjorn3_gh@...tonmail.com>,
 Benno Lossin <benno.lossin@...ton.me>,
 Andreas Hindborg <a.hindborg@...nel.org>, Alice Ryhl <aliceryhl@...gle.com>,
 Trevor Gross <tmgross@...ch.edu>, Danilo Krummrich <dakr@...nel.org>,
 David Airlie <airlied@...il.com>, Simona Vetter <simona@...ll.ch>,
 Maarten Lankhorst <maarten.lankhorst@...ux.intel.com>,
 Maxime Ripard <mripard@...nel.org>, Thomas Zimmermann <tzimmermann@...e.de>
Cc: John Hubbard <jhubbard@...dia.com>, Ben Skeggs <bskeggs@...dia.com>,
 Timur Tabi <ttabi@...dia.com>, Alistair Popple <apopple@...dia.com>,
 linux-kernel@...r.kernel.org, rust-for-linux@...r.kernel.org,
 nouveau@...ts.freedesktop.org, dri-devel@...ts.freedesktop.org,
 Shirish Baskaran <sbaskaran@...dia.com>
Subject: Re: [PATCH v4 16/20] nova-core: Add support for VBIOS ucode
 extraction for boot

Hi Lyude,

>> +bios_image! {
>> +    PciAt PciAtBiosImage,   // PCI-AT compatible BIOS image
>> +    Efi EfiBiosImage,       // EFI (Extensible Firmware Interface)
>> +    Nbsi NbsiBiosImage,     // NBSI (Nvidia Bios System Interface)
>> +    FwSecPartial FwSecBiosPartial, // FWSEC (Firmware Security)
>> +}
> 
> Maybe add a colon to separate the two fields in this macro so it looks more
> like a struct declaration?

Sure, will do.

> 
>> +
>> +struct PciAtBiosImage {
>> +    base: BiosImageBase,
>> +    bit_header: BitHeader,
>> +    bit_offset: usize,
>> +}
>> +
>> +struct EfiBiosImage {
>> +    base: BiosImageBase,
>> +    // EFI-specific fields can be added here in the future.
>> +}
>> +
>> +struct NbsiBiosImage {
>> +    base: BiosImageBase,
>> +    // NBSI-specific fields can be added here in the future.
>> +}
>> +
>> +struct FwSecBiosPartial {
>> +    base: BiosImageBase,
>> +    // FWSEC-specific fields
>> +    // These are temporary fields that are used during the construction of
>> +    // the FwSecBiosPartial. Once FwSecBiosPartial is constructed, the
>> +    // falcon_ucode_offset will be copied into a new FwSecBiosImage.
>> +
>> +    // The offset of the Falcon data from the start of Fwsec image
>> +    falcon_data_offset: Option<usize>,
>> +    // The PmuLookupTable starts at the offset of the falcon data pointer
>> +    pmu_lookup_table: Option<PmuLookupTable>,
>> +    // The offset of the Falcon ucode
>> +    falcon_ucode_offset: Option<usize>,
> 
> Shouldn't these last 3 comments be docstrings?

Sure, will change.

> 
>> +}
>> +
>> +struct FwSecBiosImage {
>> +    base: BiosImageBase,
>> +    // The offset of the Falcon ucode
> 
> Same here

Sure, will change.

> 
>> +    falcon_ucode_offset: usize,
>> +}
>> +
>> +// Convert from BiosImageBase to BiosImage
>> +impl TryFrom<BiosImageBase> for BiosImage {
>> +    type Error = Error;
>> +
>> +    fn try_from(base: BiosImageBase) -> Result<Self> {
>> +        match base.pcir.code_type {
>> +            0x00 => Ok(BiosImage::PciAt(base.try_into()?)),
>> +            0x03 => Ok(BiosImage::Efi(EfiBiosImage { base })),
>> +            0x70 => Ok(BiosImage::Nbsi(NbsiBiosImage { base })),
>> +            0xE0 => Ok(BiosImage::FwSecPartial(FwSecBiosPartial {
>> +                base,
>> +                falcon_data_offset: None,
>> +                pmu_lookup_table: None,
>> +                falcon_ucode_offset: None,
>> +            })),
>> +            _ => Err(EINVAL),
>> +        }
>> +    }
>> +}
>> +
>> +/// BIOS Image structure containing various headers and references
>> +/// fields base to all BIOS images. Each BiosImage type has a
>> +/// BiosImageBase type along with other image-specific fields.
>> +/// Note that Rust favors composition of types over inheritance.
>> +#[derive(Debug)]
>> +#[expect(dead_code)]
>> +struct BiosImageBase {
>> +    /// PCI ROM Expansion Header
>> +    rom_header: PciRomHeader,
>> +    /// PCI Data Structure
>> +    pcir: PcirStruct,
>> +    /// NVIDIA PCI Data Extension (optional)
>> +    npde: Option<NpdeStruct>,
>> +    /// Image data (includes ROM header and PCIR)
>> +    data: KVec<u8>,
>> +}
>> +
>> +impl BiosImageBase {
>> +    fn into_image(self) -> Result<BiosImage> {
>> +        BiosImage::try_from(self)
>> +    }
>> +
>> +    /// Creates a new BiosImageBase from raw byte data.
>> +    fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
>> +        // Ensure we have enough data for the ROM header
>> +        if data.len() < 26 {
>> +            dev_err!(pdev.as_ref(), "Not enough data for ROM header\n");
>> +            return Err(EINVAL);
>> +        }
>> +
>> +        // Parse the ROM header
>> +        let rom_header = PciRomHeader::new(pdev, &data[0..26])
>> +            .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PciRomHeader: {:?}\n", e))?;
>> +
>> +        // Get the PCI Data Structure using the pointer from the ROM header
>> +        let pcir_offset = rom_header.pci_data_struct_offset as usize;
>> +        let pcir_data = data
>> +            .get(pcir_offset..pcir_offset + core::mem::size_of::<PcirStruct>())
>> +            .ok_or(EINVAL)
>> +            .inspect_err(|_| {
>> +                dev_err!(
>> +                    pdev.as_ref(),
>> +                    "PCIR offset {:#x} out of bounds (data length: {})\n",
>> +                    pcir_offset,
>> +                    data.len()
>> +                );
>> +                dev_err!(
>> +                    pdev.as_ref(),
>> +                    "Consider reading more data for construction of BiosImage\n"
>> +                );
>> +            })?;
>> +
>> +        let pcir = PcirStruct::new(pdev, pcir_data)
>> +            .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PcirStruct: {:?}\n", e))?;
>> +
>> +        // Look for NPDE structure if this is not an NBSI image (type != 0x70)
>> +        let npde = NpdeStruct::find_in_data(pdev, data, &rom_header, &pcir);
>> +
>> +        // Create a copy of the data
>> +        let mut data_copy = KVec::new();
>> +        data_copy.extend_with(data.len(), 0, GFP_KERNEL)?;
>> +        data_copy.copy_from_slice(data);
>> +
>> +        Ok(BiosImageBase {
>> +            rom_header,
>> +            pcir,
>> +            npde,
>> +            data: data_copy,
>> +        })
>> +    }
>> +}
>> +
>> +/// The PciAt BIOS image is typically the first BIOS image type found in the
>> +/// BIOS image chain. It contains the BIT header and the BIT tokens.
>> +impl PciAtBiosImage {
>> +    /// Find a byte pattern in a slice
>> +    fn find_byte_pattern(haystack: &[u8], needle: &[u8]) -> Result<usize> {
>> +        haystack
>> +            .windows(needle.len())
>> +            .position(|window| window == needle)
>> +            .ok_or(EINVAL)
>> +    }
>> +
>> +    /// Find the BIT header in the PciAtBiosImage
>> +    fn find_bit_header(data: &[u8]) -> Result<(BitHeader, usize)> {
>> +        let bit_pattern = [0xff, 0xb8, b'B', b'I', b'T', 0x00];
>> +        let bit_offset = Self::find_byte_pattern(data, &bit_pattern)?;
>> +        let bit_header = BitHeader::new(&data[bit_offset..])?;
>> +
>> +        Ok((bit_header, bit_offset))
>> +    }
>> +
>> +    /// Get a BIT token entry from the BIT table in the PciAtBiosImage
>> +    fn get_bit_token(&self, token_id: u8) -> Result<BitToken> {
>> +        BitToken::from_id(self, token_id)
>> +    }
>> +
>> +    /// Find the Falcon data pointer structure in the PciAtBiosImage
>> +    /// This is just a 4 byte structure that contains a pointer to the
>> +    /// Falcon data in the FWSEC image.
>> +    fn falcon_data_ptr(&self, pdev: &pci::Device) -> Result<u32> {
>> +        let token = self.get_bit_token(BIT_TOKEN_ID_FALCON_DATA)?;
>> +
>> +        // Make sure we don't go out of bounds
>> +        if token.data_offset as usize + 4 > self.base.data.len() {
>> +            return Err(EINVAL);
>> +        }
>> +
>> +        // read the 4 bytes at the offset specified in the token
>> +        let offset = token.data_offset as usize;
>> +        let bytes: [u8; 4] = self.base.data[offset..offset + 4].try_into().map_err(|_| {
>> +            dev_err!(pdev.as_ref(), "Failed to convert data slice to array");
>> +            EINVAL
>> +        })?;
>> +
>> +        let data_ptr = u32::from_le_bytes(bytes);
>> +
>> +        if (data_ptr as usize) < self.base.data.len() {
>> +            dev_err!(pdev.as_ref(), "Falcon data pointer out of bounds\n");
>> +            return Err(EINVAL);
>> +        }
>> +
>> +        Ok(data_ptr)
> 
> Not 100% sure about this but maybe this should be data_offset and not
> data_ptr? It took me a bit to understand what was going on here since normally
> you can't tell if a pointer is valid just by comparing it to the raw length of
> a piece of data
> 

I understand it is a bit confusing, but is consistent with OpenRM code base
naming so I'll like to leave it alone for consistency.

>> +    }
>> +}
>> +
>> +impl TryFrom<BiosImageBase> for PciAtBiosImage {
>> +    type Error = Error;
>> +
>> +    fn try_from(base: BiosImageBase) -> Result<Self> {
>> +        let data_slice = &base.data;
>> +        let (bit_header, bit_offset) = PciAtBiosImage::find_bit_header(data_slice)?;
>> +
>> +        Ok(PciAtBiosImage {
>> +            base,
>> +            bit_header,
>> +            bit_offset,
>> +        })
>> +    }
>> +}
>> +
>> +/// The PmuLookupTableEntry structure is a single entry in the PmuLookupTable.
>> +/// See the PmuLookupTable description for more information.
>> +#[expect(dead_code)]
>> +struct PmuLookupTableEntry {
>> +    application_id: u8,
>> +    target_id: u8,
>> +    data: u32,
>> +}
>> +
>> +impl PmuLookupTableEntry {
>> +    fn new(data: &[u8]) -> Result<Self> {
>> +        if data.len() < 5 {
>> +            return Err(EINVAL);
>> +        }
>> +
>> +        Ok(PmuLookupTableEntry {
>> +            application_id: data[0],
>> +            target_id: data[1],
>> +            data: u32::from_le_bytes(data[2..6].try_into().map_err(|_| EINVAL)?),
>> +        })
>> +    }
>> +}
>> +
>> +/// The PmuLookupTableEntry structure is used to find the PmuLookupTableEntry
>> +/// for a given application ID. The table of entries is pointed to by the falcon
>> +/// data pointer in the BIT table, and is used to locate the Falcon Ucode.
>> +#[expect(dead_code)]
>> +struct PmuLookupTable {
>> +    version: u8,
>> +    header_len: u8,
>> +    entry_len: u8,
>> +    entry_count: u8,
>> +    table_data: KVec<u8>,
>> +}
>> +
>> +impl PmuLookupTable {
>> +    fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
>> +        if data.len() < 4 {
>> +            return Err(EINVAL);
>> +        }
>> +
>> +        let header_len = data[1] as usize;
>> +        let entry_len = data[2] as usize;
>> +        let entry_count = data[3] as usize;
>> +
>> +        let required_bytes = header_len + (entry_count * entry_len);
>> +
>> +        if data.len() < required_bytes {
>> +            dev_err!(
>> +                pdev.as_ref(),
>> +                "PmuLookupTable data length less than required\n"
>> +            );
>> +            return Err(EINVAL);
>> +        }
>> +
>> +        // Create a copy of only the table data
>> +        let table_data = {
>> +            let mut ret = KVec::new();
>> +            ret.extend_from_slice(&data[header_len..required_bytes], GFP_KERNEL)?;
>> +            ret
>> +        };
>> +
>> +        // Debug logging of entries (dumps the table data to dmesg)
>> +        if cfg!(debug_assertions) {
>> +            for i in (header_len..required_bytes).step_by(entry_len) {
>> +                dev_dbg!(
>> +                    pdev.as_ref(),
>> +                    "PMU entry: {:02x?}\n",
>> +                    &data[i..][..entry_len]
>> +                );
>> +            }
>> +        }
> 
> Not sure this makes sense - debug_assertions is supposed to be about
> assertions, we probably shouldn't try to use it for other things (especially
> since we've already got dev_dbg! here)

This was suggested by Danilo. I don't really feel strongly either way, IMO I am
also Ok with running it in production.

> 
>> +
>> +        Ok(PmuLookupTable {
>> +            version: data[0],
>> +            header_len: header_len as u8,
>> +            entry_len: entry_len as u8,
>> +            entry_count: entry_count as u8,
>> +            table_data,
>> +        })
>> +    }
>> +
>> +    fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> {
>> +        if idx >= self.entry_count {
>> +            return Err(EINVAL);
>> +        }
>> +
>> +        let index = (idx as usize) * self.entry_len as usize;
>> +        PmuLookupTableEntry::new(&self.table_data[index..])
>> +    }
>> +
>> +    // find entry by type value
>> +    fn find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry> {
>> +        for i in 0..self.entry_count {
>> +            let entry = self.lookup_index(i)?;
>> +            if entry.application_id == entry_type {
>> +                return Ok(entry);
>> +            }
>> +        }
>> +
>> +        Err(EINVAL)
>> +    }
>> +}
>> +
>> +/// The FwSecBiosImage structure contains the PMU table and the Falcon Ucode.
>> +/// The PMU table contains voltage/frequency tables as well as a pointer to the
>> +/// Falcon Ucode.
>> +impl FwSecBiosPartial {
>> +    fn setup_falcon_data(
>> +        &mut self,
>> +        pdev: &pci::Device,
>> +        pci_at_image: &PciAtBiosImage,
>> +        first_fwsec: &FwSecBiosPartial,
>> +    ) -> Result {
>> +        let mut offset = pci_at_image.falcon_data_ptr(pdev)? as usize;
>> +        let mut pmu_in_first_fwsec = false;
>> +
>> +        // 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 -= pci_at_image.base.data.len();
>> +
>> +        // The offset is now from the start of the first Fwsec image, however
>> +        // the offset points to a location in the second Fwsec image. Since
>> +        // the fwsec images are contiguous, subtract the length of the first Fwsec
>> +        // image from the offset to get the offset to the start of the second
>> +        // Fwsec image.
>> +        if offset < first_fwsec.base.data.len() {
>> +            pmu_in_first_fwsec = true;
>> +        } else {
>> +            offset -= first_fwsec.base.data.len();
>> +        }
>> +
>> +        self.falcon_data_offset = Some(offset);
>> +
>> +        if pmu_in_first_fwsec {
>> +            self.pmu_lookup_table =
>> +                Some(PmuLookupTable::new(pdev, &first_fwsec.base.data[offset..])?);
>> +        } else {
>> +            self.pmu_lookup_table = Some(PmuLookupTable::new(pdev, &self.base.data[offset..])?);
>> +        }
>> +
>> +        match self
>> +            .pmu_lookup_table
>> +            .as_ref()
>> +            .ok_or(EINVAL)?
>> +            .find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD)
>> +        {
>> +            Ok(entry) => {
>> +                let mut ucode_offset = entry.data as usize;
>> +                ucode_offset -= pci_at_image.base.data.len();
>> +                if ucode_offset < first_fwsec.base.data.len() {
>> +                    dev_err!(pdev.as_ref(), "Falcon Ucode offset not in second Fwsec.\n");
>> +                    return Err(EINVAL);
>> +                }
>> +                ucode_offset -= first_fwsec.base.data.len();
>> +                self.falcon_ucode_offset = Some(ucode_offset);
>> +            }
>> +            Err(e) => {
>> +                dev_err!(
>> +                    pdev.as_ref(),
>> +                    "PmuLookupTableEntry not found, error: {:?}\n",
>> +                    e
>> +                );
>> +                return Err(EINVAL);
>> +            }
>> +        }
>> +        Ok(())
>> +    }
>> +}
>> +
>> +impl FwSecBiosImage {
>> +    fn new(pdev: &pci::Device, data: FwSecBiosPartial) -> Result<Self> {
>> +        let ret = FwSecBiosImage {
>> +            base: data.base,
>> +            falcon_ucode_offset: data.falcon_ucode_offset.ok_or(EINVAL)?,
>> +        };
>> +
>> +        if cfg!(debug_assertions) {
>> +            // Print the desc header for debugging
>> +            let desc = ret.fwsec_header(pdev.as_ref())?;
>> +            dev_dbg!(pdev.as_ref(), "PmuLookupTableEntry desc: {:#?}\n", desc);
>> +        }
> 
> Again - definitely don't think we should be using debug_assertions for this

Same comment reply above.

>> +
>> +        Ok(ret)
>> +    }
>> +
>> +    /// Get the FwSec header (FalconUCodeDescV3)
>> +    fn fwsec_header(&self, dev: &device::Device) -> Result<&FalconUCodeDescV3> {
>> +        // Get the falcon ucode offset that was found in setup_falcon_data
>> +        let falcon_ucode_offset = self.falcon_ucode_offset;
>> +
>> +        // Make sure the offset is within the data bounds
>> +        if falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>() > self.base.data.len() {
>> +            dev_err!(dev, "fwsec-frts header not contained within BIOS bounds\n");
>> +            return Err(ERANGE);
>> +        }
>> +
>> +        // Read the first 4 bytes to get the version
>> +        let hdr_bytes: [u8; 4] = self.base.data[falcon_ucode_offset..falcon_ucode_offset + 4]
>> +            .try_into()
>> +            .map_err(|_| EINVAL)?;
>> +        let hdr = u32::from_le_bytes(hdr_bytes);
>> +        let ver = (hdr & 0xff00) >> 8;
>> +
>> +        if ver != 3 {
>> +            dev_err!(dev, "invalid fwsec firmware version: {:?}\n", ver);
>> +            return Err(EINVAL);
>> +        }
>> +
>> +        // Return a reference to the FalconUCodeDescV3 structure SAFETY: we have checked that
>> +        // `falcon_ucode_offset + size_of::<FalconUCodeDescV3` is within the bounds of `data.`
> 
> The SAFETY comment here should start on its own line in the comment

Fixed.

> 
>> +        Ok(unsafe {
>> +            &*(self.base.data.as_ptr().add(falcon_ucode_offset) as *const FalconUCodeDescV3)
> 
> FWIW: I would use cast here, not as.

Ok, I'll kindly defer this to Alex since he wrote this line.

> Also though, I think you need to justify> in the safety comment here why it's
safe to be able to hold an immutable
> reference (e.g. why can we expect this data not to be mutated for the lifetime
> of the reference?)

This data vector is ROM, also 'data' in BiosImageBase is immutable after
construction. I'll update the comment.

> 
>> +        })
>> +    }
> 
> ^ missing a newline here

Fixed.

> 
>> +    /// Get the ucode data as a byte slice
>> +    fn fwsec_ucode(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]> {
>> +        let falcon_ucode_offset = self.falcon_ucode_offset;
> 
> I think we can drop this variable if we're only calling falcon_ucode_offset
> once
> 

Good point, fixed.

>> +
>> +        // The ucode data follows the descriptor
>> +        let ucode_data_offset = falcon_ucode_offset + desc.size();
>> +        let size = (desc.imem_load_size + desc.dmem_load_size) as usize;
>> +
>> +        // Get the data slice, checking bounds in a single operation
>> +        self.base
>> +            .data
>> +            .get(ucode_data_offset..ucode_data_offset + size)
>> +            .ok_or(ERANGE)
>> +            .inspect_err(|_| dev_err!(dev, "fwsec ucode data not contained within BIOS bounds\n"))
>> +    }
>> +
>> +    /// Get the signatures as a byte slice
>> +    fn fwsec_sigs(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]> {
>> +        const SIG_SIZE: usize = 96 * 4;
>> +
>> +        let falcon_ucode_offset = self.falcon_ucode_offset;
>> +
>> +        // The signatures data follows the descriptor
>> +        let sigs_data_offset = falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>();
>> +        let size = desc.signature_count as usize * SIG_SIZE;
>> +
>> +        // Make sure the data is within bounds
>> +        if sigs_data_offset + size > self.base.data.len() {
>> +            dev_err!(
>> +                dev,
>> +                "fwsec signatures data not contained within BIOS bounds\n"
>> +            );
>> +            return Err(ERANGE);
>> +        }
>> +
>> +        Ok(&self.base.data[sigs_data_offset..sigs_data_offset + size])
>> +    }
>> +}
>>
> 
> Would be nice to get other people's take on this but I feel like that we
> probably shouldn't make these methods conditional at this point,
> FwSecBiosImage as a type name with FwSecBiosPartial implies that we should
> have already figured out if it's a valid bios image and extracted the relevant
> data in ::new() right?
> 

Do you mean these things be computed at object construction time? I am not sure
if I agree with that, because maybe computing some of these will be optional in
the future. And it will also require additional fields/footprint in the struct
to store them. Probably more LOC too.

thanks,

 - Joel



Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ