]> code.delx.au - refind/blobdiff - refind/gpt.c
Fixed uninitialized-pointer bug that manifested as a crash with
[refind] / refind / gpt.c
index 4f9be225406bf031fbb8b71299d40681baba25e7..c9b2ac20885601532e21b2533f5dc9f65ce7c020 100644 (file)
@@ -2,18 +2,27 @@
  * refind/gpt.c
  * Functions related to GPT data structures
  *
- * Copyright (c) 2014 Roderick W. Smith
+ * Copyright (c) 2014-2015 Roderick W. Smith
  * All rights reserved.
  *
- * This program is distributed under the terms of the GNU General Public
- * License (GPL) version 3 (GPLv3), a copy of which must be distributed
- * with this source code or binaries made from it.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
  *
- */
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include "gpt.h"
 #include "lib.h"
 #include "screen.h"
+#include "crc32.h"
 #include "../include/refit_call_wrapper.h"
 
 #ifdef __MAKEWITH_TIANO
 
 extern GPT_DATA *gPartitions;
 
+// Allocate data for the main GPT_DATA structure, as well as the ProtectiveMBR
+// and Header structures it contains. This function does *NOT*, however,
+// allocate memory for the Entries data structure, since its size is variable
+// and is determined by the contents of Header.
 GPT_DATA * AllocateGptData(VOID) {
    GPT_DATA *GptData;
 
@@ -39,6 +52,8 @@ GPT_DATA * AllocateGptData(VOID) {
    return GptData;
 } // GPT_DATA * AllocateGptData()
 
+// Unallocate a single GPT_DATA structure. This does NOT follow the
+// linked list, though.
 VOID ClearGptData(GPT_DATA *Data) {
    if (Data) {
       if (Data->ProtectiveMBR)
@@ -51,36 +66,70 @@ VOID ClearGptData(GPT_DATA *Data) {
    } // if
 } // VOID ClearGptData()
 
-// TODO: Add more tests, like a CRC check.
-// Returns TRUE if the GPT header data appear valid, FALSE otherwise.
-static BOOLEAN GptHeaderValid(GPT_HEADER *Header) {
-   BOOLEAN IsValid = TRUE;
+// TODO: Make this work on big-endian systems; at the moment, it contains
+// little-endian assumptions!
+// Returns TRUE if the GPT protective MBR and header data appear valid,
+// FALSE otherwise.
+static BOOLEAN GptHeaderValid(GPT_DATA *GptData) {
+   BOOLEAN IsValid;
+   UINT32 CrcValue, StoredCrcValue;
+   UINTN HeaderSize = sizeof(GPT_HEADER);
+
+   if ((GptData == NULL) || (GptData->ProtectiveMBR == NULL) || (GptData->Header == NULL))
+      return FALSE;
+
+   IsValid = (GptData->ProtectiveMBR->MBRSignature == 0xAA55);
+   IsValid = IsValid && ((GptData->ProtectiveMBR->partitions[0].type == 0xEE) ||
+                         (GptData->ProtectiveMBR->partitions[1].type == 0xEE) ||
+                         (GptData->ProtectiveMBR->partitions[2].type == 0xEE) ||
+                         (GptData->ProtectiveMBR->partitions[3].type == 0xEE));
 
-   if ((Header->signature != 0x5452415020494645ULL) || (Header->entry_count > 2048))
-      IsValid = FALSE;
+   IsValid = IsValid && ((GptData->Header->signature == 0x5452415020494645ULL) &&
+                         (GptData->Header->spec_revision == 0x00010000) &&
+                         (GptData->Header->entry_size == 128));
+
+   // Looks good so far; check CRC value....
+   if (IsValid) {
+      if (GptData->Header->header_size < HeaderSize)
+         HeaderSize = GptData->Header->header_size;
+      StoredCrcValue = GptData->Header->header_crc32;
+      GptData->Header->header_crc32 = 0;
+      CrcValue = crc32(0x0, GptData->Header, HeaderSize);
+      if (CrcValue != StoredCrcValue)
+         IsValid = FALSE;
+      GptData->Header->header_crc32 = StoredCrcValue;
+   } // if
 
    return IsValid;
-} // BOOLEAN GptHeaderValid
+} // BOOLEAN GptHeaderValid()
 
-// Read GPT data from Volume and store it in Data. Note that this function
-// may be called on a Volume that is not in fact the protective MBR of a GPT
-// disk, in which case it will return EFI_LOAD_ERROR or some other error
-// condition. In this case, *Data will be left alone.
+// Read GPT data from Volume and store it in *Data. Note that this function
+// may be called on a Volume that is not in fact a GPT disk (an MBR disk,
+// a partition, etc.), in which case it will return EFI_LOAD_ERROR or some
+// other error condition. In this case, *Data will be left alone.
+// Note also that this function checks CRCs and does other sanity checks
+// on the input data, but does NOT resort to using the backup data if the
+// primary data structures are damaged. The intent is that the function
+// be very conservative about reading GPT data. Currently (version 0.7.10),
+// rEFInd uses the data only to provide access to partition names. This is
+// non-critical data, so it's OK to return nothing, but having the program
+// hang on reading garbage or return nonsense could be very bad.
 EFI_STATUS ReadGptData(REFIT_VOLUME *Volume, GPT_DATA **Data) {
    EFI_STATUS Status = EFI_SUCCESS;
    UINT64     BufferSize;
-   GPT_DATA   *GptData; // Temporary holding storage; transferred to *Data later
+   UINTN      i;
+   GPT_DATA   *GptData = NULL; // Temporary holding storage; transferred to *Data later
 
    if ((Volume == NULL) || (Data == NULL))
-      Status = EFI_INVALID_PARAMETER;
+      return EFI_INVALID_PARAMETER;
 
    // get block i/o
    if ((Status == EFI_SUCCESS) && (Volume->BlockIO == NULL)) {
       Status = refit_call3_wrapper(BS->HandleProtocol, Volume->DeviceHandle, &BlockIoProtocol, (VOID **) &(Volume->BlockIO));
       if (EFI_ERROR(Status)) {
          Volume->BlockIO = NULL;
-         Print(L"Warning: Can't get BlockIO protocol.\n");
-         Status = EFI_INVALID_PARAMETER;
+         Print(L"Warning: Can't get BlockIO protocol in ReadGptData().\n");
+         Status = EFI_NOT_READY;
       }
    } // if
 
@@ -88,7 +137,7 @@ EFI_STATUS ReadGptData(REFIT_VOLUME *Volume, GPT_DATA **Data) {
       Status = EFI_NO_MEDIA;
 
    if (Status == EFI_SUCCESS) {
-      GptData = AllocateGptData();
+      GptData = AllocateGptData(); // Note: All but GptData->Entries
       if (GptData == NULL) {
          Status = EFI_OUT_OF_RESOURCES;
       } // if
@@ -97,40 +146,41 @@ EFI_STATUS ReadGptData(REFIT_VOLUME *Volume, GPT_DATA **Data) {
    // Read the MBR and store it in GptData->ProtectiveMBR.
    if (Status == EFI_SUCCESS) {
       Status = refit_call5_wrapper(Volume->BlockIO->ReadBlocks, Volume->BlockIO, Volume->BlockIO->Media->MediaId,
-                                   0, 512, (VOID*) GptData->ProtectiveMBR);
+                                   0, sizeof(MBR_RECORD), (VOID*) GptData->ProtectiveMBR);
    }
 
-   // If it looks like a valid protective MBR, try to do more with it....
-   if ((Status == EFI_SUCCESS) && (GptData->ProtectiveMBR->MBRSignature == 0xAA55) &&
-       ((GptData->ProtectiveMBR->partitions[0].type == 0xEE) || (GptData->ProtectiveMBR->partitions[1].type == 0xEE) ||
-        (GptData->ProtectiveMBR->partitions[2].type = 0xEE) || (GptData->ProtectiveMBR->partitions[3].type == 0xEE))) {
+   // Read the GPT header and store it in GptData->Header.
+   if (Status == EFI_SUCCESS) {
       Status = refit_call5_wrapper(Volume->BlockIO->ReadBlocks, Volume->BlockIO, Volume->BlockIO->Media->MediaId,
                                    1, sizeof(GPT_HEADER), GptData->Header);
+   }
 
-      // Do basic sanity check on GPT header.
-      if ((Status == EFI_SUCCESS) && !GptHeaderValid(GptData->Header)) {
-         Status = EFI_UNSUPPORTED;
-      }
-
-      // Load actual GPT table....
-      if (Status == EFI_SUCCESS) {
+   // If it looks like a valid protective MBR & GPT header, try to do more with it....
+   if (Status == EFI_SUCCESS) {
+      if (GptHeaderValid(GptData)) {
+         // Load actual GPT table....
          BufferSize = GptData->Header->entry_count * 128;
-         if (GptData->Entries != NULL)
-            MyFreePool(GptData->Entries);
          GptData->Entries = AllocatePool(BufferSize);
-         if (GptData->Entries == NULL) {
+         if (GptData->Entries == NULL)
             Status = EFI_OUT_OF_RESOURCES;
-         } // if
-      } // if
 
-      if (Status == EFI_SUCCESS) {
-         Status = refit_call5_wrapper(Volume->BlockIO->ReadBlocks, Volume->BlockIO, Volume->BlockIO->Media->MediaId,
-                                      GptData->Header->header_lba, BufferSize, GptData->Entries);
-      } // if
+         if (Status == EFI_SUCCESS)
+            Status = refit_call5_wrapper(Volume->BlockIO->ReadBlocks, Volume->BlockIO, Volume->BlockIO->Media->MediaId,
+                                         GptData->Header->entry_lba, BufferSize, GptData->Entries);
 
-   } else {
-      Status = EFI_LOAD_ERROR;
-   } // if/else
+         // Check CRC status of table
+         if ((Status == EFI_SUCCESS) && (crc32(0x0, GptData->Entries, BufferSize) != GptData->Header->entry_crc32))
+            Status = EFI_CRC_ERROR;
+
+         // Now, ensure that every name is null-terminated....
+         if (Status == EFI_SUCCESS) {
+            for (i = 0; i < GptData->Header->entry_count; i++)
+               GptData->Entries[i].name[35] = '\0';
+         } // if
+      } else {
+         Status = EFI_UNSUPPORTED;
+      } // if/else valid header
+   } // if header read OK
 
    if (Status == EFI_SUCCESS) {
       // Everything looks OK, so copy it over
@@ -144,11 +194,11 @@ EFI_STATUS ReadGptData(REFIT_VOLUME *Volume, GPT_DATA **Data) {
 } // EFI_STATUS ReadGptData()
 
 // Look in gPartitions for a partition with the specified Guid. If found, return
-// a pointer to that partition's name string. If not found, return a NULL pointer.
+// a pointer to that partition's data. If not found, return a NULL pointer.
 // The calling function is responsible for freeing the returned memory.
-CHAR16 * PartNameFromGuid(EFI_GUID *Guid) {
+GPT_ENTRY * FindPartWithGuid(EFI_GUID *Guid) {
    UINTN     i;
-   CHAR16    *Found = NULL;
+   GPT_ENTRY *Found = NULL;
    GPT_DATA  *GptData;
 
    if ((Guid == NULL) || (gPartitions == NULL))
@@ -158,15 +208,17 @@ CHAR16 * PartNameFromGuid(EFI_GUID *Guid) {
    while ((GptData != NULL) && (!Found)) {
       i = 0;
       while ((i < GptData->Header->entry_count) && (!Found)) {
-         if (GuidsAreEqual((EFI_GUID*) &(GptData->Entries[i].partition_guid), Guid))
-            Found = StrDuplicate(GptData->Entries[i].name);
-         else
+         if (GuidsAreEqual((EFI_GUID*) &(GptData->Entries[i].partition_guid), Guid)) {
+            Found = AllocateZeroPool(sizeof(GPT_ENTRY));
+            CopyMem(Found, &GptData->Entries[i], sizeof(GPT_ENTRY));
+         } else {
             i++;
+         } // if/else
       } // while(scanning entries)
       GptData = GptData->NextEntry;
    } // while(scanning GPTs)
    return Found;
-} // CHAR16 * PartNameFromGuid()
+} // GPT_ENTRY * FindPartWithGuid()
 
 // Erase the gPartitions linked-list data structure
 VOID ForgetPartitionTables(VOID) {
@@ -184,6 +236,7 @@ VOID ForgetPartitionTables(VOID) {
 VOID AddPartitionTable(REFIT_VOLUME *Volume) {
    GPT_DATA    *GptData = NULL, *GptList;
    EFI_STATUS  Status;
+   UINTN       NumTables = 1;
 
    Status = ReadGptData(Volume, &GptData);
    if (Status == EFI_SUCCESS) {
@@ -193,11 +246,14 @@ VOID AddPartitionTable(REFIT_VOLUME *Volume) {
          GptList = gPartitions;
          while (GptList->NextEntry != NULL) {
             GptList = GptList->NextEntry;
+            NumTables++;
          } // while
          GptList->NextEntry = GptData;
+         NumTables++;
       } // if/else
    } else if (GptData != NULL) {
       ClearGptData(GptData);
+      NumTables = 0;
    } // if/else
 } // VOID AddPartitionTable()