Skip to content

Out-of-bounds read in CDVD SCMD 0x91 and SCMD 0x8F due to an unchecked offset and size passed to memcpy

Moderate
F0bes published GHSA-69wg-97fx-8j5w Dec 12, 2025

Package

No package listed

Affected versions

<v2.5.378

Patched versions

v2.5.378

Description

Impact

An unchecked offset and size used in a memcpy operation inside PCSX2’s CDVD SCMD 0x91 and SCMD 0x8F handler allows a specially crafted disc image or ELF to cause an out-of-bounds read from emulator memory. Because the offset and size is controlled through MG header fields, a specially crafted ELF can read data beyond the bounds of mg_buffer and have it reflected back into emulated memory.

Patches

Attached is a patch that should add sufficient bounds checking before copying memory.

diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp
index 0c5ff56f02..8bece2d7e6 100644
--- a/pcsx2/CDVD/CDVD.cpp
+++ b/pcsx2/CDVD/CDVD.cpp
@@ -2942,8 +2942,19 @@ static void cdvdWrite16(u8 rt) // SCOMMAND
 
 					bit_ofs = mg_BIToffset(&cdvd.mg_buffer[0]);
 
-					memcpy(&cdvd.mg_kbit[0], &cdvd.mg_buffer[bit_ofs - 0x20], 0x10);
-					memcpy(&cdvd.mg_kcon[0], &cdvd.mg_buffer[bit_ofs - 0x10], 0x10);
+					const size_t buf_size = sizeof(cdvd.mg_buffer);
+
+					if (bit_ofs < 0x20 || (size_t)bit_ofs > buf_size)
+					{
+						fail_pol_cal();
+						break;
+					}
+
+					const size_t kbit_ofs = bit_ofs - 0x20;
+					const size_t kcon_ofs = bit_ofs - 0x10;
+
+					std::memcpy(&cdvd.mg_kbit[0], &cdvd.mg_buffer[kbit_ofs], 0x10);
+					std::memcpy(&cdvd.mg_kcon[0], &cdvd.mg_buffer[kcon_ofs], 0x10);
 
 					if ((cdvd.mg_buffer[bit_ofs + 5] || cdvd.mg_buffer[bit_ofs + 6] || cdvd.mg_buffer[bit_ofs + 7]) ||
 						(GetBufferU16(&cdvd.mg_buffer[0],bit_ofs + 4) * 16 + bit_ofs + 8 + 16 != GetBufferU16(&cdvd.mg_buffer[0], 0x14)))
@@ -2969,7 +2980,31 @@ static void cdvdWrite16(u8 rt) // SCOMMAND
 			{
 				SetSCMDResultSize(3); //in:0
 				const int bit_ofs = mg_BIToffset(&cdvd.mg_buffer[0]);
-				memcpy(&cdvd.mg_buffer[0], &cdvd.mg_buffer[bit_ofs], static_cast<size_t>(8 + 16 * static_cast<int>(cdvd.mg_buffer[bit_ofs + 4])));
+
+				if (bit_ofs < 0)
+				{
+					fail_pol_cal();
+					break;
+				}
+
+				const size_t bufsize = sizeof(cdvd.mg_buffer);
+				const size_t ofs = static_cast<size_t>(bit_ofs);
+
+				if (ofs > bufsize - 5) // Make sure we can read the block count
+				{
+					fail_pol_cal();
+					break;
+				}
+				const unsigned int blocks = static_cast<unsigned int>(cdvd.mg_buffer[ofs + 4]);
+				const size_t copy_len = 8 + 16 * static_cast<size_t>(blocks);
+
+				if (copy_len > bufsize - ofs) // Make sure we can read the blocks
+				{
+					fail_pol_cal();
+					break;
+				}
+
+				std::memmove(&cdvd.mg_buffer[0], &cdvd.mg_buffer[ofs], copy_len);
 
 				cdvd.mg_maxsize = 0; // don't allow any write
 				cdvd.mg_size = 8 + 16 * cdvd.mg_buffer[4]; //new offset, i just moved the data

Workarounds

Do not run software (homebrew) you do not trust on PCSX2.

References

#13693

Severity

Moderate

CVE ID

CVE-2025-67749

Weaknesses

Out-of-bounds Read

The product reads data past the end, or before the beginning, of the intended buffer. Learn more on MITRE.

Credits