CVE-2021-24117 (published 2021-07-14T14:15Z)

In Apache Teaclave Rust SGX SDK 1.1.3, a side-channel vulnerability in base64 PEM file decoding allows system-level (administrator) attackers to obtain information about secret RSA keys via a controlled-channel and side-channel attack on software running in isolated environments that can be single stepped, especially Intel SGX.

Severity: MEDIUM
CVSS v2 base score: 4.0 (AV:N/AC:L/Au:S/C:P/I:N/A:N)
CVSS v3 base score: 4.9, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-203
cwe_name: Observable Discrepancy
description: The product behaves differently or sends different responses under different circumstances in a way that is observable to an unauthorized actor, which exposes security-relevant information about the state of the product, such as whether a particular operation was successful or not.
extended_description: Discrepancies can take many forms, and variations may be detectable in timing, control flow, communications such as replies or requests, or general behavior. These discrepancies can reveal information about the product's operation or internal state to an unauthorized actor. In some cases, discrepancies can be used by attackers to form a side channel.
         

Fix information:
repo_url: https://github.com/dingelish/rust-base64
commit_details: repo_url: https://github.com/dingelish/rust-base64
author: Yu Ding
author_date: 2021-01-17 22:29:18-08:00
msg: fix
repository: repo_url: https://github.com/dingelish/rust-base64
repo_name: dingelish/rust-base64
description: base64, in rust
repo_language: Rust
owner: dingelish
file_changes: filename: Cargo.toml
change_type: ModificationType.MODIFY
diff: @@ -25,6 +25,7 @@ structopt = "0.3"
 default = ["std"]
 alloc = []
 std = []
+slow_but_safe = []
 
 [profile.bench]
 # Useful for better disassembly when using `perf record` and `perf annotate`

programming_language: Lua
filename: decode.rs
change_type: ModificationType.MODIFY
diff: @@ -444,6 +444,18 @@ fn write_u64(output: &mut [u8], value: u64) {
     output[..8].copy_from_slice(&value.to_be_bytes());
 }
 
+#[cfg(feature = "slow_but_safe")]
+fn decode_aligned(b64ch: u8, decode_table: &[u8; 256]) -> u8 {
+    let mut result: u8 = 0x00;
+    let mut mask: u8;
+    let idx: [u8;2] = [ b64ch % 64, b64ch % 64 + 64];
+    for i in 0..2  {
+        mask = 0xFF ^ (((idx[i] == b64ch) as i8 - 1) as u8);
+        result = result | (decode_table[idx[i] as usize] & mask);
+    }
+    result
+}
+
 /// Decode 8 bytes of input into 6 bytes of output. 8 bytes of output will be written, but only the
 /// first 6 of those contain meaningful data.
 ///
@@ -463,13 +475,19 @@ fn decode_chunk(
 ) -> Result<(), DecodeError> {
     let mut accum: u64;
 
+    #[cfg(not(feature = "slow_but_safe"))]
     let morsel = decode_table[input[0] as usize];
+    #[cfg(feature = "slow_but_safe")]
+    let morsel = decode_aligned(input[0], decode_table);
     if morsel == tables::INVALID_VALUE {
         return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0]));
     }
     accum = (morsel as u64) << 58;
 
+    #[cfg(not(feature = "slow_but_safe"))]
     let morsel = decode_table[input[1] as usize];
+    #[cfg(feature = "slow_but_safe")]
+    let morsel = decode_aligned(input[1], decode_table);
     if morsel == tables::INVALID_VALUE {
         return Err(DecodeError::InvalidByte(
             index_at_start_of_input + 1,
@@ -478,7 +496,10 @@ fn decode_chunk(
     }
     accum |= (morsel as u64) << 52;
 
+    #[cfg(not(feature = "slow_but_safe"))]
     let morsel = decode_table[input[2] as usize];
+    #[cfg(feature = "slow_but_safe")]
+    let morsel = decode_aligned(input[2], decode_table);
     if morsel == tables::INVALID_VALUE {
         return Err(DecodeError::InvalidByte(
             index_at_start_of_input + 2,
@@ -487,7 +508,10 @@ fn decode_chunk(
     }
     accum |= (morsel as u64) << 46;
 
+    #[cfg(not(feature = "slow_but_safe"))]
     let morsel = decode_table[input[3] as usize];
+    #[cfg(feature = "slow_but_safe")]
+    let morsel = decode_aligned(input[3], decode_table);
     if morsel == tables::INVALID_VALUE {
         return Err(DecodeError::InvalidByte(
             index_at_start_of_input + 3,
@@ -496,7 +520,10 @@ fn decode_chunk(
     }
     accum |= (morsel as u64) << 40;
 
+    #[cfg(not(feature = "slow_but_safe"))]
     let morsel = decode_table[input[4] as usize];
+    #[cfg(feature = "slow_but_safe")]
+    let morsel = decode_aligned(input[4], decode_table);
     if morsel == tables::INVALID_VALUE {
         return Err(DecodeError::InvalidByte(
             index_at_start_of_input + 4,
@@ -505,7 +532,10 @@ fn decode_chunk(
     }
     accum |= (morsel as u64) << 34;
 
+    #[cfg(not(feature = "slow_but_safe"))]
     let morsel = decode_table[input[5] as usize];
+    #[cfg(feature = "slow_but_safe")]
+    let morsel = decode_aligned(input[5], decode_table);
     if morsel == tables::INVALID_VALUE {
         return Err(DecodeError::InvalidByte(
             index_at_start_of_input + 5,
@@ -514,7 +544,10 @@ fn decode_chunk(
     }
     accum |= (morsel as u64) << 28;
 
+    #[cfg(not(feature = "slow_but_safe"))]
     let morsel = decode_table[input[6] as usize];
+    #[cfg(feature = "slow_but_safe")]
+    let morsel = decode_aligned(input[6], decode_table);
     if morsel == tables::INVALID_VALUE {
         return Err(DecodeError::InvalidByte(
             index_at_start_of_input + 6,
@@ -523,7 +556,10 @@ fn decode_chunk(
     }
     accum |= (morsel as u64) << 22;
 
+    #[cfg(not(feature = "slow_but_safe"))]
     let morsel = decode_table[input[7] as usize];
+    #[cfg(feature = "slow_but_safe")]
+    let morsel = decode_aligned(input[7], decode_table);
     if morsel == tables::INVALID_VALUE {
         return Err(DecodeError::InvalidByte(
             index_at_start_of_input + 7,

programming_language: Rust
filename: lib.rs
change_type: ModificationType.MODIFY
diff: @@ -138,12 +138,12 @@ impl CharacterSet {
 
     fn decode_table(self) -> &'static [u8; 256] {
         match self {
-            CharacterSet::Standard => tables::STANDARD_DECODE,
-            CharacterSet::UrlSafe => tables::URL_SAFE_DECODE,
-            CharacterSet::Crypt => tables::CRYPT_DECODE,
-            CharacterSet::Bcrypt => tables::BCRYPT_DECODE,
-            CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_DECODE,
-            CharacterSet::BinHex => tables::BINHEX_DECODE,
+            CharacterSet::Standard => &tables::STANDARD_DECODE_HOLDER.data,
+            CharacterSet::UrlSafe => &tables::URL_SAFE_DECODE_HOLDER.data,
+            CharacterSet::Crypt => &tables::CRYPT_DECODE_HOLDER.data,
+            CharacterSet::Bcrypt => &tables::BCRYPT_DECODE_HOLDER.data,
+            CharacterSet::ImapMutf7 => &tables::IMAP_MUTF7_DECODE_HOLDER.data,
+            CharacterSet::BinHex => &tables::BINHEX_DECODE_HOLDER.data,
         }
     }
 }

programming_language: Rust
filename: tables.rs
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,35 @@
+//#[repr(align(64))]
+//pub struct StructStandardEncode { pub data: [u8; 64] }
+#[repr(align(64))]
+pub struct StructStandardDecode { pub data: [u8; 256] }
+//#[repr(align(64))]
+//pub struct StructUrlSafeEncode { pub data: [u8; 64] }
+#[repr(align(64))]
+pub struct StructUrlSafeDecode { pub data: [u8; 256] }
+//#[repr(align(64))]
+//pub struct StructCryptEncode { pub data: [u8; 64] }
+#[repr(align(64))]
+pub struct StructCryptDecode { pub data: [u8; 256] }
+//#[repr(align(64))]
+//pub struct StructBcryptEncode { pub data: [u8; 64] }
+#[repr(align(64))]
+pub struct StructBcryptDecode { pub data: [u8; 256] }
+//#[repr(align(64))]
+//pub struct StructImapMutf7Encode { pub data: [u8; 64] }
+#[repr(align(64))]
+pub struct StructImapMutf7Decode { pub data: [u8; 256] }
+//#[repr(align(64))]
+//pub struct StructBinhexEncode { pub data: [u8; 64] }
+#[repr(align(64))]
+pub struct StructBinhexDecode { pub data: [u8; 256] }
+
+pub const STANDARD_DECODE_HOLDER: StructStandardDecode = StructStandardDecode { data: *STANDARD_DECODE };
+pub const URL_SAFE_DECODE_HOLDER: StructUrlSafeDecode = StructUrlSafeDecode { data: *URL_SAFE_DECODE };
+pub const CRYPT_DECODE_HOLDER: StructCryptDecode = StructCryptDecode { data: *CRYPT_DECODE };
+pub const BCRYPT_DECODE_HOLDER: StructBcryptDecode = StructBcryptDecode { data: *BCRYPT_DECODE };
+pub const IMAP_MUTF7_DECODE_HOLDER: StructImapMutf7Decode = StructImapMutf7Decode { data: *IMAP_MUTF7_DECODE };
+pub const BINHEX_DECODE_HOLDER: StructBinhexDecode = StructBinhexDecode { data: *BINHEX_DECODE };
+
 pub const INVALID_VALUE: u8 = 255;
 #[rustfmt::skip]
 pub const STANDARD_ENCODE: &[u8; 64] = &[
@@ -1955,3 +1987,19 @@ pub const BINHEX_DECODE: &[u8; 256] = &[
     INVALID_VALUE, // input 254 (0xFE)
     INVALID_VALUE, // input 255 (0xFF)
 ];
+
+#[test]
+fn alignment_check() {
+    let p: *const u8 = STANDARD_DECODE_HOLDER.data.as_ptr();
+    assert_eq!((p as u64) % 64, 0);
+    let p: *const u8 = URL_SAFE_DECODE_HOLDER.data.as_ptr();
+    assert_eq!((p as u64) % 64, 0);
+    let p: *const u8 = CRYPT_DECODE_HOLDER.data.as_ptr();
+    assert_eq!((p as u64) % 64, 0);
+    let p: *const u8 = BCRYPT_DECODE_HOLDER.data.as_ptr();
+    assert_eq!((p as u64) % 64, 0);
+    let p: *const u8 = IMAP_MUTF7_DECODE_HOLDER.data.as_ptr();
+    assert_eq!((p as u64) % 64, 0);
+    let p: *const u8 = BINHEX_DECODE_HOLDER.data.as_ptr();
+    assert_eq!((p as u64) % 64, 0);
+}

programming_language: Rust