mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
524 lines
12 KiB
C
524 lines
12 KiB
C
/** @file
|
|
Copyright (C) 2018, vit9696. All rights reserved.
|
|
Copyright (C) 2020, PMheart. All rights reserved.
|
|
|
|
All rights reserved.
|
|
|
|
This program and the accompanying materials
|
|
are licensed and made available under the terms and conditions of the BSD License
|
|
which accompanies this distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
**/
|
|
|
|
#include "ocvalidate.h"
|
|
#include "OcValidateLib.h"
|
|
|
|
INT64
|
|
GetCurrentTimestamp (
|
|
VOID
|
|
)
|
|
{
|
|
struct timeval Time;
|
|
|
|
//
|
|
// Get current time.
|
|
//
|
|
gettimeofday (&Time, NULL);
|
|
//
|
|
// Return milliseconds.
|
|
//
|
|
return Time.tv_sec * 1000LL + Time.tv_usec / 1000LL;
|
|
}
|
|
|
|
BOOLEAN
|
|
AsciiFileSystemPathIsLegal (
|
|
IN CONST CHAR8 *Path
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN PathLength;
|
|
|
|
PathLength = AsciiStrLen (Path);
|
|
|
|
for (Index = 0; Index < PathLength; ++Index) {
|
|
//
|
|
// Skip allowed characters (0-9, A-Z, a-z, '_', '-', '.', '/', and '\').
|
|
//
|
|
if ( IsAsciiNumber (Path[Index])
|
|
|| IsAsciiAlpha (Path[Index])
|
|
|| (Path[Index] == '_')
|
|
|| (Path[Index] == '-')
|
|
|| (Path[Index] == '.')
|
|
|| (Path[Index] == '/')
|
|
|| (Path[Index] == '\\'))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Disallowed characters matched.
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
AsciiCommentIsLegal (
|
|
IN CONST CHAR8 *Comment
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN CommentLength;
|
|
|
|
CommentLength = AsciiStrLen (Comment);
|
|
|
|
for (Index = 0; Index < CommentLength; ++Index) {
|
|
//
|
|
// Unprintable characters matched.
|
|
//
|
|
if (IsAsciiPrint (Comment[Index]) == 0) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
AsciiIdentifierIsLegal (
|
|
IN CONST CHAR8 *Identifier,
|
|
IN BOOLEAN IsKernelIdentifier
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN IdentifierLength;
|
|
|
|
if (IsKernelIdentifier) {
|
|
//
|
|
// Kernel patch only requires Kernel->Patch->Identifier set to kernel.
|
|
//
|
|
if (AsciiStrCmp (Identifier, "kernel") == 0) {
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
//
|
|
// Any and Apple are two fixed values accepted by Booter->Patch.
|
|
// TODO: Drop empty string support in OC.
|
|
//
|
|
if ( (AsciiStrCmp (Identifier, "Any") == 0)
|
|
|| (AsciiStrCmp (Identifier, "Apple") == 0))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// For customised bootloader, it must have .efi suffix.
|
|
//
|
|
if (!OcAsciiEndsWith (Identifier, ".efi", TRUE)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// There must be a dot for a sane Identifier.
|
|
//
|
|
if (OcAsciiStrChr (Identifier, '.') == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
IdentifierLength = AsciiStrLen (Identifier);
|
|
|
|
for (Index = 0; Index < IdentifierLength; ++Index) {
|
|
//
|
|
// Skip allowed characters (0-9, A-Z, a-z, '_', '-', and '.').
|
|
// FIXME: Discuss what exactly is legal for identifiers, or update the allowed list on request.
|
|
//
|
|
if ( IsAsciiNumber (Identifier[Index])
|
|
|| IsAsciiAlpha (Identifier[Index])
|
|
|| (Identifier[Index] == '_')
|
|
|| (Identifier[Index] == '-')
|
|
|| (Identifier[Index] == '.'))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Disallowed characters matched.
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
AsciiArchIsLegal (
|
|
IN CONST CHAR8 *Arch,
|
|
IN BOOLEAN IsKernelArch
|
|
)
|
|
{
|
|
//
|
|
// Special mode for Kernel->Scheme->KernelArch.
|
|
//
|
|
if (IsKernelArch) {
|
|
//
|
|
// Auto and i386-user32 are two special values allowed in KernelArch.
|
|
//
|
|
if ( (AsciiStrCmp (Arch, "Auto") == 0)
|
|
|| (AsciiStrCmp (Arch, "i386-user32") == 0))
|
|
{
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
//
|
|
// Any is only allowed in non-KernelArch mode.
|
|
//
|
|
if (AsciiStrCmp (Arch, "Any") == 0) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// i386 and x86_64 are allowed in both modes.
|
|
// TODO: Do not allow empty string in OC.
|
|
//
|
|
if ( (AsciiStrCmp (Arch, "i386") != 0)
|
|
&& (AsciiStrCmp (Arch, "x86_64") != 0))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
AsciiPropertyIsLegal (
|
|
IN CONST CHAR8 *Property
|
|
)
|
|
{
|
|
//
|
|
// Like comments, properties can be anything printable.
|
|
// Calling sanitiser for comments to reduce code duplication.
|
|
//
|
|
return AsciiCommentIsLegal (Property);
|
|
}
|
|
|
|
BOOLEAN
|
|
AsciiUefiDriverIsLegal (
|
|
IN CONST CHAR8 *Driver,
|
|
IN CONST UINTN DriverIndex
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN DriverLength;
|
|
|
|
DriverLength = AsciiStrLen (Driver);
|
|
if (DriverLength == 0) {
|
|
DEBUG ((DEBUG_WARN, "UEFI->Drivers[%u].Path value is missing!\n", DriverIndex));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If an EFI driver does not have .efi suffix,
|
|
// then it must be illegal.
|
|
//
|
|
if (!OcAsciiEndsWith (Driver, ".efi", TRUE)) {
|
|
DEBUG ((DEBUG_WARN, "UEFI->Drivers[%u].Path does not end with \"%a\"!\n", DriverIndex, ".efi"));
|
|
return FALSE;
|
|
}
|
|
|
|
for (Index = 0; Index < DriverLength; ++Index) {
|
|
//
|
|
// Skip allowed characters (0-9, A-Z, a-z, '_', '-', '.', '/').
|
|
//
|
|
if ( IsAsciiNumber (Driver[Index])
|
|
|| IsAsciiAlpha (Driver[Index])
|
|
|| (Driver[Index] == '_')
|
|
|| (Driver[Index] == '-')
|
|
|| (Driver[Index] == '.')
|
|
|| (Driver[Index] == '/'))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Disallowed characters matched.
|
|
//
|
|
DEBUG ((DEBUG_WARN, "UEFI->Drivers[%u].Path contains illegal character!\n", DriverIndex));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
AsciiDevicePathIsLegal (
|
|
IN CONST CHAR8 *AsciiDevicePath
|
|
)
|
|
{
|
|
BOOLEAN RetVal;
|
|
CHAR16 *UnicodeDevicePath;
|
|
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
|
|
CHAR16 *TextualDevicePath;
|
|
|
|
RetVal = TRUE;
|
|
|
|
//
|
|
// Convert ASCII device path to Unicode format.
|
|
//
|
|
UnicodeDevicePath = AsciiStrCopyToUnicode (AsciiDevicePath, 0);
|
|
if (UnicodeDevicePath != NULL) {
|
|
//
|
|
// Firstly, convert Unicode device path to binary.
|
|
//
|
|
DevicePath = ConvertTextToDevicePath (UnicodeDevicePath);
|
|
if (DevicePath != NULL) {
|
|
//
|
|
// Secondly, convert binary back to Unicode device path.
|
|
//
|
|
TextualDevicePath = ConvertDevicePathToText (DevicePath, FALSE, FALSE);
|
|
if (TextualDevicePath != NULL) {
|
|
//
|
|
// If the results before and after conversion do not match,
|
|
// then the original device path is borked.
|
|
//
|
|
if (OcStriCmp (UnicodeDevicePath, TextualDevicePath) != 0) {
|
|
DEBUG ((
|
|
DEBUG_WARN,
|
|
"Original path: %s\nPath after internal conversion: %s\n",
|
|
UnicodeDevicePath,
|
|
TextualDevicePath
|
|
));
|
|
//
|
|
// Do not return immediately in order to free properly.
|
|
//
|
|
RetVal = FALSE;
|
|
}
|
|
|
|
FreePool (TextualDevicePath);
|
|
}
|
|
|
|
FreePool (DevicePath);
|
|
}
|
|
|
|
FreePool (UnicodeDevicePath);
|
|
}
|
|
|
|
return RetVal;
|
|
}
|
|
|
|
BOOLEAN
|
|
AsciiGuidIsLegal (
|
|
IN CONST CHAR8 *AsciiGuid
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
GUID Guid;
|
|
|
|
Status = AsciiStrToGuid (AsciiGuid, &Guid);
|
|
|
|
//
|
|
// AsciiStrToGuid should never return errors on valid GUID.
|
|
//
|
|
return !EFI_ERROR (Status);
|
|
}
|
|
|
|
BOOLEAN
|
|
DataHasProperMasking (
|
|
IN CONST VOID *Data,
|
|
IN CONST VOID *Mask,
|
|
IN UINTN DataSize,
|
|
IN UINTN MaskSize
|
|
)
|
|
{
|
|
CONST UINT8 *ByteData;
|
|
CONST UINT8 *ByteMask;
|
|
UINTN Index;
|
|
|
|
if (DataSize != MaskSize) {
|
|
return FALSE;
|
|
}
|
|
|
|
ByteData = (CONST UINT8 *)Data;
|
|
ByteMask = (CONST UINT8 *)Mask;
|
|
|
|
for (Index = 0; Index < DataSize; ++Index) {
|
|
//
|
|
// Mask should only be set when corresponding bits on Data are inactive.
|
|
//
|
|
if ((ByteData[Index] & ~ByteMask[Index]) != 0) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
UINT32
|
|
ValidatePatch (
|
|
IN CONST CHAR8 *PatchSection,
|
|
IN UINT32 PatchIndex,
|
|
IN BOOLEAN FindSizeCanBeZero,
|
|
IN CONST UINT8 *Find,
|
|
IN UINT32 FindSize,
|
|
IN CONST UINT8 *Replace,
|
|
IN UINT32 ReplaceSize,
|
|
IN CONST UINT8 *Mask,
|
|
IN UINT32 MaskSize,
|
|
IN CONST UINT8 *ReplaceMask,
|
|
IN UINT32 ReplaceMaskSize
|
|
)
|
|
{
|
|
UINT32 ErrorCount;
|
|
|
|
ErrorCount = 0;
|
|
|
|
//
|
|
// If size of Find cannot be zero and it is different from that of Replace, then error.
|
|
//
|
|
if (!FindSizeCanBeZero && (FindSize != ReplaceSize)) {
|
|
DEBUG ((
|
|
DEBUG_WARN,
|
|
"%a[%u] has different Find and Replace size (%u vs %u)!\n",
|
|
PatchSection,
|
|
PatchIndex,
|
|
FindSize,
|
|
ReplaceSize
|
|
));
|
|
++ErrorCount;
|
|
}
|
|
|
|
if (MaskSize > 0) {
|
|
//
|
|
// If Mask is set, but its size is different from that of Find, then error.
|
|
//
|
|
if (MaskSize != FindSize) {
|
|
DEBUG ((
|
|
DEBUG_WARN,
|
|
"%a[%u] has Mask set but its size is different from Find (%u vs %u)!\n",
|
|
PatchSection,
|
|
PatchIndex,
|
|
MaskSize,
|
|
FindSize
|
|
));
|
|
++ErrorCount;
|
|
} else if (!DataHasProperMasking (Find, Mask, FindSize, MaskSize)) {
|
|
//
|
|
// If Mask is set without corresponding bits being active for Find, then error.
|
|
//
|
|
DEBUG ((
|
|
DEBUG_WARN,
|
|
"%a[%u]->Find requires Mask to be active for corresponding bits!\n",
|
|
PatchSection,
|
|
PatchIndex
|
|
));
|
|
++ErrorCount;
|
|
}
|
|
}
|
|
|
|
if (ReplaceMaskSize > 0) {
|
|
//
|
|
// If ReplaceMask is set, but its size is different from that of Replace, then error.
|
|
//
|
|
if (ReplaceMaskSize != ReplaceSize) {
|
|
DEBUG ((
|
|
DEBUG_WARN,
|
|
"%a[%u] has ReplaceMask set but its size is different from Replace (%u vs %u)!\n",
|
|
PatchSection,
|
|
PatchIndex,
|
|
ReplaceMaskSize,
|
|
ReplaceSize
|
|
));
|
|
++ErrorCount;
|
|
} else if (!DataHasProperMasking (Replace, ReplaceMask, ReplaceSize, ReplaceMaskSize)) {
|
|
//
|
|
// If ReplaceMask is set without corresponding bits being active for Replace, then error.
|
|
//
|
|
DEBUG ((
|
|
DEBUG_WARN,
|
|
"%a[%u]->Replace requires ReplaceMask to be active for corresponding bits!\n",
|
|
PatchSection,
|
|
PatchIndex
|
|
));
|
|
++ErrorCount;
|
|
}
|
|
}
|
|
|
|
return ErrorCount;
|
|
}
|
|
|
|
UINT32
|
|
FindArrayDuplication (
|
|
IN VOID *First,
|
|
IN UINTN Number,
|
|
IN UINTN Size,
|
|
IN DUPLICATION_CHECK DupChecker
|
|
)
|
|
{
|
|
UINT32 ErrorCount;
|
|
UINTN Index;
|
|
UINTN Index2;
|
|
CONST UINT8 *PrimaryEntry;
|
|
CONST UINT8 *SecondaryEntry;
|
|
|
|
ErrorCount = 0;
|
|
|
|
for (Index = 0; Index < Number; ++Index) {
|
|
for (Index2 = Index + 1; Index2 < Number; ++Index2) {
|
|
//
|
|
// As First is now being read byte-by-byte after casting to UINT8*,
|
|
// its next element is now First + Size * Index.
|
|
//
|
|
PrimaryEntry = (UINT8 *)First + Size * Index;
|
|
SecondaryEntry = (UINT8 *)First + Size * Index2;
|
|
if (DupChecker (PrimaryEntry, SecondaryEntry)) {
|
|
//
|
|
// DupChecker prints what is duplicated, and here the index is printed.
|
|
//
|
|
DEBUG ((DEBUG_WARN, "at Index %u and %u!\n", Index, Index2));
|
|
++ErrorCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ErrorCount;
|
|
}
|
|
|
|
BOOLEAN
|
|
StringIsDuplicated (
|
|
IN CONST CHAR8 *EntrySection,
|
|
IN CONST CHAR8 *FirstString,
|
|
IN CONST CHAR8 *SecondString
|
|
)
|
|
{
|
|
if (AsciiStrCmp (FirstString, SecondString) == 0) {
|
|
//
|
|
// Print duplicated entries whose index will be printed in the parent function (FindArrayDuplication).
|
|
//
|
|
DEBUG ((DEBUG_WARN, "%a: %a is duplicated ", EntrySection, FirstString[0] != '\0' ? FirstString : "<empty string>"));
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
UINT32
|
|
ReportError (
|
|
IN CONST CHAR8 *FuncName,
|
|
IN UINT32 ErrorCount
|
|
)
|
|
{
|
|
if (ErrorCount != 0) {
|
|
DEBUG ((DEBUG_WARN, "%a returns %u %a!\n", FuncName, ErrorCount, ErrorCount > 1 ? "errors" : "error"));
|
|
} else {
|
|
DEBUG ((DEBUG_VERBOSE, "%a returns no errors!\n", FuncName));
|
|
}
|
|
|
|
return ErrorCount;
|
|
}
|