Playing around with LVM, LUKS and the device mapper

I finally decided myself to switch my laptop to using a LVM over LUKS setup instead of LVM alone. The problem it involves is that there is currently no way that I know to do this in-place. I do have an external disk on which I could pvmove everything and back, but that would not be very challenging ;). So I started wondering how doable that would be.

LUKS has a 1032 sectors (a sector being 512 bytes) overhead in which it store the LUKS header. Following the header, encrypted data just takes as much space as its decrypted counterpart, linearly. So all it would take to do in-place LUKS conversion would be to move data by 1032 sectors, and encrypt it. Which means the main problem is to have 1032 sectors free after the current volume.

Fortunately, LVM doesn't take up all the space on the partition a physical volume is setup, except if you're unlucky or created partitions specifically so that it uses all the space. Because LVM physical volumes use the space by big chunks, 4MB by default), if the size of the partition is not 4MB aligned (modulo the LVM header), you have some free space between the physical volume end and the partition end. We thus need this free space to be greater than 1032 sectors. On my laptop, there is more than 1.8MB free there, so there would be no problem.
Note the debian-installer seems to do more than a simple cryptsetup luksFormat, as LUKS volumes it creates have an overhead of 2056 sectors instead of the 1032 sectors I got creating a volume by hand.

Next problem is how to move the data and encrypt it in-place. Bonus points if that can be done without unmounting the filesystems.

My first guess was to play around with the device mapper and a specially crafted FUSE filesystem to create a fake LUKS volume that could be half encrypted and half not, and would evolve, with time, into a fully encrypted LUKS volume. Since I didn't know much about the device mapper, I looked at it a bit, and discovered a FUSE intermediate would be unnecessary: not only can you remap on-the-fly (which I already guessed was somehow possible, considering how LVM can do it, though I didn't know much about the interaction between LVM and the d-m), but you can also mix dm-crypt and linear chunks in the same d-m table.

It also appears LVM over LUKS has an overhead that is actually not necessary. How sad both use the device mapper but yet can't do an efficient setup with it.

When you setup LVM over LUKS, you open a LUKS volume, which will create a dm-crypt device mapper, and use this device mapper as the physical volume for LVM. Logical volumes created with LVM are actually device mappers using the physical volume as destination device. When you access a block on an LVM logical volume, your access is first mapped through the LV device mapper and then mapped again through the dm-crypt mapper. So it goes through 2 device mappers, but it could do with only one: the linear mapping used in most of the cases with LVM could be replaced by crypt mappings, that are just as linear, but also handle the encryption/decryption part.

Playing around under qemu with a freshly installed etch system, I was able to implement this and came up with the following script. It should work with most setups, but use at your own risk, it comes with no warranty.

MAJOR=$(awk '/device-mapper/{print $1;exit}' /proc/devices)
dmsetup ls --target linear | while read map extra; do
  TABLE=$(dmsetup table $map | while IFS=" :" read start length linear major minor offset; do
    map2=
    if [ "$major" -eq "$MAJOR" ]; then
      IFS=":" read map2 extra <<EOF
$(dmsetup info -c --noheadings -j $major -m $minor)
EOF
    fi
    if [ -z "$map2" ] || [ $(dmsetup table "$map2" | wc -l) -gt 1 ] || [ $(dmsetup table --target crypt "$map2" | wc -l) -eq 0 ]; then
      echo $start $length $linear $major:$minor $offset
    else
      IFS=" " read cstart clength crypt format key IVoff dev coffset <<EOF
$(dmsetup table "$map2")
EOF
      echo $start $length crypt $format $key $(expr $IVoff + $offset) $dev $(expr $coffset + $offset)
    fi
  done)
  if (dmsetup table $map; echo "$TABLE") | sort | uniq -c | grep -v "^ *2 " > /dev/null 2>&1;
  then
    echo Diverting $map
    dmsetup reload $map <<EOF
$TABLE
EOF
    dmsetup resume $map
  fi
done

[ Update: There was a dmsetup resume missing in the script ]

After running this script, you should be able to run cryptsetup luksClose on your LUKS volume. Once you've done it, pvdisplay and other LVM tools will show you nothing, because the physical volume will just have disappeared... but everything will still be in place. Note that if you leave the LUKs volume open, you'd better be careful NOT to do operations with LVM (such as pvmove) because it could break everything.

Note that on sid, you would need to add a --showkey option into the $(dmsetup table "$map2") line. This option is necessary for dmsetup to display the encryption keys.

I wonder if LVM upstream would consider supporting LUKS volumes directly...

Anyways, back to the in-place conversion to LVM over LUKs. The idea would be to:

  • Create a file the size of the LUKS headers + one block.
  • Create a loopback device for this file.
  • luksFormat the loopback device.
  • luksOpen the loopback device, so that dmsetup can give us the key to use. I wonder if there are ways to get the key not involving the creation of the device mapper.
  • luksClose it, because we actually don't need it
  • Create a device mapper that linearly maps the whole LVM physical volume (i.e the real partition, but without the empty tail).
  • Remap the LVM device mappers so that they use our own device mapper instead of the partition as physical volume.
  • Here is the trickiest part, and I still have to take a look at how pvmove does to know exactly how this can be done (I guess it will require an additional device mapper): move data chunk by chunk (offsetting with the size of the LUKS headers), starting from the end of the LVM physical volume, and encrypt at the same time. Once a chunk is moved, adjust our device mapper so that it maps to the linear unmoved data followed by the moved encrypted data. As I said earlier, this is a setup that works.
  • Once everything is moved, we are in a situation where our device mapper is a single crypt mapping, in the same shape as a luksOpen would have made it. We can now copy the LUKS headers at the beginning of the partition, and add the appropriate configuration to /etc/crypttab.

If I got it all right, both LUKS and LVM should be believing they have done this setup themselves, so unmounting, lvchange, etc. should just be fine. It might be necessary to regenerate the initramfs, though.

I'll tell you when I fuck up my hard disk try it.

To be continued...

2007-06-20 19:37:16+0900

miscellaneous, p.d.o

Both comments and pings are currently closed.

One Response to “Playing around with LVM, LUKS and the device mapper”

  1. berto Says:

    Very interesting post!!