On 11/07/19 14:58, Paolo Bonzini wrote: > On 07/11/19 14:27, Laszlo Ersek wrote: >> The VirtioRngDxe driver is a UEFI driver that follows the UEFI driver >> model. Meaning (in this context), it is connected to the virtio-rng >> device in the BDS phase, by platform BDS code. >> >> Put differently, the non-privileged driver that's the source of the >> sensitive data would have to be a "platform DXE driver". The virtio >> drivers are not such drivers however. > > Yes, it would have to be a platform DXE driver. What is it that limits > virtio to the BDS phase?
For virtio-pci, we have: - The PCI host bridge / root bridge driver, which is a platform DXE driver, and produces PCI Root Bridge IO Protocol instances in its entry point. - The PCI bus driver, which is a UEFI driver that follows the UEFI driver model. It only does its actual job when BDS connects it to the PCI Root Bridge IO Protocol instances mentioned above. At that point, the PCI bus driver produces a bunch of PCI IO protocol instances. - The virtio 0.9.5 and virtio 1.0 PCI transport drivers. They are also UEFI drivers that follow the UEFI driver model. They bind PCI IO protocol instances when BDS says so, and produce VIRTIO_DEVICE_PROTOCOL instances. - The actual virtio device drivers (scsi, blk, net, rng, gpu) that bind VIRTIO_DEVICE_PROTOCOL instances, when BDS says so, and produce the use-case specific UEFI protocols (such as SCSI pass-thru, Block IO, Simple Network, RNG, and Graphics Output). For virtio-mmio, we have (on ARM only): - a platform DXE driver that enumerates the virtio-mmio transports, in its entry point function, based on the DTB that QEMU exposes. Each register block / transport is turned into a VIRTIO_DEVICE_PROTOCOL instances. - The actual virtio device drivers (scsi, blk, net, rng, gpu) that bind VIRTIO_DEVICE_PROTOCOL instances, when BDS says so, and produce the use-case specific UEFI protocols (such as SCSI pass-thru, Block IO, Simple Network, RNG, and Graphics Output). UEFI drivers that follow the UEFI driver model allow BDS to orchestrate device binding, and don't generally do anything unless called from BDS. Platform DXE drivers do their job in their entry point functions. If they have protocol dependencies, then they either spell that out as a DEPEXes (dependency expressions, honored by the DXE Core during driver dispatch), or else they operate with low-level protocol notification callbacks (= they automatically take action, regardless of BDS, when a particular protocol appears in the system). They produce their own protocols in their entry point functions, or else in said protocol notify callbacks, without being asked to by BDS. UEFI-2.* strongly encourages device driver authors to structure their drivers as such UEFI drivers that follow the UEFI driver model, and not as platform DXE drivers. A core idea in BDS is that the largest possible set of devices should *not* be bound by drivers. If we don't connect anything that we don't intend to boot off of (and don't need for other booting purposes [*], then that makes for a speedy & frugal boot. [*] such as console drivers (keyboard, serial, graphics), or RNG drivers, ... The set of devices connected by BDS is, by design, platform policy. When the device drivers follow the UEFI driver model, platform BDS can manage a large herd of 3rd party UEFI drivers, and only bind the set of devices that's actually necessary. When most device drivers are platform DXE drivers, all doing whatever they want, and all acting on their own dependencies, it's very difficult to express or implement a similar policy in a central component. In OvmfPkg and ArmVirtPkg, we originally connected all drivers to all devices. That was safe, but not too fast (especially when you had tens or hundreds of virtio devices in the system). Nowadays, we connect only what we really need for booting. That selection / binding procedure is driven by the "bootorder" fw_cfg file from QEMU. https://lists.01.org/hyperkitty/list/edk2-de...@lists.01.org/message/UAYFGPOQVI4FSHTU6MDLA6ULMUWSQ5HJ/ This selection is so effective that, when we first implemented it, we actually stopped binding the virtio RNG device. (It was a regression -- I think the kernel's UEFI stub consumes it, if it's available, and we noticed that the stub started complaining.) That happened because virtio-rng is never a *bootable* device, and so it's never listed in the "bootorder" fw_cfg file. Therefore we had to add custom BDS code to bind virtio RNG devices "forcibly". https://github.com/tianocore/edk2/commit/7ebad830d6ab6 Now, technically speaking, we could rewrite VirtioRngDxe to be a platform DXE driver. It would register a protocol notify callback for VIRTIO_DEVICE_PROTOCOL, it would investigate every such protocol instance as they appeared in the protocol database, and bind whichever was a virtio-rng device. It wouldn't make a difference in the end, because the virtio PCI transport drivers (producing VIRTIO_DEVICE_PROTOCOL) themselves are UEFI drivers that follow the UEFI driver model. Thus, they are "kicked" by BDS. The PCI Bus driver itself (producing the PCI IO instances that the virtio PCI transport drivers bind) is itself a UEFI Driver that follows the UEFI driver model. Thus, that too is set in motion by BDS. --*-- Nonetheless, we might be able to make this work, with a not too terrible hack. - In "OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c", hoist the virtio-rng binding from the end of the PlatformBootManagerBeforeConsole() function, to just before signaling End-of-Dxe. - in the constructor function of OVMF's new SMM-oriented RngLib instance, register a protocol notify for EFI_RNG_PROTOCOL. Also register an event notification function for End-of-Dxe. - In the EFI_RNG_PROTOCOL callback, fetch the seed (and make a note that the seed has been fetched). Uninstall the same callback. - In the End-of-Dxe event notification callback, check if the seed has been fetched. If not, hang the system. (We must not exit the DXE phase without having a seed.) - In the RngLib APIs that provide randomness to the caller (and so depend on a seed having been fetched), hang the system if the seed has not been fetched. This will never fire if the first such API call is made at, or after, End-of-Dxe (see above), but it could fire if the API is called earlier. For example, if the variable SMM driver actually needed some randomness before the system reaches End-of-Dxe. I'll take this last design question back to the TianoCore BZ -- can we expect the SMM drivers to fetch the seed at EndOfDxe (and not earlier)? Because the EFI_RNG_PROTOCOL instances that are present in the system *by then* can be trusted to provide the seed. Thanks, Laszlo