Skip to content
This repository has been archived by the owner on Jan 5, 2024. It is now read-only.

Detect different kinds of storage devices attached to sd-export-usb #16

Merged
merged 8 commits into from
Nov 7, 2019
Merged

Detect different kinds of storage devices attached to sd-export-usb #16

merged 8 commits into from
Nov 7, 2019

Conversation

sssoleileraaa
Copy link
Contributor

@sssoleileraaa sssoleileraaa commented Sep 23, 2019

Description

Resolves #18 (see #18 (comment)).

/cc @emkll

@eloquence
Copy link
Member

Moved back to "In development" for now. The plan of record discussed during standup today was to keep this branch alive for testing purposes, but to attempt a more complete resolution that will work for both print and export.

@eloquence
Copy link
Member

Even so, I can confirm that I was able to perform a successful export with this workaround in place! 🎉 I then unlocked the USB in Files in sd-export-usb, shut down the VM, and tried again -- and it stopped working, again at the first step, even though the bus numbering hadn't changed. I think that's #12 but I did not get any passphrase error messages in the UI. Baby steps :)

@emkll emkll self-requested a review September 26, 2019 16:49
@sssoleileraaa
Copy link
Contributor Author

I then unlocked the USB in Files in sd-export-usb, shut down the VM, and tried again -- and it stopped working, again at the first step, even though the bus numbering hadn't changed. I think that's #12 but I did not get any passphrase error messages in the UI. Baby steps :)

When you unlocked the USB in Files in sd-export-usb, did you tell Qubes to remember your passphrase forever? The workaround for #12 is to unplug and plug your transfer device back in. Not the best, but it has worked consistently for me.

As far as this workaround goes, I don't see why we should wait on merging a fix for a known issue, unless I'm underestimating the effort it takes to build and deploy.

try:
p = subprocess.check_output(["lsusb", "-s", "{}:".format(self.pci_bus_id)])
subprocess.check_output(["lsblk", "-p", "-o", "KNAME", DEVICE], stderr=subprocess.PIPE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @creviera this does indeed work reliably for disks!

The reason we used lsusb here is because we wanted to check_usb_connected function to be able to detect both printers and block devices.

The command here will return 32: lsblk: /dev/sda1/ is not a block device. I see 2 options:

a) search for a more reliable solution that works for printers and drives
b) we can separate the preflight checks for USB connected:
i. For disks, using the method described here
ii. For printers, using either lsusb or another method

what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, somehow I missed this comment! I seeeeeee. My vote goes to whatever stabilizes export the quickest, so I think that's b, and then we can research a.

@sssoleileraaa
Copy link
Contributor Author

a) search for a more reliable solution that works for printers and drives

sounds like this is the path the group is going down and @rmol has already worked on some useful code for getting busnum and devnum etc. which we can use to create a more generic solution for checking whether or not a usb disk drive OR a usb-connected printer is attached. Here is the snippet of code @rmol shared:

#!/usr/bin/env python3
import logging
import operator
import os
import re
EXPORT_CLASSES = {"07": "Printer", "08": "Storage"}
EXPORT_INTERFACE_CLASSES = EXPORT_CLASSES.keys()
VENDOR_ID_RE = re.compile("^(?P<id>[0-9A-Fa-f]{4})  (?P<name>.*)$")
PRODUCT_ID_RE = re.compile("^\t(?P<id>[0-9A-Fa-f]{4})  (?P<name>.*)$")
def load_product_info():
    info = {"vendors": {}, "products": {}}
    vendor_id = vendor_name = None
    product_id = product_name = None
    with open("/var/lib/usbutils/usb.ids") as f:
        for line in f:
            if line[0] == "#" or not line:
                continue
            p = PRODUCT_ID_RE.match(line)
            if p:
                product_id = p.group(1)
                product_name = p.group(2)
                info["products"][vendor_id][product_id] = product_name
            else:
                v = VENDOR_ID_RE.match(line)
                if v:
                    vendor_id = v.group(1)
                    vendor_name = v.group(2)
                    info["vendors"][vendor_id] = vendor_name
                    info["products"][vendor_id] = {}
                elif vendor_id:
                    # don't want all the class IDs and such below product info
                    break
    return info
def get_device_info(product_info, directory):
    interface_class_file = os.path.join(directory, "bInterfaceClass")
    try:
        if not os.path.isfile(interface_class_file):
            return None
        interface_class = open(interface_class_file).read().strip()
        if interface_class in EXPORT_INTERFACE_CLASSES:
            os.chdir(directory)
            parent = os.path.dirname(os.getcwd())
            bus_number = open(os.path.join(parent, "busnum")).read().strip()
            dev_number = open(os.path.join(parent, "devnum")).read().strip()
            vendor_id = open(os.path.join(parent, "idVendor")).read().strip()
            product_id = open(os.path.join(parent, "idProduct")).read().strip()
            info = {
                "bus": bus_number,
                "device": dev_number,
                "interface_class": EXPORT_CLASSES[interface_class],
                "product": product_info["products"].get(vendor_id, {}).get(product_id),
                "vendor": product_info["vendors"].get(vendor_id),
            }
            return info
    except Exception as e:
        logging.error("Could not read device info from %s: %s", directory, e)
def get_export_devices(product_info):
    export_devices = []
    for directory, subdirs, files in os.walk("/sys/bus/usb/devices"):
        for s in subdirs:
            s = os.path.join(directory, s)
            info = get_device_info(product_info, s)
            if info:
                export_devices.append(info)
    return export_devices
if __name__ == "__main__":
    product_info = load_product_info()
    export_devices = get_export_devices(product_info)
    export_devices.sort(key=operator.itemgetter("product"))
    export_devices.sort(key=operator.itemgetter("vendor"))
    export_devices.sort(key=operator.itemgetter("device"))
    export_devices.sort(key=operator.itemgetter("bus"))
    export_devices.sort(key=operator.itemgetter("interface_class"))
    for d in export_devices:
        print(
            "Bus: {bus:>2s} Device: {device:>2s} Type: {interface_class} "
            "Vendor: {vendor} Product: {product}".format(**d)
        )

I'm going to close this pr from my fork since I want to open it up for other people on the team to commit to the working branch and contribute directly.

@sssoleileraaa sssoleileraaa deleted the bus-id-workaround branch October 8, 2019 20:36
@sssoleileraaa sssoleileraaa restored the bus-id-workaround branch October 16, 2019 20:25
@sssoleileraaa
Copy link
Contributor Author

sssoleileraaa commented Oct 31, 2019

Reopening because:

  • lsusb -s [bus_id] -> lsblk -p -o KNAME /dev/sda1 more consistently works
    • if the partition is not /dev/sda1, then check_luks_volume will also fail, so this may need discussion
  • we are going an entirely different route for export (we don't have to concern ourselves with changing bus ids anymore 🎉 john is currently working on a script to auto-attach and remove --persistent when provisioning)
  • and...

The reason we used lsusb here is because we wanted to check_usb_connected function to be able to detect both printers and block devices.

The command here will return 32: lsblk: /dev/sda1/ is not a block device. I see 2 options:

a) search for a more reliable solution that works for printers and drives
b) we can separate the preflight checks for USB connected:
i. For disks, using the method described here
ii. For printers, using either lsusb or another method

what do you think?

After more discussion with @emkll we realized that the current code does not need to be used for print because we use lpinfo and return ERROR_PRINTER_NOT_FOUND if that returns "" for the uri or ERROR_PRINTER_URI if the call fails. Basically this code that is modified is not used by print.

@sssoleileraaa sssoleileraaa reopened this Oct 31, 2019
@eloquence
Copy link
Member

lsusb -s [bus_id] -> lsblk -p -o KNAME /dev/sda1 more consistently works

I've not yet gotten that to work, only when using /dev/sda as the device name instead /dev/sda1.

Testing with a LUKS device, I don't see any device files or lsblk entries for /dev/sda1 or a higher number, I just see /dev/sda. When the device is unlocked and mounted, I see an entry like this in the plain lsblk output

sda                                           8:16   1  14.4G  0 disk  
└─luks-69667749-86a2-49d5-956d-d754c7a98a9f 253:1    0  14.4G  0 crypt /media/erik/enc

(This is on Ubuntu, but the output on Qubes is essentially the same).

@sssoleileraaa
Copy link
Contributor Author

Ah, yes, I remember you showing me that last time and our idea was to check for sda instead of sda1. Then when it comes time to decrypt, we would make sure a) that there is only one partition under sda, and b) check if it's luks encrypted. I'm asking @kushaldas for review and feedback. He's testing now.

@kushaldas
Copy link
Contributor

Testing with a LUKS device, I don't see any device files or lsblk entries for /dev/sda1 or a higher number, I just see /dev/sda. When the device is unlocked and mounted, I see an entry like this in the plain lsblk output

This is because I think you have the whole device encrypted and then partitions inside of it. In other cases, we first create a partition and then encrypt it.

@kushaldas
Copy link
Contributor

Ah, yes, I remember you showing me that last time and our idea was to check for sda instead of sda1. Then when it comes time to decrypt, we would make sure a) that there is only one partition under sda, and b) check if it's luks encrypted. I'm asking @kushaldas for review and feedback. He's testing now.

Just checking for sda will give enough details to tell us that it is a storage device, and in future if you can loop over the partitions to find the first encrypted partition and stop the loop and go with decrypt, mounting the partition ... regular steps.

This PR is a good way to handle the issue in my mind.

@sssoleileraaa
Copy link
Contributor Author

sda 8:16 1 14.4G 0 disk
└─luks-69667749-86a2-49d5-956d-d754c7a98a9f 253:1 0 14.4G 0 crypt /media/erik/enc

@eloquence, looks like you're using the default lsblk output:

NAME                    MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda                       8:16   1  14.4G  0 disk  
└─luks-6966...          253:1    0  14.4G  0 crypt /media/erik/enc

If you do:

> sudo cryptsetup isLuks "sda"
> echo $?

do you get an output of 0?

@emkll
Copy link
Contributor

emkll commented Nov 1, 2019

Testing with a LUKS device, I don't see any device files or lsblk entries for /dev/sda1 or a higher number, I just see /dev/sda. When the device is unlocked and mounted, I see an entry like this in the plain lsblk output

When creating the export device, did you use MBR/DOS or EPT as the partitioning scheme? So far, I think we've all used the former (MBR/DOS) ? It would be interesting to see if this is what causes the inconsistency here.

I think the approach by @creviera of iterating over the partitions in sda makes sense.

(This is on Ubuntu, but the output on Qubes is essentially the same).

@eloquence (and potentially others): I would strongly advise to test these scripts/tooling under Debian/Qubes. Testing on multiple platforms (e.g. Ubuntu) introduces needless confusion to an already very complex flow. Some reasons come to mind:

  1. We want to ensure everyone is running the similar/identical package versions
  2. We want to ensure the system configuration is similar
  3. the kernel version/config is similar/identical (for example, network device names can change depending on kernel configurations)
  4. Especially when dealing with USB, having other devices plugged in may have side-effect (e.g. numbering)
  5. When unlocking partitions, you might accidentally cause breakage to your system. Isolating it to a VM is probably best

@eloquence
Copy link
Member

[tested in Qubes/Debian VM]

sudo cryptsetup isLuks "sda"
echo $?

That command results in "Device sda doesn't exist or access denied.", exit code 4. However, the command

sudo cryptsetup isLuks "/dev/sda"

results in exit code 0 (no message).

When creating the export device, did you use MBR/DOS or EPT as the partitioning scheme?

I don't recall; it was done through the GNOME Disk utility. I'll try to create a fresh one and write down the steps.

@eloquence
Copy link
Member

eloquence commented Nov 2, 2019

OK, the only way I've found to create a USB drive in gnome-disks that doesn't have a /dev/sda1 partition is as follows:

  1. Select the device
  2. Select "Format disk" from the top right hamburger menu
  3. Select "No partitioning (empty)" from the partitioning options
  4. Click the "Gears" icon
  5. Select "Format partition"
  6. Create a LUKS volume.

This results in a device like /dev/sda (yes, in Qubes) without a partition number. It is otherwise perfectly usable and will be auto-detected in GNOME etc. (Using GPT or MBR instead of the "No partitioning" option in step 3 both result in a /dev/sda1 partition.)

I suspect I created the USB drive that behaved similarly using the path above, but I don't know for sure.

@sssoleileraaa
Copy link
Contributor Author

sudo cryptsetup isLuks "/dev/sda"

results in exit code 0 (no message).

perfect, that's what I meant :]

I still need to resolve conflict but this should be ready for testing.

@sssoleileraaa
Copy link
Contributor Author

rebased and updated tests. ready for review.

@sssoleileraaa
Copy link
Contributor Author

Note: this implements part of our solution for #18 (we still need to remove persistent attachment from the Export VM and add the multiple-devices error, all of which will be done separately)

@eloquence eloquence changed the title workaround for bus id issue Detect storage devices attached to sd-export-usb using lsblk to improve reliability Nov 4, 2019
@sssoleileraaa
Copy link
Contributor Author

sssoleileraaa commented Nov 4, 2019

Test plan

This resolves the inconsistent bus ID ordering issue reported here: freedomofpress/securedrop-workstation#307 (comment) causing the lsusb check to fail when the bus id is reported different between the export vm and dom0. You will still need to reprovision sd-export-usb when the bus id changes until persistent attachment is removed, which John is working on. But you can stil see how this fixes the problem of export just not working when the bus ID ordering issue occurs.

Both Erik and I have been able to see this issue frequently, so if you have problems repro'ing based on the information in that issue linked above, please let one of us know.

Also please test and see Export now works in Erik's case where /dev/sda1 does not exist.

NAME                    MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda                       8:16   1  14.4G  0 disk  
└─luks-6966...          253:1    0  14.4G  0 crypt /media/erik/enc

Copy link
Contributor

@emkll emkll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @creviera
What partition scheme are you using? I am using a MBR formatted, ext-4 luks-encrypted partition (which is in sda1)
The initial preflight check works 🎉 . However, the preflight check for luks encryption fails. This is because it uses the device id which contains the partition number, and the root device is not a luks volume, in the context of an MBR-formatted drive:

user@sd-export-usb:~$ sudo cryptsetup isLuks /dev/sda
user@sd-export-usb:~$ echo $?
130
user@sd-export-usb:~$ sudo cryptsetup isLuks /dev/sda1
user@sd-export-usb:~$ echo $?
0
user@sd-export-usb:~$ lsblk
NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda       8:0    1 14.5G  0 disk 
└─sda1    8:1    1 14.5G  0 part 
xvda    202:0    0   10G  0 disk 
├─xvda1 202:1    0  200M  0 part 
├─xvda2 202:2    0    2M  0 part 
└─xvda3 202:3    0  9.8G  0 part /
xvdb    202:16   0    2G  0 disk /rw
xvdc    202:32   0   12G  0 disk 
├─xvdc1 202:33   0    1G  0 part [SWAP]
└─xvdc3 202:35   0   11G  0 part 
user@sd-export-usb:~$ 

In the interest of reducing confusion and the amount of possible code paths to check for encrypted device, as part of this PR, let's agree on and provide very high level documentation on what partitioning scheme, and which operating system we use to format and partition the drives. Otherwise, we will have inconsistent results, and may be difficult/time consuming to support all cases.

@sssoleileraaa
Copy link
Contributor Author

let's agree on and provide very high level documentation on what partitioning scheme, and which operating system we use to format and partition the drives

sgtm

@sssoleileraaa sssoleileraaa requested a review from emkll November 6, 2019 10:33
@sssoleileraaa sssoleileraaa changed the title Detect storage devices attached to sd-export-usb using lsblk to improve reliability Detect different kinds of storage devices attached to sd-export-usb Nov 6, 2019
@sssoleileraaa
Copy link
Contributor Author

I addressed the requested change to update the docs to describe what kind of export devices we support and how to create them.

I updated both the luks-encryption check and code to mount the device in order to support as many ways of creating luks-encrypted devices as I could think of using Disks and added support for full-disk encryption with luks as well.

Test this PR with:

  1. MBR-partitioned luks-encrypted device using Disks
  2. GPT-partitioned luks-encrypted device using Disks
  3. Full-disk luks encrypted device using cryptsetup
    See docs for more instructions on how to prepare your device to be a supported export device.

emkll
emkll previously approved these changes Nov 7, 2019
Copy link
Contributor

@emkll emkll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @creviera this works perfectly in my local testing, this is a very significant improvement 🎉

Testing was done as follows:

  • build deb package on this branch, and install in sd-export-template
  • Export works as expected with MBR + luks/ext4 parition
  • Export works as expected with GPT + luks/ext4 partition
  • Export works with FDE as instructed in the README changes introduced in this PR
  • Visual review of the diff

The two comments inline are so minor, I'll leave it at your option if you want to add a commit addressing these, or simply merging as-is.

I've discovered an (unrelated to this PR) bug in the client, the ticket is tracked in freedomofpress/securedrop-client#607

README.md Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
securedrop_export/export.py Show resolved Hide resolved
Copy link
Contributor

@emkll emkll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @creviera !

@emkll emkll merged commit 5bb6e85 into freedomofpress:master Nov 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Device detection in export VMs is unreliable
4 participants