CVE-2022-23949: Authentication Bypass by Spoofing (CWE-290)
value: In Keylime before 6.3.0, unsanitized UUIDs can be passed by a rogue agent and can lead to log spoofing on the verifier and registrar.
CWE description: This attack-focused weakness is caused by incorrectly implemented authentication schemes that are subject to spoofing attacks.
CVSS v3 base score: 7.5
Published: 2022-09-21T19:15Z
Repository: https://github.com/keylime/keylime
Language: Python
Files changed: keylime/cloud_verifier_common.py, keylime/common/validators.py, keylime/config.py, keylime/ima.py, keylime/keylime_agent.py, test/test_validators.py
Commit date: 2022-01-27 11:09:26 -0500
Commit message: validators: create validators module

Move from the config module some validators, and create the validators
module.  Add tests for them.

Signed-off-by: Alberto Planas <aplanas@suse.com>

Diff: diff --git a/keylime/cloud_verifier_common.py b/keylime/cloud_verifier_common.py
index 6c004b3..94967f9 100644
--- a/keylime/cloud_verifier_common.py
+++ b/keylime/cloud_verifier_common.py
@@ -14,11 +14,11 @@ from keylime import json
 from keylime import revocation_notifier
 from keylime.agentstates import AgentAttestStates
 from keylime.failure import Failure, Component
 from keylime.tpm.tpm_main import tpm
 from keylime.tpm.tpm_abstract import TPM_Utilities
-from keylime.common import algorithms
+from keylime.common import algorithms, validators
 from keylime import ima_file_signatures
 
 # setup logging
 logger = keylime_logging.init_logging('cloudverifier_common')
 
@@ -298,10 +298,10 @@ def validate_agent_data(agent_data):
 
     # validate that the allowlist is proper JSON
     lists = json.loads(agent_data['allowlist'])
 
     # Validate exlude list contains valid regular expressions
-    is_valid, _, err_msg = config.valid_exclude_list(lists.get('exclude'))
+    is_valid, _, err_msg = validators.valid_exclude_list(lists.get('exclude'))
     if not is_valid:
         err_msg += " Exclude list regex is misformatted. Please correct the issue and try again."
 
     return is_valid, err_msg
diff --git a/keylime/common/validators.py b/keylime/common/validators.py
new file mode 100644
index 0000000..acc67e0
--- /dev/null
+++ b/keylime/common/validators.py
@@ -0,0 +1,34 @@
+"""Validators module."""
+import re
+
+
+def valid_regex(regex):
+    """Check if string is a valid regular expression."""
+    if regex is None:
+        return True, None, None
+
+    try:
+        compiled_regex = re.compile(regex)
+    except re.error as regex_err:
+        err = "Invalid regex: " + regex_err.msg + "."
+        return False, None, err
+
+    return True, compiled_regex, None
+
+
+def valid_exclude_list(exclude_list):
+    """Check if the list is composed of valid regex."""
+    if not exclude_list:
+        return True, None, None
+
+    combined_regex = "(" + ")|(".join(exclude_list) + ")"
+    return valid_regex(combined_regex)
+
+
+def valid_hex(value):
+    """Check if the string is a valid hex number representation."""
+    try:
+        int(value, 16)
+    except Exception:
+        return False
+    return True
diff --git a/keylime/config.py b/keylime/config.py
index db988df..0bfaa78 100644
--- a/keylime/config.py
+++ b/keylime/config.py
@@ -3,11 +3,10 @@ SPDX-License-Identifier: Apache-2.0
 Copyright 2017 Massachusetts Institute of Technology.
 '''
 import os
 import os.path
 import configparser
-import re
 from typing import Optional
 
 import yaml
 try:
     from yaml import CSafeLoader as SafeLoader
@@ -177,41 +176,10 @@ def yaml_to_dict(arry, add_newlines=True, logger=None) -> Optional[dict]:
         if logger is not None:
             logger.warning("Could not load yaml as dict: %s", str(err))
     return None
 
 
-def valid_exclude_list(exclude_list):
-    if not exclude_list:
-        return True, None, None
-
-    combined_regex = "(" + ")|(".join(exclude_list) + ")"
-    return valid_regex(combined_regex)
-
-
-def valid_regex(regex):
-    if regex is None:
-        return True, None, None
-
-    try:
-        compiled_regex = re.compile(regex)
-    except re.error as regex_err:
-        err = "Invalid regex: " + regex_err.msg + "."
-        return False, None, err
-
-    return True, compiled_regex, None
-
-
-def valid_hex(value: str):
-    if not value.isalnum():
-        return False
-    try:
-        int(value, 16)
-        return True
-    except ValueError:
-        return False
-
-
 if STUB_IMA:
     IMA_ML = '../scripts/ima/ascii_runtime_measurements'
 else:
     IMA_ML = '/sys/kernel/security/ima/ascii_runtime_measurements'
 
diff --git a/keylime/ima.py b/keylime/ima.py
index d31dc58..c892e0b 100644
--- a/keylime/ima.py
+++ b/keylime/ima.py
@@ -13,15 +13,15 @@ import json
 import datetime
 import functools
 
 from keylime import config
 from keylime import gpg
-from keylime import keylime_logging
 from keylime import ima_ast
-from keylime.agentstates import AgentAttestState
 from keylime import ima_file_signatures
-from keylime.common.algorithms import Hash
+from keylime import keylime_logging
+from keylime.agentstates import AgentAttestState
+from keylime.common import algorithms, validators
 from keylime.failure import Failure, Component
 
 
 logger = keylime_logging.init_logging('ima')
 
@@ -221,28 +221,27 @@ def _process_measurement_list(agentAttestState, lines, hash_alg, lists=None, m2w
         exclude_list = lists['exclude']
     else:
         allow_list = None
         exclude_list = None
 
-    ima_log_hash_alg = Hash.SHA1
+    ima_log_hash_alg = algorithms.Hash.SHA1
     if allow_list is not None:
         try:
-            ima_log_hash_alg = Hash(allow_list["ima"]["log_hash_alg"])
+            ima_log_hash_alg = algorithms.Hash(allow_list["ima"]["log_hash_alg"])
         except ValueError:
             logger.warning("Specified IMA log hash algorithm %s is not a valid algorithm! Defaulting to SHA1.",
                            allow_list["ima"]["log_hash_alg"])
 
-
     if boot_aggregates and allow_list:
         if 'boot_aggregate' not in allow_list['hashes'] :
             allow_list['hashes']['boot_aggregate'] = []
         for alg in boot_aggregates.keys() :
             for val in boot_aggregates[alg] :
                 if val not in allow_list['hashes']['boot_aggregate'] :
                     allow_list['hashes']['boot_aggregate'].append(val)
 
-    is_valid, compiled_regex, err_msg = config.valid_exclude_list(exclude_list)
+    is_valid, compiled_regex, err_msg = validators.valid_exclude_list(exclude_list)
     if not is_valid:
         # This should not happen as the exclude list has already been validated
         # by the verifier before acceping it. This is a safety net just in case.
         err_msg += " Exclude list will be ignored."
         logger.error(err_msg)
@@ -310,11 +309,11 @@ def _process_measurement_list(agentAttestState, lines, hash_alg, lists=None, m2w
 
     return codecs.encode(running_hash, 'hex').decode('utf-8'), failure
 
 
 def process_measurement_list(agentAttestState, lines, lists=None, m2w=None, pcrval=None, ima_keyrings=None,
-                             boot_aggregates=None, hash_alg=Hash.SHA1):
+                             boot_aggregates=None, hash_alg=algorithms.Hash.SHA1):
     failure = Failure(Component.IMA)
     try:
         running_hash, failure = _process_measurement_list(agentAttestState, lines, hash_alg, lists=lists, m2w=m2w,
                                                           pcrval=pcrval, ima_keyrings=ima_keyrings,
                                                           boot_aggregates=boot_aggregates)
diff --git a/keylime/keylime_agent.py b/keylime/keylime_agent.py
index 4712407..bf41328 100644
--- a/keylime/keylime_agent.py
+++ b/keylime/keylime_agent.py
@@ -41,11 +41,11 @@ from keylime import json
 from keylime import revocation_notifier
 from keylime import registrar_client
 from keylime import secure_mount
 from keylime import web_util
 from keylime import api_version as keylime_api_version
-from keylime.common import algorithms
+from keylime.common import algorithms, validators
 from keylime.tpm.tpm_main import tpm
 from keylime.tpm.tpm_abstract import TPM_Utilities
 from keylime.tpm.tpm2_objects import pubkey_from_tpm2b_public
 
 # Configure logger
@@ -129,11 +129,11 @@ class Handler(BaseHTTPRequestHandler):
                     self, 400, "nonce not provided as an HTTP parameter in request")
                 return
 
             # Sanitization assurance (for tpm.run() tasks below)
             if not (nonce.isalnum() and
-                    (pcrmask is None or config.valid_hex(pcrmask)) and
+                    (pcrmask is None or validators.valid_hex(pcrmask)) and
                     ima_ml_entry.isalnum()):
                 logger.warning('GET quote returning 400 response. parameters should be strictly alphanumeric')
                 web_util.echo_json_response(
                     self, 400, "parameters should be strictly alphanumeric")
                 return
diff --git a/test/test_validators.py b/test/test_validators.py
new file mode 100644
index 0000000..b925ec3
--- /dev/null
+++ b/test/test_validators.py
@@ -0,0 +1,82 @@
+import unittest
+
+from keylime.common import validators
+
+
+class TestValidRegex(unittest.TestCase):
+    """Tests for valid_regex."""
+
+    def test_none(self):
+        """Check that None is a valid regex."""
+        self.assertEqual(validators.valid_regex(None), (True, None, None))
+
+    def test_valid(self):
+        """Check a well formed regex."""
+        value = validators.valid_regex(r"a.*")
+        self.assertTrue(value[0])
+        self.assertEqual(value[1].pattern, r"a.*")
+        self.assertEqual(value[2], None)
+
+    def test_invalid(self):
+        """Check a not valid regex."""
+        value = validators.valid_regex(r"a[")
+        self.assertEqual(
+            value, (False, None, "Invalid regex: unterminated character set.")
+        )
+
+
+class TestValidExcludeList(unittest.TestCase):
+    """Tests for valid_exclude_list."""
+
+    def test_none(self):
+        """Check that the empty list is valid."""
+        self.assertEqual(validators.valid_exclude_list(None), (True, None, None))
+
+    def test_single(self):
+        """Check a single exclude list element."""
+        value = validators.valid_exclude_list([r"a.*"])
+        self.assertTrue(value[0])
+        self.assertEqual(value[1].pattern, r"(a.*)")
+        self.assertEqual(value[2], None)
+
+    def test_multi(self):
+        """Check a multiple elements exclude list."""
+        value = validators.valid_exclude_list([r"a.*", r"b.*"])
+        self.assertTrue(value[0])
+        self.assertEqual(value[1].pattern, r"(a.*)|(b.*)")
+        self.assertEqual(value[2], None)
+
+    def test_invalid(self):
+        """Check an invalid exclude list."""
+        value = validators.valid_exclude_list([r"a["])
+        self.assertEqual(
+            value, (False, None, "Invalid regex: unterminated character set.")
+        )
+
+
+class TestValidHex(unittest.TestCase):
+    """Tests for valid_hex."""
+
+    def test_none(self):
+        """Check that None is not valid."""
+        self.assertFalse(validators.valid_hex(None))
+
+    def test_empty(self):
+        """Check that the empty string is not valid."""
+        self.assertFalse(validators.valid_hex(""))
+
+    def test_valid_lower(self):
+        """Check a valid lower case hexadecimal number."""
+        self.assertTrue(validators.valid_hex("123abc"))
+
+    def test_valid_upper(self):
+        """Check a valid upper case hexadecimal number."""
+        self.assertTrue(validators.valid_hex("123ABC"))
+
+    def test_invalid(self):
+        """Check and invalid hexadecimal number."""
+        self.assertFalse(validators.valid_hex("123xyz"))
+
+
+if __name__ == "__main__":
+    unittest.main()

Vulnerable code: from keylime.common import algorithms
    is_valid, _, err_msg = config.valid_exclude_list(lists.get('exclude'))
import re
def valid_exclude_list(exclude_list):
    if not exclude_list:
        return True, None, None

    combined_regex = "(" + ")|(".join(exclude_list) + ")"
    return valid_regex(combined_regex)


def valid_regex(regex):
    if regex is None:
        return True, None, None

    try:
        compiled_regex = re.compile(regex)
    except re.error as regex_err:
        err = "Invalid regex: " + regex_err.msg + "."
        return False, None, err

    return True, compiled_regex, None


def valid_hex(value: str):
    if not value.isalnum():
        return False
    try:
        int(value, 16)
        return True
    except ValueError:
        return False


from keylime import keylime_logging
from keylime.agentstates import AgentAttestState
from keylime.common.algorithms import Hash
    ima_log_hash_alg = Hash.SHA1
            ima_log_hash_alg = Hash(allow_list["ima"]["log_hash_alg"])

    is_valid, compiled_regex, err_msg = config.valid_exclude_list(exclude_list)
                             boot_aggregates=None, hash_alg=Hash.SHA1):
from keylime.common import algorithms
                    (pcrmask is None or config.valid_hex(pcrmask)) and
Fixed code: from keylime.common import algorithms, validators
    is_valid, _, err_msg = validators.valid_exclude_list(lists.get('exclude'))
"""Validators module."""
import re


def valid_regex(regex):
    """Check if string is a valid regular expression."""
    if regex is None:
        return True, None, None

    try:
        compiled_regex = re.compile(regex)
    except re.error as regex_err:
        err = "Invalid regex: " + regex_err.msg + "."
        return False, None, err

    return True, compiled_regex, None


def valid_exclude_list(exclude_list):
    """Check if the list is composed of valid regex."""
    if not exclude_list:
        return True, None, None

    combined_regex = "(" + ")|(".join(exclude_list) + ")"
    return valid_regex(combined_regex)


def valid_hex(value):
    """Check if the string is a valid hex number representation."""
    try:
        int(value, 16)
    except Exception:
        return False
    return True
from keylime import keylime_logging
from keylime.agentstates import AgentAttestState
from keylime.common import algorithms, validators
    ima_log_hash_alg = algorithms.Hash.SHA1
            ima_log_hash_alg = algorithms.Hash(allow_list["ima"]["log_hash_alg"])
    is_valid, compiled_regex, err_msg = validators.valid_exclude_list(exclude_list)
                             boot_aggregates=None, hash_alg=algorithms.Hash.SHA1):
from keylime.common import algorithms, validators
                    (pcrmask is None or validators.valid_hex(pcrmask)) and
import unittest

from keylime.common import validators


class TestValidRegex(unittest.TestCase):
    """Tests for valid_regex."""

    def test_none(self):
        """Check that None is a valid regex."""
        self.assertEqual(validators.valid_regex(None), (True, None, None))

    def test_valid(self):
        """Check a well formed regex."""
        value = validators.valid_regex(r"a.*")
        self.assertTrue(value[0])
        self.assertEqual(value[1].pattern, r"a.*")
        self.assertEqual(value[2], None)

    def test_invalid(self):
        """Check a not valid regex."""
        value = validators.valid_regex(r"a[")
        self.assertEqual(
            value, (False, None, "Invalid regex: unterminated character set.")
        )


class TestValidExcludeList(unittest.TestCase):
    """Tests for valid_exclude_list."""

    def test_none(self):
        """Check that the empty list is valid."""
        self.assertEqual(validators.valid_exclude_list(None), (True, None, None))

    def test_single(self):
        """Check a single exclude list element."""
        value = validators.valid_exclude_list([r"a.*"])
        self.assertTrue(value[0])
        self.assertEqual(value[1].pattern, r"(a.*)")
        self.assertEqual(value[2], None)

    def test_multi(self):
        """Check a multiple elements exclude list."""
        value = validators.valid_exclude_list([r"a.*", r"b.*"])
        self.assertTrue(value[0])
        self.assertEqual(value[1].pattern, r"(a.*)|(b.*)")
        self.assertEqual(value[2], None)

    def test_invalid(self):
        """Check an invalid exclude list."""
        value = validators.valid_exclude_list([r"a["])
        self.assertEqual(
            value, (False, None, "Invalid regex: unterminated character set.")
        )


class TestValidHex(unittest.TestCase):
    """Tests for valid_hex."""

    def test_none(self):
        """Check that None is not valid."""
        self.assertFalse(validators.valid_hex(None))

    def test_empty(self):
        """Check that the empty string is not valid."""
        self.assertFalse(validators.valid_hex(""))

    def test_valid_lower(self):
        """Check a valid lower case hexadecimal number."""
        self.assertTrue(validators.valid_hex("123abc"))

    def test_valid_upper(self):
        """Check a valid upper case hexadecimal number."""
        self.assertTrue(validators.valid_hex("123ABC"))

    def test_invalid(self):
        """Check and invalid hexadecimal number."""
        self.assertFalse(validators.valid_hex("123xyz"))


if __name__ == "__main__":
    unittest.main()