CVE-2022-23951 (published 2022-09-21T19:15Z)

In Keylime before 6.3.0, quote responses from the agent can contain possibly untrusted ZIP data which can lead to zip bombs.

CVSS v3 base score: 5.5, MEDIUM (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/keylime/keylime
commit_details: repo_url: https://github.com/keylime/keylime
author: Thore Sommer
author_date: 2022-01-16 16:14:07+01:00
msg: tpm: do not compress quote with zlib by default

Pythons zlib decompression has no mitigations against zip bombs or similar
attacks, so we remove the compression for the quote data.

The data itself is rather small compared to the IMA or UEFI event log.

Signed-off-by: Thore Sommer <mail@thson.de>
repository: repo_url: https://github.com/keylime/keylime
repo_name: keylime/keylime
description: A CNCF Project to Bootstrap & Maintain Trust on the Edge / Cloud and IoT
repo_language: Python
owner: keylime
file_changes: filename: cloud_verifier_common.py
change_type: ModificationType.MODIFY
diff: @@ -152,7 +152,8 @@ def process_quote_response(agent, json_response, agentAttestState) -> Failure:
         algorithms.Hash(hash_alg),
         ima_keyrings,
         mb_measurement_list,
-        agent['mb_refstate'])
+        agent['mb_refstate'],
+        compressed=(agent['supported_version'] == "1.0"))  # TODO: change this to always False after initial update
     failure.merge(quote_validation_failure)
 
     if not failure:

programming_language: Python
filename: tenant.py
change_type: ModificationType.MODIFY
diff: @@ -545,7 +545,9 @@ class Tenant():
             logger.warning("AIK not found in registrar, quote not validated")
             return False
 
-        failure = self.tpm_instance.check_quote(AgentAttestState(self.agent_uuid), self.nonce, public_key, quote, self.registrar_data['aik_tpm'], hash_alg=hash_alg)
+        failure = self.tpm_instance.check_quote(AgentAttestState(self.agent_uuid), self.nonce, public_key, quote,
+                                                self.registrar_data['aik_tpm'], hash_alg=hash_alg,
+                                                compressed=(self.supported_version == "1.0"))
         if failure:
             if self.registrar_data['regcount'] > 1:
                 logger.error("WARNING: This UUID had more than one ek-ekcert registered to it! This might indicate that your system is misconfigured or a malicious host is present. Run 'regdelete' for this agent and restart")

programming_language: Python
filename: tpm_abstract.py
change_type: ModificationType.MODIFY
diff: @@ -158,11 +158,11 @@ class AbstractTPM(metaclass=ABCMeta):
 
     # tpm_quote
     @abstractmethod
-    def create_quote(self, nonce, data=None, pcrmask=EMPTYMASK, hash_alg=None):
+    def create_quote(self, nonce, data=None, pcrmask=EMPTYMASK, hash_alg=None, compress=False):
         pass
 
     @abstractmethod
-    def check_quote(self, agentAttestState, nonce, data, quote, aikTpmFromRegistrar, tpm_policy={}, ima_measurement_list=None, allowlist={}, hash_alg=None, ima_keyrings=None, mb_measurement_list=None, mb_refstate=None):
+    def check_quote(self, agentAttestState, nonce, data, quote, aikTpmFromRegistrar, tpm_policy={}, ima_measurement_list=None, allowlist={}, hash_alg=None, ima_keyrings=None, mb_measurement_list=None, mb_refstate=None, compressed=False):
         pass
 
     def START_HASH(self, algorithm=None):

programming_language: Python
filename: tpm_main.py
change_type: ModificationType.MODIFY
diff: @@ -959,7 +959,7 @@ class tpm(tpm_abstract.AbstractTPM):
                 pcr_list.append(str(pcr))
         return ",".join(pcr_list)
 
-    def create_quote(self, nonce, data=None, pcrmask=tpm_abstract.AbstractTPM.EMPTYMASK, hash_alg=None):
+    def create_quote(self, nonce, data=None, pcrmask=tpm_abstract.AbstractTPM.EMPTYMASK, hash_alg=None, compress=False):
         if hash_alg is None:
             hash_alg = self.defaults['hash']
 
@@ -992,11 +992,15 @@ class tpm(tpm_abstract.AbstractTPM):
                     command = ["tpm2_quote", "-c", keyhandle, "-l", "%s:%s" % (hash_alg, pcrlist), "-q", nonce, "-m", quotepath.name, "-s", sigpath.name, "-o", pcrpath.name, "-g", hash_alg, "-p", aik_pw]
                 retDict = self.__run(command, lock=False, outputpaths=[quotepath.name, sigpath.name, pcrpath.name])
                 quoteraw = retDict['fileouts'][quotepath.name]
-                quote_b64encode = base64.b64encode(zlib.compress(quoteraw))
                 sigraw = retDict['fileouts'][sigpath.name]
-                sigraw_b64encode = base64.b64encode(zlib.compress(sigraw))
                 pcrraw = retDict['fileouts'][pcrpath.name]
-                pcrraw_b64encode = base64.b64encode(zlib.compress(pcrraw))
+                if compress:
+                    quoteraw = zlib.compress(quoteraw)
+                    sigraw = zlib.compress(sigraw)
+                    pcrraw = zlib.compress(pcrraw)
+                quote_b64encode = base64.b64encode(quoteraw)
+                sigraw_b64encode = base64.b64encode(sigraw)
+                pcrraw_b64encode = base64.b64encode(pcrraw)
                 quote = quote_b64encode.decode('utf-8') + ":" + sigraw_b64encode.decode('utf-8') + ":" + pcrraw_b64encode.decode('utf-8')
 
         return 'r' + quote
@@ -1018,12 +1022,13 @@ class tpm(tpm_abstract.AbstractTPM):
         retDict = self.__run(command, lock=False)
         return retDict
 
-    def _tpm2_checkquote(self, aikTpmFromRegistrar, quote, nonce, hash_alg):
+    def _tpm2_checkquote(self, aikTpmFromRegistrar, quote, nonce, hash_alg, compressed):
         """Write the files from data returned from tpm2_quote for running tpm2_checkquote
         :param aikTpmFromRegistrar: AIK used to generate the quote and is needed for verifying it now.
         :param quote: quote data in the format 'r<b64-compressed-quoteblob>:<b64-compressed-sigblob>:<b64-compressed-pcrblob>
         :param nonce: nonce that was used to create the quote
         :param hash_alg: the hash algorithm that was used
+        :param compressed: if the quote data is compressed with zlib or not
         :returns: Returns the 'retout' from running tpm2_checkquote and True in case of success, None and False in case of error.
         This function throws an Exception on bad input.
         """
@@ -1042,9 +1047,16 @@ class tpm(tpm_abstract.AbstractTPM):
         if len(quote_tokens) < 3:
             raise Exception("Quote is not compound! %s" % quote)
 
-        quoteblob = zlib.decompress(base64.b64decode(quote_tokens[0]))
-        sigblob = zlib.decompress(base64.b64decode(quote_tokens[1]))
-        pcrblob = zlib.decompress(base64.b64decode(quote_tokens[2]))
+        quoteblob = base64.b64decode(quote_tokens[0])
+        sigblob = base64.b64decode(quote_tokens[1])
+        pcrblob = base64.b64decode(quote_tokens[2])
+
+        if compressed:
+            logger.warning("Decompressing quote data which is unsafe!")
+            quoteblob = zlib.decompress(quoteblob)
+            sigblob = zlib.decompress(sigblob)
+            pcrblob = zlib.decompress(pcrblob)
+
 
         qfd = sfd = pfd = afd = -1
         quoteFile = None
@@ -1100,12 +1112,12 @@ class tpm(tpm_abstract.AbstractTPM):
 
     def check_quote(self, agentAttestState, nonce, data, quote, aikTpmFromRegistrar, tpm_policy={},
                     ima_measurement_list=None, allowlist={}, hash_alg=None, ima_keyrings=None,
-                    mb_measurement_list=None, mb_refstate=None) -> Failure:
+                    mb_measurement_list=None, mb_refstate=None, compressed=False) -> Failure:
         failure = Failure(Component.QUOTE_VALIDATION)
         if hash_alg is None:
             hash_alg = self.defaults['hash']
 
-        retout, success = self._tpm2_checkquote(aikTpmFromRegistrar, quote, nonce, hash_alg)
+        retout, success = self._tpm2_checkquote(aikTpmFromRegistrar, quote, nonce, hash_alg, compressed)
         if not success:
             # If the quote validation fails we will skip all other steps therefore this failure is irrecoverable.
             failure.add_event("quote_validation", {"message": "Quote validation using tpm2-tools", "data": retout}, False)

programming_language: Python