/** @file Copyright (C) 2019-2022, vit9696, mikebeaton. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause **/ #include "BootManagementInternal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Expands DevicePath from short-form to full-form. The only valid expansions are full Device Paths refering to a file or a volume root. Latter type may be used with custom policies to determine a bootable file. @param[in] BootContext Context of filesystems. @param[in] DevicePath The Device Path to expand. @param[in] LazyScan Lazy filesystem scanning. @param[out] FileSystem Resulting filesystem. @param[out] IsRoot Whether DevicePath refers to the root of a volume. @returns DevicePath expansion or NULL on failure. */ STATIC EFI_DEVICE_PATH_PROTOCOL * ExpandShortFormBootPath ( IN OC_BOOT_CONTEXT *BootContext, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, IN BOOLEAN LazyScan, OUT OC_BOOT_FILESYSTEM **FileSystem, OUT BOOLEAN *IsRoot ) { EFI_STATUS Status; EFI_DEVICE_PATH_PROTOCOL *FullDevicePath; EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath; EFI_DEVICE_PATH_PROTOCOL *PrevDevicePath; EFI_HANDLE FileSystemHandle; EFI_FILE_PROTOCOL *File; EFI_FILE_INFO *FileInfo; BOOLEAN IsRootPath; BOOLEAN IsDirectory; ASSERT (BootContext != NULL); ASSERT (DevicePath != NULL); ASSERT (FileSystem != NULL); ASSERT (IsRoot != NULL); // // Iteratively expand the short-form Device Path to its possible full forms. // A valid Device Path will either refer to a valid file or to a valid root // volume. // PrevDevicePath = NULL; IsDirectory = FALSE; do { FullDevicePath = OcGetNextLoadOptionDevicePath ( DevicePath, PrevDevicePath ); if (PrevDevicePath != NULL) { FreePool (PrevDevicePath); } // // When no more full representations can be built, the Device Path is // not bootable. // if (FullDevicePath == NULL) { DEBUG ((DEBUG_INFO, "OCB: Short-form DP could not be expanded\n")); return NULL; } PrevDevicePath = FullDevicePath; DebugPrintDevicePath ( DEBUG_INFO, "OCB: Expanded DP", FullDevicePath ); // // Retrieve the filesystem handle. // RemainingDevicePath = FullDevicePath; Status = gBS->LocateDevicePath ( &gEfiSimpleFileSystemProtocolGuid, &RemainingDevicePath, &FileSystemHandle ); if (EFI_ERROR (Status)) { continue; } DebugPrintDevicePath ( DEBUG_INFO, "OCB: Expanded DP remainder", RemainingDevicePath ); // // Check whether we are allowed to boot from this filesystem. // *FileSystem = InternalFileSystemForHandle (BootContext, FileSystemHandle, LazyScan, NULL); if (*FileSystem == NULL) { continue; } // // Check whether the Device Path refers to a valid file handle. // Status = OcOpenFileByRemainingDevicePath ( FileSystemHandle, RemainingDevicePath, &File, EFI_FILE_MODE_READ, 0 ); if (EFI_ERROR (Status)) { continue; } // // Retrieve file info to determine potentially bootable state. // FileInfo = OcGetFileInfo ( File, &gEfiFileInfoGuid, sizeof (EFI_FILE_INFO), NULL ); // // When File Info cannot be retrieved, assume the worst case but don't // skip the Device Path expansion as it is valid. // IsDirectory = TRUE; if (FileInfo != NULL) { IsDirectory = (FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0; FreePool (FileInfo); } File->Close (File); // // Return only Device Paths that either refer to a file or a volume root. // Root Device Paths may be expanded by custom policies (such as Apple Boot // Policy) later. // IsRootPath = IsDevicePathEnd (RemainingDevicePath); if (IsRootPath || !IsDirectory) { ASSERT (FullDevicePath != NULL); ASSERT (*FileSystem != NULL); *IsRoot = IsDirectory; return FullDevicePath; } // // Request a new device path expansion. // } while (TRUE); } /** Check boot entry visibility by device path. @param[in] Context Picker context. @param[in] DevicePath Device path of the entry. @return Entry visibility **/ STATIC INTERNAL_ENTRY_VISIBILITY ReadEntryVisibility ( IN OC_PICKER_CONTEXT *Context, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath ) { EFI_STATUS Status; CHAR8 *Visibility; CHAR8 *VisibilityCommand; CHAR8 *Walker; UINTN IdentifierLength; // // Allow root location as well as leaf, because this is a non-Apple file which will // get deleted at update if placed next to boot.efi in macOS. // Leaf (next to bootloader) is recommended location for non-macOS. // Status = OcGetBootEntryFileFromDevicePath ( DevicePath, L".contentVisibility", "visibility", OC_MAX_CONTENT_VISIBILITY_SIZE, 0, (VOID **)&Visibility, NULL, TRUE, TRUE ); if (EFI_ERROR (Status)) { return BootEntryNormal; } // // Allow for terminating new line, but be strict about it - // after removing this, things must match exactly. // Walker = AsciiStrStr (Visibility, "\r"); if (Walker != NULL) { *Walker = '\0'; } Walker = AsciiStrStr (Visibility, "\n"); if (Walker != NULL) { *Walker = '\0'; } Walker = AsciiStrStr (Visibility, ":"); if (Walker == NULL) { VisibilityCommand = Visibility; } else { if (*(Context->InstanceIdentifier) == '\0') { DEBUG ((DEBUG_INFO, "OCB: No InstanceIdentifier, ignoring qualified visibility\n")); FreePool (Visibility); return BootEntryNormal; } *Walker++ = '\0'; VisibilityCommand = Walker; Walker = Visibility; IdentifierLength = AsciiStrLen (Context->InstanceIdentifier); Status = EFI_NOT_FOUND; do { if ( (AsciiStrnCmp (Walker, Context->InstanceIdentifier, IdentifierLength) == 0) && ((Walker[IdentifierLength] == '\0') || (Walker[IdentifierLength] == ','))) { Status = EFI_SUCCESS; break; } Walker = AsciiStrStr (Walker, ","); if (Walker != NULL) { ++Walker; } } while (Walker != NULL); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OCB: \"%a\" not present in \"%a\" ignoring visibility\n", Context->InstanceIdentifier, Visibility)); FreePool (Visibility); return BootEntryNormal; } } if (AsciiStrCmp (VisibilityCommand, "Disabled") == 0) { FreePool (Visibility); return BootEntryDisabled; } if (AsciiStrCmp (VisibilityCommand, "Auxiliary") == 0) { FreePool (Visibility); return BootEntryAuxiliary; } DEBUG ((DEBUG_INFO, "OCB: Discovered unsupported visibility \"%a\"\n", VisibilityCommand)); FreePool (Visibility); return BootEntryNormal; } /** Register bootable entry on the filesystem. @param[in,out] BootContext Context of filesystems. @param[in,out] FileSystem Filesystem for creation. @param[in] BootEntry Entry to register. **/ STATIC VOID RegisterBootOption ( IN OUT OC_BOOT_CONTEXT *BootContext, IN OUT OC_BOOT_FILESYSTEM *FileSystem, IN OC_BOOT_ENTRY *BootEntry ) { CHAR16 *TextDevicePath; DEBUG_CODE_BEGIN (); if (BootEntry->DevicePath != NULL) { TextDevicePath = ConvertDevicePathToText (BootEntry->DevicePath, FALSE, FALSE); } else { TextDevicePath = NULL; } DEBUG (( DEBUG_INFO, "OCB: Registering entry %s [%a] (T:%d|F:%d|G:%d|E:%d|B:%d) - %s\n", BootEntry->Name, BootEntry->Flavour, BootEntry->Type, BootEntry->IsFolder, BootEntry->IsGeneric, BootEntry->IsExternal, BootEntry->IsBootEntryProtocol, OC_HUMAN_STRING (TextDevicePath) )); if (TextDevicePath != NULL) { FreePool (TextDevicePath); } DEBUG_CODE_END (); // // Register boot entry. // Not using RecoveryFs is intended for correct order. // InsertTailList (&FileSystem->BootEntries, &BootEntry->Link); ++BootContext->BootEntryCount; // // If no options were previously found and this entry type // is allowed in this context then this is the default one. // if ( (BootContext->DefaultEntry == NULL) && ((BootEntry->Type & OC_BOOT_EXTERNAL_TOOL) == 0) && ( ((BootEntry->Type & OC_BOOT_SYSTEM) == 0) || (BootContext->PickerContext->PickerCommand == OcPickerProtocolHotKey) ) ) { BootContext->DefaultEntry = BootEntry; } // // For tools and system options we are done. // if ((BootEntry->Type & (OC_BOOT_EXTERNAL_TOOL | OC_BOOT_SYSTEM)) != 0) { return; } // // Set override picker commands. // if (BootContext->PickerContext->PickerCommand == OcPickerBootApple) { if ( (BootContext->DefaultEntry->Type != OC_BOOT_APPLE_OS) && (BootEntry->Type == OC_BOOT_APPLE_OS)) { BootContext->DefaultEntry = BootEntry; } } } /** Create single bootable entry from device path. @param[in,out] BootContext Context of filesystems. @param[in,out] FileSystem Filesystem for creation. @param[in] DevicePath Device path of the entry. @param[in] RecoveryPart Device path is on recovery partition. @param[in] Deduplicate Ensure that duplicated entries are not added. @retval EFI_SUCCESS on success. **/ STATIC EFI_STATUS AddBootEntryOnFileSystem ( IN OUT OC_BOOT_CONTEXT *BootContext, IN OUT OC_BOOT_FILESYSTEM *FileSystem, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, IN BOOLEAN RecoveryPart, IN BOOLEAN Deduplicate ) { EFI_STATUS Status; OC_BOOT_ENTRY *BootEntry; OC_BOOT_ENTRY_TYPE EntryType; LIST_ENTRY *Link; OC_BOOT_ENTRY *ExistingEntry; CHAR16 *TextDevicePath; INTERNAL_ENTRY_VISIBILITY Visibility; BOOLEAN IsFolder; BOOLEAN IsGeneric; BOOLEAN IsReallocated; EntryType = OcGetBootDevicePathType (DevicePath, &IsFolder, &IsGeneric); if (IsFolder && (BootContext->PickerContext->DmgLoading == OcDmgLoadingDisabled)) { DevicePath = AppendFileNameDevicePath (DevicePath, L"boot.efi"); IsFolder = FALSE; IsReallocated = TRUE; DEBUG ((DEBUG_INFO, "OCB: Switching DMG boot path to boot.efi due to policy\n")); if (DevicePath == NULL) { return EFI_OUT_OF_RESOURCES; } } else { IsReallocated = FALSE; } DEBUG_CODE_BEGIN (); TextDevicePath = ConvertDevicePathToText (DevicePath, FALSE, FALSE); DEBUG (( DEBUG_INFO, "OCB: Adding entry type (T:%u|F:%d|G:%d) - %s\n", EntryType, IsFolder, IsGeneric, OC_HUMAN_STRING (TextDevicePath) )); if (TextDevicePath != NULL) { FreePool (TextDevicePath); } DEBUG_CODE_END (); // // Mark self recovery presence. // if (!RecoveryPart && (EntryType == OC_BOOT_APPLE_RECOVERY)) { FileSystem->HasSelfRecovery = TRUE; } // // Do not add recoveries when not requested (e.g. can be HFS+ recovery). // if (BootContext->PickerContext->HideAuxiliary && (EntryType == OC_BOOT_APPLE_RECOVERY)) { DEBUG ((DEBUG_INFO, "OCB: Discarding recovery entry due to auxiliary\n")); if (IsReallocated) { FreePool (DevicePath); } return EFI_UNSUPPORTED; } // // Do not add Time Machine when not requested. // if (BootContext->PickerContext->HideAuxiliary && (EntryType == OC_BOOT_APPLE_TIME_MACHINE)) { DEBUG ((DEBUG_INFO, "OCB: Discarding time machine entry due to auxiliary\n")); if (IsReallocated) { FreePool (DevicePath); } return EFI_UNSUPPORTED; } // // Skip disabled entries, like OpenCore bootloader. // Visibility = ReadEntryVisibility (BootContext->PickerContext, DevicePath); if (Visibility == BootEntryDisabled) { DEBUG ((DEBUG_INFO, "OCB: Discarding disabled entry by visibility\n")); if (IsReallocated) { FreePool (DevicePath); } return EFI_UNSUPPORTED; } // // Skip custom auxiliary entries. // if ((Visibility == BootEntryAuxiliary) && BootContext->PickerContext->HideAuxiliary) { DEBUG ((DEBUG_INFO, "OCB: Discarding auxiliary entry by visibility\n")); if (IsReallocated) { FreePool (DevicePath); } return EFI_UNSUPPORTED; } // // Skip duplicated entries, which may happen in BootOrder. // For example, macOS during hibernation may leave Boot0082 in BootNext and Boot0080 in BootOrder, // and they will have exactly the same boot entry. // if (Deduplicate) { for ( Link = GetFirstNode (&FileSystem->BootEntries); !IsNull (&FileSystem->BootEntries, Link); Link = GetNextNode (&FileSystem->BootEntries, Link)) { ExistingEntry = BASE_CR (Link, OC_BOOT_ENTRY, Link); // // All non-custom entries have DPs. // ASSERT (ExistingEntry->DevicePath != NULL); if (IsDevicePathEqual (ExistingEntry->DevicePath, DevicePath)) { DEBUG ((DEBUG_INFO, "OCB: Discarding already present DP\n")); // // We may have more than one macOS installation in APFS container. // Boot policy returns them in a defined (constant) order, and we want // to preserve this order regardless of the BootOrder. // // When an operating system is present in BootOrder it will be put to // the front of FileSystem boot entries. As a result instead of: // [OS1], [REC1], [OS2], [REC2] we may get [OS2], [OS1], [REC1], [REC2]. // The latter happens because after [REC1] discovered [OS1] is skipped // due to being already present. The code below moves [OS2] to the end // of list at [REC1] stage to fix the order. // // This change assumes that only one operating system from the container // can be present as a boot option. For now this appears to be true. // RemoveEntryList (Link); InsertTailList (&FileSystem->BootEntries, Link); if (IsReallocated) { FreePool (DevicePath); } return EFI_ALREADY_STARTED; } } } // // Allocate, initialise, and describe boot entry. // BootEntry = AllocateZeroPool (sizeof (*BootEntry)); if (BootEntry == NULL) { if (IsReallocated) { FreePool (DevicePath); } return EFI_OUT_OF_RESOURCES; } BootEntry->DevicePath = DevicePath; BootEntry->Type = EntryType; BootEntry->IsFolder = IsFolder; BootEntry->IsGeneric = IsGeneric; BootEntry->IsExternal = RecoveryPart ? FileSystem->RecoveryFs->External : FileSystem->External; Status = InternalDescribeBootEntry (BootContext, BootEntry); if (EFI_ERROR (Status)) { FreePool (BootEntry); if (IsReallocated) { FreePool (DevicePath); } return Status; } RegisterBootOption ( BootContext, FileSystem, BootEntry ); return EFI_SUCCESS; } /** Release boot entry contents allocated from pool. @param[in,out] BootEntry Located boot entry. **/ STATIC VOID FreeBootEntry ( IN OC_BOOT_ENTRY *BootEntry ) { if (BootEntry->DevicePath != NULL) { FreePool (BootEntry->DevicePath); BootEntry->DevicePath = NULL; } if (BootEntry->Id != NULL) { FreePool (BootEntry->Id); BootEntry->Id = NULL; } if (BootEntry->Name != NULL) { FreePool (BootEntry->Name); BootEntry->Name = NULL; } if (BootEntry->PathName != NULL) { FreePool (BootEntry->PathName); BootEntry->PathName = NULL; } if (BootEntry->LoadOptions != NULL) { FreePool (BootEntry->LoadOptions); BootEntry->LoadOptions = NULL; BootEntry->LoadOptionsSize = 0; } if (BootEntry->Flavour != NULL) { FreePool (BootEntry->Flavour); BootEntry->Flavour = NULL; } FreePool (BootEntry); } /** Create bootable entry from custom entry. @param[in,out] BootContext Context of filesystems. @param[in,out] FileSystem Filesystem to add custom entry. @param[in] CustomEntry Custom entry. @retval EFI_SUCCESS on success. **/ EFI_STATUS InternalAddBootEntryFromCustomEntry ( IN OUT OC_BOOT_CONTEXT *BootContext, IN OUT OC_BOOT_FILESYSTEM *FileSystem, IN OC_PICKER_ENTRY *CustomEntry, IN BOOLEAN IsBootEntryProtocol ) { EFI_STATUS Status; OC_BOOT_ENTRY *BootEntry; CHAR16 *PathName; FILEPATH_DEVICE_PATH *FilePath; CHAR8 *ContentFlavour; CHAR16 *BootDirectoryName; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem; CONST EFI_PARTITION_ENTRY *PartitionEntry; if (CustomEntry->Auxiliary && BootContext->PickerContext->HideAuxiliary) { DEBUG (( DEBUG_INFO, "OCB: Not adding hidden auxiliary entry %a (%a|B:%d) -> %a\n", CustomEntry->Name, CustomEntry->Tool ? "tool" : "os", IsBootEntryProtocol, CustomEntry->Path )); return EFI_UNSUPPORTED; } // // Allocate, initialise, and describe boot entry. // BootEntry = AllocateZeroPool (sizeof (*BootEntry)); if (BootEntry == NULL) { return EFI_OUT_OF_RESOURCES; } BootEntry->IsExternal = FileSystem->External; BootEntry->CustomRead = CustomEntry->CustomRead; BootEntry->CustomFree = CustomEntry->CustomFree; if (CustomEntry->Id != NULL) { BootEntry->Id = AsciiStrCopyToUnicode (CustomEntry->Id, 0); if (BootEntry->Id == NULL) { FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } } ASSERT (CustomEntry->Name != NULL); BootEntry->Name = AsciiStrCopyToUnicode (CustomEntry->Name, 0); if (BootEntry->Name == NULL) { FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } if (!CustomEntry->UnmanagedBootAction && !CustomEntry->SystemAction && !CustomEntry->UnmanagedDevicePath) { ASSERT (CustomEntry->Path != NULL); PathName = AsciiStrCopyToUnicode (CustomEntry->Path, 0); if (PathName == NULL) { FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } } else { ASSERT (CustomEntry->Path == NULL); PathName = NULL; } ASSERT (CustomEntry->Flavour != NULL); BootEntry->Flavour = AllocateCopyPool (AsciiStrSize (CustomEntry->Flavour), CustomEntry->Flavour); if (BootEntry->Flavour == NULL) { FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } DEBUG (( DEBUG_INFO, "OCB: Adding custom entry %s (%a|B:%d) -> %a\n", BootEntry->Name, CustomEntry->UnmanagedBootAction != NULL ? "unmanaged" : (CustomEntry->SystemAction != NULL ? "action" : (CustomEntry->Tool ? "tool" : "os")), IsBootEntryProtocol, CustomEntry->Path )); if (CustomEntry->UnmanagedBootAction) { BootEntry->Type = OC_BOOT_UNMANAGED; BootEntry->UnmanagedBootAction = CustomEntry->UnmanagedBootAction; BootEntry->UnmanagedBootGetFinalDevicePath = CustomEntry->UnmanagedBootGetFinalDevicePath; BootEntry->AudioBasePath = CustomEntry->AudioBasePath; BootEntry->AudioBaseType = CustomEntry->AudioBaseType; BootEntry->IsExternal = CustomEntry->External; BootEntry->DevicePath = DuplicateDevicePath (CustomEntry->UnmanagedDevicePath); if (BootEntry->DevicePath == NULL) { FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } } else if (CustomEntry->SystemAction) { BootEntry->Type = OC_BOOT_SYSTEM; BootEntry->SystemAction = CustomEntry->SystemAction; BootEntry->AudioBasePath = CustomEntry->AudioBasePath; BootEntry->AudioBaseType = CustomEntry->AudioBaseType; } else if (CustomEntry->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 { BootEntry->Type = OC_BOOT_EXTERNAL_OS; // // For boot entry protocol path is relative to device root, // for user entry path is absolute device path. // if (IsBootEntryProtocol) { if (CustomEntry->UnmanagedDevicePath) { BootEntry->DevicePath = DuplicateDevicePath (CustomEntry->UnmanagedDevicePath); } else { UnicodeUefiSlashes (PathName); BootEntry->DevicePath = FileDevicePath (FileSystem->Handle, PathName); FreePool (PathName); } } else { ASSERT (CustomEntry->UnmanagedDevicePath == NULL); BootEntry->DevicePath = ConvertTextToDevicePath (PathName); FreePool (PathName); } if (BootEntry->DevicePath == NULL) { FreeBootEntry (BootEntry); return EFI_OUT_OF_RESOURCES; } FilePath = (FILEPATH_DEVICE_PATH *)( FindDevicePathNodeWithType ( BootEntry->DevicePath, MEDIA_DEVICE_PATH, MEDIA_FILEPATH_DP ) ); if (FilePath == NULL) { 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; } } // // NOTE: It is not currently necessary/useful to apply .contentDetails around here because: // a) Entries have user-specified names already. // b) OpenLinuxBoot needs to read the label file early, when allowed by picker attributes, // so it can be used for pretty name with kernel version appended when required. // If any future boot entry protocol drivers do want .contentDetails applied for them, we will need // to pass back an entry flag indicating whether .contentDetails has already been applied or not. // // // Try to get content flavour from file. // If enabled and present, .contentFlavour always overrides flavour from boot entry protocol, // but is only applied to Entries if they have flavour Auto. // if ( ((BootContext->PickerContext->PickerAttributes & OC_ATTR_USE_FLAVOUR_ICON) != 0) && (IsBootEntryProtocol || (AsciiStrCmp (BootEntry->Flavour, OC_FLAVOUR_AUTO) == 0))) { Status = OcBootPolicyDevicePathToDirPath ( BootEntry->DevicePath, &BootDirectoryName, &SimpleFileSystem ); if (!EFI_ERROR (Status)) { ContentFlavour = InternalGetContentFlavour (SimpleFileSystem, BootDirectoryName); if (ContentFlavour != NULL) { // // 'Auto' read from file means do not override. // if (AsciiStrCmp (ContentFlavour, OC_FLAVOUR_AUTO) == 0) { FreePool (ContentFlavour); } else { if (BootEntry->Flavour != NULL) { FreePool (BootEntry->Flavour); } BootEntry->Flavour = ContentFlavour; } } // // There is no need for the additional flavour fixup from BootEntryInfo.c, since type // OC_BOOT_EXTERNAL_OS does not need fixing up, and already determines our voiceover. // FreePool (BootDirectoryName); } } } BootEntry->LaunchInText = CustomEntry->TextMode; BootEntry->ExposeDevicePath = CustomEntry->RealPath; BootEntry->FullNvramAccess = CustomEntry->FullNvramAccess; if ((BootEntry->UnmanagedBootAction != NULL) || (BootEntry->SystemAction != NULL) || (CustomEntry->CustomRead != NULL)) { ASSERT (CustomEntry->Arguments == NULL); } else { ASSERT (CustomEntry->Arguments != NULL); BootEntry->LoadOptionsSize = (UINT32)AsciiStrLen (CustomEntry->Arguments); if (BootEntry->LoadOptionsSize > 0) { BootEntry->LoadOptions = AllocateCopyPool ( BootEntry->LoadOptionsSize + 1, CustomEntry->Arguments ); if (BootEntry->LoadOptions == NULL) { BootEntry->LoadOptionsSize = 0; } } } BootEntry->IsCustom = TRUE; BootEntry->IsBootEntryProtocol = IsBootEntryProtocol; if (IsBootEntryProtocol && (BootEntry->UnmanagedBootAction == NULL) && (BootEntry->SystemAction == NULL)) { PartitionEntry = OcGetGptPartitionEntry (FileSystem->Handle); if (PartitionEntry == NULL) { CopyGuid (&BootEntry->UniquePartitionGUID, &gEfiPartTypeUnusedGuid); } else { CopyGuid (&BootEntry->UniquePartitionGUID, &PartitionEntry->UniquePartitionGUID); } } RegisterBootOption ( BootContext, FileSystem, BootEntry ); return EFI_SUCCESS; } /** Create bootable entries from bless policy. This function may create more than one entry, and for APFS it will likely produce a sequence of 'OS, RECOVERY' entry pairs. @param[in,out] BootContext Context of filesystems. @param[in,out] FileSystem Filesystem to scan for bless. @param[in] PredefinedPaths The predefined boot file locations to scan. @param[in] NumPredefinedPaths The number of elements in PredefinedPaths. @param[in] LazyScan Lazy filesystem scanning. @param[in] Deduplicate Ensure that duplicated entries are not added. @retval EFI_STATUS for last created option. **/ STATIC EFI_STATUS AddBootEntryFromBless ( IN OUT OC_BOOT_CONTEXT *BootContext, IN OUT OC_BOOT_FILESYSTEM *FileSystem, IN CONST CHAR16 **PredefinedPaths, IN UINTN NumPredefinedPaths, IN BOOLEAN LazyScan, IN BOOLEAN Deduplicate ) { EFI_STATUS Status; EFI_STATUS PrimaryStatus; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFs; EFI_DEVICE_PATH_PROTOCOL *DevicePath; EFI_DEVICE_PATH_PROTOCOL *DevicePathWalker; EFI_DEVICE_PATH_PROTOCOL *NewDevicePath; UINTN NewDevicePathSize; EFI_DEVICE_PATH_PROTOCOL *HdDevicePath; UINTN HdPrefixSize; INTN CmpResult; EFI_FILE_PROTOCOL *Root; CHAR16 *RecoveryPath; EFI_FILE_PROTOCOL *RecoveryRoot; EFI_HANDLE RecoveryDeviceHandle; // // We need to ensure that blessed device paths are on the same filesystem. // Read the prefix path. // Status = gBS->HandleProtocol ( FileSystem->Handle, &gEfiDevicePathProtocolGuid, (VOID **)&HdDevicePath ); if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } DebugPrintDevicePath (DEBUG_INFO, "OCB: Adding bless entry on disk", HdDevicePath); HdPrefixSize = GetDevicePathSize (HdDevicePath) - END_DEVICE_PATH_LENGTH; // // Custom bless paths have the priority, try to look them up first. // if (BootContext->PickerContext->NumCustomBootPaths > 0) { Status = gBS->HandleProtocol ( FileSystem->Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&SimpleFs ); if (!EFI_ERROR (Status)) { Status = SimpleFs->OpenVolume (SimpleFs, &Root); if (!EFI_ERROR (Status)) { Status = OcGetBooterFromPredefinedPathList ( FileSystem->Handle, Root, (CONST CHAR16 **)BootContext->PickerContext->CustomBootPaths, BootContext->PickerContext->NumCustomBootPaths, &DevicePath, NULL ); Root->Close (Root); } } } else { Status = EFI_NOT_FOUND; } // // On failure obtain normal bless paths. // if (EFI_ERROR (Status)) { Status = OcBootPolicyGetBootFileEx ( FileSystem->Handle, PredefinedPaths, NumPredefinedPaths, &DevicePath ); } // // If both custom and normal found nothing, then nothing is blessed. // if (EFI_ERROR (Status)) { return Status; } // // Since blessed paths can be multiple (e.g. when more than one macOS is present in the container). // Status = EFI_NOT_FOUND; DevicePathWalker = DevicePath; while (TRUE) { NewDevicePath = GetNextDevicePathInstance (&DevicePathWalker, &NewDevicePathSize); if (NewDevicePath == NULL) { break; } // // Blessed path is obviously too short. // if (NewDevicePathSize - END_DEVICE_PATH_LENGTH < HdPrefixSize) { FreePool (NewDevicePath); continue; } // // Blessed path does not prefix filesystem path. // CmpResult = CompareMem ( NewDevicePath, HdDevicePath, HdPrefixSize ); if (CmpResult != 0) { DEBUG (( DEBUG_INFO, "OCB: Skipping handle %p instance due to self trust violation\n", FileSystem->Handle )); DebugPrintDevicePath ( DEBUG_INFO, "OCB: Disk DP", HdDevicePath ); DebugPrintDevicePath ( DEBUG_INFO, "OCB: Instance DP", NewDevicePath ); FreePool (NewDevicePath); continue; } // // Add blessed device path. // PrimaryStatus = AddBootEntryOnFileSystem ( BootContext, FileSystem, NewDevicePath, FALSE, Deduplicate ); // // Cannot free the failed device path now as it may have recovery. // // // If the partition contains recovery on itself or recoveries are not requested, // proceed to next entry. // // First part means that APFS recovery is irrelevant, these recoveries are actually // on a different partition, but can only be pointed from Preboot. // This way we will show any 'com.apple.recovery.boot' recovery physically present // on the partition no more than once. // if (FileSystem->HasSelfRecovery || BootContext->PickerContext->HideAuxiliary) { if (EFI_ERROR (PrimaryStatus)) { FreePool (NewDevicePath); } Status = PrimaryStatus; continue; } // // Now add APFS recovery (from Recovery partition) right afterwards if present. // Status = OcBootPolicyGetApfsRecoveryFilePath ( NewDevicePath, L"\\", PredefinedPaths, NumPredefinedPaths, &RecoveryPath, &RecoveryRoot, &RecoveryDeviceHandle ); // // Can free the failed primary device path now. // if (EFI_ERROR (PrimaryStatus)) { FreePool (NewDevicePath); } if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OCB: APFS recovery is not present - %r\n", Status)); continue; } RecoveryRoot->Close (RecoveryRoot); // // Obtain recovery file system and ensure scan policy if it was not done before. // if (FileSystem->RecoveryFs == NULL) { FileSystem->RecoveryFs = InternalFileSystemForHandle (BootContext, RecoveryDeviceHandle, LazyScan, NULL); } // // If new recovery is not on the same volume or not allowed, then something went wrong, skip it. // This is technically also a performance optimisation allowing us not to lookup recovery fs every time. // if ((FileSystem->RecoveryFs == NULL) || (FileSystem->RecoveryFs->Handle != RecoveryDeviceHandle)) { FreePool (RecoveryPath); continue; } NewDevicePath = FileDevicePath (RecoveryDeviceHandle, RecoveryPath); FreePool (RecoveryPath); if (NewDevicePath == NULL) { continue; } // // Add blessed device path. // Status = AddBootEntryOnFileSystem ( BootContext, FileSystem, NewDevicePath, TRUE, Deduplicate ); if (EFI_ERROR (Status)) { FreePool (NewDevicePath); } } FreePool (DevicePath); return Status; } /** Create bootable entries from recovery files (com.apple.recovery.boot) on the volume. @param[in,out] BootContext Context of filesystems. @param[in,out] FileSystem Filesystem to scan for recovery. @retval EFI_SUCCESS on success. **/ STATIC EFI_STATUS AddBootEntryFromSelfRecovery ( IN OUT OC_BOOT_CONTEXT *BootContext, IN OUT OC_BOOT_FILESYSTEM *FileSystem ) { EFI_STATUS Status; EFI_DEVICE_PATH_PROTOCOL *DevicePath; // // If there is already one recovery (it may not be registered due to HideAuxiliary) // or if there is HideAuxiliary, do not add recoveries at all. // if (FileSystem->HasSelfRecovery || BootContext->PickerContext->HideAuxiliary) { return EFI_UNSUPPORTED; } Status = InternalGetRecoveryOsBooter ( FileSystem->Handle, &DevicePath, FALSE ); if (EFI_ERROR (Status)) { return Status; } // // Returned device path is always on the same partition, thus no scan check. // Status = AddBootEntryOnFileSystem ( BootContext, FileSystem, DevicePath, FALSE, FALSE ); if (EFI_ERROR (Status)) { FreePool (DevicePath); } return Status; } /** Create bootable entries from boot options. @param[in,out] BootContext Context of filesystems. @param[in] BootOption Boot option number. @param[in] LazyScan Lazy filesystem scanning. @param[in,out] CustomFileSystem File system on which to add user defined custom option. If non-NULL still searching for first (normally only) OC custom entry, either user defined or entry protocol. @param[out] CustomIndex Index of custom user defined entry, if matched. @param[in] EntryProtocolHandles Installed Boot Entry Protocol handles. @param[in] EntryProtocolHandleCount Installed Boot Entry Protocol handle count. @param[out] EntryProtocolPartuuid Unique partition UUID of parition with entry protocol custom entry, if matched. @param[out] EntryProtocolId Id of entry protocol custom entry, if matched. @retval EFI_SUCCESS if at least one option was added. **/ STATIC EFI_STATUS AddBootEntryFromBootOption ( IN OUT OC_BOOT_CONTEXT *BootContext, IN UINT16 BootOption, IN BOOLEAN LazyScan, IN OUT OC_BOOT_FILESYSTEM *CustomFileSystem, OUT UINT32 *CustomIndex, OPTIONAL IN EFI_HANDLE *EntryProtocolHandles, IN UINTN EntryProtocolHandleCount, OUT EFI_GUID *EntryProtocolPartuuid, OPTIONAL OUT CHAR16 **EntryProtocolId OPTIONAL ) { EFI_STATUS Status; EFI_DEVICE_PATH_PROTOCOL *DevicePath; EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath; EFI_DEVICE_PATH_PROTOCOL *ExpandedDevicePath; EFI_HANDLE FileSystemHandle; OC_BOOT_FILESYSTEM *FileSystem; UINTN DevicePathSize; CHAR16 *TextDevicePath; INTN NumPatchedNodes; BOOLEAN IsAppleLegacy; BOOLEAN IsAppleLegacyHandled; BOOLEAN IsRoot; EFI_LOAD_OPTION *LoadOption; UINTN LoadOptionSize; UINT32 Index; INTN CmpResult; UINTN NoHandles; EFI_HANDLE *Handles; CONST EFI_PARTITION_ENTRY *PartitionEntry; CONST OC_CUSTOM_BOOT_DEVICE_PATH *CustomDevPath; CONST OC_ENTRY_PROTOCOL_DEVICE_PATH *EntryProtocolDevPath; DEBUG ((DEBUG_INFO, "OCB: Building entry from Boot%04x\n", BootOption)); // // Obtain original device path. // Discard load options for security reasons. // Also discard boot name to avoid confusion. // LoadOption = OcGetBootOptionData ( &LoadOptionSize, BootOption, BootContext->BootVariableGuid ); if (LoadOption == NULL) { return EFI_NOT_FOUND; } DevicePath = InternalGetBootOptionPath ( LoadOption, LoadOptionSize ); if (DevicePath == NULL) { FreePool (LoadOption); return EFI_NOT_FOUND; } // // Re-use the Load Option buffer for the Device Path. // CopyMem (LoadOption, DevicePath, LoadOption->FilePathListLength); DevicePath = (EFI_DEVICE_PATH_PROTOCOL *)LoadOption; // // Get BootCamp device path stored in special variable. // BootCamp device path will point to disk instead of partition. // IsAppleLegacy = InternalIsAppleLegacyLoadApp (DevicePath); if (IsAppleLegacy) { FreePool (DevicePath); Status = GetVariable2 ( APPLE_BOOT_CAMP_HD_VARIABLE_NAME, &gAppleBootVariableGuid, (VOID **)&DevicePath, &DevicePathSize ); if (EFI_ERROR (Status) || !IsDevicePathValid (DevicePath, DevicePathSize)) { DEBUG ((DEBUG_INFO, "OCB: Legacy DP invalid - %r\n", Status)); if (!EFI_ERROR (Status)) { FreePool (DevicePath); } return EFI_NOT_FOUND; } else { DebugPrintDevicePath (DEBUG_INFO, "OCB: Solved legacy DP", DevicePath); } } FileSystem = NULL; IsRoot = FALSE; // // Fixup device path if necessary. // WARN: DevicePath must be allocated from pool as it may be reallocated. // NumPatchedNodes = OcFixAppleBootDevicePath ( &DevicePath, &RemainingDevicePath ); if (NumPatchedNodes > 0) { // // DevicePath size may be different on successful update. // DevicePathSize = GetDevicePathSize (DevicePath); DebugPrintDevicePath (DEBUG_INFO, "OCB: Fixed DP", DevicePath); } // // Expand BootCamp device path to EFI partition device path. // IsAppleLegacyHandled = FALSE; if (IsAppleLegacy) { // // BootCampHD always refers to a full Device Path. Failure to patch // indicates an invalid Device Path. // if (NumPatchedNodes == -1) { DEBUG ((DEBUG_INFO, "OCB: Ignoring broken legacy DP\n")); FreePool (DevicePath); return EFI_NOT_FOUND; } // // Attempt to handle detected legacy OS via Apple legacy interface. // RemainingDevicePath = DevicePath; DevicePath = OcDiskFindActiveMbrPartitionPath ( DevicePath, &DevicePathSize, &FileSystemHandle ); // // Disk with MBR or hybrid MBR was detected. // if (DevicePath != NULL) { TextDevicePath = ConvertDevicePathToText (DevicePath, FALSE, FALSE); if (TextDevicePath != NULL) { // // Add entry from externally provided legacy interface. // Boot entry ID must be active partition Device Path. // Status = OcAddEntriesFromBootEntryProtocol ( BootContext, CustomFileSystem, EntryProtocolHandles, EntryProtocolHandleCount, TextDevicePath, TRUE, FALSE ); if (!EFI_ERROR (Status)) { if (EntryProtocolId != NULL) { *EntryProtocolId = TextDevicePath; } FileSystem = CustomFileSystem; IsAppleLegacyHandled = TRUE; } else { FreePool (TextDevicePath); } } } if (!IsAppleLegacyHandled) { // // Boot option was set to Apple legacy interface incorrectly by macOS. // This will occur on Macs that normally boot Windows in legacy mode, // but have Windows installed in UEFI mode. // // Locate the ESP from the BootCampHD Device Path instead. // DevicePath = OcDiskFindSystemPartitionPath ( RemainingDevicePath, &DevicePathSize, &FileSystemHandle ); // // Ensure that we are allowed to boot from this filesystem. // if (DevicePath != NULL) { FileSystem = InternalFileSystemForHandle (BootContext, FileSystemHandle, LazyScan, NULL); if (FileSystem == NULL) { DevicePath = NULL; } } } FreePool (RemainingDevicePath); // // This is obviously always a Root Device Path. // IsRoot = TRUE; // // The Device Path returned by OcDiskFindSystemPartitionPath() is a pointer // to an installed protocol. Duplicate it so we own the memory. // if (DevicePath != NULL) { DevicePath = AllocateCopyPool (DevicePathSize, DevicePath); } if (DevicePath == NULL) { return EFI_NOT_FOUND; } // // The Device Path must be entirely locatable (and hence full-form) as // OcDiskFindSystemPartitionPath() guarantees to only return valid paths. // ASSERT (DevicePathSize > END_DEVICE_PATH_LENGTH); DevicePathSize -= END_DEVICE_PATH_LENGTH; RemainingDevicePath = (EFI_DEVICE_PATH_PROTOCOL *)((UINTN)DevicePath + DevicePathSize); } else if (DevicePath == RemainingDevicePath) { // // OcFixAppleBootDevicePath() did not advance the Device Path node, hence // it cannot be located at all and may be a short-form Device Path. // DevicePath has not been changed no matter success or failure. // DEBUG ((DEBUG_INFO, "OCB: Assuming DP is short-form (prefix)\n")); // // Expand and on failure fix the Device Path till both yields no new result. // do { // // Expand the short-form Device Path. // ExpandedDevicePath = ExpandShortFormBootPath ( BootContext, DevicePath, LazyScan, &FileSystem, &IsRoot ); if (ExpandedDevicePath != NULL) { break; } // // If short-form expansion failed, try to fix the short-form and re-try. // WARN: DevicePath must be allocated from pool here. // NumPatchedNodes = OcFixAppleBootDevicePathNode ( &DevicePath, &RemainingDevicePath, NULL, NULL ); } while (NumPatchedNodes > 0); Status = EFI_NOT_FOUND; if ((ExpandedDevicePath == NULL) && (CustomFileSystem != NULL)) { // // If non-standard device path, attempt to pre-construct a user config // custom entry found in BOOT#### so it can be set as default. // ASSERT (CustomIndex == NULL || *CustomIndex == MAX_UINT32); CustomDevPath = InternalGetOcCustomDevPath (DevicePath); if (CustomDevPath != NULL) { for (Index = 0; Index < BootContext->PickerContext->AllCustomEntryCount; ++Index) { CmpResult = MixedStrCmp ( CustomDevPath->EntryName.PathName, BootContext->PickerContext->CustomEntries[Index].Name ); if (CmpResult == 0) { if (CustomIndex != NULL) { *CustomIndex = Index; } Status = InternalAddBootEntryFromCustomEntry ( BootContext, CustomFileSystem, &BootContext->PickerContext->CustomEntries[Index], FALSE ); break; } } } else { // // If still unknown device path, attempt to pre-construct an entry protocol // entry found in BOOT#### so it can be set as default. // ASSERT (EntryProtocolId == NULL || *EntryProtocolId == NULL); ASSERT ((EntryProtocolPartuuid == NULL) == (EntryProtocolId == NULL)); 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. // Note that on, e.g., OVMF, devices do not have PartitionEntry, therefore // the first matching entry protocol ID on any filesystem will match. // NoHandles = 0; Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &NoHandles, &Handles ); if (!EFI_ERROR (Status)) { for (Index = 0; Index < NoHandles; ++Index) { PartitionEntry = OcGetGptPartitionEntry (Handles[Index]); if (CompareGuid ( (PartitionEntry == NULL) ? &gEfiPartTypeUnusedGuid : &PartitionEntry->UniquePartitionGUID, &EntryProtocolDevPath->Partuuid ) ) { FileSystem = InternalFileSystemForHandle (BootContext, Handles[Index], TRUE, NULL); if (FileSystem == NULL) { continue; } Status = OcAddEntriesFromBootEntryProtocol ( BootContext, FileSystem, EntryProtocolHandles, EntryProtocolHandleCount, EntryProtocolDevPath->EntryName.PathName, TRUE, FALSE ); if (!EFI_ERROR (Status)) { if (EntryProtocolPartuuid != NULL) { if (PartitionEntry == NULL) { CopyGuid (EntryProtocolPartuuid, &gEfiPartTypeUnusedGuid); } else { CopyGuid (EntryProtocolPartuuid, &PartitionEntry->UniquePartitionGUID); } } if (EntryProtocolId != NULL) { *EntryProtocolId = AllocateCopyPool (StrSize (EntryProtocolDevPath->EntryName.PathName), EntryProtocolDevPath->EntryName.PathName); // // If NULL allocated, just continue as if we had not matched. // } break; } } } FreePool (Handles); } } } } FreePool (DevicePath); DevicePath = ExpandedDevicePath; if (DevicePath == NULL) { return Status; } } else if (NumPatchedNodes == -1) { // // OcFixAppleBootDevicePath() advanced the Device Path node and yet failed // to locate the path, it is invalid. // DEBUG ((DEBUG_INFO, "OCB: Ignoring broken normal DP\n")); FreePool (DevicePath); return EFI_NOT_FOUND; } else { // // OcFixAppleBootDevicePath() advanced the Device Path node and succeeded // to locate the path, but it may still be a shot-form Device Path (lacking // a suffix rather than prefix). // DEBUG ((DEBUG_INFO, "OCB: Assuming DP is full-form or lacks suffix\n")); RemainingDevicePath = DevicePath; DevicePath = ExpandShortFormBootPath ( BootContext, RemainingDevicePath, LazyScan, &FileSystem, &IsRoot ); FreePool (RemainingDevicePath); if (DevicePath == NULL) { return EFI_NOT_FOUND; } } // // If we reached here we have a filesystem and device path. // ASSERT (FileSystem != NULL); ASSERT (DevicePath != NULL); // // We have a complete device path, just add this entry. // if (!IsRoot) { Status = AddBootEntryOnFileSystem ( BootContext, FileSystem, DevicePath, FALSE, TRUE ); } else { Status = EFI_UNSUPPORTED; } if (EFI_ERROR (Status)) { FreePool (DevicePath); } if (IsAppleLegacyHandled) { return EFI_SUCCESS; } // // We may have a Boot#### entry pointing to macOS with full DP (up to boot.efi), // so IsRoot will be true. However, if this is APFS, we may still have: // - Recovery for this macOS. // - Another macOS installation. // We can only detect them with bless, so we invoke bless in deduplication mode. // We also detect only the Core Apple Boot Policy predefined booter paths to // avoid detection of e.g. generic booters (such as BOOTx64) to avoid // duplicates. // // The amount of paths depends on the kind of the entry. // - If this is a root entry (i.e. it points to the partition) // we invoke full bless, as it may be Windows entry created by legacy NVRAM script. // - If this is a full entry (i.e. it points to the bootloader) // we invoke partial bless, which ignores BOOTx64.efi. // Ignoring BOOTx64.efi is important as we may already have bootmgfw.efi as our entry, // and we do not want to see Windows added twice. // Status = AddBootEntryFromBless ( BootContext, FileSystem, gAppleBootPolicyPredefinedPaths, IsRoot ? gAppleBootPolicyNumPredefinedPaths : gAppleBootPolicyCoreNumPredefinedPaths, LazyScan, TRUE ); return Status; } /** Allocate a new filesystem entry in boot entries in case it can be used according to current ScanPolicy. @param[in,out] BootContext Context of filesystems. @param[in] FileSystemHandle Filesystem handle. @param[in] FileSystemEntry Resulting filesystem, optional. @retval EFI_SUCCESS on success. **/ STATIC EFI_STATUS AddFileSystemEntry ( IN OUT OC_BOOT_CONTEXT *BootContext, IN EFI_HANDLE FileSystemHandle, OUT OC_BOOT_FILESYSTEM **FileSystemEntry OPTIONAL ) { EFI_STATUS Status; EFI_STATUS TmpStatus; BOOLEAN IsExternal; BOOLEAN LoaderFs; OC_BOOT_FILESYSTEM *Entry; EFI_DEVICE_PATH_PROTOCOL *DevicePath; CHAR16 *TextDevicePath; Status = InternalCheckScanPolicy ( FileSystemHandle, BootContext->PickerContext->ScanPolicy, &IsExternal ); LoaderFs = BootContext->PickerContext->LoaderHandle == FileSystemHandle; DEBUG_CODE_BEGIN (); TmpStatus = gBS->HandleProtocol ( FileSystemHandle, &gEfiDevicePathProtocolGuid, (VOID **)&DevicePath ); if (!EFI_ERROR (TmpStatus)) { TextDevicePath = ConvertDevicePathToText (DevicePath, FALSE, FALSE); } else { TextDevicePath = NULL; } DEBUG (( DEBUG_INFO, "OCB: Adding fs %p (E:%d|L:%d|P:%r) - %s\n", FileSystemHandle, IsExternal, LoaderFs, Status, OC_HUMAN_STRING (TextDevicePath) )); if (TextDevicePath != NULL) { FreePool (TextDevicePath); } DEBUG_CODE_END (); if (EFI_ERROR (Status)) { return Status; } Entry = AllocatePool (sizeof (*Entry)); if (Entry == NULL) { return EFI_OUT_OF_RESOURCES; } Entry->Handle = FileSystemHandle; InitializeListHead (&Entry->BootEntries); Entry->RecoveryFs = NULL; Entry->External = IsExternal; Entry->LoaderFs = LoaderFs; Entry->HasSelfRecovery = FALSE; InsertTailList (&BootContext->FileSystems, &Entry->Link); ++BootContext->FileSystemCount; if (FileSystemEntry != NULL) { *FileSystemEntry = Entry; } return EFI_SUCCESS; } STATIC OC_BOOT_FILESYSTEM * CreateFileSystemForCustom ( IN OUT CONST OC_BOOT_CONTEXT *BootContext ) { OC_BOOT_FILESYSTEM *FileSystem; FileSystem = AllocateZeroPool (sizeof (*FileSystem)); if (FileSystem == NULL) { return NULL; } FileSystem->Handle = OC_CUSTOM_FS_HANDLE; InitializeListHead (&FileSystem->BootEntries); DEBUG (( DEBUG_INFO, "OCB: Adding fs %p for %u custom entries and BEP%a\n", OC_CUSTOM_FS_HANDLE, BootContext->PickerContext->AllCustomEntryCount, BootContext->PickerContext->HideAuxiliary ? " (aux hidden)" : " (aux shown)" )); return FileSystem; } // // @retval EFI_SUCCESS One or more entries added. // @retval EFI_NOT_FOUND No entries added. // STATIC EFI_STATUS AddFileSystemEntryForCustom ( IN OUT OC_BOOT_CONTEXT *BootContext, IN OUT OC_BOOT_FILESYSTEM *FileSystem, IN UINT32 PrecreatedCustomIndex ) { EFI_STATUS ReturnStatus; EFI_STATUS Status; UINTN Index; ReturnStatus = EFI_NOT_FOUND; for (Index = 0; Index < BootContext->PickerContext->AllCustomEntryCount; ++Index) { // // Skip the custom boot entry that has already been created. // if (Index == PrecreatedCustomIndex) { continue; } Status = InternalAddBootEntryFromCustomEntry ( BootContext, FileSystem, &BootContext->PickerContext->CustomEntries[Index], FALSE ); if (!EFI_ERROR (Status)) { ReturnStatus = EFI_SUCCESS; } } return ReturnStatus; } STATIC VOID FreeFileSystemEntry ( IN OUT OC_BOOT_CONTEXT *BootContext, IN OC_BOOT_FILESYSTEM *FileSystemEntry ) { LIST_ENTRY *Link; OC_BOOT_ENTRY *BootEntry; RemoveEntryList (&FileSystemEntry->Link); --BootContext->FileSystemCount; while (!IsListEmpty (&FileSystemEntry->BootEntries)) { Link = GetFirstNode (&FileSystemEntry->BootEntries); BootEntry = BASE_CR (Link, OC_BOOT_ENTRY, Link); RemoveEntryList (Link); FreeBootEntry (BootEntry); } FreePool (FileSystemEntry); } OC_BOOT_FILESYSTEM * InternalFileSystemForHandle ( IN OC_BOOT_CONTEXT *BootContext, IN EFI_HANDLE FileSystemHandle, IN BOOLEAN LazyScan, OUT BOOLEAN *AlreadySeen OPTIONAL ) { EFI_STATUS Status; LIST_ENTRY *Link; OC_BOOT_FILESYSTEM *FileSystem; if (AlreadySeen != NULL) { *AlreadySeen = FALSE; } for ( Link = GetFirstNode (&BootContext->FileSystems); !IsNull (&BootContext->FileSystems, Link); Link = GetNextNode (&BootContext->FileSystems, Link)) { FileSystem = BASE_CR (Link, OC_BOOT_FILESYSTEM, Link); if (FileSystem->Handle == FileSystemHandle) { DEBUG ((DEBUG_INFO, "OCB: Matched fs %p%a\n", FileSystemHandle, LazyScan ? " (lazy)" : "")); if (AlreadySeen != NULL) { *AlreadySeen = TRUE; } return FileSystem; } } // // Lazily check filesystem scan policy and add it in case it is ok. // if (!LazyScan) { DEBUG ((DEBUG_INFO, "OCB: Restricted fs %p access\n", FileSystemHandle)); return NULL; } Status = AddFileSystemEntry (BootContext, FileSystemHandle, &FileSystem); if (!EFI_ERROR (Status)) { return FileSystem; } return NULL; } STATIC OC_BOOT_CONTEXT * BuildFileSystemList ( IN OC_PICKER_CONTEXT *Context, IN BOOLEAN Empty ) { OC_BOOT_CONTEXT *BootContext; EFI_STATUS Status; UINTN NoHandles; EFI_HANDLE *Handles; UINTN Index; BootContext = AllocatePool (sizeof (*BootContext)); if (BootContext == NULL) { return NULL; } BootContext->BootEntryCount = 0; BootContext->FileSystemCount = 0; InitializeListHead (&BootContext->FileSystems); if (Context->CustomBootGuid) { BootContext->BootVariableGuid = &gOcVendorVariableGuid; } else { BootContext->BootVariableGuid = &gEfiGlobalVariableGuid; } BootContext->DefaultEntry = NULL; BootContext->PickerContext = Context; if (Empty) { return BootContext; } Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &NoHandles, &Handles ); if (EFI_ERROR (Status)) { return BootContext; } for (Index = 0; Index < NoHandles; ++Index) { AddFileSystemEntry ( BootContext, Handles[Index], NULL ); } FreePool (Handles); return BootContext; } VOID OcFreeBootContext ( IN OUT OC_BOOT_CONTEXT *Context ) { LIST_ENTRY *Link; OC_BOOT_FILESYSTEM *FileSystem; while (!IsListEmpty (&Context->FileSystems)) { Link = GetFirstNode (&Context->FileSystems); FileSystem = BASE_CR (Link, OC_BOOT_FILESYSTEM, Link); FreeFileSystemEntry (Context, FileSystem); } FreePool (Context); } EFI_STATUS OcSetDefaultBootRecovery ( IN OUT OC_BOOT_CONTEXT *BootContext ) { LIST_ENTRY *FsLink; OC_BOOT_FILESYSTEM *FileSystem; LIST_ENTRY *EnLink; OC_BOOT_ENTRY *BootEntry; OC_BOOT_ENTRY *FirstRecovery; OC_BOOT_ENTRY *RecoveryInitiator; BOOLEAN UseInitiator; FirstRecovery = NULL; UseInitiator = BootContext->PickerContext->RecoveryInitiator != NULL; // // This could technically use AppleBootPolicy recovery getting function, // but it will do extra disk i/o and will not work with HFS+ recovery. // for ( FsLink = GetFirstNode (&BootContext->FileSystems); !IsNull (&BootContext->FileSystems, FsLink); FsLink = GetNextNode (&BootContext->FileSystems, FsLink)) { FileSystem = BASE_CR (FsLink, OC_BOOT_FILESYSTEM, Link); RecoveryInitiator = NULL; for ( EnLink = GetFirstNode (&FileSystem->BootEntries); !IsNull (&FileSystem->BootEntries, EnLink); EnLink = GetNextNode (&FileSystem->BootEntries, EnLink)) { BootEntry = BASE_CR (EnLink, OC_BOOT_ENTRY, Link); // // Record first found recovery in case we find nothing. // if ((FirstRecovery == NULL) && (BootEntry->Type == OC_BOOT_APPLE_RECOVERY)) { FirstRecovery = BootEntry; ASSERT (BootEntry->DevicePath != NULL); if (!UseInitiator) { DebugPrintDevicePath (DEBUG_INFO, "OCB: Using first recovery path", BootEntry->DevicePath); BootContext->DefaultEntry = FirstRecovery; return EFI_SUCCESS; } else { DebugPrintDevicePath (DEBUG_INFO, "OCB: Storing first recovery path", BootEntry->DevicePath); } } if ((RecoveryInitiator != NULL) && (BootEntry->Type == OC_BOOT_APPLE_RECOVERY)) { DebugPrintDevicePath (DEBUG_INFO, "OCB: Using initiator recovery path", BootEntry->DevicePath); BootContext->DefaultEntry = BootEntry; return EFI_SUCCESS; } if ( (BootEntry->Type == OC_BOOT_APPLE_OS) && UseInitiator && IsDevicePathEqual ( BootContext->PickerContext->RecoveryInitiator, BootEntry->DevicePath )) { DebugPrintDevicePath (DEBUG_INFO, "OCB: Found initiator", BootEntry->DevicePath); RecoveryInitiator = BootEntry; } } if (RecoveryInitiator != NULL) { if (FirstRecovery != NULL) { DEBUG ((DEBUG_INFO, "OCB: Using first recovery path for no initiator")); BootContext->DefaultEntry = FirstRecovery; return EFI_SUCCESS; } DEBUG ((DEBUG_INFO, "OCB: Looking for any first recovery due to no initiator")); UseInitiator = FALSE; } } return EFI_NOT_FOUND; } OC_BOOT_CONTEXT * OcScanForBootEntries ( IN OC_PICKER_CONTEXT *Context ) { OC_BOOT_CONTEXT *BootContext; UINTN Index; LIST_ENTRY *Link; OC_BOOT_FILESYSTEM *FileSystem; OC_BOOT_FILESYSTEM *CustomFileSystem; OC_BOOT_FILESYSTEM *CustomFileSystemDefault; UINT32 DefaultCustomIndex; ///< Index if Tools or Entries item is pre-created CHAR16 *DefaultEntryId; ///< ID if boot entry protocol item is pre-created EFI_GUID DefaultEntryPartuuid; ///< PARTUUID for pre-created boot entry protocol item BOOLEAN IsDefaultEntryProtocolPartition; EFI_HANDLE *EntryProtocolHandles; UINTN EntryProtocolHandleCount; CONST EFI_PARTITION_ENTRY *PartitionEntry; // // Obtain the list of filesystems filtered by scan policy. // BootContext = BuildFileSystemList ( Context, FALSE ); if (BootContext == NULL) { return NULL; } DEBUG ((DEBUG_INFO, "OCB: Found %u potentially bootable filesystems\n", (UINT32)BootContext->FileSystemCount)); // // Locate loaded boot entry protocol drivers. // OcLocateBootEntryProtocolHandles (&EntryProtocolHandles, &EntryProtocolHandleCount); // // Create primary boot options from BootOrder. // if (Context->BootOrder == NULL) { Context->BootOrder = InternalGetBootOrderForBooting ( BootContext->BootVariableGuid, Context->BlacklistAppleUpdate, &Context->BootOrderCount, FALSE ); } CustomFileSystem = CreateFileSystemForCustom (BootContext); // // Delay CustomFileSystem insertion to have custom entries at the end. // DefaultCustomIndex = MAX_UINT32; DefaultEntryId = NULL; if (Context->BootOrder != NULL) { CustomFileSystemDefault = CustomFileSystem; for (Index = 0; Index < Context->BootOrderCount; ++Index) { AddBootEntryFromBootOption ( BootContext, Context->BootOrder[Index], FALSE, CustomFileSystemDefault, &DefaultCustomIndex, EntryProtocolHandles, EntryProtocolHandleCount, &DefaultEntryPartuuid, &DefaultEntryId ); // // Pre-create at most one custom entry. Under normal circumstances, no // more than one should exist as a boot option anyway. // if ((DefaultCustomIndex != MAX_UINT32) || (DefaultEntryId != NULL)) { CustomFileSystemDefault = NULL; } } } DEBUG ((DEBUG_INFO, "OCB: Processing blessed list\n")); // // Create primary boot options on filesystems without options // and alternate boot options on all filesystems. // for ( Link = GetFirstNode (&BootContext->FileSystems); !IsNull (&BootContext->FileSystems, Link); Link = GetNextNode (&BootContext->FileSystems, Link)) { FileSystem = BASE_CR (Link, OC_BOOT_FILESYSTEM, Link); PartitionEntry = OcGetGptPartitionEntry (FileSystem->Handle); IsDefaultEntryProtocolPartition = ( (DefaultEntryId != NULL) && CompareGuid ( &DefaultEntryPartuuid, (PartitionEntry == NULL) ? &gEfiPartTypeUnusedGuid : &PartitionEntry->UniquePartitionGUID ) ); // // No entries, or only entry pre-created from boot entry protocol, // so process this directory with Apple Bless. // if (IsDefaultEntryProtocolPartition || IsListEmpty (&FileSystem->BootEntries)) { AddBootEntryFromBless ( BootContext, FileSystem, gAppleBootPolicyPredefinedPaths, gAppleBootPolicyNumPredefinedPaths, FALSE, FALSE ); } // // Try boot entry protocol. // Entry protocol entries are added regardless of bless; e.g. user might well // have /loader/entries in ESP, in addition to normal blessed files. // Skip any entry already created from boot options. // OcAddEntriesFromBootEntryProtocol ( BootContext, FileSystem, EntryProtocolHandles, EntryProtocolHandleCount, IsDefaultEntryProtocolPartition ? DefaultEntryId : NULL, FALSE, FALSE ); // // Record predefined recoveries. // AddBootEntryFromSelfRecovery (BootContext, FileSystem); } if (CustomFileSystem != NULL) { // // Insert the custom file system last for entry order. // InsertTailList (&BootContext->FileSystems, &CustomFileSystem->Link); ++BootContext->FileSystemCount; // // Build custom and system options. // AddFileSystemEntryForCustom (BootContext, CustomFileSystem, DefaultCustomIndex); // // Boot entry protocol also supports custom and system entries. // OcAddEntriesFromBootEntryProtocol ( BootContext, CustomFileSystem, EntryProtocolHandles, EntryProtocolHandleCount, DefaultEntryId, FALSE, FALSE ); } if (DefaultEntryId != NULL) { FreePool (DefaultEntryId); DefaultEntryId = NULL; } OcFreeBootEntryProtocolHandles (&EntryProtocolHandles); if (BootContext->BootEntryCount == 0) { OcFreeBootContext (BootContext); return NULL; } // // Find recovery. // if (BootContext->PickerContext->PickerCommand == OcPickerBootAppleRecovery) { OcSetDefaultBootRecovery (BootContext); } return BootContext; } OC_BOOT_CONTEXT * OcScanForDefaultBootEntry ( IN OC_PICKER_CONTEXT *Context, IN BOOLEAN UseBootNextOnly ) { OC_BOOT_CONTEXT *BootContext; UINTN Index; OC_BOOT_FILESYSTEM *FileSystem; BOOLEAN AlreadySeen; EFI_STATUS Status; UINTN NoHandles; EFI_HANDLE *Handles; OC_BOOT_FILESYSTEM *CustomFileSystem; EFI_HANDLE *EntryProtocolHandles; UINTN EntryProtocolHandleCount; // // Obtain empty list of filesystems. // BootContext = BuildFileSystemList (Context, TRUE); if (BootContext == NULL) { return NULL; } CustomFileSystem = CreateFileSystemForCustom (BootContext); if (CustomFileSystem != NULL) { // // The entry order does not matter, UI will not be shown. // InsertTailList (&BootContext->FileSystems, &CustomFileSystem->Link); ++BootContext->FileSystemCount; } DEBUG ((DEBUG_INFO, "OCB: Looking for default entry (%d:%a)\n", Context->PickerCommand, Context->HotKeyEntryId)); if (Context->PickerCommand != OcPickerProtocolHotKey) { // // Locate loaded boot entry protocol drivers. // OcLocateBootEntryProtocolHandles (&EntryProtocolHandles, &EntryProtocolHandleCount); // // Create primary boot options from BootOrder. // if (Context->BootOrder == NULL) { Context->BootOrder = InternalGetBootOrderForBooting ( BootContext->BootVariableGuid, Context->BlacklistAppleUpdate, &Context->BootOrderCount, UseBootNextOnly ); } if (Context->BootOrder != NULL) { for (Index = 0; Index < Context->BootOrderCount; ++Index) { // // Returned default entry values not required, as no other // entries will be created after a match here. // AddBootEntryFromBootOption ( BootContext, Context->BootOrder[Index], TRUE, CustomFileSystem, NULL, EntryProtocolHandles, EntryProtocolHandleCount, NULL, NULL ); // // Return as long as we are good. // if (BootContext->DefaultEntry != NULL) { OcFreeBootEntryProtocolHandles (&EntryProtocolHandles); return BootContext; } } } if (UseBootNextOnly) { OcFreeBootEntryProtocolHandles (&EntryProtocolHandles); OcFreeBootContext (BootContext); return NULL; } // // Obtain filesystems and try processing those remaining. // NoHandles = 0; Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &NoHandles, &Handles ); DEBUG ((DEBUG_INFO, "OCB: Processing %u blessed list - %r\n", (UINT32)NoHandles, Status)); if (!EFI_ERROR (Status)) { for (Index = 0; Index < NoHandles; ++Index) { // // If file system has been seen during BOOT#### entry processing then // bless has already been processed (and failed or we would not be here). // FileSystem = InternalFileSystemForHandle (BootContext, Handles[Index], TRUE, &AlreadySeen); if (FileSystem == NULL) { continue; } if (!AlreadySeen) { AddBootEntryFromBless ( BootContext, FileSystem, gAppleBootPolicyPredefinedPaths, gAppleBootPolicyNumPredefinedPaths, FALSE, FALSE ); if (BootContext->DefaultEntry != NULL) { OcFreeBootEntryProtocolHandles (&EntryProtocolHandles); FreePool (Handles); return BootContext; } } // // Try boot entry protocol. No need to deduplicate as won't reach // here if default entry from BOOT#### was successfully created. // OcAddEntriesFromBootEntryProtocol ( BootContext, FileSystem, EntryProtocolHandles, EntryProtocolHandleCount, NULL, FALSE, FALSE ); if (BootContext->DefaultEntry != NULL) { OcFreeBootEntryProtocolHandles (&EntryProtocolHandles); FreePool (Handles); return BootContext; } AddBootEntryFromSelfRecovery (BootContext, FileSystem); if (BootContext->DefaultEntry != NULL) { OcFreeBootEntryProtocolHandles (&EntryProtocolHandles); FreePool (Handles); return BootContext; } } FreePool (Handles); } if (CustomFileSystem != NULL) { // // Build custom and system options. Do not try to deduplicate custom options // as the list is never shown. // AddFileSystemEntryForCustom (BootContext, CustomFileSystem, MAX_UINT32); if (BootContext->DefaultEntry != NULL) { OcFreeBootEntryProtocolHandles (&EntryProtocolHandles); return BootContext; } // // Boot entry protocol for custom and system entries. // OcAddEntriesFromBootEntryProtocol ( BootContext, CustomFileSystem, EntryProtocolHandles, EntryProtocolHandleCount, NULL, FALSE, FALSE ); } OcFreeBootEntryProtocolHandles (&EntryProtocolHandles); } else { // // Filter boot entry protocol entries from selected protocol instance only for hotkey entry. // Status = OcAddEntriesFromBootEntryProtocol ( BootContext, CustomFileSystem, &Context->HotKeyProtocolHandle, 1, Context->HotKeyEntryId, TRUE, TRUE ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "OCB: Missing boot entry protocol entry for hotkey %a - %r\n", Context->HotKeyEntryId, Status)); } } if (BootContext->DefaultEntry == NULL) { OcFreeBootContext (BootContext); return NULL; } ASSERT (BootContext->BootEntryCount > 0); return BootContext; } OC_BOOT_ENTRY ** OcEnumerateEntries ( IN OC_BOOT_CONTEXT *BootContext ) { OC_BOOT_ENTRY **Entries; UINT32 EntryIndex; LIST_ENTRY *FsLink; OC_BOOT_FILESYSTEM *FileSystem; LIST_ENTRY *EnLink; OC_BOOT_ENTRY *BootEntry; Entries = AllocatePool (sizeof (*Entries) * BootContext->BootEntryCount); if (Entries == NULL) { return NULL; } EntryIndex = 0; for ( FsLink = GetFirstNode (&BootContext->FileSystems); !IsNull (&BootContext->FileSystems, FsLink); FsLink = GetNextNode (&BootContext->FileSystems, FsLink)) { FileSystem = BASE_CR (FsLink, OC_BOOT_FILESYSTEM, Link); for ( EnLink = GetFirstNode (&FileSystem->BootEntries); !IsNull (&FileSystem->BootEntries, EnLink); EnLink = GetNextNode (&FileSystem->BootEntries, EnLink)) { BootEntry = BASE_CR (EnLink, OC_BOOT_ENTRY, Link); ASSERT (EntryIndex < BootContext->BootEntryCount); Entries[EntryIndex] = BootEntry; BootEntry->EntryIndex = ++EntryIndex; } } ASSERT (EntryIndex == BootContext->BootEntryCount); ASSERT (BootContext->DefaultEntry == NULL || BootContext->DefaultEntry->EntryIndex > 0); return Entries; } EFI_STATUS OcLoadBootEntry ( IN OC_PICKER_CONTEXT *Context, IN OC_BOOT_ENTRY *BootEntry, IN EFI_HANDLE ParentHandle ) { EFI_STATUS Status; EFI_HANDLE EntryHandle; INTERNAL_DMG_LOAD_CONTEXT DmgLoadContext; VOID *CustomFreeContext; if ((BootEntry->Type & OC_BOOT_UNMANAGED) != 0) { ASSERT (BootEntry->UnmanagedBootAction != NULL); return BootEntry->UnmanagedBootAction (Context, BootEntry->DevicePath); } if ((BootEntry->Type & OC_BOOT_SYSTEM) != 0) { ASSERT (BootEntry->SystemAction != NULL); return BootEntry->SystemAction (Context); } Status = InternalLoadBootEntry ( Context, BootEntry, ParentHandle, &EntryHandle, &DmgLoadContext, &CustomFreeContext ); if (!EFI_ERROR (Status)) { // // This does nothing unless emulated NVRAM is present. A hack, basically, to allow us // to switch back to the normal macOS boot entry after booting a macOS Installer once, // because we have nothing available to correctly update the emulated NVRAM file while // the macOS installer is running and rebooting. This strategy is correct, often, and // better then the alternative (continuing to create an installer entry when it no longer // exists) in any event. See OpenVariableRuntimeDxe documentation for more details. // if (BootEntry->IsAppleInstaller) { OcSwitchToFallbackLegacyNvram (); } Status = Context->StartImage (BootEntry, EntryHandle, NULL, NULL, BootEntry->LaunchInText); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "OCB: StartImage failed - %r\n", Status)); // // 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)); } return Status; }