diff --git a/Changelog.md b/Changelog.md index b32a14aead5..1448d1d1538 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,8 @@ OpenCore Changelog #### v1.0.3 - Fixed support for `AMD_CPU_EXT_FAMILY_1AH`, thx @Shaneee - Fixed EHCI handoff logic in OpenDuet, causing older machines to hang at start +- Added OpenNetworkBoot driver to support HTTP(S) and PXE boot +- Supported DMG loading and verification (e.g. macOS Recovery) over HTTP(S) boot #### v1.0.2 - Fixed error in macrecovery when running headless, thx @mkorje diff --git a/Docs/Configuration.tex b/Docs/Configuration.tex index 63738e0c97b..fa9d680b88d 100755 --- a/Docs/Configuration.tex +++ b/Docs/Configuration.tex @@ -4119,6 +4119,7 @@ \subsection{Debug Properties}\label{miscdebugprops} \item \texttt{HDA} --- AudioDxe \item \texttt{KKT} --- KeyTester \item \texttt{LNX} --- OpenLinuxBoot + \item \texttt{NTBT} --- OpenNetworkBoot \item \texttt{MMDD} --- MmapDump \item \texttt{OCPAVP} --- PavpProvision \item \texttt{OCRST} --- ResetSystem @@ -6579,6 +6580,9 @@ \subsection{Drivers}\label{uefidrivers} & \hyperref[uefilinux]{OpenCore plugin} implementing \texttt{OC\_BOOT\_ENTRY\_PROTOCOL} to allow direct detection and booting of Linux distributions from OpenCore, without chainloading via GRUB. \\ +\href{https://github.com/acidanthera/OpenCorePkg}{\texttt{OpenNetworkBoot}}\textbf{*} +& \hyperref[uefipxe]{OpenCore plugin} implementing \texttt{OC\_BOOT\_ENTRY\_PROTOCOL} + to show available PXE and HTTP(S) boot options on the OpenCore boot menu. \\ \href{https://github.com/acidanthera/OpenCorePkg}{\texttt{OpenNtfsDxe}}\textbf{*} & New Technologies File System (NTFS) read-only driver. NTFS is the primary file system for Microsoft Windows versions that are based on Windows NT. \\ @@ -7080,9 +7084,141 @@ \subsubsection{Additional information} therefore \texttt{efibootmgr} rather than \texttt{bootctl} must be used for any low-level Linux command line interaction with the boot menu. +\subsection{OpenNetworkBoot}\label{uefipxe} + +OpenNetworkBoot is an OpenCore plugin implementing \texttt{OC\_BOOT\_ENTRY\_PROTOCOL}. +It enables PXE and HTTP(S) Boot options in the OpenCore menu if these +are supported by the underlying firmware, or if the required network boot drivers +have been loaded using OpenCore. + +It has additional support for loading \texttt{.dmg} files and their associated +\texttt{.chunklist} file over HTTP(S) Boot, allowing macOS recovery to be +started over HTTP(S) Boot: if either extension is seen in the HTTP(S) Boot URI +then the other file of the pair is automatically loaded as well, and both are +passed to OpenCore to verify and boot from the DMG file. + +PXE Boot is already supported on most firmware, so in most cases PXE Boot entries +should appear as soon as the driver is loaded. Using the additional network boot +drivers provided with OpenCore, when needed, HTTP(S) Boot should be available on +most firmware even if not natively supported. + +Detailed information about the available network boot drivers and how to configure +PXE and HTTP(S) Boot is provided on +\href{https://github.com/acidanthera/OpenCorePkg/blob/master/Platform/OpenNetworkBoot/README.md}{this page}. + +The following configuration options may be specified in the \texttt{Arguments} section for this driver: + +\begin{itemize} + \item \texttt{-4} - Boolean flag, enabled if present. \medskip + + If specified enable IPv4 for PXE and HTTP(S) Boot. Disable IPV6 + unless the \texttt{-6} flag is also present. If neither flag is + present, both are enabled by default. \medskip + + \item \texttt{-6} - Boolean flag, enabled if present. \medskip + + If specified enable IPv6 for PXE and HTTP(S) Boot. Disable IPV4 + unless the \texttt{-4} flag is also present. If neither flag is + present, both are enabled by default. \medskip + + \item \texttt{-{}-aux} - Boolean flag, enabled if present. \medskip + + If specified the driver will generate auxiliary boot entries. \medskip + + \item \texttt{-{}-delete-all-certs[:\{OWNER\_GUID\}]} - Default: not set. \medskip + + If specified, delete all certificates present for \texttt{OWNER\_GUID}. + \texttt{OWNER\_GUID} is optional, and will default to all zeros if not specified. \medskip + + \item \texttt{-{}-delete-cert[:\{OWNER\_GUID\}]="\{cert-text\}"} - Default: not set. \medskip + + If specified, delete the given certificate(s) for HTTPS Boot. The certificate(s) can be specified + as a multi-line PEM value between double quotes. + \texttt{OWNER\_GUID} is optional, and will default to all zeros if not specified. + A single PEM file can contain one or more certicates. + Multiple instances of this option can be used to delete multiple different + PEM files, if required. + + \item \texttt{-{}-enroll-cert[:\{OWNER\_GUID\}]="\{cert-text\}"} - Default: not set. \medskip + + If specified, enroll the given certificate(s) for HTTPS Boot. The certificate(s) can be specified + as a multi-line PEM value between double quotes. + \texttt{OWNER\_GUID} is optional, and will default to all zeros if not specified. + A single PEM file can contain one or more certicates. + Multiple instances of this option can be used to enroll multiple different + PEM files, if required. \medskip + + \item \texttt{-{}-http} - Boolean flag, enabled if present. \medskip + + If specified enable HTTP(S) Boot. Disable PXE Boot unless + the \texttt{-{}-pxe} flag is also present. If neither flag is + present, both are enabled by default. \medskip + + \item \texttt{-{}-https} - Boolean flag, enabled if present. \medskip + + If enabled, allow only \texttt{https://} URIs for HTTP(S) Boot. + Additionally has the same behaviour as the \texttt{-{}-http} flag. \medskip + + \item \texttt{-{}-pxe} - Boolean flag, enabled if present. \medskip + + If specified enable PXE Boot, and disable HTTP(S) Boot unless + the \texttt{-{}-http} or \texttt{-{}-https} flags are present. + If none of these flags are present, both PXE and HTTP(S) Boot are + enabled by default. \medskip + + \item \texttt{-{}-uri} - String value, no default. \medskip + + If present, specify the URI to use for HTTP(S) Boot. If not present then + DHCP boot options must be enabled on the network in order for HTTP(S) + Boot to know what to boot. + +\end{itemize} \medskip + +\subsubsection{OpenNetworkBoot Certificate Management} + +Certificates are enrolled to NVRAM storage, therefore once +a certificate has been enrolled, it will remain enrolled even if the \texttt{-{}-enroll-cert} config +option is removed. \texttt{-{}-delete-cert} or \texttt{-{}-delete-all-certs} +should be used to remove enrolled certificates. + +Checking for certificate presence by the \texttt{-{}-enroll-cert} +and \texttt{-{}-delete-cert} options uses the simple algorithm +of matching by exact file contents, not by file meaning. The intended +usage is to leave an \texttt{-{}-enroll-cert} option present in the config +file until it is time to delete it, e.g. after another more up-to-date +\texttt{-{}-enroll-cert} option has been added and tested. At this point +the user can change \texttt{-{}-enroll-cert} to \texttt{-{}-delete-cert} +for the old certificate. \medskip + +Certificate options are processed one at a time, in +order, and each will potentially make changes to the certificate NVRAM storage. +However each option will not change the NVRAM store if it is already correct +for the option at that point in time (e.g. will not enroll a certificate if it is +already enrolled). +Avoid combinations such as \texttt{-{}-delete-all-certs} followed by +\texttt{-{}-enroll-cert}, as this will modify the NVRAM certificate +storage twice on every boot. However a combination such as +\texttt{-{}-delete-cert="\{certA-text\}"} followed by \texttt{-{}-enroll-cert="\{certB-text\}"} +(with \texttt{certA-text} and \texttt{certB-text} different) is safe, +because certA will only be deleted if it is present +and certB will only be added if it is not present, therefore no +NVRAM changes will be made on the second and subsequent boots +with these options. + +In some cases (such as OVMF with https:// boot support) the +\texttt{OpenNetworkBoot} certificate configuration options manage the same +certificates as those seen in the firmware UI. In other cases of vendor customised +HTTPS Boot firmware, the certificates managed by this driver will be +separate from those managed by firmware. + +When using the debug version of this driver, the OpenCore debug log includes \texttt{NTBT:} entries +that show which certificates are enrolled and removed by these options, and which +certificates are present after all certificate configuration options have been processed. + \subsection{Other Boot Entry Protocol drivers} -In addition to the \hyperref[uefilinux]{OpenLinuxBoot} plugin, the following \texttt{OC\_BOOT\_ENTRY\_PROTOCOL} +In addition to the \hyperref[uefilinux]{OpenLinuxBoot} and \hyperref[uefipxe]{OpenNetworkBoot} plugins, +the following \texttt{OC\_BOOT\_ENTRY\_PROTOCOL} plugins are made available to add optional, configurable boot entries to the OpenCore boot picker. \subsubsection{ResetNvramEntry}\label{uefiresetnvram} diff --git a/Docs/Flavours.md b/Docs/Flavours.md index 7ab4913596b..85407dc3b60 100644 --- a/Docs/Flavours.md +++ b/Docs/Flavours.md @@ -21,7 +21,7 @@ Icon pack authors are encouraged to provide only those icons for which there is In the case of macOS only, a flavour based on the detected OS version is applied automatically (as shown below), and the user does not normally need to override this. -For icon pack authors, the **Apple** icon is recommended, **AppleRecovery** and **AppleTM** are suggested, all others are entirely optional. +For icon pack authors, the **Apple** icon is recommended, **AppleRecv** and **AppleTM** are suggested, all others are entirely optional. - **Apple12:Apple** - Monterey (`Apple12.icns`) - **Apple11:Apple** - Big Sur (`Apple11.icns`) @@ -155,18 +155,31 @@ If providing `NVRAMTool.icns`, it should be themed so that it could be applied t - **ResetNVRAM:NVRAMTool** - Reset NVRAM tool (`ResetNVRAM.icns`) - This is the recommended flavour, used for the entry created by the `ResetNvramEntry.efi` driver. - As another example of how flavours work: **ResetNVRAM:NVRAMTool** will look for `ResetNVRAM.icns`, then `NVRAMTool.icns` (and then, by OC default behaviour, `Tool.icns` then `HardDrive.icns`) - - **Note**: Including **ResetNVRAM** anywhere in a user flavour triggers picker audio-assist and builtin label support for "Reset NVRAM" + - **Note**: Including **ResetNVRAM** anywhere in a flavour triggers picker audio-assist and builtin label support for "Reset NVRAM" - **ToggleSIP:NVRAMTool** - Icon themed for Toggle SIP tool (`ToggleSIP.icns`) - **ToggleSIP_Enabled:ToggleSIP:NVRAMTool** - Icon themed for Toggle SIP tool when SIP is enabled (system is protected) - **ToggleSIP_Disabled:ToggleSIP:NVRAMTool** - Icon themed for Toggle SIP tool when SIP is disabled (system is unprotected) - - **Note**: Including **ToggleSIP_Enabled** or **ToggleSIP_Disabled** anywhere in a user flavour triggers picker audio-assist and builtin label support for the two states of the Toggle SIP menu entry + - **Note**: Including **ToggleSIP_Enabled** or **ToggleSIP_Disabled** anywhere in a flavour triggers picker audio-assist and builtin label support for the two states of the Toggle SIP menu entry + +### Network Boot + +`OpenNetworkBoot.efi` uses the following flavours: + + - **HttpBoot4:HttpBoot:NetworkBoot** - IPv4 HTTP(S) Boot + - **HttpBoot6:HttpBoot:NetworkBoot** - IPv6 HTTP(S) Boot + - **PxeBoot4:PxeBoot:NetworkBoot** - IPv4 PXE Boot + - **PxeBoot6:PxeBoot:NetworkBoot** - IPv6 PXE Boot + +If none of these icons are available, network boot is treated like an external OS, so the fallbacks are **Other** followed by **HardDrive**. + + - **Note**: Including **NetworkBoot** anywhere in a flavour triggers picker audio-assist and builtin label support for "Network Boot" ### Other Tools A list of other known tools which are common enough that some icon pack artists may wish to provide a standard icon for them: - - **FirmwareSettings** - A boot menu entry for accessing firmware settings (`FirmwareSettings.icns`) - - **Note**: Including **FirmwareSettings** anywhere in a user flavour triggers picker audio-assist and builtin label support for "Firmware Settings" + - **FirmwareSettings** - A boot menu entry for accessing firmware settings, such as generated by `FirmwareSettingsEntry.efi` (`FirmwareSettings.icns`) + - **Note**: Including **FirmwareSettings** anywhere in a flavour triggers picker audio-assist and builtin label support for "Firmware Settings" - **MemTest** - A system memory testing tool such as that available from [memtest86.com](https://www.memtest86.com/) (`MemTest.icns`) ## Bootloaders @@ -198,6 +211,7 @@ Provided by OcBinaryData. Used automatically by OC in some circumstances, if pro - **ExtAppleTM** - Apple Time Machine (on external drive) (fallback: **ExtHardDrive**) - **Shell** - Shell tool (fallback: **Tool**) - **Tool** - Generic tool (fallback: **HardDrive**) + - **Other** - Other OS (fallback: **HardDrive**) - **Windows** - Microsoft Windows (fallback: **HardDrive**) ### Additional Optional diff --git a/Docs/Sample.plist b/Docs/Sample.plist index 7ebd9181235..57065355d88 100644 --- a/Docs/Sample.plist +++ b/Docs/Sample.plist @@ -1697,6 +1697,30 @@ Path Ext4Dxe.efi + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + RngDxe.efi + + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + Hash2DxeCrypto.efi + Arguments @@ -1781,6 +1805,42 @@ Path Udp4Dxe.efi + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + Dhcp6Dxe.efi + + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + Ip6Dxe.efi + + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + Udp6Dxe.efi + Arguments @@ -1841,6 +1901,30 @@ Path HttpBootDxe.efi + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + TlsDxe.efi + + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + RamDiskDxe.efi + Arguments diff --git a/Docs/SampleCustom.plist b/Docs/SampleCustom.plist index d8841be15c8..3092006b659 100644 --- a/Docs/SampleCustom.plist +++ b/Docs/SampleCustom.plist @@ -2065,6 +2065,30 @@ Path Ext4Dxe.efi + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + RngDxe.efi + + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + Hash2DxeCrypto.efi + Arguments @@ -2149,6 +2173,42 @@ Path Udp4Dxe.efi + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + Dhcp6Dxe.efi + + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + Ip6Dxe.efi + + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + Udp6Dxe.efi + Arguments @@ -2209,6 +2269,30 @@ Path HttpBootDxe.efi + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + TlsDxe.efi + + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + RamDiskDxe.efi + Arguments diff --git a/Include/Acidanthera/Library/OcAppleDiskImageLib.h b/Include/Acidanthera/Library/OcAppleDiskImageLib.h index c3174b3748e..78e24e1df0d 100644 --- a/Include/Acidanthera/Library/OcAppleDiskImageLib.h +++ b/Include/Acidanthera/Library/OcAppleDiskImageLib.h @@ -32,6 +32,17 @@ typedef struct { APPLE_DISK_IMAGE_BLOCK_DATA **Blocks; } OC_APPLE_DISK_IMAGE_CONTEXT; +// +// Disk image preload context, for network boot. +// +typedef struct { + OC_APPLE_DISK_IMAGE_CONTEXT *DmgContext; + EFI_FILE_PROTOCOL *DmgFile; + UINT32 DmgFileSize; + VOID *ChunklistBuffer; + UINT32 ChunklistFileSize; +} OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT; + BOOLEAN OcAppleDiskImageInitializeContext ( OUT OC_APPLE_DISK_IMAGE_CONTEXT *Context, diff --git a/Include/Acidanthera/Library/OcBootManagementLib.h b/Include/Acidanthera/Library/OcBootManagementLib.h index 9c49cec5129..6be22731e51 100644 --- a/Include/Acidanthera/Library/OcBootManagementLib.h +++ b/Include/Acidanthera/Library/OcBootManagementLib.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,10 @@ typedef struct OC_HOTKEY_CONTEXT_ OC_HOTKEY_CONTEXT; #define OC_FLAVOUR_TOGGLE_SIP_ENABLED "ToggleSIP_Enabled:ToggleSIP:NVRAMTool" #define OC_FLAVOUR_TOGGLE_SIP_DISABLED "ToggleSIP_Disabled:ToggleSIP:NVRAMTool" #define OC_FLAVOUR_FIRMWARE_SETTINGS "FirmwareSettings" +#define OC_FLAVOUR_HTTP_BOOT4 "HttpBoot4:HttpBoot:NetworkBoot" +#define OC_FLAVOUR_HTTP_BOOT6 "HttpBoot6:HttpBoot:NetworkBoot" +#define OC_FLAVOUR_PXE_BOOT4 "PxeBoot4:PxeBoot:NetworkBoot" +#define OC_FLAVOUR_PXE_BOOT6 "PxeBoot6:PxeBoot:NetworkBoot" #define OC_FLAVOUR_APPLE_OS "Apple" #define OC_FLAVOUR_APPLE_RECOVERY "AppleRecv:Apple" #define OC_FLAVOUR_APPLE_FW "AppleRecv:Apple" @@ -92,6 +97,7 @@ typedef struct OC_HOTKEY_CONTEXT_ OC_HOTKEY_CONTEXT; #define OC_FLAVOUR_ID_TOGGLE_SIP_ENABLED "ToggleSIP_Enabled" #define OC_FLAVOUR_ID_TOGGLE_SIP_DISABLED "ToggleSIP_Disabled" #define OC_FLAVOUR_ID_FIRMWARE_SETTINGS "FirmwareSettings" +#define OC_FLAVOUR_ID_NETWORK_BOOT "NetworkBoot" /** Paths allowed to be accessible by the interfaces. @@ -190,6 +196,139 @@ typedef enum OC_PICKER_MODE_ { #define OC_KERN_CAPABILITY_K32_U32_U64 (OC_KERN_CAPABILITY_K32_U32 | OC_KERN_CAPABILITY_K32_U64) #define OC_KERN_CAPABILITY_ALL (OC_KERN_CAPABILITY_K32_U32 | OC_KERN_CAPABILITY_K32_K64_U64) +/** + Perform filtering based on file system basis. + Ignores all filesystems by default. + Remove this bit to allow any file system. +**/ +#define OC_SCAN_FILE_SYSTEM_LOCK BIT0 + +/** + Perform filtering based on device basis. + Ignores all devices by default. + Remove this bit to allow any device type. +**/ +#define OC_SCAN_DEVICE_LOCK BIT1 + +/** + Allow scanning APFS filesystems. +**/ +#define OC_SCAN_ALLOW_FS_APFS BIT8 + +/** + Allow scanning HFS filesystems. +**/ +#define OC_SCAN_ALLOW_FS_HFS BIT9 + +/** + Allow scanning ESP filesystems. +**/ +#define OC_SCAN_ALLOW_FS_ESP BIT10 + +/** + Allow scanning NTFS filesystems. +**/ +#define OC_SCAN_ALLOW_FS_NTFS BIT11 + +/** + Allow scanning Linux Root filesystems. + https://systemd.io/DISCOVERABLE_PARTITIONS/ +**/ +#define OC_SCAN_ALLOW_FS_LINUX_ROOT BIT12 + +/** + Allow scanning Linux Data filesystems. + https://systemd.io/DISCOVERABLE_PARTITIONS/ +**/ +#define OC_SCAN_ALLOW_FS_LINUX_DATA BIT13 + +/** + Allow scanning XBOOTLDR filesystems. +**/ +#define OC_SCAN_ALLOW_FS_XBOOTLDR BIT14 + +/** + Allow scanning SATA devices. +**/ +#define OC_SCAN_ALLOW_DEVICE_SATA BIT16 + +/** + Allow scanning SAS and Mac NVMe devices. +**/ +#define OC_SCAN_ALLOW_DEVICE_SASEX BIT17 + +/** + Allow scanning SCSI devices. +**/ +#define OC_SCAN_ALLOW_DEVICE_SCSI BIT18 + +/** + Allow scanning NVMe devices. +**/ +#define OC_SCAN_ALLOW_DEVICE_NVME BIT19 + +/** + Allow scanning ATAPI devices. +**/ +#define OC_SCAN_ALLOW_DEVICE_ATAPI BIT20 + +/** + Allow scanning USB devices. +**/ +#define OC_SCAN_ALLOW_DEVICE_USB BIT21 + +/** + Allow scanning FireWire devices. +**/ +#define OC_SCAN_ALLOW_DEVICE_FIREWIRE BIT22 + +/** + Allow scanning SD card devices. +**/ +#define OC_SCAN_ALLOW_DEVICE_SDCARD BIT23 + +/** + Allow scanning PCI devices (e.g. virtio). +**/ +#define OC_SCAN_ALLOW_DEVICE_PCI BIT24 + +/** + All device bits used by OC_SCAN_DEVICE_LOCK. +**/ +#define OC_SCAN_DEVICE_BITS (\ + OC_SCAN_ALLOW_DEVICE_SATA | OC_SCAN_ALLOW_DEVICE_SASEX | \ + OC_SCAN_ALLOW_DEVICE_SCSI | OC_SCAN_ALLOW_DEVICE_NVME | \ + OC_SCAN_ALLOW_DEVICE_ATAPI | OC_SCAN_ALLOW_DEVICE_USB | \ + OC_SCAN_ALLOW_DEVICE_FIREWIRE | OC_SCAN_ALLOW_DEVICE_SDCARD | \ + OC_SCAN_ALLOW_DEVICE_PCI) + +/** + All file system bits used by OC_SCAN_FILE_SYSTEM_LOCK. +**/ +#define OC_SCAN_FILE_SYSTEM_BITS (\ + OC_SCAN_ALLOW_FS_APFS | OC_SCAN_ALLOW_FS_HFS | OC_SCAN_ALLOW_FS_ESP | \ + OC_SCAN_ALLOW_FS_NTFS | OC_SCAN_ALLOW_FS_LINUX_ROOT | \ + OC_SCAN_ALLOW_FS_LINUX_DATA | OC_SCAN_ALLOW_FS_XBOOTLDR ) + +/** + By default allow booting from APFS from internal drives. +**/ +#define OC_SCAN_DEFAULT_POLICY (\ + OC_SCAN_FILE_SYSTEM_LOCK | OC_SCAN_DEVICE_LOCK | \ + OC_SCAN_ALLOW_FS_APFS | \ + OC_SCAN_ALLOW_DEVICE_SATA | OC_SCAN_ALLOW_DEVICE_SASEX | \ + OC_SCAN_ALLOW_DEVICE_SCSI | OC_SCAN_ALLOW_DEVICE_NVME | \ + OC_SCAN_ALLOW_DEVICE_PCI) + +/** + OcLoadBootEntry DMG loading policy rules. +**/ +typedef enum { + OcDmgLoadingDisabled, + OcDmgLoadingAnyImage, + OcDmgLoadingAppleSigned, +} OC_DMG_LOADING_SUPPORT; + /** Action to perform as part of executing a system boot entry. **/ @@ -219,11 +358,44 @@ EFI_STATUS IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath ); +/** + Forward declaration of OC_BOOT_ENTRY structure. +**/ +typedef struct OC_BOOT_ENTRY_ OC_BOOT_ENTRY; + +/** + Exposed custom entry load interface. + Returns allocated file buffer from pool on success. +**/ +typedef +EFI_STATUS +(EFIAPI *OC_CUSTOM_READ)( + IN OC_STORAGE_CONTEXT *Storage, + IN OC_BOOT_ENTRY *ChosenEntry, + OUT VOID **Data, + OUT UINT32 *DataSize, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, + OUT EFI_HANDLE *StorageHandle, + OUT EFI_DEVICE_PATH_PROTOCOL **StoragePath, + IN OC_DMG_LOADING_SUPPORT DmgLoading, + OUT OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext, + OUT VOID **CustomFreeContext + ); + +/** + Exposed custom entry interface to free any custom items after load. +**/ +typedef +EFI_STATUS +(EFIAPI *OC_CUSTOM_FREE)( + IN VOID *CustomFreeContext + ); + /** Discovered boot entry. Note, inner resources must be freed with FreeBootEntry. **/ -typedef struct OC_BOOT_ENTRY_ { +struct OC_BOOT_ENTRY_ { // // Link in entry list in OC_BOOT_FILESYSTEM. // @@ -246,6 +418,14 @@ typedef struct OC_BOOT_ENTRY_ { // OC_BOOT_UNMANAGED_GET_FINAL_DP UnmanagedBootGetFinalDevicePath; // + // Custom entry image read routine, optional for non-custom entries. + // + OC_CUSTOM_READ CustomRead; + // + // Custom entry routine to free custom items. Optional. + // + OC_CUSTOM_FREE CustomFree; + // // Id under which to save entry as default. // CHAR16 *Id; @@ -332,7 +512,7 @@ typedef struct OC_BOOT_ENTRY_ { // Audio base type for system action. Boot Entry Protocol only. // CHAR8 *AudioBaseType; -} OC_BOOT_ENTRY; +}; /** Parsed load option or shell variable. @@ -417,139 +597,6 @@ typedef struct OC_BOOT_CONTEXT_ { OC_PICKER_CONTEXT *PickerContext; } OC_BOOT_CONTEXT; -/** - Perform filtering based on file system basis. - Ignores all filesystems by default. - Remove this bit to allow any file system. -**/ -#define OC_SCAN_FILE_SYSTEM_LOCK BIT0 - -/** - Perform filtering based on device basis. - Ignores all devices by default. - Remove this bit to allow any device type. -**/ -#define OC_SCAN_DEVICE_LOCK BIT1 - -/** - Allow scanning APFS filesystems. -**/ -#define OC_SCAN_ALLOW_FS_APFS BIT8 - -/** - Allow scanning HFS filesystems. -**/ -#define OC_SCAN_ALLOW_FS_HFS BIT9 - -/** - Allow scanning ESP filesystems. -**/ -#define OC_SCAN_ALLOW_FS_ESP BIT10 - -/** - Allow scanning NTFS filesystems. -**/ -#define OC_SCAN_ALLOW_FS_NTFS BIT11 - -/** - Allow scanning Linux Root filesystems. - https://systemd.io/DISCOVERABLE_PARTITIONS/ -**/ -#define OC_SCAN_ALLOW_FS_LINUX_ROOT BIT12 - -/** - Allow scanning Linux Data filesystems. - https://systemd.io/DISCOVERABLE_PARTITIONS/ -**/ -#define OC_SCAN_ALLOW_FS_LINUX_DATA BIT13 - -/** - Allow scanning XBOOTLDR filesystems. -**/ -#define OC_SCAN_ALLOW_FS_XBOOTLDR BIT14 - -/** - Allow scanning SATA devices. -**/ -#define OC_SCAN_ALLOW_DEVICE_SATA BIT16 - -/** - Allow scanning SAS and Mac NVMe devices. -**/ -#define OC_SCAN_ALLOW_DEVICE_SASEX BIT17 - -/** - Allow scanning SCSI devices. -**/ -#define OC_SCAN_ALLOW_DEVICE_SCSI BIT18 - -/** - Allow scanning NVMe devices. -**/ -#define OC_SCAN_ALLOW_DEVICE_NVME BIT19 - -/** - Allow scanning ATAPI devices. -**/ -#define OC_SCAN_ALLOW_DEVICE_ATAPI BIT20 - -/** - Allow scanning USB devices. -**/ -#define OC_SCAN_ALLOW_DEVICE_USB BIT21 - -/** - Allow scanning FireWire devices. -**/ -#define OC_SCAN_ALLOW_DEVICE_FIREWIRE BIT22 - -/** - Allow scanning SD card devices. -**/ -#define OC_SCAN_ALLOW_DEVICE_SDCARD BIT23 - -/** - Allow scanning PCI devices (e.g. virtio). -**/ -#define OC_SCAN_ALLOW_DEVICE_PCI BIT24 - -/** - All device bits used by OC_SCAN_DEVICE_LOCK. -**/ -#define OC_SCAN_DEVICE_BITS (\ - OC_SCAN_ALLOW_DEVICE_SATA | OC_SCAN_ALLOW_DEVICE_SASEX | \ - OC_SCAN_ALLOW_DEVICE_SCSI | OC_SCAN_ALLOW_DEVICE_NVME | \ - OC_SCAN_ALLOW_DEVICE_ATAPI | OC_SCAN_ALLOW_DEVICE_USB | \ - OC_SCAN_ALLOW_DEVICE_FIREWIRE | OC_SCAN_ALLOW_DEVICE_SDCARD | \ - OC_SCAN_ALLOW_DEVICE_PCI) - -/** - All file system bits used by OC_SCAN_FILE_SYSTEM_LOCK. -**/ -#define OC_SCAN_FILE_SYSTEM_BITS (\ - OC_SCAN_ALLOW_FS_APFS | OC_SCAN_ALLOW_FS_HFS | OC_SCAN_ALLOW_FS_ESP | \ - OC_SCAN_ALLOW_FS_NTFS | OC_SCAN_ALLOW_FS_LINUX_ROOT | \ - OC_SCAN_ALLOW_FS_LINUX_DATA | OC_SCAN_ALLOW_FS_XBOOTLDR ) - -/** - By default allow booting from APFS from internal drives. -**/ -#define OC_SCAN_DEFAULT_POLICY (\ - OC_SCAN_FILE_SYSTEM_LOCK | OC_SCAN_DEVICE_LOCK | \ - OC_SCAN_ALLOW_FS_APFS | \ - OC_SCAN_ALLOW_DEVICE_SATA | OC_SCAN_ALLOW_DEVICE_SASEX | \ - OC_SCAN_ALLOW_DEVICE_SCSI | OC_SCAN_ALLOW_DEVICE_NVME | \ - OC_SCAN_ALLOW_DEVICE_PCI) - -/** - OcLoadBootEntry DMG loading policy rules. -**/ -typedef enum { - OcDmgLoadingDisabled, - OcDmgLoadingAnyImage, - OcDmgLoadingAppleSigned, -} OC_DMG_LOADING_SUPPORT; - /** Exposed start interface with chosen boot entry but otherwise equivalent to EFI_BOOT_SERVICES StartImage. @@ -564,22 +611,6 @@ EFI_STATUS IN BOOLEAN LaunchInText ); -/** - Exposed custom entry load interface. - Returns allocated file buffer from pool on success. -**/ -typedef -EFI_STATUS -(EFIAPI *OC_CUSTOM_READ)( - IN OC_STORAGE_CONTEXT *Storage, - IN OC_BOOT_ENTRY *ChosenEntry, - OUT VOID **Data, - OUT UINT32 *DataSize, - OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, - OUT EFI_HANDLE *StorageHandle, - OUT EFI_DEVICE_PATH_PROTOCOL **StoragePath - ); - /** Custom picker entry. Note that OC_BOOT_ENTRY_PROTOCOL_REVISION needs incrementing @@ -650,9 +681,17 @@ typedef struct { // OC_BOOT_UNMANAGED_GET_FINAL_DP UnmanagedBootGetFinalDevicePath; // - // Unmanaged boot Device Path. Boot Entry Protocol unmanaged boot entries only. + // Absolute Device Path. May be used instead of text Path above for Boot Entry Protocol entries. Optional. + // + EFI_DEVICE_PATH_PROTOCOL *UnmanagedDevicePath; + // + // Custom entry image read routine, optional for non-custom entries. + // + OC_CUSTOM_READ CustomRead; + // + // Custom entry routine to free custom items. Optional. // - EFI_DEVICE_PATH_PROTOCOL *UnmanagedBootDevicePath; + OC_CUSTOM_FREE CustomFree; // // Whether this entry should be labeled as external to the system. Boot Entry Protocol only. Optional. // @@ -925,7 +964,7 @@ struct OC_PICKER_CONTEXT_ { // BOOLEAN CustomBootGuid; // - // Custom entry reading routine, optional for no custom entries. + // Custom entry image read routine, optional for non-custom entries. // OC_CUSTOM_READ CustomRead; // @@ -1537,9 +1576,10 @@ typedef struct OC_BOOT_ARGUMENTS_ { } OC_BOOT_ARGUMENTS; // -// Sanity check max. size for LoadOptions. +// Sanity check max. size for LoadOptions. We need to pass PEM certificates +// to some drivers (e.g. OpenNetworkBoot), so this has to be quite large. // -#define MAX_LOAD_OPTIONS_SIZE SIZE_4KB +#define MAX_LOAD_OPTIONS_SIZE SIZE_16KB /** Are load options apparently valid (Unicode string or cleanly non-present)? diff --git a/Include/Acidanthera/Protocol/OcAudio.h b/Include/Acidanthera/Protocol/OcAudio.h index a06e59cfdcb..8aa8f4c5a41 100644 --- a/Include/Acidanthera/Protocol/OcAudio.h +++ b/Include/Acidanthera/Protocol/OcAudio.h @@ -56,6 +56,7 @@ typedef struct OC_AUDIO_PROTOCOL_ OC_AUDIO_PROTOCOL; #define OC_VOICE_OVER_AUDIO_FILE_MAC_OS_RECOVERY "macOS_Recovery" #define OC_VOICE_OVER_AUDIO_FILE_MAC_OS_TIME_MACHINE "macOS_TimeMachine" #define OC_VOICE_OVER_AUDIO_FILE_MAC_OS_UPDATE_FW "macOS_UpdateFw" +#define OC_VOICE_OVER_AUDIO_FILE_NETWORK_BOOT "NetworkBoot" #define OC_VOICE_OVER_AUDIO_FILE_OTHER_OS "OtherOS" #define OC_VOICE_OVER_AUDIO_FILE_PASSWORD_ACCEPTED "PasswordAccepted" #define OC_VOICE_OVER_AUDIO_FILE_PASSWORD_INCORRECT "PasswordIncorrect" diff --git a/Include/Acidanthera/Protocol/OcBootEntry.h b/Include/Acidanthera/Protocol/OcBootEntry.h index 9ac8326c89a..2a3e261a70a 100644 --- a/Include/Acidanthera/Protocol/OcBootEntry.h +++ b/Include/Acidanthera/Protocol/OcBootEntry.h @@ -28,7 +28,7 @@ WARNING: This protocol is currently undergoing active design. **/ -#define OC_BOOT_ENTRY_PROTOCOL_REVISION 5 +#define OC_BOOT_ENTRY_PROTOCOL_REVISION 6 /** Forward declaration of OC_BOOT_ENTRY_PROTOCOL structure. diff --git a/Library/OcBootManagementLib/BootAudio.c b/Library/OcBootManagementLib/BootAudio.c index bdc4b4ad2a0..f539feb5939 100644 --- a/Library/OcBootManagementLib/BootAudio.c +++ b/Library/OcBootManagementLib/BootAudio.c @@ -214,7 +214,11 @@ OcPlayAudioEntry ( } else if (Entry->Type == OC_BOOT_WINDOWS) { OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_WINDOWS, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE); } else if (Entry->Type == OC_BOOT_EXTERNAL_OS) { - OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_EXTERNAL_OS, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE); + if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_ID_NETWORK_BOOT) != NULL) { + OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_NETWORK_BOOT, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE); + } else { + OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_EXTERNAL_OS, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE); + } } else if (Entry->Type == OC_BOOT_SYSTEM) { OcPlayAudioFile (Context, Entry->AudioBasePath, Entry->AudioBaseType, FALSE); } else if (Entry->Type == OC_BOOT_EXTERNAL_TOOL) { diff --git a/Library/OcBootManagementLib/BootEntryManagement.c b/Library/OcBootManagementLib/BootEntryManagement.c index 4837ab74dc0..26c02174ee6 100644 --- a/Library/OcBootManagementLib/BootEntryManagement.c +++ b/Library/OcBootManagementLib/BootEntryManagement.c @@ -676,6 +676,8 @@ InternalAddBootEntryFromCustomEntry ( } BootEntry->IsExternal = FileSystem->External; + BootEntry->CustomRead = CustomEntry->CustomRead; + BootEntry->CustomFree = CustomEntry->CustomFree; if (CustomEntry->Id != NULL) { BootEntry->Id = AsciiStrCopyToUnicode (CustomEntry->Id, 0); @@ -692,7 +694,7 @@ InternalAddBootEntryFromCustomEntry ( return EFI_OUT_OF_RESOURCES; } - if (!CustomEntry->UnmanagedBootAction && !CustomEntry->SystemAction) { + if (!CustomEntry->UnmanagedBootAction && !CustomEntry->SystemAction && !CustomEntry->UnmanagedDevicePath) { ASSERT (CustomEntry->Path != NULL); PathName = AsciiStrCopyToUnicode (CustomEntry->Path, 0); if (PathName == NULL) { @@ -727,7 +729,7 @@ InternalAddBootEntryFromCustomEntry ( BootEntry->AudioBasePath = CustomEntry->AudioBasePath; BootEntry->AudioBaseType = CustomEntry->AudioBaseType; BootEntry->IsExternal = CustomEntry->External; - BootEntry->DevicePath = DuplicateDevicePath (CustomEntry->UnmanagedBootDevicePath); + BootEntry->DevicePath = DuplicateDevicePath (CustomEntry->UnmanagedDevicePath); if (BootEntry->DevicePath == NULL) { FreeBootEntry (BootEntry); @@ -739,7 +741,10 @@ InternalAddBootEntryFromCustomEntry ( BootEntry->AudioBasePath = CustomEntry->AudioBasePath; BootEntry->AudioBaseType = CustomEntry->AudioBaseType; } else if (CustomEntry->Tool) { - BootEntry->Type = OC_BOOT_EXTERNAL_TOOL; + ASSERT (CustomEntry->CustomRead == NULL && CustomEntry->CustomFree == NULL); + BootEntry->Type = OC_BOOT_EXTERNAL_TOOL; + BootEntry->CustomRead = BootContext->PickerContext->CustomRead; + BootEntry->CustomFree = NULL; UnicodeUefiSlashes (PathName); BootEntry->PathName = PathName; } else { @@ -750,13 +755,19 @@ InternalAddBootEntryFromCustomEntry ( // for user entry path is absolute device path. // if (IsBootEntryProtocol) { - UnicodeUefiSlashes (PathName); - BootEntry->DevicePath = FileDevicePath (FileSystem->Handle, PathName); + if (CustomEntry->UnmanagedDevicePath) { + BootEntry->DevicePath = DuplicateDevicePath (CustomEntry->UnmanagedDevicePath); + } else { + UnicodeUefiSlashes (PathName); + BootEntry->DevicePath = FileDevicePath (FileSystem->Handle, PathName); + FreePool (PathName); + } } else { + ASSERT (CustomEntry->UnmanagedDevicePath == NULL); BootEntry->DevicePath = ConvertTextToDevicePath (PathName); + FreePool (PathName); } - FreePool (PathName); if (BootEntry->DevicePath == NULL) { FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; @@ -770,22 +781,24 @@ InternalAddBootEntryFromCustomEntry ( ) ); if (FilePath == NULL) { - DEBUG (( - DEBUG_WARN, - "OCB: Invalid device path, not adding entry %a\n", - CustomEntry->Name - )); - FreeBootEntry (BootEntry); - return EFI_UNSUPPORTED; - } - - BootEntry->PathName = AllocateCopyPool ( - OcFileDevicePathNameSize (FilePath), - FilePath->PathName - ); - if (BootEntry->PathName == NULL) { - FreeBootEntry (BootEntry); - return EFI_OUT_OF_RESOURCES; + if (BootEntry->CustomRead == NULL) { + DEBUG (( + DEBUG_WARN, + "OCB: Invalid device path, not adding entry %a\n", + CustomEntry->Name + )); + FreeBootEntry (BootEntry); + return EFI_UNSUPPORTED; + } + } else { + BootEntry->PathName = AllocateCopyPool ( + OcFileDevicePathNameSize (FilePath), + FilePath->PathName + ); + if (BootEntry->PathName == NULL) { + FreeBootEntry (BootEntry); + return EFI_OUT_OF_RESOURCES; + } } // @@ -843,7 +856,7 @@ InternalAddBootEntryFromCustomEntry ( BootEntry->ExposeDevicePath = CustomEntry->RealPath; BootEntry->FullNvramAccess = CustomEntry->FullNvramAccess; - if ((BootEntry->UnmanagedBootAction != NULL) || (BootEntry->SystemAction != NULL)) { + if ((BootEntry->UnmanagedBootAction != NULL) || (BootEntry->SystemAction != NULL) || (CustomEntry->CustomRead != NULL)) { ASSERT (CustomEntry->Arguments == NULL); } else { ASSERT (CustomEntry->Arguments != NULL); @@ -1464,6 +1477,7 @@ AddBootEntryFromBootOption ( ); } while (NumPatchedNodes > 0); + Status = EFI_NOT_FOUND; if ((ExpandedDevicePath == NULL) && (CustomFileSystem != NULL)) { // // If non-standard device path, attempt to pre-construct a user config @@ -1484,12 +1498,12 @@ AddBootEntryFromBootOption ( *CustomIndex = Index; } - InternalAddBootEntryFromCustomEntry ( - BootContext, - CustomFileSystem, - &BootContext->PickerContext->CustomEntries[Index], - FALSE - ); + Status = InternalAddBootEntryFromCustomEntry ( + BootContext, + CustomFileSystem, + &BootContext->PickerContext->CustomEntries[Index], + FALSE + ); break; } } @@ -1503,6 +1517,36 @@ AddBootEntryFromBootOption ( EntryProtocolDevPath = InternalGetOcEntryProtocolDevPath (DevicePath); + if (EntryProtocolDevPath != NULL) { + // + // Zero GUID can be non-file-based entry (e.g. from network boot), + // or file-based entry on OVMF mounted drives where GPT GUIDs are + // not available. Try non-file-based first. + // + if (CompareGuid (&gEfiPartTypeUnusedGuid, &EntryProtocolDevPath->Partuuid)) { + Status = OcAddEntriesFromBootEntryProtocol ( + BootContext, + CustomFileSystem, + EntryProtocolHandles, + EntryProtocolHandleCount, + EntryProtocolDevPath->EntryName.PathName, + TRUE, + FALSE + ); + if (!EFI_ERROR (Status)) { + if (EntryProtocolPartuuid != NULL) { + CopyGuid (EntryProtocolPartuuid, &gEfiPartTypeUnusedGuid); + } + + if (EntryProtocolId != NULL) { + *EntryProtocolId = AllocateCopyPool (StrSize (EntryProtocolDevPath->EntryName.PathName), EntryProtocolDevPath->EntryName.PathName); + } + + EntryProtocolDevPath = NULL; + } + } + } + if (EntryProtocolDevPath != NULL) { // // Search for ID on matching device only. @@ -1574,7 +1618,7 @@ AddBootEntryFromBootOption ( DevicePath = ExpandedDevicePath; if (DevicePath == NULL) { - return EFI_NOT_FOUND; + return Status; } } else if (NumPatchedNodes == -1) { // @@ -2511,6 +2555,7 @@ OcLoadBootEntry ( EFI_STATUS Status; EFI_HANDLE EntryHandle; INTERNAL_DMG_LOAD_CONTEXT DmgLoadContext; + VOID *CustomFreeContext; if ((BootEntry->Type & OC_BOOT_UNMANAGED) != 0) { ASSERT (BootEntry->UnmanagedBootAction != NULL); @@ -2527,7 +2572,8 @@ OcLoadBootEntry ( BootEntry, ParentHandle, &EntryHandle, - &DmgLoadContext + &DmgLoadContext, + &CustomFreeContext ); if (!EFI_ERROR (Status)) { // @@ -2546,14 +2592,26 @@ OcLoadBootEntry ( if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "OCB: StartImage failed - %r\n", Status)); // - // Unload dmg if any. - // - InternalUnloadDmg (&DmgLoadContext); - // // Unload image. + // Note: This is not needed on success, since this has already been done + // and image handle is now invalid, if image was an application and it + // exited successfully: + // https://github.com/tianocore/edk2/blob/a3aab12c34dba35d1fd592f4939cb70617668f7e/MdeModulePkg/Core/Dxe/Image/Image.c#L1789-L1793 // gBS->UnloadImage (EntryHandle); } + + // + // Unload dmg if any. + // + InternalUnloadDmg (&DmgLoadContext); + // + // Unload any entry protocol custom items. + // For instance HTTP Boot natively supported RAM disk, on loading .iso or .img. + // + if (BootEntry->CustomFree != NULL) { + BootEntry->CustomFree (CustomFreeContext); + } } else { DEBUG ((DEBUG_WARN, "OCB: LoadImage failed - %r\n", Status)); } diff --git a/Library/OcBootManagementLib/BootEntryProtocol.c b/Library/OcBootManagementLib/BootEntryProtocol.c index 6bcde243c36..6d463f81589 100644 --- a/Library/OcBootManagementLib/BootEntryProtocol.c +++ b/Library/OcBootManagementLib/BootEntryProtocol.c @@ -248,6 +248,10 @@ OcAddEntriesFromBootEntryProtocol ( { BEP_ADD_ENTRIES_CONTEXT AddEntriesContext; + // + // May be CustomFileSystem, but not NULL. + // + ASSERT (FileSystem != NULL); ASSERT (!CreateDefault || (DefaultEntryId != NULL)); AddEntriesContext.ReturnStatus = EFI_NOT_FOUND; diff --git a/Library/OcBootManagementLib/BootManagementInternal.h b/Library/OcBootManagementLib/BootManagementInternal.h index 1a3ac19387a..0e156c2725d 100644 --- a/Library/OcBootManagementLib/BootManagementInternal.h +++ b/Library/OcBootManagementLib/BootManagementInternal.h @@ -133,8 +133,9 @@ InternalCheckScanPolicy ( EFI_DEVICE_PATH_PROTOCOL * InternalLoadDmg ( - IN OUT INTERNAL_DMG_LOAD_CONTEXT *Context, - IN OC_DMG_LOADING_SUPPORT DmgLoading + IN OUT INTERNAL_DMG_LOAD_CONTEXT *Context, + IN OC_DMG_LOADING_SUPPORT DmgLoading, + IN OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext ); VOID @@ -177,7 +178,8 @@ InternalLoadBootEntry ( IN OC_BOOT_ENTRY *BootEntry, IN EFI_HANDLE ParentHandle, OUT EFI_HANDLE *EntryHandle, - OUT INTERNAL_DMG_LOAD_CONTEXT *DmgLoadContext + OUT INTERNAL_DMG_LOAD_CONTEXT *DmgLoadContext, + OUT VOID **CustomFreeContext ); UINT16 * diff --git a/Library/OcBootManagementLib/DefaultEntryChoice.c b/Library/OcBootManagementLib/DefaultEntryChoice.c index d45c9b4c248..6d38bd2fce1 100644 --- a/Library/OcBootManagementLib/DefaultEntryChoice.c +++ b/Library/OcBootManagementLib/DefaultEntryChoice.c @@ -1529,19 +1529,21 @@ InternalLoadBootEntry ( IN OC_BOOT_ENTRY *BootEntry, IN EFI_HANDLE ParentHandle, OUT EFI_HANDLE *EntryHandle, - OUT INTERNAL_DMG_LOAD_CONTEXT *DmgLoadContext + OUT INTERNAL_DMG_LOAD_CONTEXT *DmgLoadContext, + OUT VOID **CustomFreeContext ) { - EFI_STATUS Status; - EFI_STATUS OptionalStatus; - EFI_DEVICE_PATH_PROTOCOL *DevicePath; - EFI_HANDLE StorageHandle; - EFI_DEVICE_PATH_PROTOCOL *StoragePath; - CHAR16 *UnicodeDevicePath; - EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; - VOID *EntryData; - UINT32 EntryDataSize; - CONST CHAR8 *Args; + EFI_STATUS Status; + EFI_STATUS OptionalStatus; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + EFI_HANDLE StorageHandle; + EFI_DEVICE_PATH_PROTOCOL *StoragePath; + CHAR16 *UnicodeDevicePath; + EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; + VOID *EntryData; + UINT32 EntryDataSize; + CONST CHAR8 *Args; + OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT DmgPreloadContext; ASSERT (BootEntry != NULL); // @@ -1558,38 +1560,57 @@ InternalLoadBootEntry ( ZeroMem (DmgLoadContext, sizeof (*DmgLoadContext)); - EntryData = NULL; - EntryDataSize = 0; - StorageHandle = NULL; - StoragePath = NULL; + EntryData = NULL; + EntryDataSize = 0; + StorageHandle = NULL; + StoragePath = NULL; + *CustomFreeContext = NULL; + ZeroMem (&DmgPreloadContext, sizeof (DmgPreloadContext)); - if (BootEntry->IsFolder) { + // + // CustomRead must be set for external tools, but may also be set for boot + // entry protocol entries. + // + ASSERT (BootEntry->Type != OC_BOOT_EXTERNAL_TOOL || BootEntry->CustomRead != NULL); + + if (BootEntry->CustomRead != NULL) { + Status = BootEntry->CustomRead ( + Context->StorageContext, + BootEntry, + &EntryData, + &EntryDataSize, + &DevicePath, + &StorageHandle, + &StoragePath, + Context->DmgLoading, + &DmgPreloadContext, + CustomFreeContext + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "OCB: Custom read failed - %r\n", Status)); + return Status; + } + } + + if ( (DmgPreloadContext.DmgFile != NULL) + || (DmgPreloadContext.DmgContext != NULL) + || BootEntry->IsFolder) + { if (Context->DmgLoading == OcDmgLoadingDisabled) { return EFI_SECURITY_VIOLATION; } DmgLoadContext->DevicePath = BootEntry->DevicePath; - DevicePath = InternalLoadDmg (DmgLoadContext, Context->DmgLoading); + DevicePath = InternalLoadDmg ( + DmgLoadContext, + Context->DmgLoading, + &DmgPreloadContext + ); if (DevicePath == NULL) { return EFI_UNSUPPORTED; } - } else if (BootEntry->Type == OC_BOOT_EXTERNAL_TOOL) { - ASSERT (Context->CustomRead != NULL); - - Status = Context->CustomRead ( - Context->StorageContext, - BootEntry, - &EntryData, - &EntryDataSize, - &DevicePath, - &StorageHandle, - &StoragePath - ); - - if (EFI_ERROR (Status)) { - return Status; - } - } else { + } else if (BootEntry->CustomRead == NULL) { DevicePath = BootEntry->DevicePath; } @@ -1691,6 +1712,9 @@ InternalLoadBootEntry ( } } else { InternalUnloadDmg (DmgLoadContext); + if (BootEntry->CustomFree != NULL) { + BootEntry->CustomFree (*CustomFreeContext); + } } return Status; diff --git a/Library/OcBootManagementLib/DmgBootSupport.c b/Library/OcBootManagementLib/DmgBootSupport.c index e0a717de6aa..e2b31ff6caa 100644 --- a/Library/OcBootManagementLib/DmgBootSupport.c +++ b/Library/OcBootManagementLib/DmgBootSupport.c @@ -317,8 +317,9 @@ InternalFindDmgChunklist ( EFI_DEVICE_PATH_PROTOCOL * InternalLoadDmg ( - IN OUT INTERNAL_DMG_LOAD_CONTEXT *Context, - IN OC_DMG_LOADING_SUPPORT DmgLoading + IN OUT INTERNAL_DMG_LOAD_CONTEXT *Context, + IN OC_DMG_LOADING_SUPPORT DmgLoading, + IN OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext ) { EFI_DEVICE_PATH_PROTOCOL *DevPath; @@ -342,130 +343,154 @@ InternalLoadDmg ( ASSERT (Context != NULL); - DevPath = Context->DevicePath; - Status = OcOpenFileByDevicePath ( - &DevPath, - &DmgDir, - EFI_FILE_MODE_READ, - EFI_FILE_DIRECTORY - ); - if (EFI_ERROR (Status)) { - DevPathText = ConvertDevicePathToText (Context->DevicePath, FALSE, FALSE); - DEBUG ((DEBUG_INFO, "OCB: Failed to open DMG directory %s\n", DevPathText)); - if (DevPathText != NULL) { - FreePool (DevPathText); - } - - return NULL; - } + if (DmgPreloadContext->DmgContext != NULL) { + Context->DmgContext = DmgPreloadContext->DmgContext; + DmgFileSize = DmgPreloadContext->DmgFileSize; + } else { + if (DmgPreloadContext->DmgFile != NULL) { + DmgFile = DmgPreloadContext->DmgFile; + DmgFileSize = DmgPreloadContext->DmgFileSize; + } else { + DevPath = Context->DevicePath; + Status = OcOpenFileByDevicePath ( + &DevPath, + &DmgDir, + EFI_FILE_MODE_READ, + EFI_FILE_DIRECTORY + ); + if (EFI_ERROR (Status)) { + DevPathText = ConvertDevicePathToText (Context->DevicePath, FALSE, FALSE); + DEBUG ((DEBUG_INFO, "OCB: Failed to open DMG directory %s\n", DevPathText)); + if (DevPathText != NULL) { + FreePool (DevPathText); + } - DmgFileInfo = InternalFindFirstDmgFileName (DmgDir, &DmgFileNameLen); - if (DmgFileInfo == NULL) { - DevPathText = ConvertDevicePathToText (Context->DevicePath, FALSE, FALSE); - DEBUG ((DEBUG_INFO, "OCB: Unable to find any DMG at %s\n")); - if (DevPathText != NULL) { - FreePool (DevPathText); - } + return NULL; + } - DmgDir->Close (DmgDir); - return NULL; - } + DmgFileInfo = InternalFindFirstDmgFileName (DmgDir, &DmgFileNameLen); + if (DmgFileInfo == NULL) { + DevPathText = ConvertDevicePathToText (Context->DevicePath, FALSE, FALSE); + DEBUG ((DEBUG_INFO, "OCB: Unable to find any DMG at %s\n")); + if (DevPathText != NULL) { + FreePool (DevPathText); + } - Status = OcSafeFileOpen ( - DmgDir, - &DmgFile, - DmgFileInfo->FileName, - EFI_FILE_MODE_READ, - 0 - ); - if (EFI_ERROR (Status)) { - DEBUG (( - DEBUG_INFO, - "OCB: Failed to open DMG file %s - %r\n", - DmgFileInfo->FileName, - Status - )); + DmgDir->Close (DmgDir); + return NULL; + } - FreePool (DmgFileInfo); - DmgDir->Close (DmgDir); - return NULL; - } + Status = OcSafeFileOpen ( + DmgDir, + &DmgFile, + DmgFileInfo->FileName, + EFI_FILE_MODE_READ, + 0 + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_INFO, + "OCB: Failed to open DMG file %s - %r\n", + DmgFileInfo->FileName, + Status + )); + + FreePool (DmgFileInfo); + DmgDir->Close (DmgDir); + return NULL; + } - Status = OcGetFileSize (DmgFile, &DmgFileSize); - if (EFI_ERROR (Status)) { - DEBUG (( - DEBUG_INFO, - "OCB: Failed to retrieve DMG file size - %r\n", - Status - )); + Status = OcGetFileSize (DmgFile, &DmgFileSize); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_INFO, + "OCB: Failed to retrieve DMG file size - %r\n", + Status + )); + + FreePool (DmgFileInfo); + DmgDir->Close (DmgDir); + DmgFile->Close (DmgFile); + return NULL; + } + } - FreePool (DmgFileInfo); - DmgDir->Close (DmgDir); - DmgFile->Close (DmgFile); - return NULL; - } + Context->DmgContext = AllocatePool (sizeof (*Context->DmgContext)); + if (Context->DmgContext == NULL) { + DEBUG ((DEBUG_INFO, "OCB: Failed to allocate DMG context\n")); + return NULL; + } - Context->DmgContext = AllocatePool (sizeof (*Context->DmgContext)); - if (Context->DmgContext == NULL) { - DEBUG ((DEBUG_INFO, "OCB: Failed to allocate DMG context\n")); - return NULL; - } + Result = OcAppleDiskImageInitializeFromFile (Context->DmgContext, DmgFile); - Result = OcAppleDiskImageInitializeFromFile (Context->DmgContext, DmgFile); + DmgFile->Close (DmgFile); - DmgFile->Close (DmgFile); + if (!Result) { + DEBUG ((DEBUG_INFO, "OCB: Failed to initialise DMG from file\n")); - if (!Result) { - DEBUG ((DEBUG_INFO, "OCB: Failed to initialise DMG from file\n")); + if (DmgPreloadContext->DmgFile == NULL) { + FreePool (DmgFileInfo); + DmgDir->Close (DmgDir); + } - FreePool (DmgFileInfo); - FreePool (Context->DmgContext); - DmgDir->Close (DmgDir); - return NULL; + FreePool (Context->DmgContext); + return NULL; + } } ChunklistBuffer = NULL; ChunklistFileSize = 0; + if ( (DmgPreloadContext->DmgFile != NULL) + || (DmgPreloadContext->DmgContext != NULL)) + { + if (DmgPreloadContext->ChunklistBuffer != NULL) { + ChunklistBuffer = DmgPreloadContext->ChunklistBuffer; + ChunklistFileSize = DmgPreloadContext->ChunklistFileSize; + } + } else { + ChunklistFileInfo = InternalFindDmgChunklist ( + DmgDir, + DmgFileInfo->FileName, + DmgFileNameLen + ); + if (ChunklistFileInfo != NULL) { + Status = OcSafeFileOpen ( + DmgDir, + &ChunklistFile, + ChunklistFileInfo->FileName, + EFI_FILE_MODE_READ, + 0 + ); + if (!EFI_ERROR (Status)) { + Status = OcGetFileSize (ChunklistFile, &ChunklistFileSize); + if (Status == EFI_SUCCESS) { + ChunklistBuffer = AllocatePool (ChunklistFileSize); - ChunklistFileInfo = InternalFindDmgChunklist ( - DmgDir, - DmgFileInfo->FileName, - DmgFileNameLen - ); - if (ChunklistFileInfo != NULL) { - Status = OcSafeFileOpen ( - DmgDir, - &ChunklistFile, - ChunklistFileInfo->FileName, - EFI_FILE_MODE_READ, - 0 - ); - if (!EFI_ERROR (Status)) { - Status = OcGetFileSize (ChunklistFile, &ChunklistFileSize); - if (Status == EFI_SUCCESS) { - ChunklistBuffer = AllocatePool (ChunklistFileSize); - - if (ChunklistBuffer == NULL) { - ChunklistFileSize = 0; - } else { - Status = OcGetFileData (ChunklistFile, 0, ChunklistFileSize, ChunklistBuffer); - if (EFI_ERROR (Status)) { - FreePool (ChunklistBuffer); - ChunklistBuffer = NULL; + if (ChunklistBuffer == NULL) { ChunklistFileSize = 0; + } else { + Status = OcGetFileData (ChunklistFile, 0, ChunklistFileSize, ChunklistBuffer); + if (EFI_ERROR (Status)) { + FreePool (ChunklistBuffer); + ChunklistBuffer = NULL; + ChunklistFileSize = 0; + } } } + + ChunklistFile->Close (ChunklistFile); } - ChunklistFile->Close (ChunklistFile); + FreePool (ChunklistFileInfo); } - - FreePool (ChunklistFileInfo); } - FreePool (DmgFileInfo); - - DmgDir->Close (DmgDir); + if ( (DmgPreloadContext->DmgFile == NULL) + && (DmgPreloadContext->DmgContext == NULL)) + { + FreePool (DmgFileInfo); + DmgDir->Close (DmgDir); + } DevPath = InternalGetDiskImageBootFile ( Context, diff --git a/Library/OcBootManagementLib/OcBootManagementLib.c b/Library/OcBootManagementLib/OcBootManagementLib.c index b1d778226fa..8f9a242e87f 100644 --- a/Library/OcBootManagementLib/OcBootManagementLib.c +++ b/Library/OcBootManagementLib/OcBootManagementLib.c @@ -508,7 +508,6 @@ OcRunBootPicker ( ); OcRestoreNvramProtection (FwRuntime); - RestoreMode (); // // Do not wait on successful return code. @@ -524,6 +523,11 @@ OcRunBootPicker ( OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_EXECUTION_SUCCESSFUL, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE); } + // + // Restore mode after any delay. + // + RestoreMode (); + // // Ensure that we flush all pressed keys after the application. // This resolves the problem of application-pressed keys being used to control the menu. diff --git a/Library/OcMainLib/OpenCoreMisc.c b/Library/OcMainLib/OpenCoreMisc.c index 070bd8bed75..999f44d98de 100644 --- a/Library/OcMainLib/OpenCoreMisc.c +++ b/Library/OcMainLib/OpenCoreMisc.c @@ -25,6 +25,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include #include #include +#include #include #include #include @@ -232,6 +233,21 @@ ProduceDebugReport ( DEBUG ((DEBUG_INFO, "OC: GOPInfo dumping - %r\n", Status)); + Status = OcSafeFileOpen ( + SysReport, + &SubReport, + L"Drivers", + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, + EFI_FILE_DIRECTORY + ); + if (!EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "OC: Dumping DriverImageNames for report...\n")); + Status = OcDriverInfoDump (SubReport); + SubReport->Close (SubReport); + } + + DEBUG ((DEBUG_INFO, "OC: DriverImageNames dumping - %r\n", Status)); + SysReport->Close (SysReport); Fs->Close (Fs); @@ -242,13 +258,16 @@ STATIC EFI_STATUS EFIAPI OcToolLoadEntry ( - IN OC_STORAGE_CONTEXT *Storage, - IN OC_BOOT_ENTRY *ChosenEntry, - OUT VOID **Data, - OUT UINT32 *DataSize, - OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, - OUT EFI_HANDLE *StorageHandle, - OUT EFI_DEVICE_PATH_PROTOCOL **StoragePath + IN OC_STORAGE_CONTEXT *Storage, + IN OC_BOOT_ENTRY *ChosenEntry, + OUT VOID **Data, + OUT UINT32 *DataSize, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, + OUT EFI_HANDLE *StorageHandle, + OUT EFI_DEVICE_PATH_PROTOCOL **StoragePath, + IN OC_DMG_LOADING_SUPPORT DmgLoading, + OUT OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext, + OUT VOID **CustomFreeContext ) { EFI_STATUS Status; diff --git a/Library/OcPngLib/lodepng.c b/Library/OcPngLib/lodepng.c index 9090f30ffc6..16aa8a55ba2 100644 --- a/Library/OcPngLib/lodepng.c +++ b/Library/OcPngLib/lodepng.c @@ -121,9 +121,6 @@ to something as fast. */ #ifdef EFIAPI -// Floating point operations are used here, this must be defined to prevent linker error -const int32_t _fltused = 0; - #define LODEPNG_MAX_ALLOC ((size_t)256*1024*1024) void* lodepng_malloc(size_t size) { diff --git a/OpenCorePkg.dsc b/OpenCorePkg.dsc index d5d157a6357..1861d4923f3 100755 --- a/OpenCorePkg.dsc +++ b/OpenCorePkg.dsc @@ -30,8 +30,8 @@ DEFINE NETWORK_ENABLE = TRUE DEFINE NETWORK_SNP_ENABLE = TRUE DEFINE NETWORK_IP4_ENABLE = TRUE - DEFINE NETWORK_IP6_ENABLE = FALSE - DEFINE NETWORK_TLS_ENABLE = FALSE + DEFINE NETWORK_IP6_ENABLE = TRUE + DEFINE NETWORK_TLS_ENABLE = TRUE DEFINE NETWORK_HTTP_ENABLE = TRUE DEFINE NETWORK_HTTP_BOOT_ENABLE = TRUE DEFINE NETWORK_ALLOW_HTTP_CONNECTIONS = TRUE @@ -160,7 +160,7 @@ UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf UefiBootManagerLib|MdeModulePkg/Library/UefiBootManagerLib/UefiBootManagerLib.inf UefiDriverEntryPoint|OpenCorePkg/Library/OcDriverEntryPoint/UefiDriverEntryPoint.inf - UefiHiiServicesLib|MdeModulePkg/Library/UefiHiiServicesLib/UefiHiiServicesLib.inf + UefiHiiServicesLib|OpenCorePkg/Library/OcHiiServicesLib/OcHiiServicesLib.inf UefiImageExtraActionLib|MdePkg/Library/BaseUefiImageExtraActionLibNull/BaseUefiImageExtraActionLibNull.inf UefiLib|MdePkg/Library/UefiLib/UefiLib.inf UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf @@ -169,8 +169,21 @@ VariableFlashInfoLib|MdeModulePkg/Library/BaseVariableFlashInfoLib/BaseVariableFlashInfoLib.inf ResetSystemLib|OpenCorePkg/Library/OcResetSystemLib/OcResetSystemLib.inf + !if $(NETWORK_TLS_ENABLE) == TRUE + BaseCryptLib|CryptoPkg/Library/BaseCryptLib/BaseCryptLib.inf + # FileExplorerLib is for TlsAuthConfigDxe only (not used by us, but enabled by NETWORK_TLS_ENABLE) + FileExplorerLib|MdeModulePkg/Library/FileExplorerLib/FileExplorerLib.inf + IntrinsicLib|MdePkg/Library/IntrinsicLib/IntrinsicLib.inf + OpensslLib|CryptoPkg/Library/OpensslLib/OpensslLib.inf + RngLib|MdeModulePkg/Library/BaseRngLibTimerLib/BaseRngLibTimerLib.inf + SafeIntLib|MdePkg/Library/BaseSafeIntLib/BaseSafeIntLib.inf + TlsLib|CryptoPkg/Library/TlsLib/TlsLib.inf + !endif + !include NetworkPkg/NetworkLibs.dsc.inc + HttpLib|NetworkPkg/Library/DxeHttpLib/DxeHttpLib.inf + !include Ext4Pkg/Ext4Defines.dsc.inc !include Ext4Pkg/Ext4Libs.dsc.inc @@ -244,7 +257,6 @@ OpenCorePkg/Library/OcBlitLib/OcBlitLib.inf OpenCorePkg/Library/OcBootManagementLib/OcBootManagementLib.inf OpenCorePkg/Library/OcBootServicesTableLib/OcBootServicesTableLib.inf - OpenCorePkg/Library/OcCompilerIntrinsicsLib/OcCompilerIntrinsicsLib.inf OpenCorePkg/Library/OcCompressionLib/OcCompressionLib.inf OpenCorePkg/Library/OcConfigurationLib/OcConfigurationLib.inf OpenCorePkg/Library/OcConsoleControlEntryModeLib/OcConsoleControlEntryModeGenericLib.inf @@ -301,6 +313,7 @@ OpenCorePkg/Platform/OpenCanopy/OpenCanopy.inf OpenCorePkg/Platform/OpenLegacyBoot/OpenLegacyBoot.inf OpenCorePkg/Platform/OpenLinuxBoot/OpenLinuxBoot.inf + OpenCorePkg/Platform/OpenNetworkBoot/OpenNetworkBoot.inf OpenCorePkg/Platform/OpenNtfsDxe/OpenNtfsDxe.inf OpenCorePkg/Platform/OpenPartitionDxe/PartitionDxe.inf OpenCorePkg/Platform/OpenRuntime/OpenRuntime.inf @@ -370,13 +383,23 @@ # Ext4 driver Ext4Pkg/Ext4Dxe/Ext4Dxe.inf + # RNG and HASH2 protocols are required by various network boot drivers since edk2-stable202405 + # REF: https://github.com/acidanthera/bugtracker/issues/2421 + SecurityPkg/RandomNumberGenerator/RngDxe/RngDxe.inf + SecurityPkg/Hash2DxeCrypto/Hash2DxeCrypto.inf + # # Network Support # !include NetworkPkg/NetworkComponents.dsc.inc + # + # Ramdisk support (driver required for network boot native .iso/.img support) + # + MdeModulePkg/Universal/Disk/RamDiskDxe/RamDiskDxe.inf + [LibraryClasses] - NULL|OpenCorePkg/Library/OcCompilerIntrinsicsLib/OcCompilerIntrinsicsLib.inf + NULL|MdePkg/Library/IntrinsicLib/IntrinsicLib.inf [PcdsFixedAtBuild] gEfiMdePkgTokenSpaceGuid.PcdMaximumAsciiStringLength|0 diff --git a/OpenDuetPkg.dsc b/OpenDuetPkg.dsc index 3704c47fdb7..f8ab35e26c6 100644 --- a/OpenDuetPkg.dsc +++ b/OpenDuetPkg.dsc @@ -245,10 +245,8 @@ OpenCorePkg/Legacy/BootPlatform/LegacyRegion2Dxe/LegacyRegion2Dxe.inf OpenCorePkg/Legacy/BootPlatform/BiosVideo/BiosVideo.inf - OpenCorePkg/Library/OcCompilerIntrinsicsLib/OcCompilerIntrinsicsLib.inf - [LibraryClasses] - NULL|OpenCorePkg/Library/OcCompilerIntrinsicsLib/OcCompilerIntrinsicsLib.inf + NULL|MdePkg/Library/IntrinsicLib/IntrinsicLib.inf [PcdsFeatureFlag] gEfiMdeModulePkgTokenSpaceGuid.PcdSupportHiiImageProtocol|FALSE diff --git a/OpenDuetPkgDefines.fdf.inc b/OpenDuetPkgDefines.fdf.inc index 4a688b389a1..40e2f7cff9c 100644 --- a/OpenDuetPkgDefines.fdf.inc +++ b/OpenDuetPkgDefines.fdf.inc @@ -22,7 +22,7 @@ !if ($(TARGET) == DEBUG) NumBlocks = 0xe #GenFv image size 0xe0000 !else - NumBlocks = 0x24 #GenFv image size 0x240000 + NumBlocks = 0x25 #GenFv image size 0x250000 !endif !endif FvAlignment = 16 #FV alignment and FV attributes setting. diff --git a/Platform/OpenCanopy/GuiApp.c b/Platform/OpenCanopy/GuiApp.c index acf6131201d..6081f5920cc 100644 --- a/Platform/OpenCanopy/GuiApp.c +++ b/Platform/OpenCanopy/GuiApp.c @@ -47,7 +47,8 @@ CONST CHAR8 * [LABEL_SHELL] = "Shell", [LABEL_SIP_IS_ENABLED] = "SIPEnabled", [LABEL_SIP_IS_DISABLED] = "SIPDisabled", - [LABEL_FIRMWARE_SETTINGS] = "FirmwareSettings" + [LABEL_FIRMWARE_SETTINGS] = "FirmwareSettings", + [LABEL_NETWORK_BOOT] = "NetworkBoot" }; STATIC diff --git a/Platform/OpenCanopy/GuiApp.h b/Platform/OpenCanopy/GuiApp.h index fc9bd0852fa..efce8957589 100644 --- a/Platform/OpenCanopy/GuiApp.h +++ b/Platform/OpenCanopy/GuiApp.h @@ -67,6 +67,7 @@ typedef enum { LABEL_SIP_IS_ENABLED, LABEL_SIP_IS_DISABLED, LABEL_FIRMWARE_SETTINGS, + LABEL_NETWORK_BOOT, LABEL_NUM_TOTAL } LABEL_TARGET; diff --git a/Platform/OpenCanopy/Views/BootPicker.c b/Platform/OpenCanopy/Views/BootPicker.c index ba714682cf0..cf0b92c26b3 100644 --- a/Platform/OpenCanopy/Views/BootPicker.c +++ b/Platform/OpenCanopy/Views/BootPicker.c @@ -1470,7 +1470,12 @@ BootPickerEntriesSet ( Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_WINDOWS]); break; case OC_BOOT_EXTERNAL_OS: - Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_OTHER]); + if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_ID_NETWORK_BOOT) != NULL) { + Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_NETWORK_BOOT]); + } else { + Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_OTHER]); + } + break; // // Use flavour-based labels for system entries (e.g. from boot entry protocol). diff --git a/Platform/OpenLegacyBoot/OpenLegacyBoot.c b/Platform/OpenLegacyBoot/OpenLegacyBoot.c index f18577f434d..51879712998 100644 --- a/Platform/OpenLegacyBoot/OpenLegacyBoot.c +++ b/Platform/OpenLegacyBoot/OpenLegacyBoot.c @@ -426,7 +426,7 @@ OcGetLegacyBootEntries ( PickerEntry->External = IsExternal; PickerEntry->UnmanagedBootAction = UnmanagedBootActionDoLegacyBoot; PickerEntry->UnmanagedBootGetFinalDevicePath = UnmanagedBootGetFinalDevicePath; - PickerEntry->UnmanagedBootDevicePath = BlockDevicePath; + PickerEntry->UnmanagedDevicePath = BlockDevicePath; if ((PickerEntry->Name == NULL) || (PickerEntry->Flavour == NULL)) { OcFlexArrayFree (&FlexPickerEntries); diff --git a/Platform/OpenNetworkBoot/BmBoot.c b/Platform/OpenNetworkBoot/BmBoot.c new file mode 100644 index 00000000000..69919829be2 --- /dev/null +++ b/Platform/OpenNetworkBoot/BmBoot.c @@ -0,0 +1,611 @@ +/** @file + Library functions which relate to booting. + +Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. +Copyright (c) 2011 - 2021, Intel Corporation. All rights reserved.
+(C) Copyright 2015-2021 Hewlett Packard Enterprise Development LP
+Copyright (C) 2024, Mike Beaton. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "NetworkBootInternal.h" + +EFI_RAM_DISK_PROTOCOL *mRamDisk = NULL; + +/** + Expand the media device path which points to a BlockIo or SimpleFileSystem instance + by appending EFI_REMOVABLE_MEDIA_FILE_NAME. + + @param DevicePath The media device path pointing to a BlockIo or SimpleFileSystem instance. + @param FullPath The full path returned by the routine in last call. + Set to NULL in first call. + + @return The next possible full path pointing to the load option. + Caller is responsible to free the memory. +**/ +STATIC +EFI_DEVICE_PATH_PROTOCOL * +BmExpandMediaDevicePath ( + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + IN EFI_DEVICE_PATH_PROTOCOL *FullPath + ) +{ + EFI_STATUS Status; + EFI_HANDLE Handle; + EFI_BLOCK_IO_PROTOCOL *BlockIo; + VOID *Buffer; + EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; + EFI_DEVICE_PATH_PROTOCOL *NextFullPath; + UINTN Size; + UINTN TempSize; + EFI_HANDLE *SimpleFileSystemHandles; + UINTN NumberSimpleFileSystemHandles; + UINTN Index; + BOOLEAN GetNext; + + GetNext = (BOOLEAN)(FullPath == NULL); + // + // Check whether the device is connected + // + TempDevicePath = DevicePath; + Status = gBS->LocateDevicePath (&gEfiSimpleFileSystemProtocolGuid, &TempDevicePath, &Handle); + if (!EFI_ERROR (Status)) { + ASSERT (IsDevicePathEnd (TempDevicePath)); + + NextFullPath = FileDevicePath (Handle, EFI_REMOVABLE_MEDIA_FILE_NAME); + // + // For device path pointing to simple file system, it only expands to one full path. + // + if (GetNext) { + return NextFullPath; + } else { + FreePool (NextFullPath); + return NULL; + } + } + + Status = gBS->LocateDevicePath (&gEfiBlockIoProtocolGuid, &TempDevicePath, &Handle); + ASSERT_EFI_ERROR (Status); + + // + // For device boot option only pointing to the removable device handle, + // should make sure all its children handles (its child partion or media handles) + // are created and connected. + // + gBS->ConnectController (Handle, NULL, NULL, TRUE); + + // + // Issue a dummy read to the device to check for media change. + // When the removable media is changed, any Block IO read/write will + // cause the BlockIo protocol be reinstalled and EFI_MEDIA_CHANGED is + // returned. After the Block IO protocol is reinstalled, subsequent + // Block IO read/write will success. + // + Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **)&BlockIo); + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + return NULL; + } + + Buffer = AllocatePool (BlockIo->Media->BlockSize); + if (Buffer != NULL) { + BlockIo->ReadBlocks ( + BlockIo, + BlockIo->Media->MediaId, + 0, + BlockIo->Media->BlockSize, + Buffer + ); + FreePool (Buffer); + } + + // + // Detect the the default boot file from removable Media + // + NextFullPath = NULL; + Size = GetDevicePathSize (DevicePath) - END_DEVICE_PATH_LENGTH; + gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiSimpleFileSystemProtocolGuid, + NULL, + &NumberSimpleFileSystemHandles, + &SimpleFileSystemHandles + ); + for (Index = 0; Index < NumberSimpleFileSystemHandles; Index++) { + // + // Get the device path size of SimpleFileSystem handle + // + TempDevicePath = DevicePathFromHandle (SimpleFileSystemHandles[Index]); + TempSize = GetDevicePathSize (TempDevicePath) - END_DEVICE_PATH_LENGTH; + // + // Check whether the device path of boot option is part of the SimpleFileSystem handle's device path + // + if ((Size <= TempSize) && (CompareMem (TempDevicePath, DevicePath, Size) == 0)) { + NextFullPath = FileDevicePath (SimpleFileSystemHandles[Index], EFI_REMOVABLE_MEDIA_FILE_NAME); + if (GetNext) { + break; + } else { + GetNext = (BOOLEAN)(CompareMem (NextFullPath, FullPath, GetDevicePathSize (NextFullPath)) == 0); + FreePool (NextFullPath); + NextFullPath = NULL; + } + } + } + + if (SimpleFileSystemHandles != NULL) { + FreePool (SimpleFileSystemHandles); + } + + return NextFullPath; +} + +/** + Check whether Left and Right are the same without matching the specific + device path data in IP device path and URI device path node. + + @retval TRUE Left and Right are the same. + @retval FALSE Left and Right are the different. +**/ +STATIC +BOOLEAN +BmMatchHttpBootDevicePath ( + IN EFI_DEVICE_PATH_PROTOCOL *Left, + IN EFI_DEVICE_PATH_PROTOCOL *Right + ) +{ + for ( ; !IsDevicePathEnd (Left) && !IsDevicePathEnd (Right) + ; Left = NextDevicePathNode (Left), Right = NextDevicePathNode (Right) + ) + { + if (CompareMem (Left, Right, DevicePathNodeLength (Left)) != 0) { + if ((DevicePathType (Left) != MESSAGING_DEVICE_PATH) || (DevicePathType (Right) != MESSAGING_DEVICE_PATH)) { + return FALSE; + } + + if (DevicePathSubType (Left) == MSG_DNS_DP) { + Left = NextDevicePathNode (Left); + } + + if (DevicePathSubType (Right) == MSG_DNS_DP) { + Right = NextDevicePathNode (Right); + } + + if (((DevicePathSubType (Left) != MSG_IPv4_DP) || (DevicePathSubType (Right) != MSG_IPv4_DP)) && + ((DevicePathSubType (Left) != MSG_IPv6_DP) || (DevicePathSubType (Right) != MSG_IPv6_DP)) && + ((DevicePathSubType (Left) != MSG_URI_DP) || (DevicePathSubType (Right) != MSG_URI_DP)) + ) + { + return FALSE; + } + } + } + + return (BOOLEAN)(IsDevicePathEnd (Left) && IsDevicePathEnd (Right)); +} + +/** + Get the file buffer from the file system produced by Load File instance. + + @param LoadFileHandle The handle of LoadFile instance. + @param RamDiskHandle Return the RAM Disk handle. + + @return The next possible full path pointing to the load option. + Caller is responsible to free the memory. +**/ +STATIC +EFI_DEVICE_PATH_PROTOCOL * +BmExpandNetworkFileSystem ( + IN EFI_HANDLE LoadFileHandle, + OUT EFI_HANDLE *RamDiskHandle + ) +{ + EFI_STATUS Status; + EFI_HANDLE Handle; + EFI_HANDLE *Handles; + UINTN HandleCount; + UINTN Index; + EFI_DEVICE_PATH_PROTOCOL *Node; + + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiBlockIoProtocolGuid, + NULL, + &HandleCount, + &Handles + ); + if (EFI_ERROR (Status)) { + Handles = NULL; + HandleCount = 0; + } + + Handle = NULL; + for (Index = 0; Index < HandleCount; Index++) { + Node = DevicePathFromHandle (Handles[Index]); + Status = gBS->LocateDevicePath (&gEfiLoadFileProtocolGuid, &Node, &Handle); + if (!EFI_ERROR (Status) && + (Handle == LoadFileHandle) && + (DevicePathType (Node) == MEDIA_DEVICE_PATH) && (DevicePathSubType (Node) == MEDIA_RAM_DISK_DP)) + { + // + // Find the BlockIo instance populated from the LoadFile. + // + Handle = Handles[Index]; + break; + } + } + + if (Handles != NULL) { + FreePool (Handles); + } + + if (Index == HandleCount) { + Handle = NULL; + } + + *RamDiskHandle = Handle; + + if (Handle != NULL) { + // + // Re-use BmExpandMediaDevicePath() to get the full device path of load option. + // But assume only one SimpleFileSystem can be found under the BlockIo. + // + return BmExpandMediaDevicePath (DevicePathFromHandle (Handle), NULL); + } else { + return NULL; + } +} + +/** + Return the RAM Disk device path created by LoadFile. + + @param FilePath The source file path. + + @return Callee-to-free RAM Disk device path +**/ +EFI_DEVICE_PATH_PROTOCOL * +BmGetRamDiskDevicePath ( + IN EFI_DEVICE_PATH_PROTOCOL *FilePath + ) +{ + EFI_STATUS Status; + EFI_DEVICE_PATH_PROTOCOL *RamDiskDevicePath; + EFI_DEVICE_PATH_PROTOCOL *Node; + EFI_HANDLE Handle; + + Node = FilePath; + Status = gBS->LocateDevicePath (&gEfiLoadFileProtocolGuid, &Node, &Handle); + if (!EFI_ERROR (Status) && + (DevicePathType (Node) == MEDIA_DEVICE_PATH) && + (DevicePathSubType (Node) == MEDIA_RAM_DISK_DP) + ) + { + // + // Construct the device path pointing to RAM Disk + // + Node = NextDevicePathNode (Node); + RamDiskDevicePath = DuplicateDevicePath (FilePath); + ASSERT (RamDiskDevicePath != NULL); + SetDevicePathEndNode ((VOID *)((UINTN)RamDiskDevicePath + ((UINTN)Node - (UINTN)FilePath))); + return RamDiskDevicePath; + } + + return NULL; +} + +/** + Return the buffer and buffer size occupied by the RAM Disk. + + @param RamDiskDevicePath RAM Disk device path. + @param RamDiskSizeInPages Return RAM Disk size in pages. + + @retval RAM Disk buffer. +**/ +STATIC +VOID * +BmGetRamDiskMemoryInfo ( + IN EFI_DEVICE_PATH_PROTOCOL *RamDiskDevicePath, + OUT UINTN *RamDiskSizeInPages + ) +{ + EFI_STATUS Status; + EFI_HANDLE Handle; + UINT64 StartingAddr; + UINT64 EndingAddr; + + ASSERT (RamDiskDevicePath != NULL); + + *RamDiskSizeInPages = 0; + + // + // Get the buffer occupied by RAM Disk. + // + Status = gBS->LocateDevicePath (&gEfiLoadFileProtocolGuid, &RamDiskDevicePath, &Handle); + ASSERT_EFI_ERROR (Status); + ASSERT ( + (DevicePathType (RamDiskDevicePath) == MEDIA_DEVICE_PATH) && + (DevicePathSubType (RamDiskDevicePath) == MEDIA_RAM_DISK_DP) + ); + StartingAddr = ReadUnaligned64 ((UINT64 *)((MEDIA_RAM_DISK_DEVICE_PATH *)RamDiskDevicePath)->StartingAddr); + EndingAddr = ReadUnaligned64 ((UINT64 *)((MEDIA_RAM_DISK_DEVICE_PATH *)RamDiskDevicePath)->EndingAddr); + *RamDiskSizeInPages = EFI_SIZE_TO_PAGES ((UINTN)(EndingAddr - StartingAddr + 1)); + return (VOID *)(UINTN)StartingAddr; +} + +/** + Destroy the RAM Disk. + + The destroy operation includes to call RamDisk.Unregister to + unregister the RAM DISK from RAM DISK driver, free the memory + allocated for the RAM Disk. + + @param RamDiskDevicePath RAM Disk device path. +**/ +VOID +BmDestroyRamDisk ( + IN EFI_DEVICE_PATH_PROTOCOL *RamDiskDevicePath + ) +{ + EFI_STATUS Status; + VOID *RamDiskBuffer; + UINTN RamDiskSizeInPages; + + ASSERT (RamDiskDevicePath != NULL); + + RamDiskBuffer = BmGetRamDiskMemoryInfo (RamDiskDevicePath, &RamDiskSizeInPages); + + // + // Destroy RAM Disk. + // + if (mRamDisk == NULL) { + Status = gBS->LocateProtocol (&gEfiRamDiskProtocolGuid, NULL, (VOID *)&mRamDisk); + ASSERT_EFI_ERROR (Status); + } + + Status = mRamDisk->Unregister (RamDiskDevicePath); + ASSERT_EFI_ERROR (Status); + FreePages (RamDiskBuffer, RamDiskSizeInPages); +} + +/** + Get the file buffer from the specified Load File instance. + + @param LoadFileHandle The specified Load File instance. + @param FilePath The file path which will pass to LoadFile(). + + @return The full device path pointing to the load option buffer. +**/ +STATIC +EFI_DEVICE_PATH_PROTOCOL * +BmExpandLoadFile ( + IN EFI_HANDLE LoadFileHandle, + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + OUT VOID **Data, + OUT UINT32 *DataSize + ) +{ + EFI_STATUS Status; + EFI_LOAD_FILE_PROTOCOL *LoadFile; + VOID *FileBuffer; + EFI_HANDLE RamDiskHandle; + UINTN BufferSize; + EFI_DEVICE_PATH_PROTOCOL *FullPath; + + ASSERT (Data != NULL); + ASSERT (DataSize != NULL); + + *Data = NULL; + *DataSize = 0; + + Status = gBS->OpenProtocol ( + LoadFileHandle, + &gEfiLoadFileProtocolGuid, + (VOID **)&LoadFile, + gImageHandle, + NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + ASSERT_EFI_ERROR (Status); + + FileBuffer = NULL; + BufferSize = 0; + Status = LoadFile->LoadFile (LoadFile, FilePath, TRUE, &BufferSize, FileBuffer); + if ((Status != EFI_WARN_FILE_SYSTEM) && (Status != EFI_BUFFER_TOO_SMALL)) { + return NULL; + } + + // + // In call tree of original BmGetLoadOptionBuffer, handling this case + // is deferred to subsequent call to GetFileBufferByFilePath. + // + if (Status == EFI_BUFFER_TOO_SMALL) { + // + // Limited to UINT32 by DataSize in OC_CUSTOM_READ, which is limited + // by Size in OcGetFileSize. + // + if (BufferSize > MAX_UINT32) { + return NULL; + } + + FileBuffer = AllocatePool (BufferSize); + if (FileBuffer == NULL) { + return NULL; + } + + // + // Call LoadFile with the correct buffer size. + // + FullPath = DevicePathFromHandle (LoadFileHandle); + Status = LoadFile->LoadFile (LoadFile, FilePath, TRUE, &BufferSize, FileBuffer); + if (EFI_ERROR (Status)) { + FreePool (FileBuffer); + return NULL; + } + + *DataSize = (UINT32)BufferSize; + *Data = FileBuffer; + + return DuplicateDevicePath (FullPath); + } + + // + // The load option resides in a RAM disk. + // + FileBuffer = AllocateReservedPages (EFI_SIZE_TO_PAGES (BufferSize)); + if (FileBuffer == NULL) { + DEBUG_CODE_BEGIN (); + EFI_DEVICE_PATH *LoadFilePath; + CHAR16 *LoadFileText; + CHAR16 *FileText; + + LoadFilePath = DevicePathFromHandle (LoadFileHandle); + if (LoadFilePath == NULL) { + LoadFileText = NULL; + } else { + LoadFileText = ConvertDevicePathToText (LoadFilePath, FALSE, FALSE); + } + + FileText = ConvertDevicePathToText (FilePath, FALSE, FALSE); + + DEBUG (( + DEBUG_ERROR, + "%a:%a: failed to allocate reserved pages: " + "BufferSize=%Lu LoadFile=\"%s\" FilePath=\"%s\"\n", + gEfiCallerBaseName, + __func__, + (UINT64)BufferSize, + LoadFileText, + FileText + )); + + if (FileText != NULL) { + FreePool (FileText); + } + + if (LoadFileText != NULL) { + FreePool (LoadFileText); + } + + DEBUG_CODE_END (); + return NULL; + } + + Status = LoadFile->LoadFile (LoadFile, FilePath, TRUE, &BufferSize, FileBuffer); + if (EFI_ERROR (Status)) { + FreePages (FileBuffer, EFI_SIZE_TO_PAGES (BufferSize)); + return NULL; + } + + FullPath = BmExpandNetworkFileSystem (LoadFileHandle, &RamDiskHandle); + if (FullPath == NULL) { + // + // Free the memory occupied by the RAM disk if there is no BlockIo or SimpleFileSystem instance. + // + BmDestroyRamDisk (DevicePathFromHandle (RamDiskHandle)); + } + + return FullPath; +} + +/** + Return the full device path pointing to the load option. + + FilePath may: + 1. Exactly matches to a LoadFile instance. + 2. Cannot match to any LoadFile instance. Wide match is required. + In either case, the routine may return: + 1. A copy of FilePath when FilePath matches to a LoadFile instance and + the LoadFile returns a load option buffer. + 2. A new device path with IP and URI information updated when wide match + happens. + 3. A new device path pointing to a load option in RAM disk. + In either case, only one full device path is returned for a specified + FilePath. + + @param FilePath The media device path pointing to a LoadFile instance. + + @return The load option buffer. +**/ +EFI_DEVICE_PATH_PROTOCOL * +BmExpandLoadFiles ( + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + OUT VOID **Data, + OUT UINT32 *DataSize, + IN BOOLEAN ValidateHttp + ) +{ + EFI_STATUS Status; + EFI_HANDLE Handle; + EFI_HANDLE *Handles; + UINTN HandleCount; + UINTN Index; + EFI_DEVICE_PATH_PROTOCOL *Node; + EFI_EVENT NotifyEvent; + + // + // Get file buffer from load file instance. + // + Node = FilePath; + Status = gBS->LocateDevicePath (&gEfiLoadFileProtocolGuid, &Node, &Handle); + if (!EFI_ERROR (Status) && IsDevicePathEnd (Node)) { + // + // When wide match happens, pass full device path to LoadFile (), + // otherwise, pass remaining device path to LoadFile (). + // + FilePath = Node; + } else { + Handle = NULL; + // + // Use wide match algorithm to find one when + // cannot find a LoadFile instance to exactly match the FilePath + // + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiLoadFileProtocolGuid, + NULL, + &HandleCount, + &Handles + ); + if (EFI_ERROR (Status)) { + Handles = NULL; + HandleCount = 0; + } + + for (Index = 0; Index < HandleCount; Index++) { + if (BmMatchHttpBootDevicePath (DevicePathFromHandle (Handles[Index]), FilePath)) { + Handle = Handles[Index]; + break; + } + } + + if (Handles != NULL) { + FreePool (Handles); + } + } + + if (Handle == NULL) { + return NULL; + } + + if (ValidateHttp) { + NotifyEvent = MonitorHttpBootCallback (Handle); + if (NotifyEvent == NULL) { + return NULL; + } + } + + Node = BmExpandLoadFile (Handle, FilePath, Data, DataSize); + + if (ValidateHttp) { + gBS->CloseEvent (NotifyEvent); + + if ((Node != NULL) && !UriWasValidated ()) { + Print (L"\n"); ///< Sort out cramped spacing + DEBUG ((DEBUG_ERROR, "NTBT: LoadFile returned value but URI was never validated\n")); + FreePool (Node); + return NULL; + } + } + + return Node; +} diff --git a/Platform/OpenNetworkBoot/BmBootDescription.c b/Platform/OpenNetworkBoot/BmBootDescription.c new file mode 100644 index 00000000000..d4a054127b4 --- /dev/null +++ b/Platform/OpenNetworkBoot/BmBootDescription.c @@ -0,0 +1,168 @@ +/** @file + Library functions which relate to boot option description. + +Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.
+(C) Copyright 2015 Hewlett Packard Enterprise Development LP
+Copyright (C) 2024, Mike Beaton. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "NetworkBootInternal.h" + +/** + Return the description for network boot device. + + @param Handle Controller handle. + + @return The description string. +**/ +CHAR16 * +BmGetNetworkDescription ( + IN EFI_HANDLE Handle + ) +{ + EFI_STATUS Status; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + MAC_ADDR_DEVICE_PATH *Mac; + VLAN_DEVICE_PATH *Vlan; + EFI_DEVICE_PATH_PROTOCOL *Ip; + EFI_DEVICE_PATH_PROTOCOL *Uri; + CHAR16 *Description; + UINTN DescriptionSize; + + Status = gBS->OpenProtocol ( + Handle, + &gEfiLoadFileProtocolGuid, + NULL, + gImageHandle, + Handle, + EFI_OPEN_PROTOCOL_TEST_PROTOCOL + ); + if (EFI_ERROR (Status)) { + return NULL; + } + + Status = gBS->OpenProtocol ( + Handle, + &gEfiDevicePathProtocolGuid, + (VOID **)&DevicePath, + gImageHandle, + Handle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + if (EFI_ERROR (Status) || (DevicePath == NULL)) { + return NULL; + } + + // + // The PXE device path is like: + // ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)] + // ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv4(...) + // ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv6(...) + // + // The HTTP device path is like: + // ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv4(...)[/Dns(...)]/Uri(...) + // ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv6(...)[/Dns(...)]/Uri(...) + // + while (!IsDevicePathEnd (DevicePath) && + ((DevicePathType (DevicePath) != MESSAGING_DEVICE_PATH) || + (DevicePathSubType (DevicePath) != MSG_MAC_ADDR_DP)) + ) + { + DevicePath = NextDevicePathNode (DevicePath); + } + + if (IsDevicePathEnd (DevicePath)) { + return NULL; + } + + Mac = (MAC_ADDR_DEVICE_PATH *)DevicePath; + DevicePath = NextDevicePathNode (DevicePath); + + // + // Locate the optional Vlan node + // + if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) && + (DevicePathSubType (DevicePath) == MSG_VLAN_DP) + ) + { + Vlan = (VLAN_DEVICE_PATH *)DevicePath; + DevicePath = NextDevicePathNode (DevicePath); + } else { + Vlan = NULL; + } + + // + // Skip the optional Wi-Fi node + // + if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) && + (DevicePathSubType (DevicePath) == MSG_WIFI_DP) + ) + { + DevicePath = NextDevicePathNode (DevicePath); + } + + // + // Locate the IP node + // + if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) && + ((DevicePathSubType (DevicePath) == MSG_IPv4_DP) || + (DevicePathSubType (DevicePath) == MSG_IPv6_DP)) + ) + { + Ip = DevicePath; + DevicePath = NextDevicePathNode (DevicePath); + } else { + Ip = NULL; + } + + // + // Skip the optional DNS node + // + if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) && + (DevicePathSubType (DevicePath) == MSG_DNS_DP) + ) + { + DevicePath = NextDevicePathNode (DevicePath); + } + + // + // Locate the URI node + // + if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) && + (DevicePathSubType (DevicePath) == MSG_URI_DP) + ) + { + Uri = DevicePath; + DevicePath = NextDevicePathNode (DevicePath); + } else { + Uri = NULL; + } + + // + // Build description like below: + // "PXE Boot IPv6 (MAC:11-22-33-44-55-66 VLAN1)" + // "HTTP Boot IPv4 (MAC:11-22-33-44-55-66)" + // + DescriptionSize = sizeof (L"HTTP Boot IPv6 (MAC:11-22-33-44-55-66 VLAN65535)"); + Description = AllocatePool (DescriptionSize); + ASSERT (Description != NULL); + UnicodeSPrint ( + Description, + DescriptionSize, + (Vlan == NULL) ? + L"%s Boot IPv%d (MAC:%02x-%02x-%02x-%02x-%02x-%02x)" : + L"%s Boot IPv%d (MAC:%02x-%02x-%02x-%02x-%02x-%02x VLAN%d)", + (Uri == NULL) ? L"PXE" : L"HTTP", + ((Ip == NULL) || (DevicePathSubType (Ip) == MSG_IPv4_DP)) ? 4 : 6, + Mac->MacAddress.Addr[0], + Mac->MacAddress.Addr[1], + Mac->MacAddress.Addr[2], + Mac->MacAddress.Addr[3], + Mac->MacAddress.Addr[4], + Mac->MacAddress.Addr[5], + (Vlan == NULL) ? 0 : Vlan->VlanId + ); + return Description; +} diff --git a/Platform/OpenNetworkBoot/HttpBootCallback.c b/Platform/OpenNetworkBoot/HttpBootCallback.c new file mode 100644 index 00000000000..145f60e8323 --- /dev/null +++ b/Platform/OpenNetworkBoot/HttpBootCallback.c @@ -0,0 +1,286 @@ +/** @file + Callback handler for HTTP Boot. + + Copyright (c) 2024, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "NetworkBootInternal.h" + +#include +#include +#include + +#define HTTP_CONTENT_TYPE_APP_EFI "application/efi" + +OC_DMG_LOADING_SUPPORT gDmgLoading; + +STATIC EFI_HTTP_BOOT_CALLBACK mOriginalHttpBootCallback; +STATIC BOOLEAN mUriValidated; + +// +// Abort if we are loading a .dmg and these are banned, or if underlying drivers have +// allowed http:// in URL but user setting for OpenNetworkBoot does not allow it. +// If PcdAllowHttpConnections was not set (via NETWORK_ALLOW_HTTP_CONNECTIONS compilation +// flag) then both HttpDxe and HttpBootDxe will enforce https:// before we get to here. +// +EFI_STATUS +ValidateDmgAndHttps ( + CHAR16 *Uri, + BOOLEAN ShowLog, + BOOLEAN *HasDmgExtension + ) +{ + CHAR8 *Match; + CHAR8 *Uri8; + UINTN UriSize; + + if (gRequireHttpsUri && !HasHttpsUri (Uri)) { + // + // Do not return ACCESS_DENIED as this will attempt to add authentication to the request. + // + if (ShowLog) { + DEBUG ((DEBUG_INFO, "NTBT: Invalid URI https:// is required\n")); + } + + return EFI_UNSUPPORTED; + } + + UriSize = StrSize (Uri); + Uri8 = AllocatePool (UriSize); + if (Uri8 == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + UnicodeStrToAsciiStrS (Uri, Uri8, UriSize); + + Match = ".dmg"; + *HasDmgExtension = UriFileHasExtension (Uri8, Match); + if (!*HasDmgExtension) { + Match = ".chunklist"; + *HasDmgExtension = UriFileHasExtension (Uri8, Match); + } + + FreePool (Uri8); + + if (gDmgLoading == OcDmgLoadingDisabled) { + if (*HasDmgExtension) { + if (ShowLog) { + DEBUG ((DEBUG_INFO, "NTBT: %a file is requested while DMG loading is disabled\n", Match)); + } + + return EFI_UNSUPPORTED; + } + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +OpenNetworkBootHttpBootCallback ( + IN EFI_HTTP_BOOT_CALLBACK_PROTOCOL *This, + IN EFI_HTTP_BOOT_CALLBACK_DATA_TYPE DataType, + IN BOOLEAN Received, + IN UINT32 DataLength, + IN VOID *Data OPTIONAL + ) +{ + EFI_STATUS Status; + EFI_HTTP_MESSAGE *HttpMessage; + EFI_HTTP_HEADER *Header; + STATIC BOOLEAN HasDmgExtension = FALSE; + + switch (DataType) { + // + // Abort if http:// is specified but only https:// is allowed. + // Since the URI can come from DHCP boot options this is important for security. + // This will fail to be enforced if this callback doesn't get registered, or + // isn't used by a given implementation of HTTP Boot (it is used by ours, ofc). + // Therefore if a file is returned from HTTP Boot, we check that this + // was really hit, and won't load it otherwise. (Which is less ideal than + // not even fetching the file, as will happen when this callback is hit.) + // + // Also abort early if .dmg or .chunklist is found when DmgLoading is disabled. + // This is a convenience, we could allow these to load and they would be + // rejected eventually anyway. + // + case HttpBootHttpRequest: + if (Data != NULL) { + HttpMessage = (EFI_HTTP_MESSAGE *)Data; + if (HttpMessage->Data.Request->Url != NULL) { + // + // Print log messages once on initial access with HTTP HEAD, don't + // log on subsequent GET which is an attempt to get file size by + // pre-loading entire file for case of chunked encoding (where file + // size is not known until it has been transferred). + // + Status = ValidateDmgAndHttps ( + HttpMessage->Data.Request->Url, + HttpMessage->Data.Request->Method == HttpMethodHead, + &HasDmgExtension + ); + if (EFI_ERROR (Status)) { + return Status; + } + + mUriValidated = TRUE; + } + } + + break; + + // + // Provide fake MIME type of 'application/efi' for .dmg and .chunklist. + // This is also a convenience of sorts, in that as long as the user + // sets 'application/efi' MIME type for these files on their web server, + // they would work anyway. + // + case HttpBootHttpResponse: + if ((Data != NULL) && HasDmgExtension) { + // + // We do not need to keep modifying Content-Type for subsequent packets. + // + HasDmgExtension = FALSE; + + HttpMessage = (EFI_HTTP_MESSAGE *)Data; + Header = HttpFindHeader (HttpMessage->HeaderCount, HttpMessage->Headers, HTTP_HEADER_CONTENT_TYPE); + + if (Header == NULL) { + Header = HttpMessage->Headers; + ++HttpMessage->HeaderCount; + HttpMessage->Headers = AllocatePool (HttpMessage->HeaderCount * sizeof (HttpMessage->Headers[0])); + if (HttpMessage->Headers == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + CopyMem (HttpMessage->Headers, Header, HttpMessage->HeaderCount * sizeof (HttpMessage->Headers[0])); + Header = &HttpMessage->Headers[HttpMessage->HeaderCount - 1]; + Header->FieldValue = NULL; + Header->FieldName = AllocateCopyPool (L_STR_SIZE (HTTP_HEADER_CONTENT_TYPE), HTTP_HEADER_CONTENT_TYPE); + if (Header->FieldName == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } else { + ASSERT (Header->FieldValue != NULL); + if (AsciiStrCmp (Header->FieldValue, HTTP_CONTENT_TYPE_APP_EFI) != 0) { + FreePool (Header->FieldValue); + Header->FieldValue = NULL; + } + } + + if (Header->FieldValue == NULL) { + Header->FieldValue = AllocateCopyPool (L_STR_SIZE (HTTP_CONTENT_TYPE_APP_EFI), HTTP_CONTENT_TYPE_APP_EFI); + if (Header->FieldValue == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } + } + + break; + + default: + break; + } + + return mOriginalHttpBootCallback ( + This, + DataType, + Received, + DataLength, + Data + ); +} + +STATIC +VOID +EFIAPI +NotifyInstallHttpBootCallback ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + EFI_HANDLE LoadFileHandle; + EFI_HTTP_BOOT_CALLBACK_PROTOCOL *HttpBootCallback; + + LoadFileHandle = Context; + + Status = gBS->HandleProtocol ( + LoadFileHandle, + &gEfiHttpBootCallbackProtocolGuid, + (VOID **)&HttpBootCallback + ); + + if (!EFI_ERROR (Status)) { + // + // Due to a bug in EDK 2 HttpBootUninstallCallback, they do not uninstall + // this protocol when they try to. This is okay as long as there is only + // one consumer of the protocol, because our hooked version stays installed + // and gets reused (found as the already installed protocol) on second + // and subsequent tries in HttpBootInstallCallback. + // REF: https://edk2.groups.io/g/devel/message/117469 + // TODO: Add edk2 bugzilla issue. + // TODO: To safely allow for more than one consumer while allowing for + // their bug, we would need to store multiple original callbacks, one + // per http boot callback protocol address. (Otherwise using consumer + // A then consumer B, so that we are already installed on both handles, + // then consumer A again, will use the original callback for B.) + // + mOriginalHttpBootCallback = HttpBootCallback->Callback; + HttpBootCallback->Callback = OpenNetworkBootHttpBootCallback; + } +} + +BOOLEAN +UriWasValidated ( + VOID + ) +{ + return mUriValidated; +} + +EFI_EVENT +MonitorHttpBootCallback ( + EFI_HANDLE LoadFileHandle + ) +{ + EFI_STATUS Status; + EFI_EVENT Event; + VOID *Registration; + + // + // Everything in our callback except https validation is convenient but optional. + // So we can make our driver fully usable with some (hypothetical?) http boot + // implementation which never hits our callback, as long as we treat the URI as + // already validated (even if the callback is never hit) when https validation + // is not turned on. + // + mUriValidated = !gRequireHttpsUri; + + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + NotifyInstallHttpBootCallback, + LoadFileHandle, + &Event + ); + + if (EFI_ERROR (Status)) { + return NULL; + } + + Status = gBS->RegisterProtocolNotify ( + &gEfiHttpBootCallbackProtocolGuid, + Event, + &Registration + ); + + if (EFI_ERROR (Status)) { + gBS->CloseEvent (Event); + return NULL; + } + + return Event; +} diff --git a/Platform/OpenNetworkBoot/HttpBootCustomRead.c b/Platform/OpenNetworkBoot/HttpBootCustomRead.c new file mode 100644 index 00000000000..fa6875da1be --- /dev/null +++ b/Platform/OpenNetworkBoot/HttpBootCustomRead.c @@ -0,0 +1,298 @@ +/** @file + Top level LoadFile protocol handler for HTTP Boot. + + Copyright (c) 2024, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "NetworkBootInternal.h" + +#include +#include + +typedef struct { + EFI_DEVICE_PATH_PROTOCOL *RamDiskDevicePath; +} CUSTOM_FREE_CONTEXT; + +STATIC +EFI_STATUS +SetDmgPreloadDmgFile ( + IN OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext, + IN OUT VOID **Data, + IN OUT UINT32 *DataSize + ) +{ + EFI_STATUS Status; + + Status = CreateVirtualFileFileNameCopy (L"__HTTPBoot__.dmg", *Data, *DataSize, NULL, &DmgPreloadContext->DmgFile); + + if (EFI_ERROR (Status)) { + FreePool (Data); + } else { + DmgPreloadContext->DmgFileSize = *DataSize; + } + + *Data = NULL; + *DataSize = 0; + + return Status; +} + +STATIC +EFI_STATUS +SetDmgPreloadChunklist ( + IN OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext, + IN OUT VOID **Data, + IN OUT UINT32 *DataSize + ) +{ + DmgPreloadContext->ChunklistBuffer = *Data; + DmgPreloadContext->ChunklistFileSize = *DataSize; + + *Data = NULL; + *DataSize = 0; + + return EFI_SUCCESS; +} + +STATIC +VOID +FreeDmgPreloadContext ( + IN OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext + ) +{ + if (DmgPreloadContext->ChunklistBuffer != NULL) { + FreePool (DmgPreloadContext->ChunklistBuffer); + } + + DmgPreloadContext->ChunklistFileSize = 0; + if (DmgPreloadContext->DmgFile != NULL) { + DmgPreloadContext->DmgFile->Close (DmgPreloadContext->DmgFile); + DmgPreloadContext->DmgFile = NULL; + } + + DmgPreloadContext->DmgFileSize = 0; +} + +// +// Equivalent to lines in original BmBoot.c which free RAM disk unconditionally +// when image loaded from RAM disk exits: +// https://github.com/tianocore/edk2/blob/a6648418c1600f0a81f2914d9dd14de1adbfe598/MdeModulePkg/Library/UefiBootManagerLib/BmBoot.c#L2061-L2071 +// +EFI_STATUS +EFIAPI +HttpBootCustomFree ( + IN VOID *Context + ) +{ + CUSTOM_FREE_CONTEXT *CustomFreeContext; + + if (Context != NULL) { + CustomFreeContext = Context; + if (CustomFreeContext->RamDiskDevicePath != NULL) { + BmDestroyRamDisk (CustomFreeContext->RamDiskDevicePath); + FreePool (CustomFreeContext->RamDiskDevicePath); + } + + FreePool (CustomFreeContext); + } + + return EFI_SUCCESS; +} + +// +// Within BmExpandLoadFiles: +// - Only DevicePath will be set if we're returning a boot file on an HTTP +// Boot native ram disk (from .iso or .img). In this case the first and +// second calls to LoadFile occur inside this method. +// - The boot file is then loaded from the RAM disk (via the returned +// device path) in a subsequent call to gBS->LoadImage made by the +// caller. +// - The above applies in the orginal and our modified method. +// - Our method is modified from EDK-II original so that the second LoadFile +// call will also be made inside the method, and Data and DataSize will be +// filled in, if a single .efi file is loaded. +// - In the EDK-II original, final loading of .efi files is delayed to the +// subsequent call to GetFileBufferByFilePath in BmGetLoadOptionBuffer. +// - Note that in the HttpBootDxe LoadFile implementation (which will be +// used by the original and our modified code), if HTTP chunked transfer +// encoding is used then the entire file is downloaded (chunked HTTP GET) +// and cached (in a linked list of fixed-sized download sections, not +// corresponding in size to the actual HTTP chunks) in order to get its +// size, before the final buffer for the file can be allocated; then within +// the second LoadFile call the file is transferred from this cache into +// the final allocated buffer. +// - For non-chunked (so, more or less, normal) transfer encoding, the +// file size is available from a simple HTTP HEAD request, then in the +// second LoadFile call the file HTTP GET is written directly into +// allocated buffer. +// - So chunked transfer encoding should ideally be avoided, especially +// for large downloads, but is supported here and in the original. +// +EFI_STATUS +EFIAPI +HttpBootCustomRead ( + IN OC_STORAGE_CONTEXT *Storage, + IN OC_BOOT_ENTRY *ChosenEntry, + OUT VOID **Data, + OUT UINT32 *DataSize, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, + OUT EFI_HANDLE *StorageHandle, + OUT EFI_DEVICE_PATH_PROTOCOL **StoragePath, + IN OC_DMG_LOADING_SUPPORT DmgLoading, + OUT OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext, + OUT VOID **Context + ) +{ + EFI_STATUS Status; + CUSTOM_FREE_CONTEXT *CustomFreeContext; + CHAR8 *OtherUri; + BOOLEAN GotDmgFirst; + EFI_DEVICE_PATH_PROTOCOL *OtherLoadFile; + EFI_DEVICE_PATH_PROTOCOL *OtherDevicePath; + + ASSERT (Context != NULL); + *Context = NULL; + + CustomFreeContext = AllocateZeroPool (sizeof (CUSTOM_FREE_CONTEXT)); + if (CustomFreeContext == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + gDmgLoading = DmgLoading; + + OcConsoleControlSetMode (EfiConsoleControlScreenText); + + // + // Load the first (or only) file. This method has been extended to + // abort early (avoiding a pointless, long, slow load of a DMG) if DmgLoading + // is disabled and the requested file extension is `.dmg` (or `.chunklist`). + // + *DevicePath = BmExpandLoadFiles (ChosenEntry->DevicePath, Data, DataSize, TRUE); + + if (*DevicePath == NULL) { + FreePool (CustomFreeContext); + return EFI_NOT_FOUND; + } + + Status = EFI_SUCCESS; + GotDmgFirst = FALSE; + OtherUri = NULL; + + // + // Only potentially treat first file as .dmg/.chunklist if it was loaded as + // a normal single file. HttpBootDxe Content-Type header handling may force + // any file, regardless of extension, to be treated as an .iso or .img and + // loaded as a RAM disk. + // + if (*DataSize != 0) { + Status = ExtractOtherUriFromDevicePath (*DevicePath, ".dmg", ".chunklist", &OtherUri, FALSE); + if (!EFI_ERROR (Status)) { + GotDmgFirst = TRUE; + Status = SetDmgPreloadDmgFile (DmgPreloadContext, Data, DataSize); + if (EFI_ERROR (Status)) { + FreePool (OtherUri); + OtherUri = NULL; + } + } else { + Status = ExtractOtherUriFromDevicePath (*DevicePath, ".chunklist", ".dmg", &OtherUri, FALSE); + if (!EFI_ERROR (Status)) { + Status = SetDmgPreloadChunklist (DmgPreloadContext, Data, DataSize); + if (EFI_ERROR (Status)) { + FreePool (OtherUri); + OtherUri = NULL; + } + } else if (Status == EFI_NOT_FOUND) { + Status = EFI_SUCCESS; + OtherUri = NULL; + } + } + } + + // + // Is there a potential matched file? + // + if (OtherUri != NULL) { + // + // Always require .dmg if .chunklist was fetched first; only fetch (and + // require) .chunklist after .dmg when it will be used. + // + if (!GotDmgFirst || (DmgLoading == OcDmgLoadingAppleSigned)) { + Status = HttpBootAddUri (ChosenEntry->DevicePath, OtherUri, OcStringFormatAscii, &OtherLoadFile); + if (!EFI_ERROR (Status)) { + // + // Sort out cramped spacing between the two HTTP Boot calls. + // Hopefully should not affect GUI-based firmware. + // + Print (L"\n"); + + // + // Load the second file of .dmg/.chunklist pair. + // + OtherDevicePath = BmExpandLoadFiles (OtherLoadFile, Data, DataSize, TRUE); + FreePool (OtherLoadFile); + if (OtherDevicePath == NULL) { + DEBUG ((DEBUG_INFO, "NTBT: Failed to fetch required matching file %a\r", OtherUri)); + Status = EFI_NOT_FOUND; + } else { + if (GotDmgFirst) { + FreePool (OtherDevicePath); + Status = SetDmgPreloadChunklist (DmgPreloadContext, Data, DataSize); + } else { + FreePool (*DevicePath); + *DevicePath = OtherDevicePath; + Status = SetDmgPreloadDmgFile (DmgPreloadContext, Data, DataSize); + } + } + } + } + + FreePool (OtherUri); + } + + // + // Sort out OC debug messages following HTTP Boot progress message on the same line, after completion. + // + Print (L"\n"); + + if (EFI_ERROR (Status)) { + FreeDmgPreloadContext (DmgPreloadContext); + FreePool (CustomFreeContext); + FreePool (*DevicePath); + *DevicePath = NULL; + return Status; + } + + // + // It is okay to follow EDK-II code and check for this when it might not be there. + // + CustomFreeContext->RamDiskDevicePath = BmGetRamDiskDevicePath (*DevicePath); + *Context = CustomFreeContext; + + return EFI_SUCCESS; +} + +// +// There is no possibility of DMGs, chunklists or ISOs with PXE boot. +// +EFI_STATUS +EFIAPI +PxeBootCustomRead ( + IN OC_STORAGE_CONTEXT *Storage, + IN OC_BOOT_ENTRY *ChosenEntry, + OUT VOID **Data, + OUT UINT32 *DataSize, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, + OUT EFI_HANDLE *StorageHandle, + OUT EFI_DEVICE_PATH_PROTOCOL **StoragePath, + IN OC_DMG_LOADING_SUPPORT DmgLoading, + OUT OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext, + OUT VOID **Context + ) +{ + OcConsoleControlSetMode (EfiConsoleControlScreenText); + + *DevicePath = BmExpandLoadFiles (ChosenEntry->DevicePath, Data, DataSize, FALSE); + + return (*DevicePath == NULL ? EFI_NOT_FOUND : EFI_SUCCESS); +} diff --git a/Platform/OpenNetworkBoot/NetworkBootInternal.h b/Platform/OpenNetworkBoot/NetworkBootInternal.h new file mode 100644 index 00000000000..bf51cf17bec --- /dev/null +++ b/Platform/OpenNetworkBoot/NetworkBootInternal.h @@ -0,0 +1,217 @@ +/** @file + Copyright (C) 2024, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#ifndef LOAD_FILE_INTERNAL_H +#define LOAD_FILE_INTERNAL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + Set if we should enforce https only within this driver. +**/ +extern BOOLEAN gRequireHttpsUri; + +/** + Current DmgLoading setting, for HTTP BOOT callback validation. +**/ +extern OC_DMG_LOADING_SUPPORT gDmgLoading; + +/** + Custom validation for network boot device path. + + @param Path Device path to validate. + + @retval EFI_SUCCESS Device path should be accepted. + @retval other Device path should be rejected. +**/ +typedef +EFI_STATUS +(*VALIDATE_BOOT_DEVICE_PATH)( + IN VOID *Context, + IN EFI_DEVICE_PATH_PROTOCOL *Path + ); + +/* + Return pointer to final node in device path, if it as a URI node. + Used to return the URI node for an HTTP Boot device path. + + @return Required device path node if available, NULL otherwise. +*/ +EFI_DEVICE_PATH_PROTOCOL * +GetUriNode ( + EFI_DEVICE_PATH_PROTOCOL *DevicePath + ); + +/// +/// BmBootDescription.c +/// + +CHAR16 * +BmGetNetworkDescription ( + IN EFI_HANDLE Handle + ); + +/// +/// BmBoot.c +/// + +EFI_DEVICE_PATH_PROTOCOL * +BmExpandLoadFiles ( + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + OUT VOID **Data, + OUT UINT32 *DataSize, + IN BOOLEAN ValidateHttp + ); + +EFI_DEVICE_PATH_PROTOCOL * +BmGetRamDiskDevicePath ( + IN EFI_DEVICE_PATH_PROTOCOL *FilePath + ); + +VOID +BmDestroyRamDisk ( + IN EFI_DEVICE_PATH_PROTOCOL *RamDiskDevicePath + ); + +/// +/// CustomRead.c +/// + +EFI_STATUS +EFIAPI +HttpBootCustomFree ( + IN VOID *Context + ); + +EFI_STATUS +EFIAPI +HttpBootCustomRead ( + IN OC_STORAGE_CONTEXT *Storage, + IN OC_BOOT_ENTRY *ChosenEntry, + OUT VOID **Data, + OUT UINT32 *DataSize, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, + OUT EFI_HANDLE *StorageHandle, + OUT EFI_DEVICE_PATH_PROTOCOL **StoragePath, + IN OC_DMG_LOADING_SUPPORT DmgLoading, + OUT OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext, + OUT VOID **Context + ); + +EFI_STATUS +EFIAPI +PxeBootCustomRead ( + IN OC_STORAGE_CONTEXT *Storage, + IN OC_BOOT_ENTRY *ChosenEntry, + OUT VOID **Data, + OUT UINT32 *DataSize, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, + OUT EFI_HANDLE *StorageHandle, + OUT EFI_DEVICE_PATH_PROTOCOL **StoragePath, + IN OC_DMG_LOADING_SUPPORT DmgLoading, + OUT OC_APPLE_DISK_IMAGE_PRELOAD_CONTEXT *DmgPreloadContext, + OUT VOID **Context + ); + +/// +/// Uri.c +/// + +BOOLEAN +HasHttpsUri ( + CHAR16 *Uri + ); + +EFI_STATUS +ExtractOtherUriFromDevicePath ( + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + IN CHAR8 *FromExt, + IN CHAR8 *ToExt, + OUT CHAR8 **OtherUri, + IN BOOLEAN OnlySearchForFromExt + ); + +BOOLEAN +UriFileHasExtension ( + IN CHAR8 *Uri, + IN CHAR8 *Ext + ); + +EFI_STATUS +HttpBootAddUri ( + EFI_DEVICE_PATH_PROTOCOL *DevicePath, + VOID *Uri, + OC_STRING_FORMAT StringFormat, + EFI_DEVICE_PATH_PROTOCOL **UriDevicePath + ); + +EFI_EVENT +MonitorHttpBootCallback ( + EFI_HANDLE LoadFileHandle + ); + +BOOLEAN +UriWasValidated ( + VOID + ); + +/// +/// TlsAuthConfigImpl.c +/// + +EFI_STATUS +LogInstalledCerts ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid + ); + +EFI_STATUS +CertIsPresent ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN EFI_GUID *OwnerGuid, + IN UINTN X509DataSize, + IN VOID *X509Data + ); + +EFI_STATUS +DeleteCertsForOwner ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN EFI_GUID *OwnerGuid, + IN UINTN X509DataSize, + IN VOID *X509Data, + OUT UINTN *DeletedCount + ); + +EFI_STATUS +EnrollX509toVariable ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN EFI_GUID *OwnerGuid, + IN UINTN X509DataSize, + IN VOID *X509Data + ); + +#endif // LOAD_FILE_INTERNAL_H diff --git a/Platform/OpenNetworkBoot/OpenNetworkBoot.c b/Platform/OpenNetworkBoot/OpenNetworkBoot.c new file mode 100644 index 00000000000..0b90a83d3b8 --- /dev/null +++ b/Platform/OpenNetworkBoot/OpenNetworkBoot.c @@ -0,0 +1,535 @@ +/** @file + Boot entry protocol handler for PXE and HTTP Boot. + + Copyright (c) 2024, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "NetworkBootInternal.h" + +#define ENROLL_CERT L"--enroll-cert" +#define DELETE_CERT L"--delete-cert" +#define DELETE_ALL_CERTS L"--delete-all-certs" + +BOOLEAN gRequireHttpsUri; + +STATIC BOOLEAN mAllowPxeBoot; +STATIC BOOLEAN mAllowHttpBoot; +STATIC BOOLEAN mAllowIpv4; +STATIC BOOLEAN mAllowIpv6; +STATIC BOOLEAN mAuxEntries; +STATIC CHAR16 *mHttpBootUri; + +STATIC CHAR16 PxeBootId[] = L"PXE Boot IPv"; +STATIC CHAR16 HttpBootId[] = L"HTTP Boot IPv"; + +VOID +InternalFreePickerEntry ( + IN OC_PICKER_ENTRY *Entry + ) +{ + ASSERT (Entry != NULL); + + if (Entry == NULL) { + return; + } + + if (Entry->Id != NULL) { + FreePool ((CHAR8 *)Entry->Id); + } + + if (Entry->Name != NULL) { + FreePool ((CHAR8 *)Entry->Name); + } + + if (Entry->Path != NULL) { + FreePool ((CHAR8 *)Entry->Path); + } + + if (Entry->Arguments != NULL) { + FreePool ((CHAR8 *)Entry->Arguments); + } + + if (Entry->UnmanagedDevicePath != NULL) { + FreePool (Entry->UnmanagedDevicePath); + } +} + +STATIC +VOID +EFIAPI +FreeNetworkBootEntries ( + IN OC_PICKER_ENTRY **Entries, + IN UINTN NumEntries + ) +{ + UINTN Index; + + ASSERT (Entries != NULL); + ASSERT (*Entries != NULL); + if ((Entries == NULL) || (*Entries == NULL)) { + return; + } + + for (Index = 0; Index < NumEntries; Index++) { + InternalFreePickerEntry (&(*Entries)[Index]); + } + + FreePool (*Entries); + *Entries = NULL; +} + +STATIC +EFI_STATUS +InternalAddEntry ( + OC_FLEX_ARRAY *FlexPickerEntries, + CHAR16 *Description, + EFI_HANDLE Handle, + CHAR16 *HttpBootUri, + BOOLEAN IsIPv4, + BOOLEAN IsHttpBoot + ) +{ + EFI_STATUS Status; + OC_PICKER_ENTRY *PickerEntry; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + EFI_DEVICE_PATH_PROTOCOL *NewDevicePath; + UINTN IdLen; + + Status = gBS->HandleProtocol ( + Handle, + &gEfiDevicePathProtocolGuid, + (VOID **)&DevicePath + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_INFO, + "NTBT: Missing device path - %r\n", + Status + )); + return Status; + } + + PickerEntry = OcFlexArrayAddItem (FlexPickerEntries); + if (PickerEntry == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + IdLen = StrLen (Description); + PickerEntry->Id = AllocatePool ((IdLen + 1) * sizeof (PickerEntry->Id[0])); + if (PickerEntry->Id == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + UnicodeStrToAsciiStrS (Description, (CHAR8 *)PickerEntry->Id, IdLen + 1); + + PickerEntry->Name = AllocateCopyPool (IdLen + 1, PickerEntry->Id); + if (PickerEntry->Name == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + if (IsHttpBoot && (HttpBootUri != NULL)) { + Status = HttpBootAddUri (DevicePath, HttpBootUri, OcStringFormatUnicode, &NewDevicePath); + if (EFI_ERROR (Status)) { + return Status; + } + } else { + NewDevicePath = DuplicateDevicePath (DevicePath); + if (NewDevicePath == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } + + PickerEntry->UnmanagedDevicePath = NewDevicePath; + + if (IsHttpBoot) { + PickerEntry->CustomRead = HttpBootCustomRead; + PickerEntry->CustomFree = HttpBootCustomFree; + PickerEntry->Flavour = IsIPv4 ? OC_FLAVOUR_HTTP_BOOT4 : OC_FLAVOUR_HTTP_BOOT6; + } else { + PickerEntry->CustomRead = PxeBootCustomRead; + PickerEntry->Flavour = IsIPv4 ? OC_FLAVOUR_PXE_BOOT4 : OC_FLAVOUR_PXE_BOOT6; + } + + PickerEntry->TextMode = TRUE; + PickerEntry->Auxiliary = mAuxEntries; + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +GetNetworkBootEntries ( + IN OUT OC_PICKER_CONTEXT *PickerContext, + IN CONST EFI_HANDLE Device OPTIONAL, + OUT OC_PICKER_ENTRY **Entries, + OUT UINTN *NumEntries + ) +{ + EFI_STATUS Status; + UINTN HandleCount; + EFI_HANDLE *HandleBuffer; + UINTN Index; + CHAR16 *NetworkDescription; + CHAR16 *IdStr; + OC_FLEX_ARRAY *FlexPickerEntries; + BOOLEAN IsIPv4; + BOOLEAN IsHttpBoot; + + // + // Here we produce custom entries only, not entries found on filesystems. + // + if (Device != NULL) { + return EFI_NOT_FOUND; + } + + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiLoadFileProtocolGuid, + NULL, + &HandleCount, + &HandleBuffer + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "NTBT: Load file protocol - %r\n", Status)); + return Status; + } + + FlexPickerEntries = OcFlexArrayInit (sizeof (OC_PICKER_ENTRY), (OC_FLEX_ARRAY_FREE_ITEM)InternalFreePickerEntry); + if (FlexPickerEntries == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + for (Index = 0; Index < HandleCount; ++Index) { + NetworkDescription = BmGetNetworkDescription (HandleBuffer[Index]); + if (NetworkDescription == NULL) { + DebugPrintDevicePathForHandle (DEBUG_INFO, "NTBT: LoadFile handle not PXE/HTTP boot DP", HandleBuffer[Index]); + } else { + // + // Use fixed format network description which we control as shortcut + // to identify PXE/HTTP and IPv4/6. + // + if ((IdStr = StrStr (NetworkDescription, PxeBootId)) != NULL) { + IsIPv4 = IdStr[L_STR_LEN (PxeBootId)] == L'4'; + ASSERT (IsIPv4 || (IdStr[L_STR_LEN (PxeBootId)] == L'6')); + IsHttpBoot = FALSE; + } else if ((IdStr = StrStr (NetworkDescription, HttpBootId)) != NULL) { + IsIPv4 = IdStr[L_STR_LEN (HttpBootId)] == L'4'; + ASSERT (IsIPv4 || (IdStr[L_STR_LEN (HttpBootId)] == L'6')); + IsHttpBoot = TRUE; + } + + if ( (IdStr != NULL) + && ((IsIPv4 && mAllowIpv4) || (!IsIPv4 && mAllowIpv6)) + && ((IsHttpBoot && mAllowHttpBoot) || (!IsHttpBoot && mAllowPxeBoot)) + ) + { + DEBUG ((DEBUG_INFO, "NTBT: Adding %s\n", NetworkDescription)); + Status = InternalAddEntry ( + FlexPickerEntries, + NetworkDescription, + HandleBuffer[Index], + IsHttpBoot ? mHttpBootUri : NULL, + IsIPv4, + IsHttpBoot + ); + } else { + DEBUG ((DEBUG_INFO, "NTBT: Ignoring %s\n", NetworkDescription)); + } + + FreePool (NetworkDescription); + } + + if (EFI_ERROR (Status)) { + break; + } + } + + FreePool (HandleBuffer); + + if (EFI_ERROR (Status)) { + OcFlexArrayFree (&FlexPickerEntries); + return Status; + } + + OcFlexArrayFreeContainer (&FlexPickerEntries, (VOID **)Entries, NumEntries); + + if (*NumEntries == 0) { + return EFI_NOT_FOUND; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +EnrollCerts ( + OC_FLEX_ARRAY *ParsedLoadOptions + ) +{ + EFI_STATUS Status; + UINTN Index; + OC_PARSED_VAR *Option; + EFI_GUID *OwnerGuid; + UINTN CertSize; + CHAR8 *CertData; + BOOLEAN EnrollCert; + BOOLEAN DeleteCert; + BOOLEAN DeleteAllCerts; + UINTN OptionLen; + UINTN DeletedCount; + + Status = EFI_SUCCESS; + + // + // Find certs in options. + // + for (Index = 0; Index < ParsedLoadOptions->Count; ++Index) { + Option = OcFlexArrayItemAt (ParsedLoadOptions, Index); + + EnrollCert = FALSE; + DeleteCert = FALSE; + DeleteAllCerts = FALSE; + + if (OcUnicodeStartsWith (Option->Unicode.Name, ENROLL_CERT, TRUE)) { + EnrollCert = TRUE; + OptionLen = L_STR_LEN (ENROLL_CERT); + } else if (OcUnicodeStartsWith (Option->Unicode.Name, DELETE_CERT, TRUE)) { + DeleteCert = TRUE; + OptionLen = L_STR_LEN (DELETE_CERT); + } else if (OcUnicodeStartsWith (Option->Unicode.Name, DELETE_ALL_CERTS, TRUE)) { + DeleteAllCerts = TRUE; + OptionLen = L_STR_LEN (DELETE_ALL_CERTS); + } + + if ( (EnrollCert || DeleteCert || DeleteAllCerts) + && (Option->Unicode.Name[OptionLen] != CHAR_NULL) + && (Option->Unicode.Name[OptionLen] != L':') + ) + { + EnrollCert = FALSE; + DeleteCert = FALSE; + DeleteAllCerts = FALSE; + } + + if ((EnrollCert || DeleteCert) && (Option->Unicode.Value == NULL)) { + DEBUG ((DEBUG_INFO, "NTBT: Ignoring %s option with no cert value\n", Option->Unicode.Name)); + EnrollCert = FALSE; + DeleteCert = FALSE; + } + + if (EnrollCert || DeleteCert || DeleteAllCerts) { + OwnerGuid = AllocateZeroPool (sizeof (EFI_GUID)); + if (OwnerGuid == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + + // + // Use all zeros GUID if no user value supplied. + // + if (Option->Unicode.Name[OptionLen] == L':') { + Status = StrToGuid (&Option->Unicode.Name[OptionLen + 1], OwnerGuid); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "NTBT: Cannot parse cert owner GUID from %s - %r\n", Option->Unicode.Name, Status)); + break; + } + } + + if (DeleteAllCerts) { + Status = DeleteCertsForOwner ( + EFI_TLS_CA_CERTIFICATE_VARIABLE, + &gEfiTlsCaCertificateGuid, + OwnerGuid, + 0, + NULL, + &DeletedCount + ); + DEBUG ((DEBUG_INFO, "NTBT: %s %u deleted - %r\n", Option->Unicode.Name, DeletedCount, Status)); + } else { + // + // We do not include the terminating '\0' in the stored certificate, + // which matches how stored by e.g. OVMF when loaded from file; + // but we must allocate space for '\0' for Unicode to ASCII conversion. + // + CertSize = StrLen (Option->Unicode.Value); + CertData = AllocateZeroPool (CertSize + 1); + if (CertData == NULL) { + Status = EFI_OUT_OF_RESOURCES; + break; + } + + UnicodeStrToAsciiStrS (Option->Unicode.Value, CertData, CertSize + 1); + + if (DeleteCert) { + Status = DeleteCertsForOwner ( + EFI_TLS_CA_CERTIFICATE_VARIABLE, + &gEfiTlsCaCertificateGuid, + OwnerGuid, + CertSize, + CertData, + &DeletedCount + ); + DEBUG ((DEBUG_INFO, "NTBT: %s %u deleted - %r\n", Option->Unicode.Name, DeletedCount, Status)); + } else { + Status = CertIsPresent ( + EFI_TLS_CA_CERTIFICATE_VARIABLE, + &gEfiTlsCaCertificateGuid, + OwnerGuid, + CertSize, + CertData + ); + if (EFI_ERROR (Status)) { + if (Status == EFI_ALREADY_STARTED) { + DEBUG ((DEBUG_INFO, "NTBT: %s already present\n", Option->Unicode.Name)); + Status = EFI_SUCCESS; + } else { + DEBUG ((DEBUG_INFO, "NTBT: Error checking for cert presence - %r\n", Status)); + } + } else { + Status = EnrollX509toVariable ( + EFI_TLS_CA_CERTIFICATE_VARIABLE, + &gEfiTlsCaCertificateGuid, + OwnerGuid, + CertSize, + CertData + ); + DEBUG ((DEBUG_INFO, "NTBT: %s - %r\n", Option->Unicode.Name, Status)); + } + } + + FreePool (CertData); + } + + FreePool (OwnerGuid); + + if (EFI_ERROR (Status)) { + break; + } + } + } + + return Status; +} + +STATIC +OC_BOOT_ENTRY_PROTOCOL + mNetworkBootEntryProtocol = { + OC_BOOT_ENTRY_PROTOCOL_REVISION, + GetNetworkBootEntries, + FreeNetworkBootEntries, + NULL +}; + +EFI_STATUS +EFIAPI +UefiMain ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; + OC_FLEX_ARRAY *ParsedLoadOptions; + CHAR16 *TempUri; + + Status = gBS->HandleProtocol ( + ImageHandle, + &gEfiLoadedImageProtocolGuid, + (VOID **)&LoadedImage + ); + if (EFI_ERROR (Status)) { + return Status; + } + + mAllowIpv4 = FALSE; + mAllowIpv6 = FALSE; + mAllowPxeBoot = FALSE; + mAllowHttpBoot = FALSE; + gRequireHttpsUri = FALSE; + mHttpBootUri = NULL; + + Status = OcParseLoadOptions (LoadedImage, &ParsedLoadOptions); + if (EFI_ERROR (Status)) { + if (Status != EFI_NOT_FOUND) { + return Status; + } + + Status = EFI_SUCCESS; + } else { + // + // e.g. --https --uri=https://imageserver.org/OpenShell.efi + // + mAllowIpv4 = OcHasParsedVar (ParsedLoadOptions, L"-4", OcStringFormatUnicode); + mAllowIpv6 = OcHasParsedVar (ParsedLoadOptions, L"-6", OcStringFormatUnicode); + mAllowPxeBoot = OcHasParsedVar (ParsedLoadOptions, L"--pxe", OcStringFormatUnicode); + mAllowHttpBoot = OcHasParsedVar (ParsedLoadOptions, L"--http", OcStringFormatUnicode); + mAuxEntries = OcHasParsedVar (ParsedLoadOptions, L"--aux", OcStringFormatUnicode); + gRequireHttpsUri = OcHasParsedVar (ParsedLoadOptions, L"--https", OcStringFormatUnicode); + + TempUri = NULL; + OcParsedVarsGetUnicodeStr (ParsedLoadOptions, L"--uri", &TempUri); + if (TempUri != NULL) { + mHttpBootUri = AllocateCopyPool (StrSize (TempUri), TempUri); + if (mHttpBootUri == NULL) { + Status = EFI_OUT_OF_RESOURCES; + } + } + + if (!EFI_ERROR (Status)) { + Status = EnrollCerts (ParsedLoadOptions); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "NTBT: Failed to enroll certs - %r\n", Status)); + } + + DEBUG_CODE_BEGIN (); + LogInstalledCerts (EFI_TLS_CA_CERTIFICATE_VARIABLE, &gEfiTlsCaCertificateGuid); + DEBUG_CODE_END (); + } + } + + if (!EFI_ERROR (Status)) { + if (!mAllowIpv4 && !mAllowIpv6) { + mAllowIpv4 = TRUE; + mAllowIpv6 = TRUE; + } + + if (!gRequireHttpsUri && !mAllowHttpBoot && !mAllowPxeBoot) { + mAllowHttpBoot = TRUE; + mAllowPxeBoot = TRUE; + } + + if (gRequireHttpsUri) { + mAllowHttpBoot = TRUE; + } + + if (mHttpBootUri != NULL) { + if (!mAllowHttpBoot) { + DEBUG ((DEBUG_INFO, "NTBT: URI specified but HTTP boot is disabled\n")); + } else { + if (gRequireHttpsUri && !HasHttpsUri (mHttpBootUri)) { + DEBUG ((DEBUG_WARN, "NTBT: Invalid URI https:// is required\n")); + mAllowHttpBoot = FALSE; + } + } + } + + Status = gBS->InstallMultipleProtocolInterfaces ( + &ImageHandle, + &gOcBootEntryProtocolGuid, + &mNetworkBootEntryProtocol, + NULL + ); + } + + if (ParsedLoadOptions != NULL) { + OcFlexArrayFree (&ParsedLoadOptions); + } + + if (EFI_ERROR (Status) && (mHttpBootUri != NULL)) { + FreePool (mHttpBootUri); + } + + return Status; +} diff --git a/Platform/OpenNetworkBoot/OpenNetworkBoot.inf b/Platform/OpenNetworkBoot/OpenNetworkBoot.inf new file mode 100644 index 00000000000..b892e86b11f --- /dev/null +++ b/Platform/OpenNetworkBoot/OpenNetworkBoot.inf @@ -0,0 +1,54 @@ +## @file +# Boot entry protocol handler for PXE and HTTP Boot. +# +# Copyright (C) 2024, Mike Beaton. All rights reserved.
+# SPDX-License-Identifier: BSD-3-Clause +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = OpenNetworkBoot + ENTRY_POINT = UefiMain + FILE_GUID = CA2BA189-98E8-4126-ABA5-2CE5C9B4C2D8 + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + +[Packages] + OpenCorePkg/OpenCorePkg.dec + MdePkg/MdePkg.dec + NetworkPkg/NetworkPkg.dec + +[LibraryClasses] + DebugLib + DevicePathLib + HttpLib + OcConsoleLib + OcBootManagementLib + OcFlexArrayLib + OcVirtualFsLib + PrintLib + UefiBootServicesTableLib + UefiRuntimeServicesTableLib + UefiDriverEntryPoint + UefiLib + +[Protocols] + gEfiHttpBootCallbackProtocolGuid ## CONSUMES + gEfiLoadFileProtocolGuid ## CONSUMES + gEfiRamDiskProtocolGuid ## SOMETIMES_CONSUMES + gOcBootEntryProtocolGuid ## PRODUCES + +[Guids] + gEfiTlsCaCertificateGuid ## SOMETIMES_CONSUMES ## Variable:L"TlsCaCertificate" + gEdkiiHttpTlsCipherListGuid ## SOMETIMES_CONSUMES ## Variable:L"HttpTlsCipherList" + gEfiCertX509Guid ## SOMETIMES_CONSUMES ## GUID # Check the cert type + +[Sources] + BmBoot.c + BmBootDescription.c + HttpBootCallback.c + HttpBootCustomRead.c + NetworkBootInternal.h + OpenNetworkBoot.c + TlsAuthConfigImpl.c + Uri.c diff --git a/Platform/OpenNetworkBoot/README.md b/Platform/OpenNetworkBoot/README.md new file mode 100644 index 00000000000..587f757e595 --- /dev/null +++ b/Platform/OpenNetworkBoot/README.md @@ -0,0 +1,464 @@ +# OpenNetworkBoot + +`OpenNetworkBoot` is an OpenCore boot entry protocol driver which provides +PXE and HTTP boot entries if the underlying firmware supports it, or if +the required network boot drivers have been loaded using OpenCore. Using the +additional network boot drivers provided with OpenCore, when needed, HTTP +boot should be available on most firmware even if not natively supported. + +See [OpenCore documentation](https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/Configuration.pdf) +for information on the optional configuration arguments which are available for this driver. + +> **Note**: In this file 'HTTP boot' refers to booting using either +`http://` or `https://` URIs. The additional steps to configure a certificate for +`https://` (and to lock `OpenNetworkBoot` to `https://` only, if required) +are covered below. + +## PXE Boot + +On almost all firmware, the drivers for PXE boot will already be present; +adding `OpenNetworkBoot.efi` to the OpenCore drivers should produce PXE +boot entries. + +> **Note**: On some firmware (e.g. HP) the native network boot drivers are not loaded +if the system boots directly to OpenCore and it is necessary to start +OpenCore from the firmware boot menu in order to see PXE and HTTP boot entries. +(Alternatively, it should be possible to load the network boot stack provided with +OpenCore, see end of this document.) + +## HTTP Boot + +On most recent firmware either no or only a few additional drivers are needed +for HTTP boot, as most of the required drivers are already present in firmware. + +After adding `OpenNetworkBoot`, if no HTTP boot entries are seen, +try adding just the driver `HttpBootDxe`. If this does not produce +network boot entries, try also adding `HttpDxe` and `HttpUtilitiesDxe`. +If `http://` URIs can be booted but not `https://` try adding `TlsDxe.efi`. + +If the above steps do not work, proceed to the next section to identify +which drivers are required. + +> **Note 1**: When using `https://` as opposed to `http://` URIs, one or +more certificates, as required to validate the connection, must +be configured on the network boot client. This can be done using +OpenNetworkBoot's certificate configuration options, as documented in the +[OpenCore documentation](https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/Configuration.pdf). + +> **Note 2**: In some firmware the existing `HttpBootDxe` driver may produce +options which do not work correctly (e.g. blank screen when selected, +because they are designed to work with a firmware UI which is not present +when OpenCore is running). +If so, in order to get working HTTP Boot options it may be necessary to use +the `UEFI/Unload` config setting to unload the existing `HttpBootDxe` driver +before loading the `HttpBootDxe` driver provided with OpenCore. + +> **Note 3**: In some firmware the existing `HttpDxe` (and `HttpBootDxe`) drivers +may be locked down to `https://` URIs (even if the machine +has no BIOS UI for HTTP Boot; e.g. Dell OptiPlex 3070). +This means that while the `HttpBootDxe` from OpenCore can +work with the native `HttpDxe`, it will only boot from `https://` URIs, giving a +failure message otherwise. If `http://` URIs are required, this limitation can +be worked around by using the `UEFI/Unload` config setting to unload the existing +`HttpDxe` driver before loading the `HttpDxe` driver provided with OpenCore. + +> **Note 4**: During HTTP Boot '*Error: Could not retrieve NBP file size from HTTP server*' +is a very generic error message for 'something went wrong'. +It could be that `http://` URIs are not allowed by `HttpDxe` or `HttpBootDxe`, +or that a file does not exist at the specified URI on the server, or that the certificates +(if any) stored in NVRAM could not be used to validate an `https://` URI, or any one of a number +of other similar problems. + +> **Note 5**: During HTTP Boot, an initial error such as 'IP address not found' or 'Server response timeout', +even if preceded by the above message, may mean that no IP address was issued by DHCP, or +that the additional NBP (i.e. boot file) info requested over DHCP was not found. Using `dnsmasq` as the DHCP helper +with the logging options shown below can be helpful to resolve this: any DHCP request which reaches `dnsmasq` +will show a couple of log lines. If these are not seen during a network boot attempt then there is no chance of +`dnsmasq` responding, and network connectivity issues need to be resolved. (It is worth noting that unless blocked, +this DHCP request traffic will normally be broadcast between local networks.) If these log lines are seen but `dnsmasq` does not +additionally log that it is responding with NBP information, make sure that it is configured with the correct +subnetwork for the client machine. It can help to boot the client computer into an OS to confirm which subnetwork +it is on. + +## Identifying missing network boot drivers + +The `dh` command in UEFI Shell (e.g. `OpenShell` provided with +OpenCore) is useful for working out which drivers are missing for network +boot. + +`dh -p LoadFile` shows available network boot entries. Handles with a device +path ending in an IPv4 or IPv6 address should indicate available PXE boot +options. Handles with a device path ending in `Uri(...)` should indicate +available HTTP boot options + +> **Note 1**: On some systems, there may be additional +`LoadFile` handles with vendor-specific device paths. These may correspond, for +instance, to GUI network boot options. These will not produce boot entries when +using OpenNetworkBoot. + +After identifying the handles for network boot entries, +the other handles just before and after these, in the full +list of handles displayed by `dh`, should correspond to the currently loaded +network boot drivers. If there are no LoadFile options, then +search in the full handle list for strings such as 'tcp', 'tls', 'http' +(normally the native network boot drivers will appear grouped together). +Examining the names printed by `dh` for these handles +and comparing them to the available network boot drivers (see Network Boot Stack +section) can be used to identify missing drivers. + +> **Note 2**: On systems with reasonably fast console text output, the `-b` +option can be used with `dh` (as with most UEFI Shell commands) to +display results one page at a time. + +> **Note 3**: For systems with slow console output, it may be more +convenient to pipe the results of `dh` to a file on a convenient file system +to examine later, within a booted OS. For example `dh > fs0:\dh_dump` or: + +``` +> fs0: +> dh > dh_dump +``` + +## Configuring a network boot server + +In standard PXE and HTTP boot, the network's normal DHCP server responds to a +client device's request for an IP address, but a separate DHCP helper service +(often running on a different machine from the DHCP server) responds to the +device's DHCP extension request to know which network boot program (NBP) to +load. It is possible (but less standard, even on enterprise networks; +and usually more complex) to configure the main DHCP server to respond to both +requests. + +**Note 1**: The NBP for HTTP boot can be configured by specifying the `--uri` +flag for `OpenNetworkBoot`. When using this option, only an HTTP server (and +certificate, for HTTPS), needs to be set up, but no DHCP helper service is +required. + +**Note 2**: No equivalent NBP path option is provided for PXE boot, since the most +standard (and recommended) set up is for the program specifying the +NBP file and the program serving it via TFTP to be one and the same. + +### PXE Boot + +In PXE boot, the NBP is loaded via TFTP, which is a slow protocol not suitable +for large files. Standard PXE boot NBPs typically load any further large files +which they need using their own network stack and not via TFTP. + +`dnsmasq`, WDS, or other options, such as FOGProject, may be used to specify +PXE boot responses. + +#### dnsmasq + +`dnsmasq` can be used to both provide the location of the PXE boot NBP file +and then serve it by TFTP. + +A basic `dnsmasq` PXE boot configuration is as follows: + +``` +# Disable DNS Server +port=0 + +# Run as network boot DHCP proxy +dhcp-range=192.168.10.0,proxy + +# Identify requested arch +# REF: https://wiki.archlinux.org/title/dnsmasq#PXE_server +dhcp-match=set:arch_x64,option:client-arch,7 +dhcp-match=set:arch_x64,option:client-arch,9 +dhcp-match=set:arch_ia32,option:client-arch,6 + +# Specify one or more boot options per architecture +pxe-service=tag:arch_x64,x86-64_EFI,"Open Shell",OpenShell.efi +pxe-service=tag:arch_x64,x86-64_EFI,"Boot Helper",BootHelper.efi + +# Enable TFTP support +enable-tftp +tftp-root=/home/mjsbeaton/tftp +``` + +A more advanced configuration might serve different files to different +machines, depending on their hardware id. (The same point applies to +HTTP boot.) See `dnsmasq` documentation. + +See **HTTP Boot** **dnsmasq** section below for command to quickly start +`dnsmasq` for testing. + +#### WDS + +Windows Deployment Services (WDS, which is incuded with Windows Server) can be +used to provide responses to PXE boot requests, and can be configured to serve +non-Windows NBP files. + +**Note 1**: Certain aspects of WDS are now deprecated: +https://aka.ms/WDSSupport + +**Note 2**: On certain systems, the OpenCore `TextRenderer` setting +must be set to one of the `System` values in order to see the early output of +`wdsmgfw.efi` (the NBP served by default by WDS). If this text is not visible, +this can be worked round by pressing either `F12` or `Enter` in the pause +after the program has loaded, in order to access the next screen. +The issue of the early text of this software not appearing in some circumstances +is not unique to OpenCore: https://serverfault.com/q/683314 + +### HTTP Boot + +#### dnsmasq + +Although `dnsmasq` does not provide complete support for HTTP +boot, as it does for PXE boot, its PXE boot features can be used +to respond to requests for the location of HTTP boot NBP files. + +A basic `dnsmasq` HTTP boot configuration is as follows: + +``` +# Disable DNS Server +port=0 + +# Run as PXE Boot DHCP proxy for specified network (use default /24 network size) +dhcp-range=192.168.2.0,proxy + +# Trigger PXE Boot support on HTTP Boot client request +dhcp-pxe-vendor=HTTPClient + +# Set triggering tag if correct arch is present in option 60 +dhcp-match=set:arch_x64,option:client-arch,16 + +# Make PXE Boot support believe it has something to send... +pxe-service=tag:arch_x64,x86-64_EFI,"Network Boot" + +# Specify bootfile-name via PXE Boot setting +dhcp-boot=tag:arch_x64,https://michaels-air.lan:8443/images/OpenShell.efi + +# Force required vendor class in response, even if not requested +dhcp-option-force=tag:arch_x64,option:vendor-class,HTTPClient +``` + +To quickly start `dnsmasq` for testing, without running it as a server, +the following command can be used: + +``` +sudo dnsmasq --no-daemon -C dnsmasq.conf --log-dhcp --log-debug +``` + +An HTTP server (such as Apache, nginx, or multiple other options) will be +required to serve the actual NBP files over `http://` or `https://`. + +References: + - https://github.com/ipxe/ipxe/discussions/569 + - https://www.mail-archive.com/dnsmasq-discuss@lists.thekelleys.org.uk/msg16278.html + +### HTTPS Boot + +Note that the certificate for validating `https://` requests should be loaded into +firmware using the OpenNetworkBoot `--enroll-cert` option. + +A normal https site would not serve files using a self-signed certificate +authority (CA), but since we are only attempting to serve files to HTTP boot +clients, in this case we can do so. + +## Booting ISO and IMG files + +Though not often noted in the documentation, the majority of HTTP Boot +implementations support loading `.iso` and `.img` files, which will be +automatically mounted as a ramdisk. If the mounted filesystem includes +`\EFI\BOOT\BOOTx64.efi` (or `\EFI\BOOT\BOOTIA32.efi` for 32-bit) then this +file will be loaded from the ramdisk and started. + +The MIME types corresponding to `.iso` and `.img` files are: + + - `application/vnd.efi-iso` + - `application/vnd.efi-img` + +The MIME type for `.efi` files is: + + - `application/efi` + +If the MIME type is none of the above, then the corresponding file +extensions (`.iso`, `.img` and `.efi`) are used instead to identify the NBP +file type. + +Additionally, for boot options generated by `OpenNetworkBoot`, `.dmg` +and `.chunklist` files will be recognised by extension and loaded, +regardless of MIME type. + +> **Note**: Files which cannot be recognised by any of the above MIME types or +file extensions will _not_ be loaded by HTTP boot drivers. + +## Booting DMG files + +In order to allow booting macOS Recovery, `OpenNetworkBoot` includes +additional support for loading `.dmg` files via HTTP boot. If the NBP +filename is `{filename}.dmg` or `{filename}.chunklist` then the other +file of this pair will be automatically loaded, in order to allow DMG +chunklist verification, and both files will be used for OpenCore DMG booting. + +### `DmgLoading` configuration setting + +The behaviour of `OpenNetworkBoot`'s DMG support depends on the OpenCore +`DmgLoading` setting as follows: + + - If `DmgLoading` is set to `Signed` then both `.chunklist` and `.dmg` files +must be available from the HTTP server. Either file can be specified as +the NBP, and the other matching file will be loaded afterwards, automatically. + - If `DmgLoading` is set to `Disabled` and either of these two file extensions +are found as the NBP, then the HTTP boot process will be aborted. (If we allowed +these files to load and then passed them to the OpenCore DMG loading process, +they would be rejected at that point anyway.) + - If `DmgLoading` is set to `Any` and the NBP is `{filename}.dmg` then only +the `.dmg` file will be loaded, as verification via `.chunklist` is not +carried out with this setting. If the NBP is `{filename}.chunklist` then +the `.chunklist` followed by the `.dmg` will be loaded, but only the `.dmg` +will be used. + +## OVMF + +OVMF can be compiled with the following flags for full network boot support: + +`-D NETWORK_HTTP_BOOT_ENABLE -D NETWORK_TLS_ENABLE -D NETWORK_IP6_ENABLE` + +Since a May 2024 security update to the network boot stack, Random +Number Generator (RNG) protocol support is required. If running OVMF +with an Ivy Bridge or above CPU, then the `RngDxe` driver included in +OVMF will provide the required support. For CPUs below Ivy Bridge +the qemu option `-device virtio-rng-pci` must be provided, so that +the `VirtioRngDxe` driver which is also present in OVMF can provide +the required RNG support. + +If OVMF is compiled without network boot support (`-D NETWORK_ENABLE=0`), then +network boot support provided with OpenCore may be added by loading the full +Network Boot Stack (see below). + +### OVMF networking + +If any network boot clients (e.g. OVMF, VMWare) or server programs +(e.g. Apache, `dnsmasq`, WDS) are running on VMs, it is normally easiest +to set these up using bridged network support, which allows the VM to +appear as a separate device with its own IP address on the network. + +To start OVMF with bridged network support the following macOS-specific +`qemu` options (which require `sudo`) may be used: + +``` +-netdev vmnet-bridged,id=mynet0,ifname=en0 \ +-device e1000,netdev=mynet0,id=mynic0 +``` + +PXE boot may also be tested in OVMF using qemu's built-in TFTP/PXE server, +available with the qemu user mode network stack, for example using the +following options: + +``` +-netdev user,id=net0,tftp=/Users/user/tftp,bootfile=/OpenShell.efi \ +-device virtio-net-pci,netdev=net0 +``` + +No equivalent option is available for HTTP boot, so to experiment with this, +a combination such as bridged networking and `dnsmasq` should be used. + +### OVMF HTTPS certificate + +When using `https://` as opposed to `http://`, a certificate must be +configured on the network boot client. Within the OVMF menus this may +be done using +`Device Manager/Tls Auth Configuration/Server CA Configuration/Enroll Cert/Enroll Cert Using File`. + +> **Tip**: No GUID needs to be provided in the above dialog. All zeroes will be +used if nothing is specified, which is fine if only a single certificate is going +to be configured and managed. + +### Debugging network boot on OVMF + +Building OVMF with the `-D DEBUG_ON_SERIAL_PORT` option and then passing the +`-serial stdio` option to qemu (and then scrolling back in the output as +needed, to the lines generated during a failed network boot) can be very +useful when trying to debug network boot setup. + +OVMF can capture packets using +`-object filter-dump,netdev={net-id},id=filter0,file=/Users/user/ovmf.cap` +(`{net-id}` should be replaced as appropriate with the `id` value specified in the +corresponding `-netdev` option). + +For additional information on debugging OpenCore using OVMF, +see https://github.com/acidanthera/OpenCorePkg/blob/master/Debug/README.md. + +## Network Boot Stack + +The following drivers supplied with OpenCore make up the network boot +stack. Please follow the procedures given towards the start of this +document for deciding which drivers to add. + +### Prerequisites +Various network boot drivers depend on the presence of HiiDatabase. + +A recent (May 2024) security update to the EDK 2 network stack +means that various drivers also depend on the RNG and Hash2 protocols. + +These protocols can be checked for in UEFI Shell with: + +``` +dh -p HIIDatabase +dh -p Rng +dh -p Hash2 +``` + +If not present, the respective drivers should be loaded before +the network boot stack. + +``` +HiiDatabase +RngDxe +Hash2DxeCrypto +``` + +### RAM Disk +Required if not already present in firmware, and the user wishes +to load `.iso` or `.img` files with HTTP Boot. + +Can be checked for in UEFI Shell with `dh -p RamDisk`. + +``` +RamDiskDxe +``` + +### Network Boot Base +``` +DpcDxe +SnpDxe +MnpDxe +TcpDxe +``` + +### IPv4 +``` +ArpDxe +Dhcp4Dxe +Ip4Dxe +Udp4Dxe +``` + +### IPv6 +``` +Dhcp6Dxe +Ip6Dxe +Udp6Dxe +``` + +### HTTP Boot +``` +DnsDxe +HttpDxe +HttpUtilitiesDxe +HttpBootDxe +``` + +### HTTPS (TLS) support for HTTP Boot +``` +TlsDxe +``` + +### PXE +For PXE Boot support it would also be necessary to add (not provided with OpenCore): +``` +Mtftp4Dxe (IPv4) +Mtftp6Dxe (IPv6) +``` diff --git a/Platform/OpenNetworkBoot/TlsAuthConfigImpl.c b/Platform/OpenNetworkBoot/TlsAuthConfigImpl.c new file mode 100644 index 00000000000..ed2aa313e23 --- /dev/null +++ b/Platform/OpenNetworkBoot/TlsAuthConfigImpl.c @@ -0,0 +1,519 @@ +/** @file + Miscellaneous routines for TLS auth config. + + Copyright (c) 2016 - 2018, Intel Corporation. All rights reserved.
+ Copyright (c) 2024, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "NetworkBootInternal.h" + +#define TLS_AUTH_CONFIG_VAR_BASE_ATTR (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS) + +typedef +EFI_STATUS +(*PROCESS_CERT)( + IN VOID *Context, + IN UINTN CertIndex, + IN UINTN CertSize, + IN EFI_SIGNATURE_DATA *Cert + ); + +typedef struct { + EFI_GUID *OwnerGuid; + UINTN X509DataSize; + VOID *X509Data; +} CERT_IS_PRESENT_CONTEXT; + +/** + Perform action for all signatures in specified database, with + possibility of aborting early. + + @param[in] VariableName The variable name of the vendor's signature database. + @param[in] VendorGuid A unique identifier for the signature database vendor. + @param[in] ProcessCert The method to call for each certificate. + @param[in] Context Context for ProcessCert, if required. + + @retval EFI_SUCCESS Looped over all signatures. + @retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. + @retval Other Other error or return code from from ProcessCert. +**/ +STATIC +EFI_STATUS +ProcessAllCerts ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN PROCESS_CERT ProcessCert, + IN VOID *Context OPTIONAL + ) +{ + EFI_STATUS Status; + UINT32 Index; + UINTN CertCount; + UINTN GuidIndex; + UINTN DataSize; + UINT8 *Data; + EFI_SIGNATURE_LIST *CertList; + EFI_SIGNATURE_DATA *Cert; + UINT32 ItemDataSize; + + ASSERT (ProcessCert != NULL); + + Data = NULL; + CertList = NULL; + Cert = NULL; + + // + // Read Variable. + // + DataSize = 0; + Status = gRT->GetVariable (VariableName, VendorGuid, NULL, &DataSize, Data); + if (EFI_ERROR (Status) && (Status != EFI_BUFFER_TOO_SMALL)) { + if (Status == EFI_NOT_FOUND) { + Status = EFI_SUCCESS; + } + + return Status; + } + + Data = (UINT8 *)AllocateZeroPool (DataSize); + if (Data == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = gRT->GetVariable (VariableName, VendorGuid, NULL, &DataSize, Data); + if (EFI_ERROR (Status)) { + FreePool (Data); + return Status; + } + + // + // Enumerate all data. + // + ItemDataSize = (UINT32)DataSize; + CertList = (EFI_SIGNATURE_LIST *)Data; + GuidIndex = 0; + + while ((ItemDataSize > 0) && (ItemDataSize >= CertList->SignatureListSize)) { + if (!CompareGuid (&CertList->SignatureType, &gEfiCertX509Guid)) { + // + // The signature type is not supported in current implementation. + // + ItemDataSize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *)((UINT8 *)CertList + CertList->SignatureListSize); + continue; + } + + Status = EFI_SUCCESS; + CertCount = (CertList->SignatureListSize - sizeof (EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize; + for (Index = 0; Index < CertCount; Index++) { + Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)CertList + + sizeof (EFI_SIGNATURE_LIST) + + CertList->SignatureHeaderSize + + Index * CertList->SignatureSize); + + Status = ProcessCert (Context, GuidIndex, CertList->SignatureSize, Cert); + if (EFI_ERROR (Status)) { + break; + } + + ++GuidIndex; + } + + if (EFI_ERROR (Status)) { + break; + } + + ItemDataSize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *)((UINT8 *)CertList + CertList->SignatureListSize); + } + + FreePool (Data); + + return Status; +} + +/** + @retval EFI_SUCCESS Continue processing. +**/ +STATIC +EFI_STATUS +LogCert ( + IN VOID *Context, + IN UINTN CertIndex, + IN UINTN CertSize, + IN EFI_SIGNATURE_DATA *Cert + ) +{ + DEBUG ((DEBUG_INFO, "NTBT: Cert %u owner %g\n", CertIndex, &Cert->SignatureOwner)); + return EFI_SUCCESS; +} + +/** + Log owner GUID of each installed certificate in signature database. + + @param[in] VariableName The variable name of the signature database. + @param[in] VendorGuid A unique identifier for the signature database vendor. + + @retval EFI_SUCCESS Success. +**/ +EFI_STATUS +LogInstalledCerts ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid + ) +{ + DEBUG ((DEBUG_INFO, "NTBT: Listing installed certs...\n")); + return ProcessAllCerts ( + VariableName, + VendorGuid, + LogCert, + NULL + ); +} + +/** + @retval EFI_SUCCESS Certificate not found; continue processing. + @retval EFI_ALREADY_STARTED Certificate found; stop processing. +**/ +STATIC +EFI_STATUS +CheckCertPresent ( + IN VOID *VoidContext, + IN UINTN CertIndex, + IN UINTN CertSize, + IN EFI_SIGNATURE_DATA *Cert + ) +{ + CERT_IS_PRESENT_CONTEXT *Context; + + Context = VoidContext; + + if (!CompareGuid (&Cert->SignatureOwner, Context->OwnerGuid)) { + return EFI_SUCCESS; + } + + if ( (CertSize == sizeof (EFI_SIGNATURE_DATA) - 1 + Context->X509DataSize) + && (CompareMem (Cert->SignatureData, Context->X509Data, Context->X509DataSize) == 0) + ) + { + return EFI_ALREADY_STARTED; + } + + return EFI_SUCCESS; +} + +/** + Report whether specified signature is already enrolled for given owner. + + @param[in] VariableName Variable name of CA database. + @param[in] VendorGuid Unique identifier for the CA database vendor. + @param[in] OwnerGuid Unique identifier for owner of the certificate to be searched for. + @param[in] X509DataSize Certificate data size. + @param[in] X509Data Certificate data. + + @retval EFI_SUCCESS Certificate is already enrolled. + @retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. +**/ +EFI_STATUS +CertIsPresent ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN EFI_GUID *OwnerGuid, + IN UINTN X509DataSize, + IN VOID *X509Data + ) +{ + CERT_IS_PRESENT_CONTEXT Context; + + ASSERT (X509Data != NULL); + + Context.OwnerGuid = OwnerGuid; + Context.X509DataSize = X509DataSize; + Context.X509Data = X509Data; + + return ProcessAllCerts ( + VariableName, + VendorGuid, + CheckCertPresent, + &Context + ); +} + +/** + Delete specific entry or all entries with owner guid from signature database. + (Based on original EDK 2 DeleteCert which removes one cert, identified by index.) + + @param[in] VariableName The variable name of the signature database. + @param[in] VendorGuid A unique identifier for the signature database vendor. + @param[in] OwnerGuid A unique identifier for owner of the certificate(s) to be deleted. + @param[in] X509DataSize Optional certificate data size. + @param[in] X509Data Optional certificate data. If non-NULL, delete only specific certificate + for owner, if present. If NULL, delete all certificates for owner. + @param[in] DeletedCount Optional return count of deleted certificates. + + @retval EFI_SUCCESS Delete signature successfully. + @retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. +**/ +EFI_STATUS +DeleteCertsForOwner ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN EFI_GUID *OwnerGuid, + IN UINTN X509DataSize, + IN VOID *X509Data, + OUT UINTN *DeletedCount + ) +{ + EFI_STATUS Status; + UINTN DataSize; + UINT8 *Data; + UINT8 *OldData; + UINT32 Attr; + UINT32 Index; + EFI_SIGNATURE_LIST *CertList; + EFI_SIGNATURE_LIST *NewCertList; + EFI_SIGNATURE_DATA *Cert; + UINTN CertCount; + UINT32 Offset; + UINTN LocalDeleteCount; + UINT32 ItemDataSize; + + ASSERT ((X509Data == NULL) || (X509DataSize != 0)); + + if (DeletedCount == NULL) { + DeletedCount = &LocalDeleteCount; + } + + *DeletedCount = 0; + + Data = NULL; + OldData = NULL; + CertList = NULL; + Cert = NULL; + Attr = 0; + + // + // Get original signature list data. + // + DataSize = 0; + Status = gRT->GetVariable (VariableName, VendorGuid, NULL, &DataSize, NULL); + if (EFI_ERROR (Status) && (Status != EFI_BUFFER_TOO_SMALL)) { + if (Status == EFI_NOT_FOUND) { + Status = EFI_SUCCESS; + } + + return Status; + } + + OldData = AllocateZeroPool (DataSize); + if (OldData == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = gRT->GetVariable (VariableName, VendorGuid, &Attr, &DataSize, OldData); + if (EFI_ERROR (Status)) { + FreePool (OldData); + return Status; + } + + // + // Allocate space for new variable. + // + Data = AllocateZeroPool (DataSize); + if (Data == NULL) { + FreePool (OldData); + return EFI_OUT_OF_RESOURCES; + } + + // + // Enumerate all data, erasing target items. + // + ItemDataSize = (UINT32)DataSize; + CertList = (EFI_SIGNATURE_LIST *)OldData; + Offset = 0; + while ((ItemDataSize > 0) && (ItemDataSize >= CertList->SignatureListSize)) { + if (CompareGuid (&CertList->SignatureType, &gEfiCertX509Guid)) { + // + // Copy EFI_SIGNATURE_LIST header then calculate the signature count in this list. + // + CopyMem (Data + Offset, CertList, (sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize)); + NewCertList = (EFI_SIGNATURE_LIST *)(Data + Offset); + Offset += (sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); + Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); + CertCount = (CertList->SignatureListSize - sizeof (EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize; + for (Index = 0; Index < CertCount; Index++) { + if ( CompareGuid (&Cert->SignatureOwner, OwnerGuid) + && ( (X509Data == NULL) + || ( (CertList->SignatureSize == (UINT32)(sizeof (EFI_SIGNATURE_DATA) - 1 + X509DataSize)) + && (CompareMem ((UINT8 *)(Cert->SignatureData), X509Data, X509DataSize) == 0) + ) + ) + ) + { + // + // Find it! Skip it! + // + NewCertList->SignatureListSize -= CertList->SignatureSize; + ++(*DeletedCount); + } else { + // + // This item doesn't match. Copy it to the Data buffer. + // + CopyMem (Data + Offset, (UINT8 *)(Cert), CertList->SignatureSize); + Offset += CertList->SignatureSize; + } + + Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)Cert + CertList->SignatureSize); + } + } else { + // + // This List doesn't match. Just copy it to the Data buffer. + // + CopyMem (Data + Offset, (UINT8 *)(CertList), CertList->SignatureListSize); + Offset += CertList->SignatureListSize; + } + + ItemDataSize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *)((UINT8 *)CertList + CertList->SignatureListSize); + } + + if (*DeletedCount != 0) { + // + // Delete the EFI_SIGNATURE_LIST header if there is no signature remaining in any list. + // + ItemDataSize = Offset; + CertList = (EFI_SIGNATURE_LIST *)Data; + Offset = 0; + ZeroMem (OldData, ItemDataSize); + while ((ItemDataSize > 0) && (ItemDataSize >= CertList->SignatureListSize)) { + CertCount = (CertList->SignatureListSize - sizeof (EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize; + if (CertCount != 0) { + CopyMem (OldData + Offset, (UINT8 *)(CertList), CertList->SignatureListSize); + Offset += CertList->SignatureListSize; + } + + ItemDataSize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *)((UINT8 *)CertList + CertList->SignatureListSize); + } + + DataSize = Offset; + + // + // Set (or delete if everything was removed) the Variable. + // + Status = gRT->SetVariable ( + VariableName, + VendorGuid, + Attr, + DataSize, + OldData + ); + } + + FreePool (Data); + FreePool (OldData); + + return Status; +} + +/** + Enroll a new X509 certificate into Variable. + + @param[in] VariableName Variable name of CA database. + @param[in] VendorGuid Unique identifier for the CA database vendor. + @param[in] OwnerGuid Unique identifier for owner of the certificate to be installed. + @param[in] X509DataSize Certificate data size. + @param[in] X509Data Certificate data. + + @retval EFI_SUCCESS New X509 is enrolled successfully. + @retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. +**/ +EFI_STATUS +EnrollX509toVariable ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN EFI_GUID *OwnerGuid, + IN UINTN X509DataSize, + IN VOID *X509Data + ) +{ + EFI_STATUS Status; + EFI_SIGNATURE_LIST *CACert; + EFI_SIGNATURE_DATA *CACertData; + VOID *Data; + UINTN DataSize; + UINTN SigDataSize; + UINT32 Attr; + + SigDataSize = 0; + DataSize = 0; + CACert = NULL; + CACertData = NULL; + Data = NULL; + Attr = 0; + + ASSERT (X509Data != NULL); + + // + // Note: As implemented in EDK 2, each signature list can have multiple + // instances of signature data (owner guid followed by raw signature data), + // but every instance in one list must have the same size. + // The signature data is the unprocessed contents of a .pem or .der file. + // It is not immediately obvious how the multiple signature feature would + // be useful as signature file data does not in general have a fixed size + // (not even for .pem files: https://security.stackexchange.com/q/152584). + // + SigDataSize = sizeof (EFI_SIGNATURE_LIST) + sizeof (EFI_SIGNATURE_DATA) - 1 + X509DataSize; + + Data = AllocateZeroPool (SigDataSize); + if (Data == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Fill Certificate Database parameters. + // + CACert = (EFI_SIGNATURE_LIST *)Data; + CACert->SignatureListSize = (UINT32)SigDataSize; + CACert->SignatureHeaderSize = 0; + CACert->SignatureSize = (UINT32)(sizeof (EFI_SIGNATURE_DATA) - 1 + X509DataSize); + CopyGuid (&CACert->SignatureType, &gEfiCertX509Guid); + + CACertData = (EFI_SIGNATURE_DATA *)((UINT8 *)CACert + sizeof (EFI_SIGNATURE_LIST)); + CopyGuid (&CACertData->SignatureOwner, OwnerGuid); + CopyMem ((UINT8 *)(CACertData->SignatureData), X509Data, X509DataSize); + + // + // Check if the signature database entry already exists. If it does, use the + // EFI_VARIABLE_APPEND_WRITE attribute to append the new signature data to + // the original variable, plus preserve the original variable attributes. + // + Status = gRT->GetVariable ( + VariableName, + VendorGuid, + &Attr, + &DataSize, + NULL + ); + if (Status == EFI_BUFFER_TOO_SMALL) { + Attr |= EFI_VARIABLE_APPEND_WRITE; + } else if (Status == EFI_NOT_FOUND) { + Attr = TLS_AUTH_CONFIG_VAR_BASE_ATTR; + } else { + FreePool (Data); + return Status; + } + + Status = gRT->SetVariable ( + VariableName, + VendorGuid, + Attr, + SigDataSize, + Data + ); + + FreePool (Data); + + return Status; +} diff --git a/Platform/OpenNetworkBoot/Uri.c b/Platform/OpenNetworkBoot/Uri.c new file mode 100644 index 00000000000..6a3ce120f81 --- /dev/null +++ b/Platform/OpenNetworkBoot/Uri.c @@ -0,0 +1,304 @@ +/** @file + URI utilities for HTTP Boot. + + Copyright (c) 2024, Mike Beaton. All rights reserved.
+ SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "NetworkBootInternal.h" + +STATIC EFI_DEVICE_PATH_PROTOCOL mEndDevicePath = { + END_DEVICE_PATH_TYPE, + END_ENTIRE_DEVICE_PATH_SUBTYPE, + { + END_DEVICE_PATH_LENGTH, + 0 + } +}; + +BOOLEAN +HasHttpsUri ( + CHAR16 *Uri + ) +{ + ASSERT (Uri != NULL); + + return (OcStrniCmp (L"https://", Uri, L_STR_LEN (L"https://")) == 0); +} + +EFI_DEVICE_PATH_PROTOCOL * +GetUriNode ( + EFI_DEVICE_PATH_PROTOCOL *DevicePath + ) +{ + EFI_DEVICE_PATH_PROTOCOL *Previous; + EFI_DEVICE_PATH_PROTOCOL *Node; + + for ( Previous = NULL, Node = DevicePath + ; !IsDevicePathEnd (Node) + ; Node = NextDevicePathNode (Node)) + { + Previous = Node; + } + + if ( (Previous != NULL) + && (DevicePathType (Previous) == MESSAGING_DEVICE_PATH) + && (DevicePathSubType (Previous) == MSG_URI_DP) + ) + { + return Previous; + } + + return NULL; +} + +// +// See HttpBootCheckImageType and HttpUrlGetPath within it for how to get +// the proper filename from URL: we would need to use HttpParseUrl, then +// HttpUrlGetPath, then HttpUrlFreeParser. +// +// However, here are some examples: +// +// - https://myserver.com/images/OpenShell.efi +// - https://myserver.com/download.php?a=1&b=2&filename=OpenShell.efi +// - https://myserver.com/images/OpenShell.efi?secure=123 +// +// Rather than parse the URL properly, we can handle all of the above +// (one of which would not be handled by proper parsing of the filename +// part, since the filename is in a parameter; even though this is a fudge, +// as the parameter must come last to be fixed up) if we check if url ends +// with the file ext, and replace it if so; otherwise check if the part +// ending at '?' ends with ext and replace it if so. +// +STATIC +EFI_STATUS +ExtractOtherUriFromUri ( + IN CHAR8 *Uri, + IN CHAR8 *FromExt, + IN CHAR8 *ToExt, + OUT CHAR8 **OtherUri, + IN BOOLEAN OnlySearchForFromExt + ) +{ + CHAR8 *SearchUri; + UINTN UriLen; + UINTN OtherUriLen; + UINTN FromExtLen; + UINTN ToExtLen; + INTN SizeChange; + CHAR8 *ParamsStart; + + ASSERT (FromExt != NULL); + + if (!OnlySearchForFromExt) { + ASSERT (ToExt != NULL); + ASSERT (OtherUri != NULL); + *OtherUri = NULL; + } + + UriLen = AsciiStrLen (Uri); + if (UriLen > MAX_INTN - 1) { + ///< i.e. UriSize > MAX_INTN + return EFI_INVALID_PARAMETER; + } + + FromExtLen = AsciiStrLen (FromExt); + if (FromExtLen > MAX_INTN - 1) { + ///< i.e. FromExtSize > MAX_INTN + return EFI_INVALID_PARAMETER; + } + + if (UriLen < FromExtLen) { + return EFI_NOT_FOUND; + } + + if (OnlySearchForFromExt) { + SearchUri = Uri; + } else { + ToExtLen = AsciiStrLen (ToExt); + if (ToExtLen > MAX_INTN - 1) { + ///< i.e. ToExtSize > MAX_INTN + return EFI_INVALID_PARAMETER; + } + + if (BaseOverflowSubSN ((INTN)ToExtLen, (INTN)FromExtLen, &SizeChange)) { + return EFI_INVALID_PARAMETER; + } + + if (BaseOverflowAddSN ((INTN)UriLen, SizeChange, (INTN *)&OtherUriLen)) { + return EFI_INVALID_PARAMETER; + } + + if (OtherUriLen > MAX_INTN - 1) { + ///< i.e. OtherUriSize > MAX_INTN + return EFI_INVALID_PARAMETER; + } + + *OtherUri = AllocatePool (MAX (UriLen, OtherUriLen) + 1); + if (*OtherUri == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + CopyMem (*OtherUri, Uri, UriLen + 1); + SearchUri = *OtherUri; + } + + if (AsciiStrnCmp (&SearchUri[UriLen - FromExtLen], FromExt, FromExtLen) == 0) { + if (!OnlySearchForFromExt) { + CopyMem (&SearchUri[UriLen - FromExtLen], ToExt, ToExtLen + 1); + if (SizeChange < -1) { + ZeroMem (&SearchUri[UriLen + SizeChange + 1], -(SizeChange + 1)); + } + } + + return EFI_SUCCESS; + } + + ParamsStart = AsciiStrStr (SearchUri, "?"); + if ( (ParamsStart != NULL) + && (AsciiStrnCmp (ParamsStart - FromExtLen, FromExt, FromExtLen) == 0)) + { + if (!OnlySearchForFromExt) { + CopyMem (ParamsStart + SizeChange, ParamsStart, &SearchUri[UriLen] - ParamsStart + 1); + CopyMem (ParamsStart - FromExtLen, ToExt, ToExtLen); + if (SizeChange < -1) { + ZeroMem (&SearchUri[UriLen + SizeChange + 1], -(SizeChange + 1)); + } + } + + return EFI_SUCCESS; + } + + if (!OnlySearchForFromExt) { + FreePool (*OtherUri); + *OtherUri = NULL; + } + + return EFI_NOT_FOUND; +} + +// +// We have the filename in the returned device path, so we can try to load the +// matching file (.chunklist or .dmg, whichever one was not fetched), if we +// we find the other. +// +EFI_STATUS +ExtractOtherUriFromDevicePath ( + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + IN CHAR8 *FromExt, + IN CHAR8 *ToExt, + OUT CHAR8 **OtherUri, + IN BOOLEAN OnlySearchForFromExt + ) +{ + EFI_DEVICE_PATH_PROTOCOL *Previous; + CHAR8 *Uri; + + ASSERT (DevicePath != NULL); + + Previous = GetUriNode (DevicePath); + if (Previous == NULL) { + return EFI_INVALID_PARAMETER; + } + + Uri = (CHAR8 *)Previous + sizeof (EFI_DEVICE_PATH_PROTOCOL); + + return ExtractOtherUriFromUri ( + Uri, + FromExt, + ToExt, + OtherUri, + OnlySearchForFromExt + ); +} + +// +// Determine whether file at URI has extension. This isn't only checking +// the file part of the URI, instead it first checks the argument to the last +// param, if there is one. This is a convenience to allow the 'download.php' +// example shown at ExtractOtherUriFromUri. +// +BOOLEAN +UriFileHasExtension ( + IN CHAR8 *Uri, + IN CHAR8 *Ext + ) +{ + return (!EFI_ERROR (ExtractOtherUriFromUri (Uri, Ext, NULL, NULL, TRUE))); +} + +EFI_STATUS +HttpBootAddUri ( + EFI_DEVICE_PATH_PROTOCOL *DevicePath, + VOID *Uri, + OC_STRING_FORMAT StringFormat, + EFI_DEVICE_PATH_PROTOCOL **UriDevicePath + ) +{ + UINTN Length; + UINTN UriSize; + VOID *OriginalUri; + EFI_DEVICE_PATH_PROTOCOL *Previous; + EFI_DEVICE_PATH_PROTOCOL *Node; + EFI_DEVICE_PATH_PROTOCOL *TmpDevicePath; + + ASSERT (UriDevicePath != NULL); + *UriDevicePath = NULL; + + TmpDevicePath = DuplicateDevicePath (DevicePath); + if (TmpDevicePath == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // If last non-end node is a URI node, replace it with an end node. + // + Previous = GetUriNode (TmpDevicePath); + if (Previous == NULL) { + FreePool (TmpDevicePath); + return EFI_INVALID_PARAMETER; + } + + CopyMem (Previous, &mEndDevicePath, sizeof (mEndDevicePath)); + + // + // Create replacement URI node with the input boot file URI. + // + if (StringFormat == OcStringFormatUnicode) { + OriginalUri = Uri; + UriSize = StrSize (Uri); + Uri = AllocatePool (UriSize * sizeof (CHAR8)); + if (Uri == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + UnicodeStrToAsciiStrS (OriginalUri, Uri, UriSize); + } else { + OriginalUri = NULL; + UriSize = AsciiStrSize (Uri); + } + + Length = sizeof (EFI_DEVICE_PATH_PROTOCOL) + UriSize; + Node = AllocatePool (Length); + if (Node == NULL) { + FreePool (TmpDevicePath); + return EFI_OUT_OF_RESOURCES; + } + + Node->Type = MESSAGING_DEVICE_PATH; + Node->SubType = MSG_URI_DP; + SetDevicePathNodeLength (Node, Length); + CopyMem ((UINT8 *)Node + sizeof (EFI_DEVICE_PATH_PROTOCOL), Uri, UriSize); + if (OriginalUri != NULL) { + FreePool (Uri); + } + + *UriDevicePath = AppendDevicePathNode (TmpDevicePath, Node); + FreePool (Node); + FreePool (TmpDevicePath); + if (*UriDevicePath == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + return EFI_SUCCESS; +} diff --git a/build_oc.tool b/build_oc.tool index 374fc943918..ab20e1d15e0 100755 --- a/build_oc.tool +++ b/build_oc.tool @@ -186,21 +186,25 @@ package() { "BiosVideo.efi" "CrScreenshotDxe.efi" "Dhcp4Dxe.efi" + "Dhcp6Dxe.efi" "DnsDxe.efi" "DpcDxe.efi" "Ext4Dxe.efi" "FirmwareSettingsEntry.efi" + "Hash2DxeCrypto.efi" "HiiDatabase.efi" "HttpBootDxe.efi" "HttpDxe.efi" "HttpUtilitiesDxe.efi" "Ip4Dxe.efi" + "Ip6Dxe.efi" "MnpDxe.efi" "NvmExpressDxe.efi" "OpenCanopy.efi" "OpenHfsPlus.efi" "OpenLegacyBoot.efi" "OpenLinuxBoot.efi" + "OpenNetworkBoot.efi" "OpenNtfsDxe.efi" "OpenPartitionDxe.efi" "OpenRuntime.efi" @@ -208,11 +212,15 @@ package() { "OpenVariableRuntimeDxe.efi" "Ps2KeyboardDxe.efi" "Ps2MouseDxe.efi" + "RamDiskDxe.efi" "ResetNvramEntry.efi" + "RngDxe.efi" "SnpDxe.efi" "TcpDxe.efi" + "TlsDxe.efi" "ToggleSipEntry.efi" "Udp4Dxe.efi" + "Udp6Dxe.efi" "UsbMouseDxe.efi" "XhciDxe.efi" )