CVE-2021-3979: Improper Authentication (CWE-287)
value: A key length flaw was found in Red Hat Ceph Storage. An attacker can exploit the fact that the key length is incorrectly passed in an encryption algorithm to create a non random key, which is weaker and can be exploited for loss of confidentiality and integrity on encrypted disks.
CWE description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
CVSS v3 base score: 6.5
Published: 2022-08-25T20:15Z
Repository: https://github.com/ceph/ceph
Language: Python
Files changed: src/ceph-volume/ceph_volume/tests/util/test_encryption.py, src/ceph-volume/ceph_volume/util/encryption.py
Commit date: 2022-02-10 02:39:03 +0100
Commit message: ceph-volume: honour osd_dmcrypt_key_size option

ceph-volume doesn't honour osd_dmcrypt_key_size.
It means the default size is always applied.

It also changes the default value in `get_key_size_from_conf()`

From cryptsetup manpage:

> For XTS mode you can optionally set a key size of 512 bits with the -s option.

Using more than 512bits will end up with the following error message:

```
Key size in XTS mode must be 256 or 512 bits.
```

Fixes: https://tracker.ceph.com/issues/54006

Signed-off-by: Guillaume Abrioux <gabrioux@redhat.com>

Diff: diff --git a/src/ceph-volume/ceph_volume/tests/util/test_encryption.py b/src/ceph-volume/ceph_volume/tests/util/test_encryption.py
index e1420b440d3..c86dc50b7c7 100644
--- a/src/ceph-volume/ceph_volume/tests/util/test_encryption.py
+++ b/src/ceph-volume/ceph_volume/tests/util/test_encryption.py
@@ -1,7 +1,33 @@
 from ceph_volume.util import encryption
+import base64
 
+class TestGetKeySize(object):
+    def test_get_size_from_conf_default(self, conf_ceph_stub):
+        conf_ceph_stub('''
+        [global]
+        fsid=asdf
+        ''')
+        assert encryption.get_key_size_from_conf() == '512'
+
+    def test_get_size_from_conf_custom(self, conf_ceph_stub):
+        conf_ceph_stub('''
+        [global]
+        fsid=asdf
+        [osd]
+        osd_dmcrypt_key_size=256
+        ''')
+        assert encryption.get_key_size_from_conf() == '256'
+
+    def test_get_size_from_conf_custom_invalid(self, conf_ceph_stub):
+        conf_ceph_stub('''
+        [global]
+        fsid=asdf
+        [osd]
+        osd_dmcrypt_key_size=1024
+        ''')
+        assert encryption.get_key_size_from_conf() == '512'
 
 class TestStatus(object):
 
     def test_skips_unuseful_lines(self, stub_call):
         out = ['some line here', '  device: /dev/sdc1']
@@ -35,19 +61,8 @@ class TestDmcryptClose(object):
         assert fake_run.calls == []
 
 
 class TestDmcryptKey(object):
 
-    def test_dmcrypt_with_default_size(self, conf_ceph_stub):
-        conf_ceph_stub('[global]\nfsid=asdf-lkjh')
-        result = encryption.create_dmcrypt_key()
-        assert len(result) == 172
-
-    def test_dmcrypt_with_custom_size(self, conf_ceph_stub):
-        conf_ceph_stub('''
-        [global]
-        fsid=asdf
-        [osd]
-        osd_dmcrypt_size=8
-        ''')
+    def test_dmcrypt(self):
         result = encryption.create_dmcrypt_key()
-        assert len(result) == 172
+        assert len(base64.b64decode(result)) == 128
diff --git a/src/ceph-volume/ceph_volume/util/encryption.py b/src/ceph-volume/ceph_volume/util/encryption.py
index 72a0ccf121e..2a2c03337b6 100644
--- a/src/ceph-volume/ceph_volume/util/encryption.py
+++ b/src/ceph-volume/ceph_volume/util/encryption.py
@@ -7,25 +7,33 @@ from ceph_volume.util.device import Device
 from .prepare import write_keyring
 from .disk import lsblk, device_family, get_part_entry_type
 
 logger = logging.getLogger(__name__)
 
-
-def create_dmcrypt_key():
+def get_key_size_from_conf():
     """
-    Create the secret dm-crypt key used to decrypt a device.
+    Return the osd dmcrypt key size from config file.
+    Default is 512.
     """
-    # get the customizable dmcrypt key size (in bits) from ceph.conf fallback
-    # to the default of 1024
-    dmcrypt_key_size = conf.ceph.get_safe(
+    default_key_size = '512'
+    key_size = conf.ceph.get_safe(
         'osd',
         'osd_dmcrypt_key_size',
-        default=1024,
-    )
-    # The size of the key is defined in bits, so we must transform that
-    # value to bytes (dividing by 8) because we read in bytes, not bits
-    random_string = os.urandom(int(dmcrypt_key_size / 8))
+        default='512')
+
+    if key_size not in ['256', '512']:
+        logger.warning(("Invalid value set for osd_dmcrypt_key_size ({}). "
+                        "Falling back to {}bits".format(key_size, default_key_size)))
+        return default_key_size
+
+    return key_size
+
+def create_dmcrypt_key():
+    """
+    Create the secret dm-crypt key (KEK) used to encrypt/decrypt the Volume Key.
+    """
+    random_string = os.urandom(128)
     key = base64.b64encode(random_string).decode('utf-8')
     return key
 
 
 def luks_format(key, device):
@@ -36,10 +44,12 @@ def luks_format(key, device):
     :param device: Absolute path to device
     """
     command = [
         'cryptsetup',
         '--batch-mode', # do not prompt
+        '--key-size',
+        get_key_size_from_conf(),
         '--key-file', # misnomer, should be key
         '-',          # because we indicate stdin for the key here
         'luksFormat',
         device,
     ]
@@ -81,10 +91,12 @@ def luks_open(key, device, mapping):
     :param device: absolute path to device
     :param mapping: mapping name used to correlate device. Usually a UUID
     """
     command = [
         'cryptsetup',
+        '--key-size',
+        get_key_size_from_conf(),
         '--key-file',
         '-',
         '--allow-discards',  # allow discards (aka TRIM) requests for device
         'luksOpen',
         device,

Vulnerable code:     def test_dmcrypt_with_default_size(self, conf_ceph_stub):
        conf_ceph_stub('[global]\nfsid=asdf-lkjh')
        result = encryption.create_dmcrypt_key()
        assert len(result) == 172

    def test_dmcrypt_with_custom_size(self, conf_ceph_stub):
        conf_ceph_stub('''
        [global]
        fsid=asdf
        [osd]
        osd_dmcrypt_size=8
        ''')
        assert len(result) == 172

def create_dmcrypt_key():
    Create the secret dm-crypt key used to decrypt a device.
    # get the customizable dmcrypt key size (in bits) from ceph.conf fallback
    # to the default of 1024
    dmcrypt_key_size = conf.ceph.get_safe(
        default=1024,
    )
    # The size of the key is defined in bits, so we must transform that
    # value to bytes (dividing by 8) because we read in bytes, not bits
    random_string = os.urandom(int(dmcrypt_key_size / 8))
Fixed code: import base64
class TestGetKeySize(object):
    def test_get_size_from_conf_default(self, conf_ceph_stub):
        conf_ceph_stub('''
        [global]
        fsid=asdf
        ''')
        assert encryption.get_key_size_from_conf() == '512'

    def test_get_size_from_conf_custom(self, conf_ceph_stub):
        conf_ceph_stub('''
        [global]
        fsid=asdf
        [osd]
        osd_dmcrypt_key_size=256
        ''')
        assert encryption.get_key_size_from_conf() == '256'

    def test_get_size_from_conf_custom_invalid(self, conf_ceph_stub):
        conf_ceph_stub('''
        [global]
        fsid=asdf
        [osd]
        osd_dmcrypt_key_size=1024
        ''')
        assert encryption.get_key_size_from_conf() == '512'
    def test_dmcrypt(self):
        assert len(base64.b64decode(result)) == 128
def get_key_size_from_conf():
    Return the osd dmcrypt key size from config file.
    Default is 512.
    default_key_size = '512'
    key_size = conf.ceph.get_safe(
        default='512')

    if key_size not in ['256', '512']:
        logger.warning(("Invalid value set for osd_dmcrypt_key_size ({}). "
                        "Falling back to {}bits".format(key_size, default_key_size)))
        return default_key_size

    return key_size

def create_dmcrypt_key():
    """
    Create the secret dm-crypt key (KEK) used to encrypt/decrypt the Volume Key.
    """
    random_string = os.urandom(128)
        '--key-size',
        get_key_size_from_conf(),
        '--key-size',
        get_key_size_from_conf(),