773 lines
30 KiB
C
773 lines
30 KiB
C
// Dynamic partition discovery. This file owns MBR/GPT parsing and keeps structural identity out of filesystem providers.
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <common/boot_volume_selector.h>
|
|
#include <ntlite/ddi/io_driver.h>
|
|
#include <ntlite/ddk/mutex.h>
|
|
#include <kernel/io_partition.h>
|
|
#include <kernel/ke_debug.h>
|
|
#include <kernel/mm_pool.h>
|
|
|
|
#define IOP_SECTOR_SIZE 512u
|
|
#define IOP_GPT_SIGNATURE "EFI PART"
|
|
#define IOP_GPT_REVISION_1_0 0x00010000u
|
|
#define IOP_GPT_MIN_ENTRY_SIZE 128u
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct IOP_MBR_PARTITION_ENTRY {
|
|
uint8_t BootIndicator;
|
|
uint8_t StartingChs[3];
|
|
uint8_t Type;
|
|
uint8_t EndingChs[3];
|
|
uint32_t StartingLba;
|
|
uint32_t SectorCount;
|
|
} IOP_MBR_PARTITION_ENTRY;
|
|
|
|
typedef struct IOP_GPT_HEADER {
|
|
char Signature[8];
|
|
uint32_t Revision;
|
|
uint32_t HeaderSize;
|
|
uint32_t HeaderCrc32;
|
|
uint32_t Reserved;
|
|
uint64_t MyLba;
|
|
uint64_t AlternateLba;
|
|
uint64_t FirstUsableLba;
|
|
uint64_t LastUsableLba;
|
|
BOOT_VOLUME_GUID DiskGuid;
|
|
uint64_t PartitionEntryLba;
|
|
uint32_t NumberOfPartitionEntries;
|
|
uint32_t SizeOfPartitionEntry;
|
|
uint32_t PartitionEntryArrayCrc32;
|
|
} IOP_GPT_HEADER;
|
|
|
|
typedef struct IOP_GPT_ENTRY_PREFIX {
|
|
BOOT_VOLUME_GUID PartitionTypeGuid;
|
|
BOOT_VOLUME_GUID UniquePartitionGuid;
|
|
uint64_t StartingLba;
|
|
uint64_t EndingLba;
|
|
uint64_t Attributes;
|
|
} IOP_GPT_ENTRY_PREFIX;
|
|
#pragma pack(pop)
|
|
|
|
_Static_assert(sizeof(IOP_MBR_PARTITION_ENTRY) == 16u, "MBR partition entry layout must match disk format");
|
|
_Static_assert(sizeof(IOP_GPT_HEADER) == 92u, "GPT header prefix layout must match disk format");
|
|
_Static_assert(sizeof(IOP_GPT_ENTRY_PREFIX) == 56u, "GPT entry prefix layout must match disk format");
|
|
|
|
typedef struct IOP_PARTITION_RECORD {
|
|
struct IOP_PARTITION_RECORD* Next;
|
|
IO_PARTITION_INFO Info;
|
|
BOOT_VOLUME_SELECTOR Identity;
|
|
} IOP_PARTITION_RECORD;
|
|
|
|
typedef struct IOP_PARTITION_CALLBACK_ENTRY {
|
|
struct IOP_PARTITION_CALLBACK_ENTRY* Next;
|
|
IO_PARTITION_CHANGE_CALLBACK Callback;
|
|
void* Context;
|
|
} IOP_PARTITION_CALLBACK_ENTRY;
|
|
|
|
typedef struct IOP_GPT_VALIDATED_HEADER {
|
|
IOP_GPT_HEADER Header;
|
|
uint64_t EntryBytes;
|
|
uint64_t EntrySectors;
|
|
} IOP_GPT_VALIDATED_HEADER;
|
|
|
|
static MUTEX* gPartitionMutex;
|
|
static IOP_PARTITION_RECORD* gPartitions;
|
|
static IOP_PARTITION_CALLBACK_ENTRY* gPartitionCallbacks;
|
|
static BOOT_VOLUME_SELECTOR gSystemVolumeSelector;
|
|
static bool gPartitionManagerInitialized;
|
|
|
|
// Returns whether two shared GUID values are identical.
|
|
static bool IopGuidEquals(const BOOT_VOLUME_GUID* left, const BOOT_VOLUME_GUID* right) {
|
|
return left && right && memcmp(left, right, sizeof(*left)) == 0;
|
|
}
|
|
|
|
// Returns whether one shared GUID value is all zeroes.
|
|
static bool IopGuidIsZero(const BOOT_VOLUME_GUID* guid) {
|
|
static const BOOT_VOLUME_GUID zero = {0};
|
|
return guid && IopGuidEquals(guid, &zero);
|
|
}
|
|
|
|
// Returns whether one internal partition identity equals another.
|
|
static bool IopIdentityEquals(const BOOT_VOLUME_SELECTOR* left, const BOOT_VOLUME_SELECTOR* right) {
|
|
if (!left || !right || left->Kind != right->Kind)
|
|
return false;
|
|
|
|
if (left->Kind == BootVolumeSelectorMbr) {
|
|
return left->Identity.Mbr.DiskSignature == right->Identity.Mbr.DiskSignature &&
|
|
left->Identity.Mbr.PartitionNumber == right->Identity.Mbr.PartitionNumber;
|
|
}
|
|
if (left->Kind == BootVolumeSelectorGpt) {
|
|
return IopGuidEquals(&left->Identity.Gpt.PartitionGuid,
|
|
&right->Identity.Gpt.PartitionGuid);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Acquires the partition-manager mutex for registry and callback serialization.
|
|
static bool IopAcquirePartitionMutex(void) {
|
|
if (!gPartitionMutex)
|
|
return false;
|
|
return KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(gPartitionMutex, KE_WAIT_INFINITE));
|
|
}
|
|
|
|
// Frees one dynamically discovered partition list.
|
|
static void IopFreePartitionList(IOP_PARTITION_RECORD* head) {
|
|
while (head) {
|
|
IOP_PARTITION_RECORD* next = head->Next;
|
|
MmFreePool(head);
|
|
head = next;
|
|
}
|
|
}
|
|
|
|
// Updates one CRC32 accumulator using the GPT/IEEE polynomial.
|
|
static uint32_t IopCrc32Update(uint32_t crc, const uint8_t* data, size_t length) {
|
|
for (size_t i = 0; i < length; ++i) {
|
|
crc ^= data[i];
|
|
for (uint32_t bit = 0; bit < 8; ++bit)
|
|
crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int32_t)(crc & 1u));
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
// Computes a complete GPT CRC32 value.
|
|
static uint32_t IopCrc32(const void* data, size_t length) {
|
|
uint32_t crc = IopCrc32Update(0xffffffffu, (const uint8_t*)data, length);
|
|
return crc ^ 0xffffffffu;
|
|
}
|
|
|
|
// Reads an arbitrary byte span through the kernel's 512-byte block-device API.
|
|
static KSTATUS IopReadDiskBytes(uint8_t diskIndex,
|
|
uint64_t diskSectors,
|
|
uint64_t byteOffset,
|
|
void* buffer,
|
|
size_t length) {
|
|
if (!buffer || length == 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (diskSectors > UINT64_MAX / IOP_SECTOR_SIZE)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint64_t diskBytes = diskSectors * IOP_SECTOR_SIZE;
|
|
if (byteOffset > diskBytes || (uint64_t)length > diskBytes - byteOffset)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint8_t sector[IOP_SECTOR_SIZE];
|
|
uint8_t* out = (uint8_t*)buffer;
|
|
size_t remaining = length;
|
|
while (remaining) {
|
|
uint64_t lba = byteOffset / IOP_SECTOR_SIZE;
|
|
uint32_t inSector = (uint32_t)(byteOffset % IOP_SECTOR_SIZE);
|
|
size_t chunk = IOP_SECTOR_SIZE - inSector;
|
|
if (chunk > remaining)
|
|
chunk = remaining;
|
|
|
|
KSTATUS status = IoReadDiskSectors(diskIndex, lba, 1, sector);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
memcpy(out, sector + inSector, chunk);
|
|
out += chunk;
|
|
byteOffset += chunk;
|
|
remaining -= chunk;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns whether one MBR type is an extended-partition container we intentionally do not enumerate.
|
|
static bool IopMbrTypeIsExtended(uint8_t type) {
|
|
return type == 0x05u || type == 0x0fu || type == 0x85u;
|
|
}
|
|
|
|
// Allocates one immutable partition record.
|
|
static IOP_PARTITION_RECORD* IopAllocatePartition(uint8_t diskIndex,
|
|
uint64_t startLba,
|
|
uint64_t sectorCount,
|
|
const BOOT_VOLUME_SELECTOR* identity) {
|
|
if (!sectorCount || !identity)
|
|
return NULL;
|
|
|
|
IOP_PARTITION_RECORD* record = MmAllocatePool(sizeof(*record));
|
|
if (!record)
|
|
return NULL;
|
|
memset(record, 0, sizeof(*record));
|
|
record->Info.DiskIndex = diskIndex;
|
|
record->Info.StartLba = startLba;
|
|
record->Info.SectorCount = sectorCount;
|
|
record->Identity = *identity;
|
|
return record;
|
|
}
|
|
|
|
// Inserts one discovered partition in ascending LBA order and rejects overlap or duplicate identity.
|
|
static KSTATUS IopInsertDiscoveredPartition(IOP_PARTITION_RECORD** head,
|
|
IOP_PARTITION_RECORD* record) {
|
|
if (!head || !record || record->Info.SectorCount == 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
if (record->Info.StartLba > UINT64_MAX - record->Info.SectorCount)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t recordEnd = record->Info.StartLba + record->Info.SectorCount;
|
|
|
|
IOP_PARTITION_RECORD* previous = NULL;
|
|
IOP_PARTITION_RECORD* current = *head;
|
|
while (current && current->Info.StartLba < record->Info.StartLba) {
|
|
if (IopIdentityEquals(¤t->Identity, &record->Identity))
|
|
return NTLSTATUS_OBJECT_NAME_COLLISION;
|
|
previous = current;
|
|
current = current->Next;
|
|
}
|
|
for (IOP_PARTITION_RECORD* scan = current; scan; scan = scan->Next) {
|
|
if (IopIdentityEquals(&scan->Identity, &record->Identity))
|
|
return NTLSTATUS_OBJECT_NAME_COLLISION;
|
|
}
|
|
|
|
if (previous) {
|
|
uint64_t previousEnd = previous->Info.StartLba + previous->Info.SectorCount;
|
|
if (previousEnd > record->Info.StartLba)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
if (current && recordEnd > current->Info.StartLba)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
record->Next = current;
|
|
if (previous)
|
|
previous->Next = record;
|
|
else
|
|
*head = record;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Validates the exact CRC32 of one GPT partition-entry array without imposing an entry-count cap.
|
|
static KSTATUS IopValidateGptEntryArrayCrc(uint8_t diskIndex,
|
|
const IOP_GPT_VALIDATED_HEADER* validated) {
|
|
uint8_t sector[IOP_SECTOR_SIZE];
|
|
uint64_t remaining = validated->EntryBytes;
|
|
uint64_t lba = validated->Header.PartitionEntryLba;
|
|
uint32_t crc = 0xffffffffu;
|
|
while (remaining) {
|
|
KSTATUS status = IoReadDiskSectors(diskIndex, lba++, 1, sector);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
size_t chunk = remaining < IOP_SECTOR_SIZE ? (size_t)remaining : IOP_SECTOR_SIZE;
|
|
crc = IopCrc32Update(crc, sector, chunk);
|
|
remaining -= chunk;
|
|
}
|
|
crc ^= 0xffffffffu;
|
|
return crc == validated->Header.PartitionEntryArrayCrc32
|
|
? NTLSTATUS_SUCCESS
|
|
: NTLSTATUS_CHECKSUM_MISMATCH;
|
|
}
|
|
|
|
// Validates one primary or backup GPT header and its partition-entry-array CRC.
|
|
static KSTATUS IopValidateGptHeader(uint8_t diskIndex,
|
|
uint64_t diskSectors,
|
|
uint64_t headerLba,
|
|
uint64_t expectedAlternateLba,
|
|
IOP_GPT_VALIDATED_HEADER* validatedOut) {
|
|
if (!validatedOut || diskSectors < 3 || headerLba >= diskSectors ||
|
|
expectedAlternateLba >= diskSectors || headerLba == expectedAlternateLba) {
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint8_t sector[IOP_SECTOR_SIZE];
|
|
KSTATUS status = IoReadDiskSectors(diskIndex, headerLba, 1, sector);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
IOP_GPT_HEADER header;
|
|
memcpy(&header, sector, sizeof(header));
|
|
if (memcmp(header.Signature, IOP_GPT_SIGNATURE, 8) != 0 ||
|
|
header.Revision < IOP_GPT_REVISION_1_0 ||
|
|
header.HeaderSize < sizeof(IOP_GPT_HEADER) ||
|
|
header.HeaderSize > IOP_SECTOR_SIZE ||
|
|
header.Reserved != 0 ||
|
|
header.MyLba != headerLba ||
|
|
header.AlternateLba != expectedAlternateLba ||
|
|
header.FirstUsableLba > header.LastUsableLba ||
|
|
header.LastUsableLba >= diskSectors ||
|
|
IopGuidIsZero(&header.DiskGuid) ||
|
|
header.NumberOfPartitionEntries == 0 ||
|
|
header.SizeOfPartitionEntry < IOP_GPT_MIN_ENTRY_SIZE ||
|
|
(header.SizeOfPartitionEntry & 7u) != 0) {
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint32_t expectedHeaderCrc = header.HeaderCrc32;
|
|
memset(sector + offsetof(IOP_GPT_HEADER, HeaderCrc32), 0, sizeof(header.HeaderCrc32));
|
|
if (IopCrc32(sector, header.HeaderSize) != expectedHeaderCrc)
|
|
return NTLSTATUS_CHECKSUM_MISMATCH;
|
|
|
|
if ((uint64_t)header.NumberOfPartitionEntries > UINT64_MAX / header.SizeOfPartitionEntry)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t entryBytes = (uint64_t)header.NumberOfPartitionEntries * header.SizeOfPartitionEntry;
|
|
if (entryBytes > UINT64_MAX - (IOP_SECTOR_SIZE - 1u))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t entrySectors = (entryBytes + IOP_SECTOR_SIZE - 1u) / IOP_SECTOR_SIZE;
|
|
if (entrySectors == 0 || header.PartitionEntryLba >= diskSectors ||
|
|
entrySectors > diskSectors - header.PartitionEntryLba) {
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint64_t entryLastLba = header.PartitionEntryLba + entrySectors - 1u;
|
|
if (headerLba == 1u) {
|
|
if (header.PartitionEntryLba <= headerLba || entryLastLba >= header.FirstUsableLba)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
} else {
|
|
if (header.PartitionEntryLba <= header.LastUsableLba || entryLastLba >= headerLba)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
IOP_GPT_VALIDATED_HEADER validated = {0};
|
|
validated.Header = header;
|
|
validated.EntryBytes = entryBytes;
|
|
validated.EntrySectors = entrySectors;
|
|
status = IopValidateGptEntryArrayCrc(diskIndex, &validated);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
*validatedOut = validated;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns whether independently valid primary and backup GPT headers describe the same table.
|
|
static bool IopGptHeadersAgree(const IOP_GPT_VALIDATED_HEADER* primary,
|
|
const IOP_GPT_VALIDATED_HEADER* backup) {
|
|
if (!primary || !backup)
|
|
return false;
|
|
return IopGuidEquals(&primary->Header.DiskGuid, &backup->Header.DiskGuid) &&
|
|
primary->Header.FirstUsableLba == backup->Header.FirstUsableLba &&
|
|
primary->Header.LastUsableLba == backup->Header.LastUsableLba &&
|
|
primary->Header.NumberOfPartitionEntries == backup->Header.NumberOfPartitionEntries &&
|
|
primary->Header.SizeOfPartitionEntry == backup->Header.SizeOfPartitionEntry &&
|
|
primary->Header.PartitionEntryArrayCrc32 == backup->Header.PartitionEntryArrayCrc32;
|
|
}
|
|
|
|
// Parses all populated GPT entries from one already validated table.
|
|
static KSTATUS IopParseGptEntries(uint8_t diskIndex,
|
|
uint64_t diskSectors,
|
|
const IOP_GPT_VALIDATED_HEADER* validated,
|
|
IOP_PARTITION_RECORD** partitionsOut) {
|
|
if (!validated || !partitionsOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
IOP_PARTITION_RECORD* partitions = NULL;
|
|
if (validated->Header.PartitionEntryLba > UINT64_MAX / IOP_SECTOR_SIZE)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t arrayByteBase = validated->Header.PartitionEntryLba * IOP_SECTOR_SIZE;
|
|
|
|
for (uint32_t index = 0; index < validated->Header.NumberOfPartitionEntries; ++index) {
|
|
uint64_t entryOffset = (uint64_t)index * validated->Header.SizeOfPartitionEntry;
|
|
if (arrayByteBase > UINT64_MAX - entryOffset) {
|
|
IopFreePartitionList(partitions);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
IOP_GPT_ENTRY_PREFIX entry;
|
|
KSTATUS status = IopReadDiskBytes(diskIndex,
|
|
diskSectors,
|
|
arrayByteBase + entryOffset,
|
|
&entry,
|
|
sizeof(entry));
|
|
if (!KSUCCESS(status)) {
|
|
IopFreePartitionList(partitions);
|
|
return status;
|
|
}
|
|
if (IopGuidIsZero(&entry.PartitionTypeGuid))
|
|
continue;
|
|
if (IopGuidIsZero(&entry.UniquePartitionGuid) ||
|
|
entry.EndingLba < entry.StartingLba ||
|
|
entry.StartingLba < validated->Header.FirstUsableLba ||
|
|
entry.EndingLba > validated->Header.LastUsableLba) {
|
|
IopFreePartitionList(partitions);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint64_t sectorCount = entry.EndingLba - entry.StartingLba + 1u;
|
|
BOOT_VOLUME_SELECTOR identity = {0};
|
|
identity.Kind = BootVolumeSelectorGpt;
|
|
identity.Identity.Gpt.PartitionGuid = entry.UniquePartitionGuid;
|
|
IOP_PARTITION_RECORD* record = IopAllocatePartition(diskIndex,
|
|
entry.StartingLba,
|
|
sectorCount,
|
|
&identity);
|
|
if (!record) {
|
|
IopFreePartitionList(partitions);
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
status = IopInsertDiscoveredPartition(&partitions, record);
|
|
if (!KSUCCESS(status)) {
|
|
MmFreePool(record);
|
|
IopFreePartitionList(partitions);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
*partitionsOut = partitions;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Discovers one GPT disk from primary/backup metadata and rejects inconsistent valid copies.
|
|
static KSTATUS IopDiscoverGptDisk(uint8_t diskIndex,
|
|
uint64_t diskSectors,
|
|
IOP_PARTITION_RECORD** partitionsOut) {
|
|
IOP_GPT_VALIDATED_HEADER primary = {0};
|
|
IOP_GPT_VALIDATED_HEADER backup = {0};
|
|
KSTATUS primaryStatus = IopValidateGptHeader(diskIndex, diskSectors, 1u,
|
|
diskSectors - 1u, &primary);
|
|
KSTATUS backupStatus = IopValidateGptHeader(diskIndex, diskSectors, diskSectors - 1u,
|
|
1u, &backup);
|
|
bool primaryValid = KSUCCESS(primaryStatus);
|
|
bool backupValid = KSUCCESS(backupStatus);
|
|
if (!primaryValid && !backupValid) {
|
|
DbgPrint("partition: disk %u GPT primary=0x%x backup=0x%x\n",
|
|
diskIndex, primaryStatus, backupStatus);
|
|
return primaryStatus == NTLSTATUS_CHECKSUM_MISMATCH || backupStatus == NTLSTATUS_CHECKSUM_MISMATCH
|
|
? NTLSTATUS_CHECKSUM_MISMATCH
|
|
: NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
if (primaryValid && backupValid && !IopGptHeadersAgree(&primary, &backup)) {
|
|
DbgPrint("partition: disk %u GPT primary/backup metadata disagree\n", diskIndex);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
const IOP_GPT_VALIDATED_HEADER* selected = primaryValid ? &primary : &backup;
|
|
if (!primaryValid)
|
|
DbgPrint("partition: disk %u using valid backup GPT\n", diskIndex);
|
|
return IopParseGptEntries(diskIndex, diskSectors, selected, partitionsOut);
|
|
}
|
|
|
|
// Discovers either an MBR primary-partition map or a protective-MBR GPT map.
|
|
static KSTATUS IopDiscoverDiskPartitions(uint8_t diskIndex,
|
|
DEVICE_OBJECT* device,
|
|
IOP_PARTITION_RECORD** partitionsOut) {
|
|
if (!device || !partitionsOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
*partitionsOut = NULL;
|
|
|
|
if (device->u.Disk.SectorSize != IOP_SECTOR_SIZE || device->u.Disk.SectorCount == 0) {
|
|
DbgPrint("partition: disk %u unsupported sector geometry size=%u sectors=%llu\n",
|
|
diskIndex, device->u.Disk.SectorSize,
|
|
(unsigned long long)device->u.Disk.SectorCount);
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
}
|
|
uint64_t diskSectors = device->u.Disk.SectorCount;
|
|
|
|
uint8_t sector[IOP_SECTOR_SIZE];
|
|
KSTATUS status = IoReadDiskSectors(diskIndex, 0, 1, sector);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (sector[510] != 0x55u || sector[511] != 0xaau)
|
|
return NTLSTATUS_NOT_FOUND;
|
|
|
|
uint32_t diskSignature = 0;
|
|
memcpy(&diskSignature, sector + 440u, sizeof(diskSignature));
|
|
IOP_MBR_PARTITION_ENTRY entries[4];
|
|
memcpy(entries, sector + 446u, sizeof(entries));
|
|
|
|
bool protective = false;
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
if (entries[i].Type == 0xeeu && entries[i].SectorCount != 0)
|
|
protective = true;
|
|
}
|
|
if (protective)
|
|
return IopDiscoverGptDisk(diskIndex, diskSectors, partitionsOut);
|
|
|
|
// Validate the complete primary table before emitting supported partitions. Extended containers remain
|
|
// unsupported, but malformed container extents must not make a partially trusted MBR map acceptable.
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
const IOP_MBR_PARTITION_ENTRY* entry = &entries[i];
|
|
if (entry->BootIndicator != 0x00u && entry->BootIndicator != 0x80u)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (entry->Type == 0 && entry->SectorCount == 0)
|
|
continue;
|
|
if (entry->Type == 0 || entry->SectorCount == 0 ||
|
|
entry->StartingLba == 0 || entry->StartingLba >= diskSectors ||
|
|
(uint64_t)entry->SectorCount > diskSectors - entry->StartingLba) {
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint64_t entryEnd = (uint64_t)entry->StartingLba + entry->SectorCount;
|
|
for (uint32_t previous = 0; previous < i; ++previous) {
|
|
const IOP_MBR_PARTITION_ENTRY* prior = &entries[previous];
|
|
if (prior->Type == 0 && prior->SectorCount == 0)
|
|
continue;
|
|
uint64_t priorEnd = (uint64_t)prior->StartingLba + prior->SectorCount;
|
|
if ((uint64_t)entry->StartingLba < priorEnd &&
|
|
(uint64_t)prior->StartingLba < entryEnd) {
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
}
|
|
|
|
IOP_PARTITION_RECORD* partitions = NULL;
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
const IOP_MBR_PARTITION_ENTRY* entry = &entries[i];
|
|
if (entry->Type == 0 || IopMbrTypeIsExtended(entry->Type))
|
|
continue;
|
|
|
|
BOOT_VOLUME_SELECTOR identity = {0};
|
|
identity.Kind = BootVolumeSelectorMbr;
|
|
identity.Identity.Mbr.DiskSignature = diskSignature;
|
|
identity.Identity.Mbr.PartitionNumber = i + 1u;
|
|
IOP_PARTITION_RECORD* record = IopAllocatePartition(diskIndex,
|
|
entry->StartingLba,
|
|
entry->SectorCount,
|
|
&identity);
|
|
if (!record) {
|
|
IopFreePartitionList(partitions);
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
status = IopInsertDiscoveredPartition(&partitions, record);
|
|
if (!KSUCCESS(status)) {
|
|
MmFreePool(record);
|
|
IopFreePartitionList(partitions);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
*partitionsOut = partitions;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns whether the registry already contains at least one partition for this disk index.
|
|
static bool IopDiskAlreadyDiscovered(uint8_t diskIndex) {
|
|
bool found = false;
|
|
if (!IopAcquirePartitionMutex())
|
|
return false;
|
|
for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) {
|
|
if (record->Info.DiskIndex == diskIndex) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
KeReleaseMutex(gPartitionMutex);
|
|
return found;
|
|
}
|
|
|
|
// Appends one discovered list to the live registry and synchronously notifies every subscriber.
|
|
static KSTATUS IopPublishPartitions(uint8_t diskIndex, IOP_PARTITION_RECORD* discovered) {
|
|
if (!IopAcquirePartitionMutex()) {
|
|
IopFreePartitionList(discovered);
|
|
return NTLSTATUS_FAILURE;
|
|
}
|
|
|
|
for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) {
|
|
if (record->Info.DiskIndex == diskIndex) {
|
|
KeReleaseMutex(gPartitionMutex);
|
|
IopFreePartitionList(discovered);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
IOP_PARTITION_RECORD** tail = &gPartitions;
|
|
while (*tail)
|
|
tail = &(*tail)->Next;
|
|
*tail = discovered;
|
|
|
|
for (IOP_PARTITION_RECORD* record = discovered; record; record = record->Next) {
|
|
if (record->Identity.Kind == BootVolumeSelectorMbr) {
|
|
DbgPrint("partition: disk=%u mbr/%08X/%u start=%llu sectors=%llu\n",
|
|
record->Info.DiskIndex,
|
|
record->Identity.Identity.Mbr.DiskSignature,
|
|
record->Identity.Identity.Mbr.PartitionNumber,
|
|
(unsigned long long)record->Info.StartLba,
|
|
(unsigned long long)record->Info.SectorCount);
|
|
} else {
|
|
const BOOT_VOLUME_GUID* guid = &record->Identity.Identity.Gpt.PartitionGuid;
|
|
DbgPrint("partition: disk=%u gpt/%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X start=%llu sectors=%llu\n",
|
|
record->Info.DiskIndex,
|
|
guid->Data1, guid->Data2, guid->Data3,
|
|
guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
|
|
guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7],
|
|
(unsigned long long)record->Info.StartLba,
|
|
(unsigned long long)record->Info.SectorCount);
|
|
}
|
|
if (IopIdentityEquals(&record->Identity, &gSystemVolumeSelector)) {
|
|
DbgPrint("partition: configured system_volume matches disk=%u start=%llu sectors=%llu\n",
|
|
record->Info.DiskIndex,
|
|
(unsigned long long)record->Info.StartLba,
|
|
(unsigned long long)record->Info.SectorCount);
|
|
}
|
|
for (IOP_PARTITION_CALLBACK_ENTRY* entry = gPartitionCallbacks; entry; entry = entry->Next)
|
|
entry->Callback(&record->Info, IoPartitionArrived, entry->Context);
|
|
}
|
|
|
|
KeReleaseMutex(gPartitionMutex);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Discovers and publishes all partitions for one newly registered disk.
|
|
static void IopDiskArrived(uint8_t diskIndex, DEVICE_OBJECT* device) {
|
|
if (!device || IopDiskAlreadyDiscovered(diskIndex))
|
|
return;
|
|
|
|
IOP_PARTITION_RECORD* discovered = NULL;
|
|
KSTATUS status = IopDiscoverDiskPartitions(diskIndex, device, &discovered);
|
|
if (!KSUCCESS(status)) {
|
|
if (status != NTLSTATUS_NOT_FOUND) {
|
|
DbgPrint("partition: disk %u discovery failed status=0x%x\n", diskIndex, status);
|
|
} else {
|
|
DbgPrint("partition: disk %u has no partition table\n", diskIndex);
|
|
}
|
|
return;
|
|
}
|
|
if (!discovered) {
|
|
DbgPrint("partition: disk %u contains no supported partitions\n", diskIndex);
|
|
return;
|
|
}
|
|
|
|
status = IopPublishPartitions(diskIndex, discovered);
|
|
if (!KSUCCESS(status))
|
|
DbgPrint("partition: disk %u publish failed status=0x%x\n", diskIndex, status);
|
|
}
|
|
|
|
// Removes all partitions belonging to one disk and notifies consumers before freeing their snapshots.
|
|
static void IopDiskRemoved(uint8_t diskIndex, IO_PARTITION_CHANGE change) {
|
|
if (!IopAcquirePartitionMutex())
|
|
return;
|
|
|
|
IOP_PARTITION_RECORD** link = &gPartitions;
|
|
while (*link) {
|
|
IOP_PARTITION_RECORD* record = *link;
|
|
if (record->Info.DiskIndex != diskIndex) {
|
|
link = &record->Next;
|
|
continue;
|
|
}
|
|
|
|
*link = record->Next;
|
|
for (IOP_PARTITION_CALLBACK_ENTRY* entry = gPartitionCallbacks; entry; entry = entry->Next)
|
|
entry->Callback(&record->Info, change, entry->Context);
|
|
MmFreePool(record);
|
|
}
|
|
|
|
KeReleaseMutex(gPartitionMutex);
|
|
}
|
|
|
|
// Receives the underlying disk namespace and translates it into partition changes.
|
|
static void IopDiskChange(uint8_t diskIndex,
|
|
DEVICE_OBJECT* device,
|
|
IO_DEVICE_CHANGE change,
|
|
void* context) {
|
|
(void)context;
|
|
switch (change) {
|
|
case IoDeviceArrived:
|
|
IopDiskArrived(diskIndex, device);
|
|
break;
|
|
case IoDeviceSurpriseRemoved:
|
|
case IoDeviceRemoved:
|
|
IopDiskRemoved(diskIndex,
|
|
change == IoDeviceSurpriseRemoved
|
|
? IoPartitionSurpriseRemoved
|
|
: IoPartitionRemoved);
|
|
break;
|
|
case IoDeviceQueryRemove:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Replays one disk that predates partition-manager initialization.
|
|
static void IopReplayDisk(uint8_t diskIndex, DEVICE_OBJECT* device, void* context) {
|
|
(void)context;
|
|
IopDiskArrived(diskIndex, device);
|
|
}
|
|
|
|
// Initializes dynamic partition discovery and the mandatory structural system-volume selector.
|
|
KSTATUS IoInitializePartitionManager(const char* systemVolumeSelector) {
|
|
if (!systemVolumeSelector || !*systemVolumeSelector)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (gPartitionManagerInitialized)
|
|
return NTLSTATUS_ALREADY_EXISTS;
|
|
|
|
BOOT_VOLUME_SELECTOR selector = {0};
|
|
if (!BootVolumeSelectorParse(systemVolumeSelector, strlen(systemVolumeSelector), &selector))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
MUTEX* mutex = NULL;
|
|
KSTATUS status = KeMutexCreate(&mutex);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
gPartitionMutex = mutex;
|
|
gSystemVolumeSelector = selector;
|
|
|
|
status = IoRegisterDiskChangeCallback(IopDiskChange, NULL);
|
|
if (!KSUCCESS(status)) {
|
|
gPartitionMutex = NULL;
|
|
memset(&gSystemVolumeSelector, 0, sizeof(gSystemVolumeSelector));
|
|
ObDereferenceObject(&mutex->Header);
|
|
return status;
|
|
}
|
|
|
|
// The disk-change subscription is permanent, so the manager becomes live before replaying existing disks.
|
|
gPartitionManagerInitialized = true;
|
|
status = IoEnumerateDiskDevices(IopReplayDisk, NULL);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
DbgPrint("partition: dynamic discovery initialized for system_volume=%s\n", systemVolumeSelector);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Registers one partition-change consumer and replays every currently discovered partition before returning.
|
|
__declspec(dllexport) KSTATUS IoRegisterPartitionChangeCallback(IO_PARTITION_CHANGE_CALLBACK callback,
|
|
void* context) {
|
|
if (!callback || !gPartitionManagerInitialized)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
IOP_PARTITION_CALLBACK_ENTRY* entry = MmAllocatePool(sizeof(*entry));
|
|
if (!entry)
|
|
return NTLSTATUS_NO_MEMORY;
|
|
entry->Next = NULL;
|
|
entry->Callback = callback;
|
|
entry->Context = context;
|
|
|
|
if (!IopAcquirePartitionMutex()) {
|
|
MmFreePool(entry);
|
|
return NTLSTATUS_FAILURE;
|
|
}
|
|
|
|
IOP_PARTITION_CALLBACK_ENTRY** tail = &gPartitionCallbacks;
|
|
while (*tail)
|
|
tail = &(*tail)->Next;
|
|
*tail = entry;
|
|
|
|
for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next)
|
|
callback(&record->Info, IoPartitionArrived, context);
|
|
|
|
KeReleaseMutex(gPartitionMutex);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns the uniquely selected system partition, or NOT_FOUND/COLLISION while discovery is incomplete or ambiguous.
|
|
KSTATUS IoQuerySystemPartition(IO_PARTITION_INFO* partitionOut) {
|
|
if (!partitionOut || !gPartitionManagerInitialized)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
if (!IopAcquirePartitionMutex())
|
|
return NTLSTATUS_FAILURE;
|
|
|
|
const IOP_PARTITION_RECORD* match = NULL;
|
|
uint32_t matchCount = 0;
|
|
for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) {
|
|
if (!IopIdentityEquals(&record->Identity, &gSystemVolumeSelector))
|
|
continue;
|
|
match = record;
|
|
matchCount++;
|
|
}
|
|
|
|
KSTATUS status;
|
|
if (matchCount == 0) {
|
|
status = NTLSTATUS_NOT_FOUND;
|
|
} else if (matchCount != 1) {
|
|
status = NTLSTATUS_OBJECT_NAME_COLLISION;
|
|
} else {
|
|
*partitionOut = match->Info;
|
|
status = NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
KeReleaseMutex(gPartitionMutex);
|
|
return status;
|
|
}
|