| =============== | |
| QEMU TPM Device | |
| =============== | |
| Guest-side hardware interface | |
| ============================= | |
| TIS interface | |
| ------------- | |
| The QEMU TPM emulation implements a TPM TIS hardware interface | |
| following the Trusted Computing Group's specification "TCG PC Client | |
| Specific TPM Interface Specification (TIS)", Specification Version | |
| 1.3, 21 March 2013. (see the `TIS specification`_, or a later version | |
| of it). | |
| The TIS interface makes a memory mapped IO region in the area | |
| 0xfed40000-0xfed44fff available to the guest operating system. | |
| QEMU files related to TPM TIS interface: | |
| - ``hw/tpm/tpm_tis_common.c`` | |
| - ``hw/tpm/tpm_tis_isa.c`` | |
| - ``hw/tpm/tpm_tis_sysbus.c`` | |
| - ``hw/tpm/tpm_tis.h`` | |
| Both an ISA device and a sysbus device are available. The former is | |
| used with pc/q35 machine while the latter can be instantiated in the | |
| Arm virt machine. | |
| CRB interface | |
| ------------- | |
| QEMU also implements a TPM CRB interface following the Trusted | |
| Computing Group's specification "TCG PC Client Platform TPM Profile | |
| (PTP) Specification", Family "2.0", Level 00 Revision 01.03 v22, May | |
| 22, 2017. (see the `CRB specification`_, or a later version of it) | |
| The CRB interface makes a memory mapped IO region in the area | |
| 0xfed40000-0xfed40fff (1 locality) available to the guest | |
| operating system. | |
| QEMU files related to TPM CRB interface: | |
| - ``hw/tpm/tpm_crb.c`` | |
| SPAPR interface | |
| --------------- | |
| pSeries (ppc64) machines offer a tpm-spapr device model. | |
| QEMU files related to the SPAPR interface: | |
| - ``hw/tpm/tpm_spapr.c`` | |
| fw_cfg interface | |
| ================ | |
| The bios/firmware may read the ``"etc/tpm/config"`` fw_cfg entry for | |
| configuring the guest appropriately. | |
| The entry of 6 bytes has the following content, in little-endian: | |
| .. code-block:: c | |
| #define TPM_VERSION_UNSPEC 0 | |
| #define TPM_VERSION_1_2 1 | |
| #define TPM_VERSION_2_0 2 | |
| #define TPM_PPI_VERSION_NONE 0 | |
| #define TPM_PPI_VERSION_1_30 1 | |
| struct FwCfgTPMConfig { | |
| uint32_t tpmppi_address; /* PPI memory location */ | |
| uint8_t tpm_version; /* TPM version */ | |
| uint8_t tpmppi_version; /* PPI version */ | |
| }; | |
| ACPI interface | |
| ============== | |
| The TPM device is defined with ACPI ID "PNP0C31". QEMU builds a SSDT | |
| and passes it into the guest through the fw_cfg device. The device | |
| description contains the base address of the TIS interface 0xfed40000 | |
| and the size of the MMIO area (0x5000). In case a TPM2 is used by | |
| QEMU, a TPM2 ACPI table is also provided. The device is described to | |
| be used in polling mode rather than interrupt mode primarily because | |
| no unused IRQ could be found. | |
| To support measurement logs to be written by the firmware, | |
| e.g. SeaBIOS, a TCPA table is implemented. This table provides a 64kb | |
| buffer where the firmware can write its log into. For TPM 2 only a | |
| more recent version of the TPM2 table provides support for | |
| measurements logs and a TCPA table does not need to be created. | |
| The TCPA and TPM2 ACPI tables follow the Trusted Computing Group | |
| specification "TCG ACPI Specification" Family "1.2" and "2.0", Level | |
| 00 Revision 00.37. (see the `ACPI specification`_, or a later version | |
| of it) | |
| ACPI PPI Interface | |
| ------------------ | |
| QEMU supports the Physical Presence Interface (PPI) for TPM 1.2 and | |
| TPM 2. This interface requires ACPI and firmware support. (see the | |
| `PPI specification`_) | |
| PPI enables a system administrator (root) to request a modification to | |
| the TPM upon reboot. The PPI specification defines the operation | |
| requests and the actions the firmware has to take. The system | |
| administrator passes the operation request number to the firmware | |
| through an ACPI interface which writes this number to a memory | |
| location that the firmware knows. Upon reboot, the firmware finds the | |
| number and sends commands to the TPM. The firmware writes the TPM | |
| result code and the operation request number to a memory location that | |
| ACPI can read from and pass the result on to the administrator. | |
| The PPI specification defines a set of mandatory and optional | |
| operations for the firmware to implement. The ACPI interface also | |
| allows an administrator to list the supported operations. In QEMU the | |
| ACPI code is generated by QEMU, yet the firmware needs to implement | |
| support on a per-operations basis, and different firmwares may support | |
| a different subset. Therefore, QEMU introduces the virtual memory | |
| device for PPI where the firmware can indicate which operations it | |
| supports and ACPI can enable the ones that are supported and disable | |
| all others. This interface lies in main memory and has the following | |
| layout: | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | Field | Length | Offset | Description | | |
| +=============+========+========+===========================================+ | |
| | ``func`` | 0x100 | 0x000 | Firmware sets values for each supported | | |
| | | | | operation. See defined values below. | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``ppin`` | 0x1 | 0x100 | SMI interrupt to use. Set by firmware. | | |
| | | | | Not supported. | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``ppip`` | 0x4 | 0x101 | ACPI function index to pass to SMM code. | | |
| | | | | Set by ACPI. Not supported. | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``pprp`` | 0x4 | 0x105 | Result of last executed operation. Set by | | |
| | | | | firmware. See function index 5 for values.| | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``pprq`` | 0x4 | 0x109 | Operation request number to execute. See | | |
| | | | | 'Physical Presence Interface Operation | | |
| | | | | Summary' tables in specs. Set by ACPI. | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``pprm`` | 0x4 | 0x10d | Operation request optional parameter. | | |
| | | | | Values depend on operation. Set by ACPI. | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``lppr`` | 0x4 | 0x111 | Last executed operation request number. | | |
| | | | | Copied from pprq field by firmware. | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``fret`` | 0x4 | 0x115 | Result code from SMM function. | | |
| | | | | Not supported. | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``res1`` | 0x40 | 0x119 | Reserved for future use | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| |``next_step``| 0x1 | 0x159 | Operation to execute after reboot by | | |
| | | | | firmware. Used by firmware. | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| | ``movv`` | 0x1 | 0x15a | Memory overwrite variable | | |
| +-------------+--------+--------+-------------------------------------------+ | |
| The following values are supported for the ``func`` field. They | |
| correspond to the values used by ACPI function index 8. | |
| +----------+-------------------------------------------------------------+ | |
| | Value | Description | | |
| +==========+=============================================================+ | |
| | 0 | Operation is not implemented. | | |
| +----------+-------------------------------------------------------------+ | |
| | 1 | Operation is only accessible through firmware. | | |
| +----------+-------------------------------------------------------------+ | |
| | 2 | Operation is blocked for OS by firmware configuration. | | |
| +----------+-------------------------------------------------------------+ | |
| | 3 | Operation is allowed and physically present user required. | | |
| +----------+-------------------------------------------------------------+ | |
| | 4 | Operation is allowed and physically present user is not | | |
| | | required. | | |
| +----------+-------------------------------------------------------------+ | |
| The location of the table is given by the fw_cfg ``tpmppi_address`` | |
| field. The PPI memory region size is 0x400 (``TPM_PPI_ADDR_SIZE``) to | |
| leave enough room for future updates. | |
| QEMU files related to TPM ACPI tables: | |
| - ``hw/i386/acpi-build.c`` | |
| - ``include/hw/acpi/tpm.h`` | |
| TPM backend devices | |
| =================== | |
| The TPM implementation is split into two parts, frontend and | |
| backend. The frontend part is the hardware interface, such as the TPM | |
| TIS interface described earlier, and the other part is the TPM backend | |
| interface. The backend interfaces implement the interaction with a TPM | |
| device, which may be a physical or an emulated device. The split | |
| between the front- and backend devices allows a frontend to be | |
| connected with any available backend. This enables the TIS interface | |
| to be used with the passthrough backend or the swtpm backend. | |
| QEMU files related to TPM backends: | |
| - ``backends/tpm.c`` | |
| - ``include/sysemu/tpm.h`` | |
| - ``include/sysemu/tpm_backend.h`` | |
| The QEMU TPM passthrough device | |
| ------------------------------- | |
| In case QEMU is run on Linux as the host operating system it is | |
| possible to make the hardware TPM device available to a single QEMU | |
| guest. In this case the user must make sure that no other program is | |
| using the device, e.g., /dev/tpm0, before trying to start QEMU with | |
| it. | |
| The passthrough driver uses the host's TPM device for sending TPM | |
| commands and receiving responses from. Besides that it accesses the | |
| TPM device's sysfs entry for support of command cancellation. Since | |
| none of the state of a hardware TPM can be migrated between hosts, | |
| virtual machine migration is disabled when the TPM passthrough driver | |
| is used. | |
| Since the host's TPM device will already be initialized by the host's | |
| firmware, certain commands, e.g. ``TPM_Startup()``, sent by the | |
| virtual firmware for device initialization, will fail. In this case | |
| the firmware should not use the TPM. | |
| Sharing the device with the host is generally not a recommended usage | |
| scenario for a TPM device. The primary reason for this is that two | |
| operating systems can then access the device's single set of | |
| resources, such as platform configuration registers | |
| (PCRs). Applications or kernel security subsystems, such as the Linux | |
| Integrity Measurement Architecture (IMA), are not expecting to share | |
| PCRs. | |
| QEMU files related to the TPM passthrough device: | |
| - ``backends/tpm/tpm_passthrough.c`` | |
| - ``backends/tpm/tpm_util.c`` | |
| - ``include/sysemu/tpm_util.h`` | |
| Command line to start QEMU with the TPM passthrough device using the host's | |
| hardware TPM ``/dev/tpm0``: | |
| .. code-block:: console | |
| qemu-system-x86_64 -display sdl -accel kvm \ | |
| -m 1024 -boot d -bios bios-256k.bin -boot menu=on \ | |
| -tpmdev passthrough,id=tpm0,path=/dev/tpm0 \ | |
| -device tpm-tis,tpmdev=tpm0 test.img | |
| The following commands should result in similar output inside the VM | |
| with a Linux kernel that either has the TPM TIS driver built-in or | |
| available as a module (assuming a TPM 2 is passed through): | |
| .. code-block:: console | |
| # dmesg | grep -i tpm | |
| [ 0.012560] ACPI: TPM2 0x000000000BFFD1900 00004C (v04 BOCHS \ | |
| BXPC 0000001 BXPC 00000001) | |
| # ls -l /dev/tpm* | |
| crw-rw----. 1 tss root 10, 224 Sep 6 12:36 /dev/tpm0 | |
| crw-rw----. 1 tss rss 253, 65536 Sep 6 12:36 /dev/tpmrm0 | |
| Starting with Linux 5.12 there are PCR entries for TPM 2 in sysfs: | |
| # find /sys/devices/ -type f | grep pcr-sha | |
| ... | |
| /sys/devices/LNXSYSTEM:00/LNXSYBUS:00/MSFT0101:00/tpm/tpm0/pcr-sha256/1 | |
| ... | |
| /sys/devices/LNXSYSTEM:00/LNXSYBUS:00/MSFT0101:00/tpm/tpm0/pcr-sha256/9 | |
| ... | |
| The QEMU TPM emulator device | |
| ---------------------------- | |
| The TPM emulator device uses an external TPM emulator called 'swtpm' | |
| for sending TPM commands to and receiving responses from. The swtpm | |
| program must have been started before trying to access it through the | |
| TPM emulator with QEMU. | |
| The TPM emulator implements a command channel for transferring TPM | |
| commands and responses as well as a control channel over which control | |
| commands can be sent. (see the `SWTPM protocol`_ specification) | |
| The control channel serves the purpose of resetting, initializing, and | |
| migrating the TPM state, among other things. | |
| The swtpm program behaves like a hardware TPM and therefore needs to | |
| be initialized by the firmware running inside the QEMU virtual | |
| machine. One necessary step for initializing the device is to send | |
| the TPM_Startup command to it. SeaBIOS, for example, has been | |
| instrumented to initialize a TPM 1.2 or TPM 2 device using this | |
| command. | |
| QEMU files related to the TPM emulator device: | |
| - ``backends/tpm/tpm_emulator.c`` | |
| - ``backends/tpm/tpm_util.c`` | |
| - ``include/sysemu/tpm_util.h`` | |
| The following commands start the swtpm with a UnixIO control channel over | |
| a socket interface. They do not need to be run as root. | |
| .. code-block:: console | |
| mkdir /tmp/mytpm1 | |
| swtpm socket --tpmstate dir=/tmp/mytpm1 \ | |
| --ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \ | |
| --tpm2 \ | |
| --log level=20 | |
| Command line to start QEMU with the TPM emulator device communicating | |
| with the swtpm (x86): | |
| .. code-block:: console | |
| qemu-system-x86_64 -display sdl -accel kvm \ | |
| -m 1024 -boot d -bios bios-256k.bin -boot menu=on \ | |
| -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ | |
| -tpmdev emulator,id=tpm0,chardev=chrtpm \ | |
| -device tpm-tis,tpmdev=tpm0 test.img | |
| In case a pSeries machine is emulated, use the following command line: | |
| .. code-block:: console | |
| qemu-system-ppc64 -display sdl -machine pseries,accel=kvm \ | |
| -m 1024 -bios slof.bin -boot menu=on \ | |
| -nodefaults -device VGA -device pci-ohci -device usb-kbd \ | |
| -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ | |
| -tpmdev emulator,id=tpm0,chardev=chrtpm \ | |
| -device tpm-spapr,tpmdev=tpm0 \ | |
| -device spapr-vscsi,id=scsi0,reg=0x00002000 \ | |
| -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x3,drive=drive-virtio-disk0,id=virtio-disk0 \ | |
| -drive file=test.img,format=raw,if=none,id=drive-virtio-disk0 | |
| In case an Arm virt machine is emulated, use the following command line: | |
| .. code-block:: console | |
| qemu-system-aarch64 -machine virt,gic-version=3,accel=kvm \ | |
| -cpu host -m 4G \ | |
| -nographic -no-acpi \ | |
| -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ | |
| -tpmdev emulator,id=tpm0,chardev=chrtpm \ | |
| -device tpm-tis-device,tpmdev=tpm0 \ | |
| -device virtio-blk-pci,drive=drv0 \ | |
| -drive format=qcow2,file=hda.qcow2,if=none,id=drv0 \ | |
| -drive if=pflash,format=raw,file=flash0.img,readonly=on \ | |
| -drive if=pflash,format=raw,file=flash1.img | |
| In case SeaBIOS is used as firmware, it should show the TPM menu item | |
| after entering the menu with 'ESC'. | |
| .. code-block:: console | |
| Select boot device: | |
| 1. DVD/CD [ata1-0: QEMU DVD-ROM ATAPI-4 DVD/CD] | |
| [...] | |
| 5. Legacy option rom | |
| t. TPM Configuration | |
| The following commands should result in similar output inside the VM | |
| with a Linux kernel that either has the TPM TIS driver built-in or | |
| available as a module: | |
| .. code-block:: console | |
| # dmesg | grep -i tpm | |
| [ 0.012560] ACPI: TPM2 0x000000000BFFD1900 00004C (v04 BOCHS \ | |
| BXPC 0000001 BXPC 00000001) | |
| # ls -l /dev/tpm* | |
| crw-rw----. 1 tss root 10, 224 Sep 6 12:36 /dev/tpm0 | |
| crw-rw----. 1 tss rss 253, 65536 Sep 6 12:36 /dev/tpmrm0 | |
| Starting with Linux 5.12 there are PCR entries for TPM 2 in sysfs: | |
| # find /sys/devices/ -type f | grep pcr-sha | |
| ... | |
| /sys/devices/LNXSYSTEM:00/LNXSYBUS:00/MSFT0101:00/tpm/tpm0/pcr-sha256/1 | |
| ... | |
| /sys/devices/LNXSYSTEM:00/LNXSYBUS:00/MSFT0101:00/tpm/tpm0/pcr-sha256/9 | |
| ... | |
| Migration with the TPM emulator | |
| =============================== | |
| The TPM emulator supports the following types of virtual machine | |
| migration: | |
| - VM save / restore (migration into a file) | |
| - Network migration | |
| - Snapshotting (migration into storage like QoW2 or QED) | |
| The following command sequences can be used to test VM save / restore. | |
| In a 1st terminal start an instance of a swtpm using the following command: | |
| .. code-block:: console | |
| mkdir /tmp/mytpm1 | |
| swtpm socket --tpmstate dir=/tmp/mytpm1 \ | |
| --ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \ | |
| --tpm2 \ | |
| --log level=20 | |
| In a 2nd terminal start the VM: | |
| .. code-block:: console | |
| qemu-system-x86_64 -display sdl -accel kvm \ | |
| -m 1024 -boot d -bios bios-256k.bin -boot menu=on \ | |
| -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ | |
| -tpmdev emulator,id=tpm0,chardev=chrtpm \ | |
| -device tpm-tis,tpmdev=tpm0 \ | |
| -monitor stdio \ | |
| test.img | |
| Verify that the attached TPM is working as expected using applications | |
| inside the VM. | |
| To store the state of the VM use the following command in the QEMU | |
| monitor in the 2nd terminal: | |
| .. code-block:: console | |
| (qemu) migrate "exec:cat > testvm.bin" | |
| (qemu) quit | |
| At this point a file called ``testvm.bin`` should exists and the swtpm | |
| and QEMU processes should have ended. | |
| To test 'VM restore' you have to start the swtpm with the same | |
| parameters as before. If previously a TPM 2 [--tpm2] was saved, --tpm2 | |
| must now be passed again on the command line. | |
| In the 1st terminal restart the swtpm with the same command line as | |
| before: | |
| .. code-block:: console | |
| swtpm socket --tpmstate dir=/tmp/mytpm1 \ | |
| --ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \ | |
| --log level=20 --tpm2 | |
| In the 2nd terminal restore the state of the VM using the additional | |
| '-incoming' option. | |
| .. code-block:: console | |
| qemu-system-x86_64 -display sdl -accel kvm \ | |
| -m 1024 -boot d -bios bios-256k.bin -boot menu=on \ | |
| -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ | |
| -tpmdev emulator,id=tpm0,chardev=chrtpm \ | |
| -device tpm-tis,tpmdev=tpm0 \ | |
| -incoming "exec:cat < testvm.bin" \ | |
| test.img | |
| Troubleshooting migration | |
| ------------------------- | |
| There are several reasons why migration may fail. In case of problems, | |
| please ensure that the command lines adhere to the following rules | |
| and, if possible, that identical versions of QEMU and swtpm are used | |
| at all times. | |
| VM save and restore: | |
| - QEMU command line parameters should be identical apart from the | |
| '-incoming' option on VM restore | |
| - swtpm command line parameters should be identical | |
| VM migration to 'localhost': | |
| - QEMU command line parameters should be identical apart from the | |
| '-incoming' option on the destination side | |
| - swtpm command line parameters should point to two different | |
| directories on the source and destination swtpm (--tpmstate dir=...) | |
| (especially if different versions of libtpms were to be used on the | |
| same machine). | |
| VM migration across the network: | |
| - QEMU command line parameters should be identical apart from the | |
| '-incoming' option on the destination side | |
| - swtpm command line parameters should be identical | |
| VM Snapshotting: | |
| - QEMU command line parameters should be identical | |
| - swtpm command line parameters should be identical | |
| Besides that, migration failure reasons on the swtpm level may include | |
| the following: | |
| - the versions of the swtpm on the source and destination sides are | |
| incompatible | |
| - downgrading of TPM state may not be supported | |
| - the source and destination libtpms were compiled with different | |
| compile-time options and the destination side refuses to accept the | |
| state | |
| - different migration keys are used on the source and destination side | |
| and the destination side cannot decrypt the migrated state | |
| (swtpm ... --migration-key ... ) | |
| .. _TIS specification: | |
| https://trustedcomputinggroup.org/pc-client-work-group-pc-client-specific-tpm-interface-specification-tis/ | |
| .. _CRB specification: | |
| https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ | |
| .. _ACPI specification: | |
| https://trustedcomputinggroup.org/tcg-acpi-specification/ | |
| .. _PPI specification: | |
| https://trustedcomputinggroup.org/resource/tcg-physical-presence-interface-specification/ | |
| .. _SWTPM protocol: | |
| https://github.com/stefanberger/swtpm/blob/master/man/man3/swtpm_ioctls.pod |