CVE-2012-5625: Exposure of Sensitive Information to an Unauthorized Actor (CWE-200)
value: OpenStack Compute (Nova) Folsom before 2012.2.2 and Grizzly, when using libvirt and LVM backed instances, does not properly clear physical volume (PV) content when reallocating for instances, which allows attackers to obtain sensitive information by reading the memory of the previous logical volume (LV).
CWE description: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
Published: 2012-12-26T22:55Z
Repository: https://github.com/openstack/nova
Language: Python
Files changed: nova/virt/libvirt/utils.py
Commit date: 2012-12-11 16:21:34 +0100
Commit message: Don't leak info from libvirt LVM backed instances

* nova/virt/libvirt/utils.py (remove_logical_volumes):
Overwrite each logical volume with zero
(clear_logical_volume): LV obfuscation implementation.
(logical_volume_size): A utility function used by
clear_logical_volume()

Fixes bug: 1070539
Change-Id: I4e1024de8dfe9b0be3b0d6437c836d2042862f85

Diff: diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
index 523d4def3f..4b53664b82 100644
--- a/nova/virt/libvirt/utils.py
+++ b/nova/virt/libvirt/utils.py
@@ -170,12 +170,59 @@ def logical_volume_info(path):
         raise RuntimeError(_("Path %s must be LVM logical volume") % path)
 
     return dict(zip(*info))
 
 
+def logical_volume_size(path):
+    """Get logical volume size in bytes.
+
+    :param path: logical volume path
+    """
+    # TODO(p-draigbrady) POssibly replace with the more general
+    # use of blockdev --getsize64 in future
+    out, _err = execute('lvs', '-o', 'lv_size', '--noheadings', '--units',
+                        'b', '--nosuffix', path, run_as_root=True)
+
+    return int(out)
+
+
+def clear_logical_volume(path):
+    """Obfuscate the logical volume.
+
+    :param path: logical volume path
+    """
+    # TODO(p-draigbrady): We currently overwrite with zeros
+    # but we may want to make this configurable in future
+    # for more or less security conscious setups.
+
+    vol_size = logical_volume_size(path)
+    bs = 1024 * 1024
+    direct_flags = ('oflag=direct',)
+    remaining_bytes = vol_size
+
+    # The loop caters for versions of dd that
+    # don't support the iflag=count_bytes option.
+    while remaining_bytes:
+        zero_blocks = remaining_bytes / bs
+        seek_blocks = (vol_size - remaining_bytes) / bs
+        zero_cmd = ('dd', 'bs=%s' % bs,
+                    'if=/dev/zero', 'of=%s' % path,
+                    'seek=%s' % seek_blocks, 'count=%s' % zero_blocks)
+        zero_cmd += direct_flags
+        if zero_blocks:
+            utils.execute(*zero_cmd, run_as_root=True)
+        remaining_bytes %= bs
+        bs /= 1024  # Limit to 3 iterations
+        direct_flags = ()  # Only use O_DIRECT with initial block size
+
+
 def remove_logical_volumes(*paths):
     """Remove one or more logical volume."""
+
+    for path in paths:
+        clear_logical_volume(path)
+
     if paths:
         lvremove = ('lvremove', '-f') + paths
         execute(*lvremove, attempts=3, run_as_root=True)
 
 

Fixed code: def logical_volume_size(path):
    """Get logical volume size in bytes.

    :param path: logical volume path
    """
    # TODO(p-draigbrady) POssibly replace with the more general
    # use of blockdev --getsize64 in future
    out, _err = execute('lvs', '-o', 'lv_size', '--noheadings', '--units',
                        'b', '--nosuffix', path, run_as_root=True)

    return int(out)


def clear_logical_volume(path):
    """Obfuscate the logical volume.

    :param path: logical volume path
    """
    # TODO(p-draigbrady): We currently overwrite with zeros
    # but we may want to make this configurable in future
    # for more or less security conscious setups.

    vol_size = logical_volume_size(path)
    bs = 1024 * 1024
    direct_flags = ('oflag=direct',)
    remaining_bytes = vol_size

    # The loop caters for versions of dd that
    # don't support the iflag=count_bytes option.
    while remaining_bytes:
        zero_blocks = remaining_bytes / bs
        seek_blocks = (vol_size - remaining_bytes) / bs
        zero_cmd = ('dd', 'bs=%s' % bs,
                    'if=/dev/zero', 'of=%s' % path,
                    'seek=%s' % seek_blocks, 'count=%s' % zero_blocks)
        zero_cmd += direct_flags
        if zero_blocks:
            utils.execute(*zero_cmd, run_as_root=True)
        remaining_bytes %= bs
        bs /= 1024  # Limit to 3 iterations
        direct_flags = ()  # Only use O_DIRECT with initial block size



    for path in paths:
        clear_logical_volume(path)