From 666f952f6cf3f2c4d0cf29d668346f6ef95b4fe2 Mon Sep 17 00:00:00 2001 From: Mike Beaton Date: Tue, 4 Jun 2024 21:53:30 +0100 Subject: [PATCH] OpenNetworkBoot: Add PXE and HTTP(S) Boot support --- Changelog.md | 5 + Docs/Configuration.tex | 169 ++++- Docs/Flavours.md | 24 +- Docs/Sample.plist | 74 +++ Docs/SampleCustom.plist | 74 +++ .../Acidanthera/Library/OcAppleDiskImageLib.h | 11 + .../Acidanthera/Library/OcBootManagementLib.h | 454 +++++++------ .../Acidanthera/Library/OcConfigurationLib.h | 10 +- Include/Acidanthera/Library/OcMainLib.h | 22 + Include/Acidanthera/Protocol/OcAudio.h | 1 + Include/Acidanthera/Protocol/OcBootEntry.h | 2 +- Library/OcBootManagementLib/BootAudio.c | 6 +- .../OcBootManagementLib/BootEntryManagement.c | 152 +++-- .../OcBootManagementLib/BootEntryProtocol.c | 4 + .../BootManagementInternal.h | 8 +- .../OcBootManagementLib/DefaultEntryChoice.c | 132 ++-- Library/OcBootManagementLib/DmgBootSupport.c | 229 ++++--- .../OcBootManagementLib/OcBootManagementLib.c | 6 +- .../OcConfigurationLib/OcConfigurationLib.c | 6 + Library/OcConsoleLib/ConsoleFont.c | 2 +- Library/OcMainLib/OcMainLib.inf | 2 + Library/OcMainLib/OpenCoreMisc.c | 33 +- Library/OcMainLib/OpenCoreUefi.c | 2 + Library/OcMainLib/OpenCoreUefiUnloadDrivers.c | 350 ++++++++++ Library/OcPngLib/lodepng.c | 3 - OpenCorePkg.dsc | 30 +- OpenDuetPkg.dsc | 4 +- OpenDuetPkgDefines.fdf.inc | 2 +- Platform/OpenCanopy/GuiApp.c | 3 +- Platform/OpenCanopy/GuiApp.h | 1 + Platform/OpenCanopy/Views/BootPicker.c | 9 +- Platform/OpenLegacyBoot/OpenLegacyBoot.c | 26 +- Platform/OpenNetworkBoot/BmBoot.c | 610 ++++++++++++++++++ Platform/OpenNetworkBoot/BmBootDescription.c | 168 +++++ Platform/OpenNetworkBoot/HttpBootCallback.c | 279 ++++++++ Platform/OpenNetworkBoot/HttpBootCustomRead.c | 298 +++++++++ .../OpenNetworkBoot/NetworkBootInternal.h | 217 +++++++ Platform/OpenNetworkBoot/OpenNetworkBoot.c | 535 +++++++++++++++ Platform/OpenNetworkBoot/OpenNetworkBoot.inf | 54 ++ Platform/OpenNetworkBoot/README.md | 430 ++++++++++++ Platform/OpenNetworkBoot/TlsAuthConfigDxe.c | 127 ++++ Platform/OpenNetworkBoot/TlsAuthConfigImpl.c | 519 +++++++++++++++ Platform/OpenNetworkBoot/Uri.c | 304 +++++++++ build_oc.tool | 7 + 44 files changed, 4946 insertions(+), 458 deletions(-) create mode 100644 Library/OcMainLib/OpenCoreUefiUnloadDrivers.c create mode 100644 Platform/OpenNetworkBoot/BmBoot.c create mode 100644 Platform/OpenNetworkBoot/BmBootDescription.c create mode 100644 Platform/OpenNetworkBoot/HttpBootCallback.c create mode 100644 Platform/OpenNetworkBoot/HttpBootCustomRead.c create mode 100644 Platform/OpenNetworkBoot/NetworkBootInternal.h create mode 100644 Platform/OpenNetworkBoot/OpenNetworkBoot.c create mode 100644 Platform/OpenNetworkBoot/OpenNetworkBoot.inf create mode 100644 Platform/OpenNetworkBoot/README.md create mode 100644 Platform/OpenNetworkBoot/TlsAuthConfigDxe.c create mode 100644 Platform/OpenNetworkBoot/TlsAuthConfigImpl.c create mode 100644 Platform/OpenNetworkBoot/Uri.c diff --git a/Changelog.md b/Changelog.md index 4ccf6d47d40..f8dee29eef8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,10 @@ OpenCore Changelog ================== +#### v1.0.2 +- Added OpenNetworkBoot driver to support HTTP(S) and PXE boot +- Supported DMG loading and verification (e.g. macOS Recovery) over HTTP(S) boot +- Added `UEFI` `Unload` option to unload existing firmware drivers + #### v1.0.1 - Updated code and added progress bar to macrecovery, thx @soyeonswife63 - Bundled fat binary i386/x64 10.6+ compatible `nvramdump` with LogoutHook release diff --git a/Docs/Configuration.tex b/Docs/Configuration.tex index 4cd24241ba0..2bf3b3a9aa1 100755 --- a/Docs/Configuration.tex +++ b/Docs/Configuration.tex @@ -4115,6 +4115,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 @@ -6575,6 +6576,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. \\ @@ -7076,9 +7080,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} @@ -7560,6 +7696,37 @@ \subsection{Properties}\label{uefiprops} could be the second 256 MB corrupted by the Intel HD 3000 or an area with faulty RAM. Refer to the \hyperref[uefirsvdprops]{ReservedMemory Properties} section below for details. +\item + \texttt{Unload}\\ + \textbf{Type}: \texttt{plist\ array}\\ + \textbf{Failsafe}: Empty\\ + \textbf{Description}: Unload specified firmware drivers. + + To be filled with \texttt{plist\ string} entries containing + the names of firmware drivers to unload before loading the + \texttt{Drivers} section. + This setting is typically only required if a user-provided driver is a variant of + an existing system firmware driver, and if the new driver would detect itself + as partially loaded, or otherwise fail to operate correctly, if the old + driver is not unloaded first. + + \textbf{Warning}: Unloading system firmware drivers is usually not required + and not recommended. + + \emph{Note 1}: The list of driver which this option can attempt to unload + can be found in \texttt{SysReport/Drivers/DriverImageNames.txt}. + The relevant name is the driver component name. Drivers are only listed if they + implement \texttt{DriverBindingProtocol} and \texttt{LoadedImageProtocol}, + and have an available component name. + + \emph{Note 2}: The NVRAM \texttt{Lang} and \texttt{PlatformLang} variables + are ignored when determining the driver component names recognised by this + option, and listed in the \texttt{SysReport} file. This is in order to make + unloading images stable across changes in these variables. + The UEFI Shell \texttt{dh} command takes account of these variables, + so in some circumstances may display different driver component names from + those listed for this option, unless these variables are cleared. + \end{enumerate} \subsection{APFS Properties}\label{uefiapfsprops} 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 673180dc09b..c0a7e4f7858 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,18 @@ Path HttpBootDxe.efi + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + TlsDxe.efi + Arguments @@ -2051,6 +2123,8 @@ RuntimeCode + Unload + diff --git a/Docs/SampleCustom.plist b/Docs/SampleCustom.plist index 8ea9dacf0c5..210ea7dfae3 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,18 @@ Path HttpBootDxe.efi + + Arguments + + Comment + + Enabled + + LoadEarly + + Path + TlsDxe.efi + Arguments @@ -2419,6 +2491,8 @@ RuntimeCode + Unload + 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 ccbf447ebce..3f26e31e4a8 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. @@ -157,7 +163,7 @@ typedef UINT32 OC_BOOT_ENTRY_TYPE; #define OC_BOOT_EXTERNAL_OS BIT6 #define OC_BOOT_EXTERNAL_TOOL BIT7 #define OC_BOOT_SYSTEM BIT8 -#define OC_BOOT_EXTERNAL_SYSTEM BIT9 +#define OC_BOOT_UNMANAGED BIT9 /** Picker mode. @@ -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. **/ @@ -200,139 +339,180 @@ EFI_STATUS ); /** - Action to perform as part of executing an external boot system boot entry. + Action to perform as part of executing an unmanaged boot entry. **/ typedef EFI_STATUS -(*OC_BOOT_EXTERNAL_SYSTEM_ACTION) ( +(*OC_BOOT_UNMANAGED_ACTION) ( IN OUT OC_PICKER_CONTEXT *PickerContext, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath ); /** - Gets Device Path for external boot system boot entry. + Gets Device Path for unmanaged boot entry. **/ typedef EFI_STATUS -(*OC_BOOT_EXTERNAL_SYSTEM_GET_DP) ( +(*OC_BOOT_UNMANAGED_GET_DP) ( IN OUT OC_PICKER_CONTEXT *PickerContext, 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. // - LIST_ENTRY Link; + LIST_ENTRY Link; // // Device path to booter or its directory. // Can be NULL, for example, for custom or system entries. // - EFI_DEVICE_PATH_PROTOCOL *DevicePath; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; // // Action to perform on execution. Only valid for system entries. // - OC_BOOT_SYSTEM_ACTION SystemAction; + OC_BOOT_SYSTEM_ACTION SystemAction; // - // Action to perform on execution. Only valid for external boot system entries. + // Action to perform on execution. Only valid for unmanaged boot entries. // - OC_BOOT_EXTERNAL_SYSTEM_ACTION ExternalSystemAction; + OC_BOOT_UNMANAGED_ACTION UnmanagedBootAction; // - // Gets Device Path for external boot system boot entry. Only valid for external boot system entries. + // Gets Device Path for boot entry. Only valid for unmanaged boot entries. // - OC_BOOT_EXTERNAL_SYSTEM_GET_DP ExternalSystemGetDevicePath; + OC_BOOT_UNMANAGED_GET_DP UnmanagedBootGetDevicePath; + // + // 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; + CHAR16 *Id; // // Obtained human visible name. // - CHAR16 *Name; + CHAR16 *Name; // // Obtained boot path directory. // For custom entries this contains tool path. // - CHAR16 *PathName; + CHAR16 *PathName; // // Content flavour. // - CHAR8 *Flavour; + CHAR8 *Flavour; // // Heuristical value signaling inferred type of booted os. // WARNING: Non-definitive, do not rely on for any security purposes. // - OC_BOOT_ENTRY_TYPE Type; + OC_BOOT_ENTRY_TYPE Type; // // Entry index number, assigned by picker. // - UINT32 EntryIndex; + UINT32 EntryIndex; // // Set when this entry is an externally available entry (e.g. USB). // - BOOLEAN IsExternal; + BOOLEAN IsExternal; // // Should try booting from first dmg found in DevicePath. // - BOOLEAN IsFolder; + BOOLEAN IsFolder; // // Set when this entry refers to a generic booter (e.g. BOOTx64.EFI). // - BOOLEAN IsGeneric; + BOOLEAN IsGeneric; // // Set when this entry refers to a custom boot entry. // - BOOLEAN IsCustom; + BOOLEAN IsCustom; // // Set when entry was created by OC_BOOT_ENTRY_PROTOCOL. // - BOOLEAN IsBootEntryProtocol; + BOOLEAN IsBootEntryProtocol; // // Set when entry is identified as macOS installer. // - BOOLEAN IsAppleInstaller; + BOOLEAN IsAppleInstaller; // // Should make this option default boot option. // - BOOLEAN SetDefault; + BOOLEAN SetDefault; // // Should launch this entry in text mode. // - BOOLEAN LaunchInText; + BOOLEAN LaunchInText; // // Should expose real device path when dealing with custom entries. // - BOOLEAN ExposeDevicePath; + BOOLEAN ExposeDevicePath; // // Should disable OpenRuntime NVRAM protection around invocation of tool. // - BOOLEAN FullNvramAccess; + BOOLEAN FullNvramAccess; // // Partition UUID of entry device. // Set for non-system action boot entry protocol boot entries only. // - EFI_GUID UniquePartitionGUID; + EFI_GUID UniquePartitionGUID; // // Load option data (usually "boot args") size. // - UINT32 LoadOptionsSize; + UINT32 LoadOptionsSize; // // Load option data (usually "boot args"). // - VOID *LoadOptions; + VOID *LoadOptions; // // Audio base path for system action. Boot Entry Protocol only. // - CHAR8 *AudioBasePath; + CHAR8 *AudioBasePath; // // Audio base type for system action. Boot Entry Protocol only. // - CHAR8 *AudioBaseType; -} OC_BOOT_ENTRY; + CHAR8 *AudioBaseType; +}; /** 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 @@ -591,72 +622,80 @@ typedef struct { // Multiple entries may share an id - allows e.g. newest version // of Linux install to automatically become selected default. // - CONST CHAR8 *Id; + CONST CHAR8 *Id; // // Entry name. // - CONST CHAR8 *Name; + CONST CHAR8 *Name; // // Absolute device path to file for user custom entries, // file path relative to device root for boot entry protocol. // - CONST CHAR8 *Path; + CONST CHAR8 *Path; // // Entry boot arguments. // - CONST CHAR8 *Arguments; + CONST CHAR8 *Arguments; // // Content flavour. // - CONST CHAR8 *Flavour; + CONST CHAR8 *Flavour; // // Whether this entry is auxiliary. // - BOOLEAN Auxiliary; + BOOLEAN Auxiliary; // // Whether this entry is a tool. // - BOOLEAN Tool; + BOOLEAN Tool; // // Whether it should be started in text mode. // - BOOLEAN TextMode; + BOOLEAN TextMode; // // Whether we should pass the actual device path (if possible). // - BOOLEAN RealPath; + BOOLEAN RealPath; // // Should disable OpenRuntime NVRAM protection around invocation of tool. // - BOOLEAN FullNvramAccess; + BOOLEAN FullNvramAccess; // // System action. Boot Entry Protocol only. Optional. // - OC_BOOT_SYSTEM_ACTION SystemAction; + OC_BOOT_SYSTEM_ACTION SystemAction; // // Audio base path for system action. Boot Entry Protocol only. Optional. // - CHAR8 *AudioBasePath; + CHAR8 *AudioBasePath; // // Audio base type for system action. Boot Entry Protocol only. Optional. // - CHAR8 *AudioBaseType; + CHAR8 *AudioBaseType; + // + // Unmanaged boot action. Boot Entry Protocol only. Optional. // - // External boot system action. Boot Entry Protocol only. Optional. + OC_BOOT_UNMANAGED_ACTION UnmanagedBootAction; // - OC_BOOT_EXTERNAL_SYSTEM_ACTION ExternalSystemAction; + // Gets Device Path for unmanaged boot entry. Boot Entry Protocol only. Optional. // - // Gets Device Path for external boot system boot entry. Boot Entry Protocol only. Optional. + OC_BOOT_UNMANAGED_GET_DP UnmanagedBootGetDevicePath; // - OC_BOOT_EXTERNAL_SYSTEM_GET_DP ExternalSystemGetDevicePath; + // External Device Path. For unmanaged boot and possibly other Boot Entry Protocol entries. Optional. + // + EFI_DEVICE_PATH_PROTOCOL *UnmanagedBootDevicePath; + // + // Custom entry image read routine, optional for non-custom entries. + // + OC_CUSTOM_READ CustomRead; // - // External boot system Device Path. Boot Entry Protocol only. Optional. + // Custom entry routine to free custom items. Optional. // - EFI_DEVICE_PATH_PROTOCOL *ExternalSystemDevicePath; + OC_CUSTOM_FREE CustomFree; // // Whether this entry should be labeled as external to the system. Boot Entry Protocol only. Optional. // - BOOLEAN External; + BOOLEAN External; } OC_PICKER_ENTRY; /** @@ -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/Library/OcConfigurationLib.h b/Include/Acidanthera/Library/OcConfigurationLib.h index 470f54e8ccb..d92e1f64939 100644 --- a/Include/Acidanthera/Library/OcConfigurationLib.h +++ b/Include/Acidanthera/Library/OcConfigurationLib.h @@ -606,6 +606,13 @@ OC_DECLARE (OC_PLATFORM_CONFIG) Uefi section **/ +/// +/// Array of driver names to unload. +/// +#define OC_UEFI_UNLOAD_ARRAY_FIELDS(_, __) \ + OC_ARRAY (OC_STRING, _, __) +OC_DECLARE (OC_UEFI_UNLOAD_ARRAY) + /// /// Drivers is an ordered array of drivers to load. /// @@ -782,7 +789,8 @@ OC_DECLARE (OC_UEFI_RSVD_ARRAY) _(OC_UEFI_OUTPUT , Output , , OC_CONSTR2 (OC_UEFI_OUTPUT, _, __) , OC_DESTR (OC_UEFI_OUTPUT)) \ _(OC_UEFI_PROTOCOL_OVERRIDES , ProtocolOverrides , , OC_CONSTR2 (OC_UEFI_PROTOCOL_OVERRIDES, _, __) , OC_DESTR (OC_UEFI_PROTOCOL_OVERRIDES)) \ _(OC_UEFI_QUIRKS , Quirks , , OC_CONSTR2 (OC_UEFI_QUIRKS, _, __) , OC_DESTR (OC_UEFI_QUIRKS)) \ - _(OC_UEFI_RSVD_ARRAY , ReservedMemory , , OC_CONSTR2 (OC_UEFI_RSVD_ARRAY, _, __) , OC_DESTR (OC_UEFI_RSVD_ARRAY)) + _(OC_UEFI_RSVD_ARRAY , ReservedMemory , , OC_CONSTR2 (OC_UEFI_RSVD_ARRAY, _, __) , OC_DESTR (OC_UEFI_RSVD_ARRAY)) \ + _(OC_UEFI_UNLOAD_ARRAY , Unload , , OC_CONSTR2 (OC_UEFI_UNLOAD_ARRAY, _, __) , OC_DESTR (OC_UEFI_UNLOAD_ARRAY)) OC_DECLARE (OC_UEFI_CONFIG) /** diff --git a/Include/Acidanthera/Library/OcMainLib.h b/Include/Acidanthera/Library/OcMainLib.h index 32d61feb3ee..4a1339eadea 100644 --- a/Include/Acidanthera/Library/OcMainLib.h +++ b/Include/Acidanthera/Library/OcMainLib.h @@ -428,4 +428,26 @@ OcPlatformIs64BitSupported ( IN UINT32 KernelVersion ); +/** + Unload loaded images by name. + + @param[in] Config OpenCore configuration. +**/ +VOID +OcUnloadDrivers ( + IN OC_GLOBAL_CONFIG *Config + ); + +/** + Dump loaded image driver info to the specified directory. + + @param[in] Root Directory to write CPU data. + + @retval EFI_SUCCESS on success. +**/ +EFI_STATUS +OcDriverInfoDump ( + IN EFI_FILE_PROTOCOL *Root + ); + #endif // OC_MAIN_LIB 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 57cfb51880c..9561f8a6223 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->ExternalSystemAction && !CustomEntry->SystemAction) { + if (!CustomEntry->UnmanagedBootAction && !CustomEntry->SystemAction && !CustomEntry->UnmanagedBootDevicePath) { ASSERT (CustomEntry->Path != NULL); PathName = AsciiStrCopyToUnicode (CustomEntry->Path, 0); if (PathName == NULL) { @@ -715,19 +717,19 @@ InternalAddBootEntryFromCustomEntry ( DEBUG_INFO, "OCB: Adding custom entry %s (%a|B:%d) -> %a\n", BootEntry->Name, - CustomEntry->ExternalSystemAction != NULL ? "ext-action" : (CustomEntry->SystemAction != NULL ? "action" : (CustomEntry->Tool ? "tool" : "os")), + CustomEntry->UnmanagedBootAction != NULL ? "unmanaged" : (CustomEntry->SystemAction != NULL ? "action" : (CustomEntry->Tool ? "tool" : "os")), IsBootEntryProtocol, CustomEntry->Path )); - if (CustomEntry->ExternalSystemAction) { - BootEntry->Type = OC_BOOT_EXTERNAL_SYSTEM; - BootEntry->ExternalSystemAction = CustomEntry->ExternalSystemAction; - BootEntry->ExternalSystemGetDevicePath = CustomEntry->ExternalSystemGetDevicePath; - BootEntry->AudioBasePath = CustomEntry->AudioBasePath; - BootEntry->AudioBaseType = CustomEntry->AudioBaseType; - BootEntry->IsExternal = CustomEntry->External; - BootEntry->DevicePath = DuplicateDevicePath (CustomEntry->ExternalSystemDevicePath); + if (CustomEntry->UnmanagedBootAction) { + BootEntry->Type = OC_BOOT_UNMANAGED; + BootEntry->UnmanagedBootAction = CustomEntry->UnmanagedBootAction; + BootEntry->UnmanagedBootGetDevicePath = CustomEntry->UnmanagedBootGetDevicePath; + BootEntry->AudioBasePath = CustomEntry->AudioBasePath; + BootEntry->AudioBaseType = CustomEntry->AudioBaseType; + BootEntry->IsExternal = CustomEntry->External; + BootEntry->DevicePath = DuplicateDevicePath (CustomEntry->UnmanagedBootDevicePath); 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->UnmanagedBootDevicePath) { + BootEntry->DevicePath = DuplicateDevicePath (CustomEntry->UnmanagedBootDevicePath); + } else { + UnicodeUefiSlashes (PathName); + BootEntry->DevicePath = FileDevicePath (FileSystem->Handle, PathName); + FreePool (PathName); + } } else { + ASSERT (CustomEntry->UnmanagedBootDevicePath == 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->ExternalSystemAction != NULL) || (BootEntry->SystemAction != NULL)) { + if ((BootEntry->UnmanagedBootAction != NULL) || (BootEntry->SystemAction != NULL) || (CustomEntry->CustomRead != NULL)) { ASSERT (CustomEntry->Arguments == NULL); } else { ASSERT (CustomEntry->Arguments != NULL); @@ -861,7 +874,7 @@ InternalAddBootEntryFromCustomEntry ( BootEntry->IsCustom = TRUE; BootEntry->IsBootEntryProtocol = IsBootEntryProtocol; - if (IsBootEntryProtocol && (BootEntry->ExternalSystemAction == NULL) && (BootEntry->SystemAction == NULL)) { + if (IsBootEntryProtocol && (BootEntry->UnmanagedBootAction == NULL) && (BootEntry->SystemAction == NULL)) { PartitionEntry = OcGetGptPartitionEntry (FileSystem->Handle); if (PartitionEntry == NULL) { CopyGuid (&BootEntry->UniquePartitionGUID, &gEfiPartTypeUnusedGuid); @@ -1463,6 +1476,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 @@ -1483,12 +1497,12 @@ AddBootEntryFromBootOption ( *CustomIndex = Index; } - InternalAddBootEntryFromCustomEntry ( - BootContext, - CustomFileSystem, - &BootContext->PickerContext->CustomEntries[Index], - FALSE - ); + Status = InternalAddBootEntryFromCustomEntry ( + BootContext, + CustomFileSystem, + &BootContext->PickerContext->CustomEntries[Index], + FALSE + ); break; } } @@ -1502,6 +1516,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. @@ -1573,7 +1617,7 @@ AddBootEntryFromBootOption ( DevicePath = ExpandedDevicePath; if (DevicePath == NULL) { - return EFI_NOT_FOUND; + return Status; } } else if (NumPatchedNodes == -1) { // @@ -2510,10 +2554,11 @@ OcLoadBootEntry ( EFI_STATUS Status; EFI_HANDLE EntryHandle; INTERNAL_DMG_LOAD_CONTEXT DmgLoadContext; + VOID *CustomFreeContext; - if ((BootEntry->Type & OC_BOOT_EXTERNAL_SYSTEM) != 0) { - ASSERT (BootEntry->ExternalSystemAction != NULL); - return BootEntry->ExternalSystemAction (Context, BootEntry->DevicePath); + if ((BootEntry->Type & OC_BOOT_UNMANAGED) != 0) { + ASSERT (BootEntry->UnmanagedBootAction != NULL); + return BootEntry->UnmanagedBootAction (Context, BootEntry->DevicePath); } if ((BootEntry->Type & OC_BOOT_SYSTEM) != 0) { @@ -2526,7 +2571,8 @@ OcLoadBootEntry ( BootEntry, ParentHandle, &EntryHandle, - &DmgLoadContext + &DmgLoadContext, + &CustomFreeContext ); if (!EFI_ERROR (Status)) { // @@ -2545,14 +2591,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 94b36c93759..9c5d5bc00ee 100644 --- a/Library/OcBootManagementLib/DefaultEntryChoice.c +++ b/Library/OcBootManagementLib/DefaultEntryChoice.c @@ -795,7 +795,7 @@ OcSetDefaultBootEntry ( UINTN BootChosenIndex; UINTN Index; UINTN DevicePathSize; - UINTN ExtSystemDevPathSize; + UINTN UnmanagedBootDevPathSize; UINTN LoadOptionSize; UINTN LoadOptionIdSize; UINTN LoadOptionNameSize; @@ -805,7 +805,7 @@ OcSetDefaultBootEntry ( CONST OC_CUSTOM_BOOT_DEVICE_PATH *CustomDevPath; CONST OC_ENTRY_PROTOCOL_DEVICE_PATH *EntryProtocolDevPath; - EFI_DEVICE_PATH_PROTOCOL *ExtSystemDevPath; + EFI_DEVICE_PATH_PROTOCOL *UnmanagedBootDevPath; VENDOR_DEVICE_PATH *DestCustomDevPath; FILEPATH_DEVICE_PATH *DestCustomEntryName; EFI_DEVICE_PATH_PROTOCOL *DestCustomEndNode; @@ -825,16 +825,16 @@ OcSetDefaultBootEntry ( } // - // Get final device path for external boot system entries. + // Get final device path for unmanaged boot entries. // - ExtSystemDevPath = NULL; - if ((Entry->Type == OC_BOOT_EXTERNAL_SYSTEM) && (Entry->ExternalSystemGetDevicePath != NULL)) { - ExtSystemDevPath = Entry->DevicePath; - Status = Entry->ExternalSystemGetDevicePath (Context, &ExtSystemDevPath); + UnmanagedBootDevPath = NULL; + if ((Entry->Type == OC_BOOT_UNMANAGED) && (Entry->UnmanagedBootGetDevicePath != NULL)) { + UnmanagedBootDevPath = Entry->DevicePath; + Status = Entry->UnmanagedBootGetDevicePath (Context, &UnmanagedBootDevPath); if (EFI_ERROR (Status)) { - ExtSystemDevPath = NULL; + UnmanagedBootDevPath = NULL; } else { - ExtSystemDevPathSize = GetDevicePathSize (ExtSystemDevPath); + UnmanagedBootDevPathSize = GetDevicePathSize (UnmanagedBootDevPath); } } @@ -890,10 +890,10 @@ OcSetDefaultBootEntry ( continue; } - if (ExtSystemDevPath != NULL) { + if (UnmanagedBootDevPath != NULL) { DevicePathSize = GetDevicePathSize (BootOptionDevicePath); - if (DevicePathSize >= ExtSystemDevPathSize) { - MatchedEntry = CompareMem (BootOptionDevicePath, ExtSystemDevPath, ExtSystemDevPathSize) == 0; + if (DevicePathSize >= UnmanagedBootDevPathSize) { + MatchedEntry = CompareMem (BootOptionDevicePath, UnmanagedBootDevPath, UnmanagedBootDevPathSize) == 0; } } else { BootOptionRemainingDevicePath = BootOptionDevicePath; @@ -973,8 +973,8 @@ OcSetDefaultBootEntry ( } } - if (ExtSystemDevPath != NULL) { - DevicePathSize = ExtSystemDevPathSize; + if (UnmanagedBootDevPath != NULL) { + DevicePathSize = UnmanagedBootDevPathSize; } else if (!Entry->IsCustom) { DevicePathSize = GetDevicePathSize (Entry->DevicePath); } else { @@ -1021,8 +1021,8 @@ OcSetDefaultBootEntry ( CopyMem (LoadOption + 1, LoadOptionName, LoadOptionNameSize); } - if (ExtSystemDevPath != NULL) { - CopyMem ((UINT8 *)(LoadOption + 1) + LoadOptionNameSize, ExtSystemDevPath, DevicePathSize); + if (UnmanagedBootDevPath != NULL) { + CopyMem ((UINT8 *)(LoadOption + 1) + LoadOptionNameSize, UnmanagedBootDevPath, DevicePathSize); } else if (!Entry->IsCustom) { CopyMem ((UINT8 *)(LoadOption + 1) + LoadOptionNameSize, Entry->DevicePath, DevicePathSize); } else { @@ -1084,9 +1084,9 @@ OcSetDefaultBootEntry ( FreePool (LoadOption); - if (ExtSystemDevPath != NULL) { - FreePool (ExtSystemDevPath); - ExtSystemDevPath = NULL; + if (UnmanagedBootDevPath != NULL) { + FreePool (UnmanagedBootDevPath); + UnmanagedBootDevPath = NULL; } if (EFI_ERROR (Status)) { @@ -1529,25 +1529,27 @@ 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); // // System entries are not loaded but called directly. // - ASSERT ((BootEntry->Type & OC_BOOT_EXTERNAL_SYSTEM) == 0); + ASSERT ((BootEntry->Type & OC_BOOT_UNMANAGED) == 0); ASSERT ((BootEntry->Type & OC_BOOT_SYSTEM) == 0); ASSERT (Context != NULL); ASSERT (DmgLoadContext != 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/OcConfigurationLib/OcConfigurationLib.c b/Library/OcConfigurationLib/OcConfigurationLib.c index 09749e8769a..108ca05a566 100644 --- a/Library/OcConfigurationLib/OcConfigurationLib.c +++ b/Library/OcConfigurationLib/OcConfigurationLib.c @@ -73,6 +73,7 @@ OC_STRUCTORS (OC_PLATFORM_NVRAM_CONFIG, ()) OC_STRUCTORS (OC_PLATFORM_SMBIOS_CONFIG, ()) OC_STRUCTORS (OC_PLATFORM_CONFIG, ()) +OC_ARRAY_STRUCTORS (OC_UEFI_UNLOAD_ARRAY) OC_STRUCTORS (OC_UEFI_DRIVER_ENTRY, ()) OC_ARRAY_STRUCTORS (OC_UEFI_DRIVER_ARRAY) OC_STRUCTORS (OC_UEFI_APFS, ()) @@ -852,6 +853,10 @@ STATIC OC_SCHEMA mUefiReservedMemorySchema = OC_SCHEMA_DICT (NULL, mUefiReservedMemoryEntrySchema); +STATIC +OC_SCHEMA + mMiscUnloadImagesSchema = OC_SCHEMA_STRING (NULL); + STATIC OC_SCHEMA mUefiConfigurationSchema[] = { @@ -865,6 +870,7 @@ OC_SCHEMA OC_SCHEMA_DICT ("ProtocolOverrides", mUefiProtocolOverridesSchema), OC_SCHEMA_DICT ("Quirks", mUefiQuirksSchema), OC_SCHEMA_ARRAY_IN ("ReservedMemory", OC_GLOBAL_CONFIG, Uefi.ReservedMemory, &mUefiReservedMemorySchema), + OC_SCHEMA_ARRAY_IN ("Unload", OC_GLOBAL_CONFIG, Uefi.Unload, &mMiscUnloadImagesSchema), }; // diff --git a/Library/OcConsoleLib/ConsoleFont.c b/Library/OcConsoleLib/ConsoleFont.c index b4891b5baaf..b7177a3868e 100644 --- a/Library/OcConsoleLib/ConsoleFont.c +++ b/Library/OcConsoleLib/ConsoleFont.c @@ -238,7 +238,7 @@ STATIC UINT8 mIsoFontDataPage1[(PAGE1_CHAR_MAX - PAGE1_CHAR_MIN) * (ISO_CHAR_HE #endif /* - * EFI mandated https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/SimpleTextOut.h#L34-L98 + * EFI mandated https://github.com/tianocore/edk2/blob/2f88bd3a1296c522317f1c21377876de63de5be7/MdePkg/Include/Protocol/SimpleTextOut.h#L34-L98 * unicode box drawing, block element and arrow characters (which are used e.g. by memtest86). * Unicode font pages derived from https://github.com/farsil/ibmfonts * SPDX-License-Identifier: CC-BY-SA-4.0 diff --git a/Library/OcMainLib/OcMainLib.inf b/Library/OcMainLib/OcMainLib.inf index 21109ab6430..b3f770b6483 100644 --- a/Library/OcMainLib/OcMainLib.inf +++ b/Library/OcMainLib/OcMainLib.inf @@ -31,6 +31,7 @@ OpenCoreUefi.c OpenCoreUefiAudio.c OpenCoreUefiInOut.c + OpenCoreUefiUnloadDrivers.c OpenCoreVault.c [Packages] @@ -47,6 +48,7 @@ gEfiAudioDecodeProtocolGuid ## SOMETIMES_CONSUMES gEfiDevicePathProtocolGuid ## CONSUMES gEfiDevicePathProtocolGuid ## CONSUMES + gEfiDriverBindingProtocolGuid ## CONSUMES gEfiSimplePointerProtocolGuid ## SOMETIMES_CONSUMES gEfiLoadedImageProtocolGuid ## CONSUMES gEfiSimpleFileSystemProtocolGuid ## CONSUMES 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/OcMainLib/OpenCoreUefi.c b/Library/OcMainLib/OpenCoreUefi.c index 35e26c73680..6ad1b6929cd 100644 --- a/Library/OcMainLib/OpenCoreUefi.c +++ b/Library/OcMainLib/OpenCoreUefi.c @@ -897,6 +897,8 @@ OcLoadUefiSupport ( EFI_EVENT Event; BOOLEAN AccelEnabled; + OcUnloadDrivers (Config); + OcReinstallProtocols (Config); OcImageLoaderInit (Config->Booter.Quirks.ProtectUefiServices, Config->Booter.Quirks.FixupAppleEfiImages); diff --git a/Library/OcMainLib/OpenCoreUefiUnloadDrivers.c b/Library/OcMainLib/OpenCoreUefiUnloadDrivers.c new file mode 100644 index 00000000000..5434eb56447 --- /dev/null +++ b/Library/OcMainLib/OpenCoreUefiUnloadDrivers.c @@ -0,0 +1,350 @@ +/** @file + Unload images by name. Includes image name code from ShellPkg UefiHandleParsingLib.c. + + Copyright (c) 2010 - 2017, Intel Corporation. All rights reserved.
+ (C) Copyright 2013-2015 Hewlett-Packard Development Company, L.P.
+ (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 + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +typedef +VOID +(*PROCESS_IMAGE)( + IN VOID *Context, + IN CONST CHAR16 *Name, + IN EFI_HANDLE Handle + ); + +typedef struct { + CHAR16 *UnloadName; + BOOLEAN Unloaded; +} UNLOAD_INFO; + +typedef struct { + UINTN UnloadNameCount; + UNLOAD_INFO *UnloadInfo; +} UNLOAD_IMAGE_CONTEXT; + +typedef struct { + CHAR8 *FileBuffer; + UINTN FileBufferSize; +} DRIVER_REPORT_CONTEXT; + +/** + Get the best supported language for this driver. + + First base on the user input language to search, otherwise get the first language + from the supported language list. The caller needs to free the best language buffer. + + @param[in] SupportedLanguages The supported languages for this driver. + @param[in] InputLanguage The user input language. + @param[in] Iso639Language Whether get language for ISO639. + + @return The best supported language for this driver. +**/ +CHAR8 * +EFIAPI +GetBestLanguageForDriver ( + IN CONST CHAR8 *SupportedLanguages, + IN CONST CHAR8 *InputLanguage, + IN BOOLEAN Iso639Language + ) +{ + CHAR8 *BestLanguage; + + BestLanguage = GetBestLanguage ( + SupportedLanguages, + Iso639Language, + (InputLanguage != NULL) ? InputLanguage : "", + SupportedLanguages, + NULL + ); + + return BestLanguage; +} + +/** + Function to retrieve the driver name (if possible) from the ComponentName or + ComponentName2 protocol + + @param[in] TheHandle The driver handle to get the name of. + @param[in] Language The language to use. + + @retval NULL The name could not be found. + @return A pointer to the string name. Do not de-allocate the memory. +**/ +CONST CHAR16 * +EFIAPI +GetStringNameFromHandle ( + IN CONST EFI_HANDLE TheHandle, + IN CONST CHAR8 *Language + ) +{ + EFI_COMPONENT_NAME2_PROTOCOL *CompNameStruct; + EFI_STATUS Status; + CHAR16 *RetVal; + CHAR8 *BestLang; + + BestLang = NULL; + + Status = gBS->OpenProtocol ( + TheHandle, + &gEfiComponentName2ProtocolGuid, + (VOID **)&CompNameStruct, + gImageHandle, + NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + if (!EFI_ERROR (Status)) { + BestLang = GetBestLanguageForDriver (CompNameStruct->SupportedLanguages, Language, FALSE); + Status = CompNameStruct->GetDriverName (CompNameStruct, BestLang, &RetVal); + if (BestLang != NULL) { + FreePool (BestLang); + BestLang = NULL; + } + + if (!EFI_ERROR (Status)) { + return (RetVal); + } + } + + Status = gBS->OpenProtocol ( + TheHandle, + &gEfiComponentNameProtocolGuid, + (VOID **)&CompNameStruct, + gImageHandle, + NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + if (!EFI_ERROR (Status)) { + BestLang = GetBestLanguageForDriver (CompNameStruct->SupportedLanguages, Language, FALSE); + Status = CompNameStruct->GetDriverName (CompNameStruct, BestLang, &RetVal); + if (BestLang != NULL) { + FreePool (BestLang); + } + + if (!EFI_ERROR (Status)) { + return (RetVal); + } + } + + return (NULL); +} + +VOID +ReportImageName ( + IN VOID *Context, + IN CONST CHAR16 *Name, + IN EFI_HANDLE Handle + ) +{ + DRIVER_REPORT_CONTEXT *ReportContext; + + ReportContext = Context; + + OcAsciiPrintBuffer ( + &ReportContext->FileBuffer, + &ReportContext->FileBufferSize, + "%s\n", + Name + ); +} + +VOID +UnloadImageByName ( + IN VOID *Context, + IN CONST CHAR16 *Name, + IN EFI_HANDLE Handle + ) +{ + EFI_STATUS Status; + UINTN Index; + UNLOAD_IMAGE_CONTEXT *UnloadContext; + + UnloadContext = Context; + + for (Index = 0; Index < UnloadContext->UnloadNameCount; Index++) { + if (StrCmp (UnloadContext->UnloadInfo[Index].UnloadName, Name) == 0) { + Status = gBS->UnloadImage (Handle); + DEBUG (( + EFI_ERROR (Status) ? DEBUG_WARN : DEBUG_INFO, + "OC: UnloadImage %s - %r\n", + UnloadContext->UnloadInfo[Index].UnloadName, + Status + )); + UnloadContext->UnloadInfo[Index].Unloaded = TRUE; ///< Do not report as 'not found' even if we failed to unload it. + break; + } + } +} + +// +// Perform action for all driver binding protocols which are +// present on the same handle as loaded image protocol and +// where the handle has a usable name. +// +// Note: In terms of choosing a name to identify the driver, while the loaded image +// section name is shorter and corresponds better to the driver's file name, and the +// firmware volume GUID from the loaded image path identifies the driver more uniquely, +// these are both not always available for a loaded image (across various firmware), +// whereas the driver component name normally is. +// +VOID +ProcessAllDrivers ( + IN VOID *Context, + PROCESS_IMAGE ProcessImage + ) +{ + EFI_STATUS Status; + UINTN HandleCount; + EFI_HANDLE *HandleBuffer; + EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; + UINTN Index; + CONST CHAR16 *Name; + + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiDriverBindingProtocolGuid, + NULL, + &HandleCount, + &HandleBuffer + ); + if (EFI_ERROR (Status)) { + return; + } + + for (Index = 0; Index < HandleCount; Index++) { + Status = gBS->HandleProtocol ( + HandleBuffer[Index], + &gEfiLoadedImageProtocolGuid, + (VOID **)&LoadedImage + ); + if (!EFI_ERROR (Status)) { + Name = GetStringNameFromHandle (HandleBuffer[Index], NULL); ///< Do not free this one. + if (Name != NULL) { + ProcessImage (Context, Name, HandleBuffer[Index]); + } + } + } + + return; +} + +VOID +OcUnloadDrivers ( + IN OC_GLOBAL_CONFIG *Config + ) +{ + UINTN Index; + UINTN Index2; + UINTN UnloadInfoSize; + UNLOAD_IMAGE_CONTEXT UnloadContext; + + if (Config->Uefi.Unload.Count == 0) { + return; + } + + if (!BaseOverflowMulUN ( + Config->Uefi.Unload.Count, + sizeof (*UnloadContext.UnloadInfo), + &UnloadInfoSize + )) + { + UnloadContext.UnloadInfo = AllocateZeroPool (UnloadInfoSize); + } else { + UnloadContext.UnloadInfo = NULL; + } + + if (UnloadContext.UnloadInfo == NULL) { + DEBUG ((DEBUG_ERROR, "OC: Failed to allocate unload names!\n")); + return; + } + + for (Index = 0; Index < Config->Uefi.Unload.Count; ++Index) { + UnloadContext.UnloadInfo[Index].UnloadName = AsciiStrCopyToUnicode ( + OC_BLOB_GET ( + Config->Uefi.Unload.Values[Index] + ), + 0 + ); + if (UnloadContext.UnloadInfo[Index].UnloadName == NULL) { + for (Index2 = 0; Index2 < Index; ++Index2) { + FreePool (UnloadContext.UnloadInfo[Index2].UnloadName); + } + + FreePool (UnloadContext.UnloadInfo); + DEBUG ((DEBUG_ERROR, "OC: Failed to allocate unload names!\n")); + return; + } + } + + UnloadContext.UnloadNameCount = Config->Uefi.Unload.Count; + + ProcessAllDrivers (&UnloadContext, UnloadImageByName); + + for (Index = 0; Index < Config->Uefi.Unload.Count; ++Index) { + if (!UnloadContext.UnloadInfo[Index].Unloaded) { + DEBUG ((DEBUG_INFO, "OC: Unload %s - %r\n", UnloadContext.UnloadInfo[Index].UnloadName, EFI_NOT_FOUND)); + } + + FreePool (UnloadContext.UnloadInfo[Index].UnloadName); + } + + FreePool (UnloadContext.UnloadInfo); + + return; +} + +EFI_STATUS +OcDriverInfoDump ( + IN EFI_FILE_PROTOCOL *Root + ) +{ + EFI_STATUS Status; + DRIVER_REPORT_CONTEXT Context; + CHAR16 TmpFileName[32]; + + ASSERT (Root != NULL); + + Context.FileBufferSize = SIZE_1KB; + Context.FileBuffer = AllocateZeroPool (Context.FileBufferSize); + if (Context.FileBuffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + ProcessAllDrivers (&Context, ReportImageName); + + // + // Save dumped driver info to file. + // + if (Context.FileBuffer != NULL) { + UnicodeSPrint (TmpFileName, sizeof (TmpFileName), L"DriverImageNames.txt"); + Status = OcSetFileData (Root, TmpFileName, Context.FileBuffer, (UINT32)AsciiStrLen (Context.FileBuffer)); + DEBUG ((DEBUG_INFO, "OC: Dumped driver info - %r\n", Status)); + + FreePool (Context.FileBuffer); + } + + return EFI_SUCCESS; +} diff --git a/Library/OcPngLib/lodepng.c b/Library/OcPngLib/lodepng.c index 8a67e54ce90..c53c702326a 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..1babb76d140 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|CryptoPkg/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,20 @@ # 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 + CryptoPkg/Library/IntrinsicLib/IntrinsicLib.inf + [LibraryClasses] - NULL|OpenCorePkg/Library/OcCompilerIntrinsicsLib/OcCompilerIntrinsicsLib.inf + NULL|CryptoPkg/Library/IntrinsicLib/IntrinsicLib.inf [PcdsFixedAtBuild] gEfiMdePkgTokenSpaceGuid.PcdMaximumAsciiStringLength|0 diff --git a/OpenDuetPkg.dsc b/OpenDuetPkg.dsc index 3704c47fdb7..522c235c405 100644 --- a/OpenDuetPkg.dsc +++ b/OpenDuetPkg.dsc @@ -245,10 +245,10 @@ OpenCorePkg/Legacy/BootPlatform/LegacyRegion2Dxe/LegacyRegion2Dxe.inf OpenCorePkg/Legacy/BootPlatform/BiosVideo/BiosVideo.inf - OpenCorePkg/Library/OcCompilerIntrinsicsLib/OcCompilerIntrinsicsLib.inf + CryptoPkg/Library/IntrinsicLib/IntrinsicLib.inf [LibraryClasses] - NULL|OpenCorePkg/Library/OcCompilerIntrinsicsLib/OcCompilerIntrinsicsLib.inf + NULL|CryptoPkg/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 30ffe5b36d5..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). @@ -1495,7 +1500,7 @@ BootPickerEntriesSet ( } break; - case OC_BOOT_EXTERNAL_SYSTEM: + case OC_BOOT_UNMANAGED: if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_WINDOWS) != NULL) { Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_WINDOWS]); } else { diff --git a/Platform/OpenLegacyBoot/OpenLegacyBoot.c b/Platform/OpenLegacyBoot/OpenLegacyBoot.c index 1a157a2f2e2..ac019919f05 100644 --- a/Platform/OpenLegacyBoot/OpenLegacyBoot.c +++ b/Platform/OpenLegacyBoot/OpenLegacyBoot.c @@ -110,7 +110,7 @@ FreePickerEntry ( STATIC EFI_STATUS -ExternalSystemActionDoLegacyBoot ( +UnmanagedBootActionDoLegacyBoot ( IN OUT OC_PICKER_CONTEXT *PickerContext, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath ) @@ -211,7 +211,7 @@ ExternalSystemActionDoLegacyBoot ( STATIC EFI_STATUS -ExternalSystemGetDevicePath ( +UnmanagedBootGetDevicePath ( IN OUT OC_PICKER_CONTEXT *PickerContext, IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath ) @@ -416,17 +416,17 @@ OcGetLegacyBootEntries ( PickerEntry->Name = GetLegacyEntryName (LegacyOsType); } - PickerEntry->Id = AsciiDevicePath; - PickerEntry->Path = NULL; - PickerEntry->Arguments = NULL; - PickerEntry->Flavour = GetLegacyEntryFlavour (LegacyOsType); - PickerEntry->Tool = FALSE; - PickerEntry->TextMode = FALSE; - PickerEntry->RealPath = FALSE; - PickerEntry->External = IsExternal; - PickerEntry->ExternalSystemAction = ExternalSystemActionDoLegacyBoot; - PickerEntry->ExternalSystemGetDevicePath = ExternalSystemGetDevicePath; - PickerEntry->ExternalSystemDevicePath = BlockDevicePath; + PickerEntry->Id = AsciiDevicePath; + PickerEntry->Path = NULL; + PickerEntry->Arguments = NULL; + PickerEntry->Flavour = GetLegacyEntryFlavour (LegacyOsType); + PickerEntry->Tool = FALSE; + PickerEntry->TextMode = FALSE; + PickerEntry->RealPath = FALSE; + PickerEntry->External = IsExternal; + PickerEntry->UnmanagedBootAction = UnmanagedBootActionDoLegacyBoot; + PickerEntry->UnmanagedBootGetDevicePath = UnmanagedBootGetDevicePath; + PickerEntry->UnmanagedBootDevicePath = 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..d124f0c996d --- /dev/null +++ b/Platform/OpenNetworkBoot/BmBoot.c @@ -0,0 +1,610 @@ +/** @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 ()) { + 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..3ca8b96fb25 --- /dev/null +++ b/Platform/OpenNetworkBoot/HttpBootCallback.c @@ -0,0 +1,279 @@ +/** @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; + + mUriValidated = FALSE; + + 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..45384d6471f --- /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->UnmanagedBootDevicePath != NULL) { + FreePool (Entry->UnmanagedBootDevicePath); + } +} + +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->UnmanagedBootDevicePath = 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..6797b792f44 --- /dev/null +++ b/Platform/OpenNetworkBoot/README.md @@ -0,0 +1,430 @@ +# 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 OVMF instructions.) + +## 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 enrties, 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. + +## 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 OVMF +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. + +#### 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 +``` + +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, OVMF's native +network boot support, compiled as above, requires the qemu option +`-device virtio-rng-pci`. For more information, see the section on RNG +Support in the Network Boot Stack, below. + +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 +``` + +### 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/TlsAuthConfigDxe.c b/Platform/OpenNetworkBoot/TlsAuthConfigDxe.c new file mode 100644 index 00000000000..ebd668df76d --- /dev/null +++ b/Platform/OpenNetworkBoot/TlsAuthConfigDxe.c @@ -0,0 +1,127 @@ +/** @file + The DriverEntryPoint for TlsAuthConfigDxe driver. + + Copyright (c) 2016 - 2018, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "TlsAuthConfigImpl.h" + +/** + Unloads an image. + + @param ImageHandle Handle that identifies the image to be unloaded. + + @retval EFI_SUCCESS The image has been unloaded. + @retval EFI_INVALID_PARAMETER ImageHandle is not a valid image handle. + +**/ +EFI_STATUS +EFIAPI +TlsAuthConfigDxeUnload ( + IN EFI_HANDLE ImageHandle + ) +{ + EFI_STATUS Status; + TLS_AUTH_CONFIG_PRIVATE_DATA *PrivateData; + + Status = gBS->HandleProtocol ( + ImageHandle, + &gEfiCallerIdGuid, + (VOID **)&PrivateData + ); + if (EFI_ERROR (Status)) { + return Status; + } + + ASSERT (PrivateData->Signature == TLS_AUTH_CONFIG_PRIVATE_DATA_SIGNATURE); + + gBS->UninstallMultipleProtocolInterfaces ( + ImageHandle, + &gEfiCallerIdGuid, + PrivateData, + NULL + ); + + TlsAuthConfigFormUnload (PrivateData); + + return EFI_SUCCESS; +} + +/** + This is the declaration of an EFI image entry point. This entry point is + the same for UEFI Applications, UEFI OS Loaders, and UEFI Drivers including + both device drivers and bus drivers. + + @param ImageHandle The firmware allocated handle for the UEFI image. + @param SystemTable A pointer to the EFI System Table. + + @retval EFI_SUCCESS The operation completed successfully. + @retval Others An unexpected error occurred. +**/ +EFI_STATUS +EFIAPI +TlsAuthConfigDxeDriverEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + + TLS_AUTH_CONFIG_PRIVATE_DATA *PrivateData; + + PrivateData = NULL; + + // + // If already started, return. + // + Status = gBS->OpenProtocol ( + ImageHandle, + &gEfiCallerIdGuid, + NULL, + ImageHandle, + ImageHandle, + EFI_OPEN_PROTOCOL_TEST_PROTOCOL + ); + if (!EFI_ERROR (Status)) { + return EFI_ALREADY_STARTED; + } + + // + // Initialize the private data structure. + // + PrivateData = AllocateZeroPool (sizeof (TLS_AUTH_CONFIG_PRIVATE_DATA)); + if (PrivateData == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Initialize the HII configuration form. + // + Status = TlsAuthConfigFormInit (PrivateData); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + // + // Install private GUID. + // + Status = gBS->InstallMultipleProtocolInterfaces ( + &ImageHandle, + &gEfiCallerIdGuid, + PrivateData, + NULL + ); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + return EFI_SUCCESS; + +ON_ERROR: + TlsAuthConfigFormUnload (PrivateData); + + return Status; +} 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..717cab9b632 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" @@ -209,10 +213,13 @@ package() { "Ps2KeyboardDxe.efi" "Ps2MouseDxe.efi" "ResetNvramEntry.efi" + "RngDxe.efi" "SnpDxe.efi" "TcpDxe.efi" + "TlsDxe.efi" "ToggleSipEntry.efi" "Udp4Dxe.efi" + "Udp6Dxe.efi" "UsbMouseDxe.efi" "XhciDxe.efi" )