From 5e0899b73dedacd3613c640bcd1adf55330f7470 Mon Sep 17 00:00:00 2001
From: Mohsen Zohrevandi <mohsen.zohrevandi@fortanix.com>
Date: Mon, 6 Jan 2025 12:06:11 -0800
Subject: [PATCH 1/2] dcap-ql: Fix length validation logic for quote signatures

---
 intel-sgx/dcap-ql/src/quote.rs | 65 +++++++++++++++++++++++++++++++---
 1 file changed, 61 insertions(+), 4 deletions(-)

diff --git a/intel-sgx/dcap-ql/src/quote.rs b/intel-sgx/dcap-ql/src/quote.rs
index 56f732820..4dd9a54a2 100644
--- a/intel-sgx/dcap-ql/src/quote.rs
+++ b/intel-sgx/dcap-ql/src/quote.rs
@@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize};
 #[cfg(feature = "verify")]
 use sgx_isa::Report;
 use std::borrow::Cow;
-use std::mem;
+use std::{cmp, mem};
 use anyhow::bail;
 
 // ====================================================
@@ -236,14 +236,19 @@ impl<'a> Quote3Signature<'a> for Quote3SignatureEcdsaP256<'a> {
             bail!("Invalid attestation key type: {:?}", type_)
         }
 
-        let sig_len = data.take_prefix(mem::size_of::<u32>()).map(|v| LE::read_u32(&v))?;
-        if sig_len as usize != data.len() {
+        let sig_len = data.take_prefix(mem::size_of::<u32>()).map(|v| LE::read_u32(&v))? as usize;
+        if sig_len > data.len() {
             bail!(
-                "Invalid signature length. Got {}, expected {}",
+                "Invalid signature length. Got {}, expected >= {}",
                 data.len(),
                 sig_len
             );
         }
+        // NOTE: data may contain trailing zeros due to `get_quote_size` and
+        // `get_quote` C APIs allowing larger than necessary buffer to be
+        // allocated to hold the quote.
+        data = data.take_prefix(cmp::min(data.len(), sig_len))?;
+
         let signature = data.take_prefix(ECDSA_P256_SIGNATURE_LEN)?;
         let attestation_public_key = data.take_prefix(ECDSA_P256_PUBLIC_KEY_LEN)?;
         let qe3_report = data.take_prefix(REPORT_BODY_LEN)?;
@@ -774,4 +779,56 @@ mod tests {
         #[cfg(feature = "verify")]
         assert!(Quote::verify::<Quote3SignatureEcdsaP256>(TEST_QUOTE, &mut verifier).is_err());
     }
+
+    #[test]
+    fn test_quote_with_trailing_zeros() {
+        let with_trailing_zeros = {
+            const TEST_QUOTE: &[u8] = &*include_bytes!("../tests/quote_pck_cert_chain.bin");
+            let mut with_trailing_zeros = vec![0u8; TEST_QUOTE.len() + 3];
+            with_trailing_zeros[..TEST_QUOTE.len()].copy_from_slice(TEST_QUOTE);
+            with_trailing_zeros
+        };
+
+        let quote = Quote::parse(&with_trailing_zeros).unwrap();
+        let &QuoteHeader::V3 {
+            attestation_key_type,
+            ..
+        } = quote.header();
+
+        assert_eq!(attestation_key_type, Quote3AttestationKeyType::EcdsaP256);
+        let sig = quote.signature::<Quote3SignatureEcdsaP256>().unwrap();
+
+        #[cfg(feature = "verify")]
+        let mut verifier = MyVerifier {
+            // See the note in `fn test_quote_verification`.
+            // TODO: Update the example quote with a matching qe3_identity.json file
+            qe3_identity: include_str!("../tests/corrupt_qe3_identity.json").to_string(),
+        };
+        #[cfg(feature = "verify")]
+        assert!(Quote::verify::<Quote3SignatureEcdsaP256>(&with_trailing_zeros, &mut verifier).is_ok())
+    }
+
+    #[test]
+    fn test_quote_too_short() {
+        let too_short = {
+            const TEST_QUOTE: &[u8] = &*include_bytes!("../tests/quote_pck_cert_chain.bin");
+            let mut too_short = vec![0u8; TEST_QUOTE.len() - 5];
+            let n = too_short.len();
+            too_short.copy_from_slice(&TEST_QUOTE[..n]);
+            too_short
+        };
+
+        // We won't detect the issue until we try to parse the signature
+        let quote = Quote::parse(&too_short).unwrap();
+        let &QuoteHeader::V3 {
+            attestation_key_type,
+            ..
+        } = quote.header();
+
+        assert_eq!(attestation_key_type, Quote3AttestationKeyType::EcdsaP256);
+        match quote.signature::<Quote3SignatureEcdsaP256>() {
+            Ok(_) => panic!("unexpected Ok result"),
+            Err(e) => assert_eq!(e.to_string(), "Invalid signature length. Got 4137, expected >= 4142"),
+        }
+    }
 }

From a4d33f30cb5741eeb831b14afa746c3476d85f82 Mon Sep 17 00:00:00 2001
From: Mohsen Zohrevandi <mohsen.zohrevandi@fortanix.com>
Date: Fri, 17 Jan 2025 12:16:12 -0800
Subject: [PATCH 2/2] Don't ignore trailing bytes

---
 Cargo.lock                               | 10 ++--
 intel-sgx/dcap-provider/Cargo.toml       |  4 +-
 intel-sgx/dcap-provider/src/lib.rs       |  9 +++-
 intel-sgx/dcap-ql/Cargo.toml             |  2 +-
 intel-sgx/dcap-ql/src/quote.rs           | 64 ++++++++++++++++--------
 intel-sgx/dcap-retrieve-pckid/Cargo.toml |  4 +-
 intel-sgx/dcap-retrieve-pckid/src/lib.rs |  7 ++-
 intel-sgx/pcs/Cargo.toml                 |  4 +-
 intel-sgx/pcs/src/pckcrt.rs              |  2 +-
 intel-sgx/sgxs-tools/Cargo.toml          |  4 +-
 10 files changed, 72 insertions(+), 38 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 279729f0d..2797a1a20 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -778,7 +778,7 @@ dependencies = [
 
 [[package]]
 name = "dcap-provider"
-version = "0.4.0"
+version = "0.4.1"
 dependencies = [
  "byteorder 1.3.4",
  "dcap-ql",
@@ -792,7 +792,7 @@ dependencies = [
 
 [[package]]
 name = "dcap-ql"
-version = "0.4.0"
+version = "0.5.0"
 dependencies = [
  "anyhow",
  "byteorder 1.3.4",
@@ -823,7 +823,7 @@ dependencies = [
 
 [[package]]
 name = "dcap-retrieve-pckid"
-version = "0.2.0"
+version = "0.2.1"
 dependencies = [
  "aesm-client",
  "anyhow",
@@ -2728,7 +2728,7 @@ dependencies = [
 
 [[package]]
 name = "pcs"
-version = "0.3.0"
+version = "0.3.1"
 dependencies = [
  "anyhow",
  "b64-ct",
@@ -3724,7 +3724,7 @@ dependencies = [
 
 [[package]]
 name = "sgxs-tools"
-version = "0.8.6"
+version = "0.8.7"
 dependencies = [
  "aesm-client",
  "anyhow",
diff --git a/intel-sgx/dcap-provider/Cargo.toml b/intel-sgx/dcap-provider/Cargo.toml
index 1b3d2ceb2..f8217ddb3 100644
--- a/intel-sgx/dcap-provider/Cargo.toml
+++ b/intel-sgx/dcap-provider/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "dcap-provider"
-version = "0.4.0"
+version = "0.4.1"
 authors = ["Fortanix, Inc."]
 edition = "2018"
 license = "MPL-2.0"
@@ -34,7 +34,7 @@ crate-type = ["cdylib"]
 
 [dependencies]
 # Project dependencies
-"dcap-ql" = { version = "0.4.0", path = "../dcap-ql", features = ["link"] }
+"dcap-ql" = { version = "0.5.0", path = "../dcap-ql", features = ["link"] }
 "report-test" = { version = "0.4.0", path = "../report-test" }
 
 # External dependencies
diff --git a/intel-sgx/dcap-provider/src/lib.rs b/intel-sgx/dcap-provider/src/lib.rs
index 62521ea9e..5fc2a8d90 100644
--- a/intel-sgx/dcap-provider/src/lib.rs
+++ b/intel-sgx/dcap-provider/src/lib.rs
@@ -116,13 +116,18 @@ fn parse_certdata(
         return Err(Quote3Error::NoPlatformCertData);
     }
 
-    let sig = quote
+    let (sig, trailing) = quote
         .signature::<quote::Quote3SignatureEcdsaP256>()
         .map_err(|e| {
             error!("PPID query: {}", e);
             Quote3Error::NoPlatformCertData
         })?;
 
+    if !trailing.is_empty() {
+        error!("trailing data after quote signature: {} bytes", trailing.len());
+        return Err(Quote3Error::NoPlatformCertData);
+    }
+
     let cd = sig
         .certification_data::<quote::Qe3CertDataPpid>()
         .map_err(|e| {
@@ -368,7 +373,7 @@ pub extern "C" fn sgx_ql_free_quote_config(p_cert_config: *const Config) -> Quot
             .ok_or(Quote3Error::InvalidParameter)?
             .cert_data_size;
         let buflen = cert_data_size as usize + mem::size_of::<Config>();
-        Box::from_raw(slice::from_raw_parts_mut(p_cert_config as *mut u8, buflen));
+        let _ = Box::from_raw(slice::from_raw_parts_mut(p_cert_config as *mut u8, buflen));
         Ok(())
     })() {
         Ok(()) => Quote3Error::Success,
diff --git a/intel-sgx/dcap-ql/Cargo.toml b/intel-sgx/dcap-ql/Cargo.toml
index 4f0a97251..36397b457 100644
--- a/intel-sgx/dcap-ql/Cargo.toml
+++ b/intel-sgx/dcap-ql/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "dcap-ql"
-version = "0.4.0"
+version = "0.5.0"
 authors = ["Fortanix, Inc."]
 license = "MPL-2.0"
 description = """
diff --git a/intel-sgx/dcap-ql/src/quote.rs b/intel-sgx/dcap-ql/src/quote.rs
index 4dd9a54a2..08bd37031 100644
--- a/intel-sgx/dcap-ql/src/quote.rs
+++ b/intel-sgx/dcap-ql/src/quote.rs
@@ -159,7 +159,9 @@ impl<'a> TakePrefix for Cow<'a, str> {
 }
 
 pub trait Quote3Signature<'a>: Sized {
-    fn parse(type_: Quote3AttestationKeyType, data: Cow<'a, [u8]>) -> Result<Self>;
+    /// Parse the signature encoded in data and return the parsed value along
+    /// with any trailing data that was not part of the signature.
+    fn parse(type_: Quote3AttestationKeyType, data: Cow<'a, [u8]>) -> Result<(Self, Cow<'a, [u8]>)>;
 }
 
 pub trait Qe3CertData<'a>: Sized {
@@ -231,7 +233,7 @@ fn get_ecdsa_sig_der(sig: &[u8]) -> Result<Vec<u8>> {
 }
 
 impl<'a> Quote3Signature<'a> for Quote3SignatureEcdsaP256<'a> {
-    fn parse(type_: Quote3AttestationKeyType, mut data: Cow<'a, [u8]>) -> Result<Self> {
+    fn parse(type_: Quote3AttestationKeyType, mut data: Cow<'a, [u8]>) -> Result<(Self, Cow<'a, [u8]>)> {
         if type_ != Quote3AttestationKeyType::EcdsaP256 {
             bail!("Invalid attestation key type: {:?}", type_)
         }
@@ -247,7 +249,10 @@ impl<'a> Quote3Signature<'a> for Quote3SignatureEcdsaP256<'a> {
         // NOTE: data may contain trailing zeros due to `get_quote_size` and
         // `get_quote` C APIs allowing larger than necessary buffer to be
         // allocated to hold the quote.
-        data = data.take_prefix(cmp::min(data.len(), sig_len))?;
+        let (mut data, trailing) = {
+            let prefix = data.take_prefix(cmp::min(data.len(), sig_len))?;
+            (prefix, data)
+        };
 
         let signature = data.take_prefix(ECDSA_P256_SIGNATURE_LEN)?;
         let attestation_public_key = data.take_prefix(ECDSA_P256_PUBLIC_KEY_LEN)?;
@@ -267,7 +272,7 @@ impl<'a> Quote3Signature<'a> for Quote3SignatureEcdsaP256<'a> {
             );
         }
 
-        Ok(Quote3SignatureEcdsaP256 {
+        Ok((Quote3SignatureEcdsaP256 {
             signature,
             attestation_public_key,
             qe3_report,
@@ -275,7 +280,7 @@ impl<'a> Quote3Signature<'a> for Quote3SignatureEcdsaP256<'a> {
             authentication_data,
             certification_data_type,
             certification_data: data,
-        })
+        }, trailing))
     }
 }
 
@@ -362,7 +367,9 @@ impl<'a> Quote<'a> {
         &self.report_body
     }
 
-    pub fn signature<T: Quote3Signature<'a>>(&self) -> Result<T> {
+    /// Parse the signature encoded in `self.signature` and return the parsed
+    /// value along with any trailing data that was not part of the signature.
+    pub fn signature<T: Quote3Signature<'a>>(&self) -> Result<(T, Cow<'a, [u8]>)> {
         let QuoteHeader::V3 {
             attestation_key_type,
             ..
@@ -543,11 +550,15 @@ impl<'a> Quote3SignatureVerify<'a> for Quote3SignatureEcdsaP256<'a> {
 
 impl<'a> Quote<'a> {
     #[cfg(feature = "verify")]
-    pub fn verify<T: Quote3SignatureVerify<'a>>(quote: &'a [u8], root_of_trust: T::TrustRoot) -> Result<Self> {
+    /// Parses the `Quote` encoded in `quote`, verifies the signature within the
+    /// quote, and returns the parsed quote along with any trailing data that
+    /// was not part of the signature. It's up to the caller to decide what to
+    /// do with the trailing bytes.
+    pub fn verify<T: Quote3SignatureVerify<'a>>(quote: &'a [u8], root_of_trust: T::TrustRoot) -> Result<(Self, Cow<'a, [u8]>)> {
         let parsed_quote = Self::parse(quote)?;
-        let sig = parsed_quote.signature::<T>()?;
+        let (sig, trailing) = parsed_quote.signature::<T>()?;
         Quote3SignatureVerify::verify(&sig, quote, root_of_trust)?;
-        Ok(parsed_quote)
+        Ok((parsed_quote, trailing))
     }
 }
 
@@ -646,8 +657,10 @@ mod tests {
         assert_eq!(user_data, &ud);
 
         assert_eq!(attestation_key_type, Quote3AttestationKeyType::EcdsaP256);
-        let sig = quote.signature::<Quote3SignatureEcdsaP256>().unwrap();
+        let (sig, trailing) = quote.signature::<Quote3SignatureEcdsaP256>().unwrap();
 
+        let expected_trailing: &[u8] = &[];
+        assert_eq!(&*trailing, expected_trailing);
         assert_eq!(
             sig.certification_data_type(),
             CertificationDataType::PpidEncryptedRsa3072
@@ -727,8 +740,11 @@ mod tests {
             // TODO: Update the example quote with a matching qe3_identity.json file
             qe3_identity: include_str!("../tests/corrupt_qe3_identity.json").to_string(),
         };
-        #[cfg(feature = "verify")]
-        assert!(Quote::verify::<Quote3SignatureEcdsaP256>(TEST_QUOTE, &mut verifier).is_ok())
+        #[cfg(feature = "verify")] {
+            let (_, trailing) = Quote::verify::<Quote3SignatureEcdsaP256>(TEST_QUOTE, &mut verifier).unwrap();
+            let expected_trailing: &[u8] = &[];
+            assert_eq!(&*trailing, expected_trailing);
+        }
     }
 
     #[test]
@@ -782,21 +798,27 @@ mod tests {
 
     #[test]
     fn test_quote_with_trailing_zeros() {
-        let with_trailing_zeros = {
+        let expected_trailing: &[u8] = &[42, 75, 92];
+        let with_trailing_data = {
             const TEST_QUOTE: &[u8] = &*include_bytes!("../tests/quote_pck_cert_chain.bin");
-            let mut with_trailing_zeros = vec![0u8; TEST_QUOTE.len() + 3];
-            with_trailing_zeros[..TEST_QUOTE.len()].copy_from_slice(TEST_QUOTE);
-            with_trailing_zeros
+            let n = TEST_QUOTE.len();
+            let t = expected_trailing.len();
+            let mut with_trailing_data = vec![0u8; n + t];
+            with_trailing_data[..n].copy_from_slice(TEST_QUOTE);
+            with_trailing_data[n..].copy_from_slice(expected_trailing);
+            with_trailing_data
         };
 
-        let quote = Quote::parse(&with_trailing_zeros).unwrap();
+        let quote = Quote::parse(&with_trailing_data).unwrap();
         let &QuoteHeader::V3 {
             attestation_key_type,
             ..
         } = quote.header();
 
         assert_eq!(attestation_key_type, Quote3AttestationKeyType::EcdsaP256);
-        let sig = quote.signature::<Quote3SignatureEcdsaP256>().unwrap();
+        let (sig, trailing) = quote.signature::<Quote3SignatureEcdsaP256>().unwrap();
+
+        assert_eq!(&*trailing, expected_trailing);
 
         #[cfg(feature = "verify")]
         let mut verifier = MyVerifier {
@@ -804,8 +826,10 @@ mod tests {
             // TODO: Update the example quote with a matching qe3_identity.json file
             qe3_identity: include_str!("../tests/corrupt_qe3_identity.json").to_string(),
         };
-        #[cfg(feature = "verify")]
-        assert!(Quote::verify::<Quote3SignatureEcdsaP256>(&with_trailing_zeros, &mut verifier).is_ok())
+        #[cfg(feature = "verify")] {
+            let (_, trailing) = Quote::verify::<Quote3SignatureEcdsaP256>(&with_trailing_data, &mut verifier).unwrap();
+            assert_eq!(&*trailing, expected_trailing);
+        }
     }
 
     #[test]
diff --git a/intel-sgx/dcap-retrieve-pckid/Cargo.toml b/intel-sgx/dcap-retrieve-pckid/Cargo.toml
index bffddc56f..71b8d6306 100644
--- a/intel-sgx/dcap-retrieve-pckid/Cargo.toml
+++ b/intel-sgx/dcap-retrieve-pckid/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "dcap-retrieve-pckid"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Fortanix, Inc."]
 license = "MPL-2.0"
 edition = "2018"
@@ -15,7 +15,7 @@ categories = ["command-line-utilities"]
 [dependencies]
 # Project dependencies
 "aesm-client" = { version = "0.6.0", path = "../aesm-client", features = ["sgxs"] }
-"dcap-ql" = { version = "0.4.0", path = "../dcap-ql", default-features = false }
+"dcap-ql" = { version = "0.5.0", path = "../dcap-ql", default-features = false }
 "report-test" = { version = "0.4.0", path = "../report-test" }
 "sgx-isa" = { version = "0.4.0", path = "../sgx-isa" }
 "sgxs-loaders" = { version = "0.4.0", path = "../sgxs-loaders" }
diff --git a/intel-sgx/dcap-retrieve-pckid/src/lib.rs b/intel-sgx/dcap-retrieve-pckid/src/lib.rs
index 60ae55bc5..7061834b5 100644
--- a/intel-sgx/dcap-retrieve-pckid/src/lib.rs
+++ b/intel-sgx/dcap-retrieve-pckid/src/lib.rs
@@ -86,9 +86,14 @@ pub fn retrieve_pckid_str() -> Result<PckId, anyhow::Error> {
 
     let quote = Quote::parse(res.quote()).map_err(|err| err.context("Error parsing quote"))?;
     let QuoteHeader::V3 { user_data, .. } = quote.header();
-    let sig = quote
+    let (sig, trailing) = quote
         .signature::<Quote3SignatureEcdsaP256>()
         .map_err(|err| err.context("Error parsing requested signature type"))?;
+
+    if !trailing.is_empty() {
+        return Err(anyhow!("trailing data after quote signature: {} bytes", trailing.len()));
+    }
+
     let cd_ppid = sig
         .certification_data::<Qe3CertDataPpid>()
         .map_err(|err| err.context("Error parsing requested signature type"))?;
diff --git a/intel-sgx/pcs/Cargo.toml b/intel-sgx/pcs/Cargo.toml
index a33045e2b..960a3fb89 100644
--- a/intel-sgx/pcs/Cargo.toml
+++ b/intel-sgx/pcs/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pcs"
-version = "0.3.0"
+version = "0.3.1"
 authors = ["Fortanix, Inc."]
 license = "MPL-2.0"
 edition = "2018"
@@ -19,7 +19,6 @@ categories = ["os", "hardware-support"]
 
 
 [dependencies]
-dcap-ql = { version = "0.4.0", path = "../dcap-ql", default-features = false }
 sgx-isa = { version = "0.4.1", path = "../sgx-isa", default-features = true }
 pkix = "0.2.0"
 yasna = { version = "0.3", features = ["num-bigint", "bit-vec"] }
@@ -37,6 +36,7 @@ num = "0.2"
 mbedtls = { version = "0.12.3", features = ["std", "time"], default-features = false, optional = true }
 
 [dev-dependencies]
+dcap-ql = { version = "0.5.0", path = "../dcap-ql", default-features = false }
 hex = "0.4.2"
 
 [features]
diff --git a/intel-sgx/pcs/src/pckcrt.rs b/intel-sgx/pcs/src/pckcrt.rs
index 4e03335ac..591a1b911 100644
--- a/intel-sgx/pcs/src/pckcrt.rs
+++ b/intel-sgx/pcs/src/pckcrt.rs
@@ -1008,7 +1008,7 @@ mod tests {
     fn pckcrt_from_quote() {
         let quote = include_bytes!("../tests/data/quote.bin");
         let quote = Quote::parse(Cow::from(&quote[..])).unwrap();
-        let sig = quote.signature::<Quote3SignatureEcdsaP256>().unwrap();
+        let (sig, _) = quote.signature::<Quote3SignatureEcdsaP256>().unwrap();
         let pck_chain: Qe3CertDataPckCertChain = sig.certification_data().unwrap();
         let pck = PckCert::from_pck_chain(pck_chain.certs.into()).unwrap();
         let sgx_extension = pck.sgx_extension().unwrap();
diff --git a/intel-sgx/sgxs-tools/Cargo.toml b/intel-sgx/sgxs-tools/Cargo.toml
index e967d4be2..3624c104f 100644
--- a/intel-sgx/sgxs-tools/Cargo.toml
+++ b/intel-sgx/sgxs-tools/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "sgxs-tools"
-version = "0.8.6"
+version = "0.8.7"
 authors = ["Fortanix, Inc."]
 license = "MPL-2.0"
 description = """
@@ -59,7 +59,7 @@ serde_derive = "1.0.84"                          # MIT/Apache-2.0
 serde_yaml = "0.8.8"                             # MIT/Apache-2.0
 
 [target.'cfg(unix)'.dependencies]
-"dcap-ql" = { version = "0.4.0", path = "../dcap-ql" }
+"dcap-ql" = { version = "0.5.0", path = "../dcap-ql" }
 
 [target.'cfg(windows)'.dependencies]
 winapi = { version = "0.3.7", features = ["winbase"] }