From ebaa7d561a262e96830c03ac98e3689f2a70e6d0 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 4 Feb 2022 17:41:44 +0000 Subject: [PATCH] [core] add file System detection from superblock * Also prevent GitHub Actions from failing on VirusTotal upload --- .github/workflows/codeql.nope | 1 + .github/workflows/mingw.yml | 1 + .github/workflows/vs2022.yml | 1 + src/drive.c | 131 +++++++++++++++++++++++++++++++++- src/rufus.c | 3 +- src/rufus.rc | 10 +-- 6 files changed, 140 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.nope b/.github/workflows/codeql.nope index 2209fa97..e1c70317 100644 --- a/.github/workflows/codeql.nope +++ b/.github/workflows/codeql.nope @@ -1,3 +1,4 @@ +# Disabled on account of https://github.com/github/codeql-action/issues/850 name: "CodeQL" on: diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 2552b3ef..d17ca45b 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -80,6 +80,7 @@ jobs: run: sha256sum ./rufus.exe - name: Upload to VirusTotal + continue-on-error: true if: ${{ matrix.env == 'i686' && github.event_name == 'push' }} run: | curl --request POST --url https://www.virustotal.com/vtapi/v2/file/scan --form apikey=${{ secrets.VIRUSTOTAL_API_KEY }} --form file=@./rufus.exe diff --git a/.github/workflows/vs2022.yml b/.github/workflows/vs2022.yml index 227d4c56..488bc216 100644 --- a/.github/workflows/vs2022.yml +++ b/.github/workflows/vs2022.yml @@ -70,6 +70,7 @@ jobs: run: sha256sum ./rufus_${{ matrix.TARGET_PLATFORM }}.exe - name: Upload to VirusTotal + continue-on-error: true if: ${{ github.event_name == 'push' }} run: | curl --request POST --url https://www.virustotal.com/vtapi/v2/file/scan --form apikey=${{ secrets.VIRUSTOTAL_API_KEY }} --form file=@./rufus_${{ matrix.TARGET_PLATFORM }}.exe diff --git a/src/drive.c b/src/drive.c index f8bceeda..f1079b96 100644 --- a/src/drive.c +++ b/src/drive.c @@ -1638,6 +1638,132 @@ out: return ret; } +// This is a crude attempt at detecting file systems through their superblock magic. +// Note that we only attempt to detect the file systems that Rufus can actually format. +const char* GetFsName(HANDLE hPhysical, LARGE_INTEGER StartingOffset) +{ + typedef struct { + const char* name; + const uint8_t magic[8]; + } win_fs_type; + const win_fs_type win_fs_types[] = { + { "exFAT", { 'E', 'X', 'F', 'A', 'T', ' ', ' ', ' ' } }, + { "NTFS", { 'N', 'T', 'F', 'S', ' ', ' ', ' ', ' ' } }, + { "ReFS", { 'R', 'e', 'F', 'S', 0, 0, 0, 0 } } + }; + const win_fs_type fat_fs_types[] = { + { "FAT", { 'F', 'A', 'T', ' ', ' ', ' ', ' ', ' ' } }, + { "FAT12", { 'F', 'A', 'T', '1', '2', ' ', ' ', ' ' } }, + { "FAT16", { 'F', 'A', 'T', '1', '6', ' ', ' ', ' ' } }, + { "FAT32", { 'F', 'A', 'T', '3', '2', ' ', ' ', ' ' } }, + }; + const uint32_t ext_feature[3][3] = { + // feature_compat + { 0x0000017B, 0x00000004, 0x00000E00 }, + // feature_ro_compat + { 0x00000003, 0x00000000, 0x00008FF8 }, + // feature_incompat + { 0x00000013, 0x0000004C, 0x0003F780 } + }; + const char* ext_names[] = { "ext", "ext2", "ext3", "ext4" }; + const char* ret = "(Unrecognized)"; + DWORD i, j, offset, size, sector_size = 512; + uint8_t* buf = calloc(sector_size, 1); + if (buf == NULL) + goto out; + + // 1. Try to detect FAT/exFAT/NTFS/ReFS through the 512 bytes superblock at offset 0 + if (!SetFilePointerEx(hPhysical, StartingOffset, NULL, FILE_BEGIN)) + goto out; + if (!ReadFile(hPhysical, buf, sector_size, &size, NULL) || size != sector_size) + goto out; + + + // The beginning of a superblock for FAT/exFAT/NTFS/ReFS is pretty much always the same: + // There are 3 bytes potentially used for a jump instruction, and then are 8 bytes of + // OEM Name which, even if *not* technically correct, we are going to assume hold an + // immutable file system magic for exFAT/NTFS/ReFS (but not for FAT, see below). + for (i = 0; i < ARRAYSIZE(win_fs_types); i++) + if (memcmp(&buf[0x03], win_fs_types[i].magic, 8) == 0) + break; + if (i < ARRAYSIZE(win_fs_types)) { + ret = win_fs_types[i].name; + goto out; + } + + // For FAT, because the OEM Name may actually be set to something else than what we + // expect, we poke the FAT12/16 Extended BIOS Parameter Block: + // https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#Extended_BIOS_Parameter_Block + // or FAT32 Extended BIOS Parameter Block: + // https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#FAT32_Extended_BIOS_Parameter_Block + for (offset = 0x36; offset <= 0x52; offset += 0x1C) { + for (i = 0; i < ARRAYSIZE(fat_fs_types); i++) + if (memcmp(&buf[offset], fat_fs_types[i].magic, 8) == 0) + break; + if (i < ARRAYSIZE(fat_fs_types)) { + ret = fat_fs_types[i].name; + goto out; + } + } + + // 2. Try to detect Apple AFS/HFS/HFS+ through the 512 bytes superblock at either offset 0 or 1024 + // "NXSB" at offset 0x20 => APFS + if (strncmp("NXSB", &buf[0x20], 4) == 0) { + ret = "APFS"; + goto out; + } + // Switch to offset 1024 + memset(buf, sector_size, 0); + StartingOffset.QuadPart += 0x0400ULL; + if (!SetFilePointerEx(hPhysical, StartingOffset, NULL, FILE_BEGIN)) + goto out; + if (!ReadFile(hPhysical, buf, sector_size, &size, NULL) || size != sector_size) + goto out; + // "HX" or "H+" at offset 0x00 => HFS/HFS+ + if (buf[0] == 'H' && (buf[1] == 'X' || buf[1] == '+')) { + ret = "HFS/HFS+"; + goto out; + } + + // 3. Try to detect ext2/ext3/ext4 through the 512 bytes superblock at offset 1024 + // We're already at the right offset + if (!SetFilePointerEx(hPhysical, StartingOffset, NULL, FILE_BEGIN)) + goto out; + if (!ReadFile(hPhysical, buf, sector_size, &size, NULL) || size != sector_size) + goto out; + if (buf[0x38] == 0x53 && buf[0x39] == 0xEF) { + uint32_t rev = 0; + for (i = 0; i < 3; i++) { + uint32_t feature = *((uint32_t*)&buf[0x5C + 4 * i]); + for (j = 0; j < 3; j++) { + if (feature & ext_feature[i][j] && rev <= j) + rev = j + 1; + } + } + assert(rev < ARRAYSIZE(ext_names)); + ret = ext_names[rev]; + goto out; + } + + // 4. Try to detect UDF through by looking for a "BEA01\0" string at offset 0xC001 + // NB: This is not thorough UDF detection but good enough for our purpose. + // For the full specs see: http://www.osta.org/specs/pdf/udf260.pdf + memset(buf, sector_size, 0); + StartingOffset.QuadPart += 0x8000ULL - 0x0400ULL; + if (!SetFilePointerEx(hPhysical, StartingOffset, NULL, FILE_BEGIN)) + goto out; + if (!ReadFile(hPhysical, buf, sector_size, &size, NULL) || size != sector_size) + goto out; + if (strncmp("BEA01", &buf[1], 5) == 0) { + ret = "UDF"; + goto out; + } + +out: + free(buf); + return ret; +} + /* * Fill the drive properties (size, FS, etc) * Returns TRUE if the drive has a partition that can be mounted in Windows, FALSE otherwise @@ -1754,9 +1880,11 @@ BOOL GetDrivePartitionData(DWORD DriveIndex, char* FileSystemName, DWORD FileSys SelectedDrive.PartitionOffset[i] = DriveLayout->PartitionEntry[i].StartingOffset.QuadPart; SelectedDrive.PartitionSize[i] = DriveLayout->PartitionEntry[i].PartitionLength.QuadPart; } - suprintf(" Type: %s (0x%02x)\r\n Size: %s (%lld bytes)\r\n Start Sector: %lld, Boot: %s", + suprintf(" Type: %s (0x%02x)\r\n Detected File System: %s\r\n" + " Size: %s (%lld bytes)\r\n Start Sector: %lld, Boot: %s", ((part_type == 0x07 || super_floppy_disk) && (FileSystemName[0] != 0)) ? FileSystemName : GetMBRPartitionType(part_type), super_floppy_disk ? 0: part_type, + GetFsName(hPhysical, DriveLayout->PartitionEntry[i].StartingOffset), SizeToHumanReadable(DriveLayout->PartitionEntry[i].PartitionLength.QuadPart, TRUE, FALSE), DriveLayout->PartitionEntry[i].PartitionLength.QuadPart, DriveLayout->PartitionEntry[i].StartingOffset.QuadPart / SelectedDrive.SectorSize, @@ -1789,6 +1917,7 @@ BOOL GetDrivePartitionData(DWORD DriveIndex, char* FileSystemName, DWORD FileSys GetGPTPartitionType(&DriveLayout->PartitionEntry[i].Gpt.PartitionType)); if (DriveLayout->PartitionEntry[i].Gpt.Name[0] != 0) suprintf(" Name: '%S'", DriveLayout->PartitionEntry[i].Gpt.Name); + suprintf(" Detected File System: %s", GetFsName(hPhysical, DriveLayout->PartitionEntry[i].StartingOffset)); suprintf(" ID: %s\r\n Size: %s (%" PRIi64 " bytes)\r\n Start Sector: %" PRIi64 ", Attributes: 0x%016" PRIX64, GuidToString(&DriveLayout->PartitionEntry[i].Gpt.PartitionId), SizeToHumanReadable(DriveLayout->PartitionEntry[i].PartitionLength.QuadPart, TRUE, FALSE), diff --git a/src/rufus.c b/src/rufus.c index dfab1b0f..ff72eb2b 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -406,7 +406,8 @@ static BOOL IsRefsAvailable(MEDIA_TYPE MediaType) return FALSE; if (nWindowsVersion < WINDOWS_8_1 || nWindowsBuildNumber <= 0) return FALSE; - if (nWindowsBuildNumber < 16000) + // Per https://gist.github.com/0xbadfca11/da0598e47dd643d933dc + if (nWindowsBuildNumber < 16226) return TRUE; switch (nWindowsEdition) { case 0x0000000A: // Enterprise Server diff --git a/src/rufus.rc b/src/rufus.rc index 9fda43af..7c87f66c 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 3.18.1866" +CAPTION "Rufus 3.18.1867" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -395,8 +395,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,18,1866,0 - PRODUCTVERSION 3,18,1866,0 + FILEVERSION 3,18,1867,0 + PRODUCTVERSION 3,18,1867,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -414,13 +414,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "3.18.1866" + VALUE "FileVersion", "3.18.1867" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2022 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-3.18.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "3.18.1866" + VALUE "ProductVersion", "3.18.1867" END END BLOCK "VarFileInfo"