Page MenuHomeFreeBSD

bhyve: TPM 2.0 emulation with swtpm
ClosedPublic

Authored by rosenfeld_grumpf.hope-2000.org on Aug 20 2024, 9:21 AM.
Tags
None
Referenced Files
Unknown Object (File)
Thu, Nov 7, 1:28 AM
Unknown Object (File)
Wed, Nov 6, 11:58 PM
Unknown Object (File)
Thu, Oct 31, 5:13 AM
Unknown Object (File)
Tue, Oct 22, 5:44 PM
Unknown Object (File)
Oct 8 2024, 1:07 AM
Unknown Object (File)
Oct 8 2024, 1:01 AM
Unknown Object (File)
Oct 4 2024, 12:53 AM
Unknown Object (File)
Oct 1 2024, 11:26 PM

Details

Summary

Implement a TPM 2.0 emulation backend to connect to a running swtpm instance using a UNIX domain socket.

For this to work, swtpm needs to be running and listening to a socket already. For testing, it can be started like this:

# swtpm socket --tpmstate backend-uri=file:///path/to/tpm.state --tpm2 --server type=unixio,path=/path/to/tpm.socket --log file=/path/to/tpm.log --flags not-need-init --daemon
# bhyve [...] [...] -l tpm,swtpm,/path/to/tpm.socket [...]

The swtpm backend doesn't do much error handling at this point. If the connection to the swtpm process is lost while bhyve attempts to use it, it will simply stop the VM.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped
Build Status
Buildable 59103
Build 55990: arc lint + arc unit

Event Timeline

usr.sbin/bhyve/tpm_emul_swtpm.c
98–100

I should add that this is mandated by the TPM CRB spec. See TPM Library, Part3: Commands, Section 5.2: Command Header Validation

Quoting https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-3-Commands-01.38.pdf:
"The TPM shall successfully unmarshal a UINT32 as the commandSize. If the TPM has an interface
buffer that is loaded by some hardware process, the number of octets in the input buffer for the
command reported by the hardware process shall exactly match the value in commandSize
(TPM_RC_COMMAND_SIZE)"

I think that the TPM passthru backend is wrong to ignore this, and this should actually be fixed in tpm_crb_thread() in tpm_intf_crb.c, but as I don't have real TPM hardware to test this with I have the fix in the swtpm backend for now as swtpm does validate this and errors out otherwise.

usr.sbin/bhyve/tpm_intf_crb.c
386–390

Strictly speaking, this isn't related to the swtpm backend. I fixed this after noticing during testing that a VM running NetBSD 10 would crash bhyve when NetBSDs tpm driver accessed these registers through its ISA attachment. While this happened only because I ran my test VM with the wrong firmware, which didn't provide the TPM ACPI table, a real hardware TPM wouldn't reset a physical machine either if unsupported registers are accessed, and neither should that happen on bhyve.

Please let me know if you want me to move this out into a separate review.

corvink added inline comments.
usr.sbin/bhyve/tpm_emul_swtpm.c
98–100

I don't think that we should fix this. The guest has to provide us valid values. If the guest sets invalid values on physical hardware, it will fail too. So, why should we "fix" the guest in virtualization?

143

A manpage entry for the new device and it's parameters is missing.

144–146

Nice! Didn't expected this to work without adding some additional callbacks (see Qemu [1]).

[1] https://elixir.bootlin.com/qemu/v9.0.2/source/include/sysemu/tpm_backend.h#L62

usr.sbin/bhyve/tpm_intf_crb.c
386–390

IMO, it should get its own review because you should explain in the commit message in detail why it's a good idea to ignore all remaining register.

Ignoring unknown register writes may lead to strange issues due to some missing emulation bits. For that reason, I decided to error out here when writing this code. If a guest accesses one of those register, bhyve crashes and reports the accessed register. So, you can easily check whether we have to add some additional emulation bits or simply ignore the access. After that, you can open a review to add the new register.

Document the swtpm backend in bhyve.8 and bhyve_config.5.

rosenfeld_grumpf.hope-2000.org added inline comments.
usr.sbin/bhyve/tpm_emul_swtpm.c
98–100

The guest (FreeBSD running tpm2-tools, in this case) sets the correct values in the request header, and that's where I'm getting them from here.

Note that tpm_crb_thread() pulls the command size to write into the buffer out of the cmd_size register, which has been initialized to TPM_CRB_DATA_BUFFER_SIZE in tpm_crb_init(). According to the CRB spec, this specifies the size of the command buffer, not the size of the command. See TPM 2.0 Mobile Command Response Buffer Interface, Section 3.7: Command Size

Quoting https://trustedcomputinggroup.org/wp-content/uploads/Mobile-Command-Response-Buffer-Interface-v2-r12-Specification_FINAL2.pdf:
"This is the size of the Command buffer in bytes.
The value of Command Size field should be chosen such that the largest command that can be issued for
the specific TPM implementation fits into the command buffer"

This is a completely different thing than the size of an individual command, which is what it should use. While I can see why a hardware TPM or even a firmware TPM would ignore this when receiving commands, given that they have no insight into how much of the command buffer in memory has actually been written before the START bit has been flipped. So when accessing real hardware you may get away with this, but it's still in violation of the spec, and it falls apart as soon as you talk to a different kind of TPM that enforces this part of the spec.

LGTM. Will test it.

usr.sbin/bhyve/bhyve_config.5
593 ↗(On Diff #142285)

Nit: Not sure about doc style. Should this be e.g. "tpm.path" instead of "pc-testdev"?

usr.sbin/bhyve/tpm_emul_swtpm.c
98–100

Thanks for clarification!

Btw. should we error out early if cmd_size < hdr->len?

Fix manpage as suggested by Corvin.
No longer get the cmd length from the request header as the CRB emulation should pass the correct length instead.

This revision is now accepted and ready to land.Sep 9 2024, 9:16 AM
This revision was automatically updated to reflect the committed changes.