Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ EC Firmware
PD Controllers
Right (01): 0.0.0E (MainFw)
Left (23): 0.0.0E (MainFw)
AGESA: AGESA!V9 StrixKrackanPI-FP8 1.1.0.0c
[...]
```

Expand Down
4 changes: 4 additions & 0 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,10 @@ fn print_versions(ec: &CrosEc) {
println!(" Unknown");
}
}

if let Some(agesa) = smbios::get_agesa_version() {
println!("AGESA: {}", agesa);
}
#[cfg(feature = "rusb")]
let _ignore_err = check_camera_version();

Expand Down
53 changes: 52 additions & 1 deletion framework_lib/src/smbios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::prelude::v1::*;

use crate::util::Config;
pub use crate::util::{Platform, PlatformFamily};
use dmidecode::{EntryPoint, Structure};
use dmidecode::{EntryPoint, InfoType, RawStructure, Structure};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
#[cfg(feature = "uefi")]
Expand Down Expand Up @@ -174,6 +174,57 @@ pub fn get_family() -> Option<PlatformFamily> {
get_platform().and_then(Platform::which_family)
}

/// Minimum size of an Additional Information entry (SMBIOS Type 40)
const DMI_A_INFO_ENT_MIN_SIZE: usize = 6;

/// Extract AGESA version string from an SMBIOS Type 40 (Additional Information) structure.
///
/// On AMD Zen systems, the AGESA version is stored here.
/// Sample string: "AGESA!V9 StrixKrackanPI-FP8 1.1.0.0c"
fn find_agesa_in_type40(raw: &RawStructure) -> Option<String> {
if raw.info != InfoType::Oem(40) {
return None;
}

// raw.data layout (after the 4-byte header):
// [0]: count — number of Additional Information entries
// [1..]: entries, each starting with a length byte
let count = *raw.data.first()? as usize;
let mut remaining = raw.data.get(1..)?;

for _ in 0..count {
if remaining.len() < DMI_A_INFO_ENT_MIN_SIZE {
break;
}
let entry_len = remaining[0] as usize;
if entry_len == 0 || entry_len > remaining.len() {
break;
}
// String number is at offset 4 within the entry
let str_num = remaining[4];
if let Ok(s) = raw.find_string(str_num) {
Comment on lines +196 to +205
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type 40 entry parsing uses entry_len to delimit each Additional Information entry, but it doesn’t validate that entry_len is at least the minimum size needed to read the string number at offset 4. If entry_len is < 5/6, remaining[4] is read from outside the current entry, which can mis-associate strings and return an incorrect AGESA value on malformed tables. Add a check like if entry_len < DMI_A_INFO_ENT_MIN_SIZE { break/continue; } before reading remaining[4] (and consider skipping to the next entry if you can safely advance).

Copilot uses AI. Check for mistakes.
if s.starts_with("AGESA") {
return Some(s.to_string());
}
}
remaining = &remaining[entry_len..];
Comment on lines +189 to +210
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parsing relies on raw.data and a comment that it is laid out “after the 4-byte header”. Elsewhere in this repo (e.g., framework_lib/src/csme.rs), RawStructure parsing consistently uses raw.get::<T>(offset) with SMBIOS offsets that include the 4-byte header, which avoids ambiguity about what raw.data contains and keeps bounds checks tied to raw.length. Consider switching Type 40 parsing to use raw.get(4) for count and then index subsequent bytes via raw.get()/raw.length (or clearly document/verify raw.data’s semantics) to prevent future off-by-4 mistakes if dmidecode’s representation differs from the assumption here.

Suggested change
// raw.data layout (after the 4-byte header):
// [0]: count — number of Additional Information entries
// [1..]: entries, each starting with a length byte
let count = *raw.data.first()? as usize;
let mut remaining = raw.data.get(1..)?;
for _ in 0..count {
if remaining.len() < DMI_A_INFO_ENT_MIN_SIZE {
break;
}
let entry_len = remaining[0] as usize;
if entry_len == 0 || entry_len > remaining.len() {
break;
}
// String number is at offset 4 within the entry
let str_num = remaining[4];
if let Ok(s) = raw.find_string(str_num) {
if s.starts_with("AGESA") {
return Some(s.to_string());
}
}
remaining = &remaining[entry_len..];
// SMBIOS Type 40 formatted section:
// offset 0x04: count — number of Additional Information entries
// offset 0x05: entries, each starting with a length byte
let count = usize::from(*raw.get::<u8>(4).ok()?);
let mut offset = 5usize;
let structure_len = usize::from(raw.length);
for _ in 0..count {
if offset + DMI_A_INFO_ENT_MIN_SIZE > structure_len {
break;
}
let entry_len = match raw.get::<u8>(offset) {
Ok(len) => usize::from(*len),
Err(_) => break,
};
if entry_len == 0 || offset + entry_len > structure_len {
break;
}
// String number is at offset 4 within the entry
let str_num = match raw.get::<u8>(offset + 4) {
Ok(num) => *num,
Err(_) => break,
};
if let Ok(s) = raw.find_string(str_num) {
if s.starts_with("AGESA") {
return Some(s.to_string());
}
}
offset += entry_len;

Copilot uses AI. Check for mistakes.
}

None
}

/// Get the AGESA version from SMBIOS Type 40 Additional Information entries
pub fn get_agesa_version() -> Option<String> {
let smbios = get_smbios()?;
smbios.structures().find_map(|result| {
if let Ok(Structure::Other(ref raw)) = result {
find_agesa_in_type40(raw)
} else {
None
}
})
}

pub fn get_platform() -> Option<Platform> {
#[cfg(feature = "uefi")]
let mut cached_platform = CACHED_PLATFORM.lock();
Expand Down