/** @file Copyright (c) 2022, Mikhail Krichanov. All rights reserved. SPDX-License-Identifier: BSD-3-Clause Functional and structural descriptions follow NTFS Documentation by Richard Russon and Yuval Fledel **/ #include "NTFS.h" #include "Helper.h" UINT64 mUnitSize; STATIC UINT64 GetLcn ( IN RUNLIST *Runlist, IN UINT64 Vcn ) { EFI_STATUS Status; UINT64 Delta; ASSERT (Runlist != NULL); if (Vcn >= Runlist->NextVcn) { Status = ReadRunListElement (Runlist); if (EFI_ERROR (Status)) { return (UINT64)(-1); } return Runlist->CurrentLcn; } Delta = Vcn - Runlist->CurrentVcn; return Runlist->IsSparse ? 0 : (Runlist->CurrentLcn + Delta); } STATIC EFI_STATUS ReadClusters ( IN RUNLIST *Runlist, IN UINT64 Offset, IN UINTN Length, OUT UINT8 *Dest ) { EFI_STATUS Status; UINT64 Index; UINT64 ClustersTotal; UINT64 Cluster; UINT64 OffsetInsideCluster; UINTN Size; UINTN ClusterSize; ASSERT (Runlist != NULL); ASSERT (Dest != NULL); ClusterSize = Runlist->Unit.FileSystem->ClusterSize; OffsetInsideCluster = Offset & (ClusterSize - 1U); Size = ClusterSize; ClustersTotal = DivU64x64Remainder (Length + Offset + ClusterSize - 1U, ClusterSize, NULL); for (Index = Runlist->TargetVcn; Index < ClustersTotal; ++Index) { Cluster = GetLcn (Runlist, Index); if (Cluster == (UINT64)(-1)) { return EFI_DEVICE_ERROR; } Cluster *= ClusterSize; if (Index == (ClustersTotal - 1U)) { Size = (UINTN)((Length + Offset) & (ClusterSize - 1U)); if (Size == 0) { Size = ClusterSize; } if (Index != Runlist->TargetVcn) { OffsetInsideCluster = 0; } } if (Index == Runlist->TargetVcn) { Size -= (UINTN)OffsetInsideCluster; } if ((Index != Runlist->TargetVcn) && (Index != (ClustersTotal - 1U))) { Size = ClusterSize; OffsetInsideCluster = 0; } if (Cluster != 0) { Status = DiskRead ( Runlist->Unit.FileSystem, Cluster + OffsetInsideCluster, Size, Dest ); if (EFI_ERROR (Status)) { return Status; } } else { SetMem (Dest, Size, 0); } Dest += Size; } return EFI_SUCCESS; } EFI_STATUS EFIAPI DiskRead ( IN EFI_FS *FileSystem, IN UINT64 Offset, IN UINTN Size, IN OUT VOID *Buffer ) { EFI_STATUS Status; EFI_BLOCK_IO_MEDIA *Media; ASSERT (FileSystem != NULL); ASSERT (FileSystem->DiskIo != NULL); ASSERT (FileSystem->BlockIo != NULL); Media = FileSystem->BlockIo->Media; Status = FileSystem->DiskIo->ReadDisk ( FileSystem->DiskIo, Media->MediaId, Offset, Size, Buffer ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "NTFS: Could not read disk at address %Lx\n", Offset)); } return Status; } EFI_STATUS EFIAPI ReadMftRecord ( IN EFI_NTFS_FILE *File, OUT UINT8 *Buffer, IN UINT64 RecordNumber ) { EFI_STATUS Status; UINTN FileRecordSize; ASSERT (File != NULL); ASSERT (Buffer != NULL); FileRecordSize = File->FileSystem->FileRecordSize; Status = ReadAttr ( &File->MftFile.Attr, Buffer, RecordNumber * FileRecordSize, FileRecordSize ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "NTFS: Could not read MFT Record 0x%Lx\n", RecordNumber)); FreeAttr (&File->MftFile.Attr); return Status; } return Fixup ( Buffer, FileRecordSize, SIGNATURE_32 ('F', 'I', 'L', 'E'), File->FileSystem->SectorSize ); } EFI_STATUS EFIAPI ReadAttr ( IN NTFS_ATTR *Attr, OUT UINT8 *Dest, IN UINT64 Offset, IN UINTN Length ) { EFI_STATUS Status; UINT8 *Current; UINT32 Type; UINT8 *AttrStart; UINT64 Vcn; ATTR_LIST_RECORD *Record; ATTR_HEADER_RES *Res; UINTN ClusterSize; ASSERT (Attr != NULL); ASSERT (Dest != NULL); Current = Attr->Current; Attr->Next = Attr->Current; ClusterSize = Attr->BaseMftRecord->File->FileSystem->ClusterSize; if ((Attr->Flags & NTFS_AF_ALST) != 0) { if (Attr->Last < (Attr->Next + sizeof (ATTR_LIST_RECORD))) { DEBUG ((DEBUG_INFO, "NTFS: $ATTRIBUTE_LIST does not contain even a single record.\n")); return EFI_VOLUME_CORRUPTED; } Record = (ATTR_LIST_RECORD *)Attr->Next; Type = Record->Type; Vcn = DivU64x64Remainder (Offset, ClusterSize, NULL); if (COMPRESSION_BLOCK >= ClusterSize) { Vcn &= ~0xFULL; } Record = (ATTR_LIST_RECORD *)((UINT8 *)Record + Record->RecordLength); while (((UINT8 *)Record + sizeof (ATTR_LIST_RECORD)) <= Attr->Last) { if (Record->Type != Type) { break; } if (Record->StartingVCN > Vcn) { break; } if (Record->RecordLength == 0) { return EFI_VOLUME_CORRUPTED; } Attr->Next = (UINT8 *)Record; Record = (ATTR_LIST_RECORD *)((UINT8 *)Record + Record->RecordLength); } } else { Res = (ATTR_HEADER_RES *)Attr->Next; Type = Res->Type; } AttrStart = FindAttr (Attr, Type); if (AttrStart != NULL) { Status = ReadData (Attr, AttrStart, Dest, Offset, Length); } else { DEBUG ((DEBUG_INFO, "NTFS: Attribute not found\n")); Status = EFI_VOLUME_CORRUPTED; } Attr->Current = Current; return Status; } EFI_STATUS EFIAPI ReadData ( IN NTFS_ATTR *Attr, IN UINT8 *AttrStart, OUT UINT8 *Dest, IN UINT64 Offset, IN UINTN Length ) { EFI_STATUS Status; RUNLIST *Runlist; ATTR_HEADER_NONRES *NonRes; ATTR_HEADER_RES *Res; UINT64 Sector0; UINT64 Sector1; UINT64 OffsetInsideCluster; UINT64 BufferSize; UINTN FileRecordSize; UINTN SectorSize; UINTN ClusterSize; if (Length == 0) { return EFI_SUCCESS; } FileRecordSize = Attr->BaseMftRecord->File->FileSystem->FileRecordSize; SectorSize = Attr->BaseMftRecord->File->FileSystem->SectorSize; ClusterSize = Attr->BaseMftRecord->File->FileSystem->ClusterSize; BufferSize = FileRecordSize - (Attr->Current - Attr->BaseMftRecord->FileRecord); // // Resident Attribute // if (BufferSize < sizeof (ATTR_HEADER_RES)) { DEBUG ((DEBUG_INFO, "NTFS: (ReadData #1) Attribute is corrupted.\n")); return EFI_VOLUME_CORRUPTED; } Res = (ATTR_HEADER_RES *)AttrStart; if (Res->NonResFlag == 0) { if ((Offset + Length) > Res->InfoLength) { DEBUG ((DEBUG_INFO, "NTFS: Read out of range\n")); return EFI_VOLUME_CORRUPTED; } if (BufferSize < (Res->InfoOffset + Offset + Length)) { DEBUG ((DEBUG_INFO, "NTFS: Read out of buffer.\n")); return EFI_VOLUME_CORRUPTED; } CopyMem (Dest, (UINT8 *)Res + Res->InfoOffset + Offset, Length); return EFI_SUCCESS; } // // Non-Resident Attribute // if (BufferSize < sizeof (ATTR_HEADER_NONRES)) { DEBUG ((DEBUG_INFO, "NTFS: (ReadData #2) Attribute is corrupted.\n")); return EFI_VOLUME_CORRUPTED; } NonRes = (ATTR_HEADER_NONRES *)AttrStart; Runlist = (RUNLIST *)AllocateZeroPool (sizeof (RUNLIST)); if (Runlist == NULL) { return EFI_OUT_OF_RESOURCES; } Runlist->Attr = Attr; Runlist->Unit.FileSystem = Attr->BaseMftRecord->File->FileSystem; if ( (NonRes->DataRunsOffset > BufferSize) || (NonRes->DataRunsOffset > NonRes->RealSize) || (NonRes->RealSize > NonRes->AllocatedSize)) { DEBUG ((DEBUG_INFO, "NTFS: Non-Resident Attribute is corrupted.\n")); FreePool (Runlist); return EFI_VOLUME_CORRUPTED; } Runlist->NextDataRun = (UINT8 *)NonRes + NonRes->DataRunsOffset; Runlist->NextVcn = NonRes->StartingVCN; Runlist->CurrentLcn = 0; Runlist->TargetVcn = NonRes->StartingVCN + DivU64x64Remainder (Offset, ClusterSize, NULL); if (Runlist->TargetVcn < NonRes->StartingVCN) { DEBUG ((DEBUG_INFO, "NTFS: Overflow: StartingVCN is too large.\n")); FreePool (Runlist); return EFI_VOLUME_CORRUPTED; } if ((Offset + Length) > NonRes->RealSize) { DEBUG ((DEBUG_INFO, "NTFS: Read out of range\n")); FreePool (Runlist); return EFI_VOLUME_CORRUPTED; } if ( ((NonRes->Flags & FLAG_COMPRESSED) != 0) && ((Attr->Flags & NTFS_AF_GPOS) == 0) && (NonRes->Type == AT_DATA)) { if (NonRes->CompressionUnitSize != 4U) { DEBUG ((DEBUG_INFO, "NTFS: Invalid copmression unit size.\n")); FreePool (Runlist); return EFI_VOLUME_CORRUPTED; } mUnitSize = LShiftU64 (1ULL, NonRes->CompressionUnitSize); Status = Decompress (Runlist, Offset, Length, Dest); FreePool (Runlist); return Status; } while (Runlist->NextVcn <= Runlist->TargetVcn) { Status = ReadRunListElement (Runlist); if (EFI_ERROR (Status)) { FreePool (Runlist); return EFI_VOLUME_CORRUPTED; } } if (Attr->Flags & NTFS_AF_GPOS) { OffsetInsideCluster = Offset & (ClusterSize - 1U); Sector0 = DivU64x64Remainder ( (Runlist->TargetVcn - Runlist->CurrentVcn + Runlist->CurrentLcn) * ClusterSize + OffsetInsideCluster, SectorSize, NULL ); Sector1 = Sector0 + 1U; if (Sector1 == DivU64x64Remainder ( (Runlist->NextVcn - Runlist->CurrentVcn + Runlist->CurrentLcn) * ClusterSize, SectorSize, NULL )) { Status = ReadRunListElement (Runlist); if (EFI_ERROR (Status)) { FreePool (Runlist); return EFI_VOLUME_CORRUPTED; } Sector1 = DivU64x64Remainder ( Runlist->CurrentLcn * ClusterSize, SectorSize, NULL ); } WriteUnaligned32 ((UINT32 *)Dest, (UINT32)Sector0); WriteUnaligned32 ((UINT32 *)(Dest + 4U), (UINT32)Sector1); FreePool (Runlist); return EFI_SUCCESS; } Status = ReadClusters ( Runlist, Offset, Length, Dest ); FreePool (Runlist); return Status; } STATIC UINT64 ReadField ( IN CONST UINT8 *Run, IN UINT8 FieldSize, IN BOOLEAN Signed ) { UINT64 Value; ASSERT (Run != NULL); Value = 0; // // Offset to the starting LCN of the previous element is a signed value. // So we must check the most significant bit. // if (Signed && (FieldSize != 0) && ((Run[FieldSize - 1U] & 0x80U) != 0)) { Value = (UINT64)(-1); } CopyMem (&Value, Run, FieldSize); return Value; } /** Table 4.10. Layout of a Data Run ____________________________________________________________________ Offset | Size | Description -------------------------------------------------------------------- 0 | 0.5 | F=Size of the Offset field 0.5 | 0.5 | L=Size of the Length field 1 | L | Length of the run 1+L | F | Offset to the starting LCN of the previous element (signed) -------------------------------------------------------------------- **/ EFI_STATUS EFIAPI ReadRunListElement ( IN OUT RUNLIST *Runlist ) { UINT8 LengthSize; UINT8 OffsetSize; UINT64 OffsetLcn; UINT8 *Run; ATTR_HEADER_NONRES *Attr; UINT64 BufferSize; UINTN FileRecordSize; ASSERT (Runlist != NULL); Run = Runlist->NextDataRun; FileRecordSize = Runlist->Attr->BaseMftRecord->File->FileSystem->FileRecordSize; BufferSize = FileRecordSize - (Run - Runlist->Attr->BaseMftRecord->FileRecord); retry: if (BufferSize == 0) { DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #1) Runlist is corrupted.\n")); return EFI_VOLUME_CORRUPTED; } LengthSize = *Run & 0xFU; OffsetSize = (*Run >> 4U) & 0xFU; ++Run; --BufferSize; if ( (LengthSize > 8U) || (OffsetSize > 8U) || ((LengthSize == 0) && (OffsetSize != 0))) { DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #2) Runlist is corrupted.\n")); return EFI_VOLUME_CORRUPTED; } // // End of Runlist: LengthSize == 0, OffsetSize == 0 // if ((LengthSize == 0) && (OffsetSize == 0)) { if ( (Runlist->Attr != NULL) && ((Runlist->Attr->Flags & NTFS_AF_ALST) != 0)) { Attr = (ATTR_HEADER_NONRES *)Runlist->Attr->Current; Attr = (ATTR_HEADER_NONRES *)FindAttr (Runlist->Attr, Attr->Type); if (Attr != NULL) { if (Attr->NonResFlag == 0) { DEBUG ((DEBUG_INFO, "NTFS: $DATA should be non-resident\n")); return EFI_VOLUME_CORRUPTED; } Run = (UINT8 *)Attr + Attr->DataRunsOffset; Runlist->CurrentLcn = 0; BufferSize = FileRecordSize - Attr->DataRunsOffset; goto retry; } } DEBUG ((DEBUG_INFO, "NTFS: Run list overflown\n")); return EFI_VOLUME_CORRUPTED; } Runlist->CurrentVcn = Runlist->NextVcn; if (BufferSize < LengthSize) { DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #3) Runlist is corrupted.\n")); return EFI_VOLUME_CORRUPTED; } Runlist->NextVcn += ReadField (Run, LengthSize, FALSE); if (Runlist->NextVcn <= Runlist->CurrentVcn) { DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #3.1) Runlist is corrupted.\n")); return EFI_VOLUME_CORRUPTED; } Run += LengthSize; BufferSize -= LengthSize; if (BufferSize < OffsetSize) { DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #4) Runlist is corrupted.\n")); return EFI_VOLUME_CORRUPTED; } OffsetLcn = ReadField (Run, OffsetSize, TRUE); Runlist->CurrentLcn += OffsetLcn; Run += OffsetSize; BufferSize -= OffsetSize; Runlist->NextDataRun = Run; Runlist->IsSparse = (OffsetLcn == 0); return EFI_SUCCESS; } CHAR16 * ReadSymlink ( IN NTFS_FILE *File ) { EFI_STATUS Status; SYMLINK Symlink; CHAR16 *Substitute; CHAR16 *Letter; UINT64 Offset; ASSERT (File != NULL); File->FileRecord = AllocateZeroPool (File->File->FileSystem->FileRecordSize); if (File->FileRecord == NULL) { return NULL; } Status = ReadMftRecord (File->File, File->FileRecord, File->Inode); if (EFI_ERROR (Status)) { return NULL; } if (LocateAttr (&File->Attr, File, AT_SYMLINK) == NULL) { DEBUG ((DEBUG_INFO, "NTFS: no $SYMLINK in MFT 0x%Lx\n", File->Inode)); return NULL; } Status = ReadAttr (&File->Attr, (UINT8 *)&Symlink, 0, sizeof (Symlink)); if (EFI_ERROR (Status)) { FreeAttr (&File->Attr); return NULL; } switch (Symlink.Type) { case (IS_ALIAS | IS_MICROSOFT | S_SYMLINK): Offset = sizeof (Symlink) + 4U + (UINT64)Symlink.SubstituteOffset; break; case (IS_ALIAS | IS_MICROSOFT | S_FILENAME): Offset = sizeof (Symlink) + (UINT64)Symlink.SubstituteOffset; break; default: DEBUG ((DEBUG_INFO, "NTFS: Symlink type invalid (%x)\n", Symlink.Type)); return NULL; } Substitute = AllocateZeroPool (Symlink.SubstituteLength + sizeof (CHAR16)); if (Substitute == NULL) { return NULL; } Status = ReadAttr (&File->Attr, (UINT8 *)Substitute, Offset, Symlink.SubstituteLength); if (EFI_ERROR (Status)) { FreeAttr (&File->Attr); FreePool (Substitute); return NULL; } for (Letter = Substitute; *Letter != L'\0'; ++Letter) { if (*Letter == L'\\') { *Letter = L'/'; } } return Substitute; }