Skip to content
API Documentation
BastionGuard™
partition_parser.cpp
Go to the documentation of this file.
2#include "i18n.hpp"
3
4#include <array>
5#include <iomanip>
6#include <sstream>
7#include <string>
8#include <vector>
9
10namespace {
11
12uint32_t le32(const unsigned char* p) {
13 return static_cast<uint32_t>(p[0]) |
14 (static_cast<uint32_t>(p[1]) << 8) |
15 (static_cast<uint32_t>(p[2]) << 16) |
16 (static_cast<uint32_t>(p[3]) << 24);
17}
18
19uint64_t le64(const unsigned char* p) {
20 return static_cast<uint64_t>(p[0]) |
21 (static_cast<uint64_t>(p[1]) << 8) |
22 (static_cast<uint64_t>(p[2]) << 16) |
23 (static_cast<uint64_t>(p[3]) << 24) |
24 (static_cast<uint64_t>(p[4]) << 32) |
25 (static_cast<uint64_t>(p[5]) << 40) |
26 (static_cast<uint64_t>(p[6]) << 48) |
27 (static_cast<uint64_t>(p[7]) << 56);
28}
29
30std::string hex2(unsigned v) {
31 std::ostringstream oss;
32 oss << "0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << v;
33 return oss.str();
34}
35
36// Bug #8 fix: read GPT entries from a given header offset using the disk's
37// actual logical sector size, not a hardcoded 512.
39 uint64_t header_lba,
40 uint32_t sector_size,
41 std::vector<ScanResult>& out,
42 std::vector<std::string>& logs,
43 bool is_backup) {
44 std::array<unsigned char, 92> hdr{};
45 const uint64_t header_offset = header_lba * sector_size;
46
47 if (!reader.read_at(header_offset, hdr.data(), hdr.size())) {
48 logs.push_back(std::string("GPT: header read failed at offset ") + std::to_string(header_offset));
49 return false;
50 }
51
52 if (std::string(reinterpret_cast<const char*>(hdr.data()), 8) != "EFI PART") {
53 if (is_backup) {
54 logs.push_back(trs("GPT: backup EFI PART signature not found."));
55 } else {
56 logs.push_back(trs("GPT: EFI PART header not found."));
57 }
58 return false;
59 }
60
61 const uint64_t table_lba = le64(hdr.data() + 72);
62 const uint32_t num_entries = le32(hdr.data() + 80);
63 const uint32_t entry_size = le32(hdr.data() + 84);
64
65 if (num_entries == 0 || entry_size < 128 || entry_size > 4096) {
66 logs.push_back(trs("GPT: invalid partition table."));
67 return false;
68 }
69
70 const uint64_t table_offset = table_lba * sector_size;
71
72 for (uint32_t i = 0; i < num_entries; ++i) {
73 std::vector<unsigned char> entry(entry_size);
74 if (!reader.read_at(table_offset + static_cast<uint64_t>(i) * entry_size,
75 entry.data(), entry.size())) {
76 break;
77 }
78
79 // Bug #21 fix: check both type GUID AND first/last LBA for empty entry
80 bool type_guid_zero = true;
81 for (size_t k = 0; k < 16; ++k) {
82 if (entry[k] != 0) { type_guid_zero = false; break; }
83 }
84 const uint64_t first_lba = le64(entry.data() + 32);
85 const uint64_t last_lba = le64(entry.data() + 40);
86
87 if (type_guid_zero && first_lba == 0 && last_lba == 0) {
88 continue;
89 }
90 if (last_lba < first_lba || first_lba == 0) {
91 continue;
92 }
93
94 ScanResult r;
95 r.kind = ResultKind::Partition;
96 r.offset_bytes = first_lba * sector_size;
97 r.size_bytes = (last_lba - first_lba + 1) * sector_size;
98 r.type = is_backup ? trs("GPT partition (backup)") : trs("GPT partition");
99 r.source = is_backup ? trs("GPT backup") : trs("GPT");
100 r.detail = is_backup
101 ? trs("GPT partition table entry (recovered from backup header)")
102 : trs("GPT partition table entry");
103 r.confidence = is_backup ? 0.92 : 0.98;
104 out.push_back(r);
105 }
106
107 return true;
108}
109
110} // namespace
111
112std::vector<ScanResult> PartitionParser::parse_mbr(DiskReader& reader, std::vector<std::string>& logs) {
113 std::vector<ScanResult> out;
114 // Bug #8 fix: use the disk's logical sector size for partition LBA scaling
115 const uint32_t sector_size = reader.logical_sector_size() != 0
116 ? reader.logical_sector_size() : 512u;
117
118 std::vector<unsigned char> sector(sector_size);
119 if (!reader.read_at(0, sector.data(), sector.size())) {
120 logs.push_back(trs("MBR: read failed."));
121 return out;
122 }
123
124 if (sector.size() < 512 || !(sector[510] == 0x55 && sector[511] == 0xAA)) {
125 logs.push_back(trs("MBR: 0x55AA signature not found."));
126 return out;
127 }
128
129 logs.push_back(trs("MBR: valid signature found."));
130
131 for (int i = 0; i < 4; ++i) {
132 const size_t off = 446 + static_cast<size_t>(i) * 16;
133 const auto* entry = sector.data() + off;
134 const unsigned char ptype = entry[4];
135 const uint32_t start_lba = le32(entry + 8);
136 const uint32_t num_sectors = le32(entry + 12);
137 if (ptype == 0 || num_sectors == 0) {
138 continue;
139 }
140 ScanResult r;
141 r.kind = ResultKind::Partition;
142 r.offset_bytes = static_cast<uint64_t>(start_lba) * sector_size;
143 r.size_bytes = static_cast<uint64_t>(num_sectors) * sector_size;
144 r.type = trs("MBR ") + hex2(ptype);
145 r.source = trs("MBR");
146 r.detail = trs("MBR partition table entry");
147 r.confidence = 0.95;
148 out.push_back(r);
149 }
150 return out;
151}
152
153std::vector<ScanResult> PartitionParser::parse_gpt(DiskReader& reader, std::vector<std::string>& logs) {
154 std::vector<ScanResult> out;
155 // Bug #8 fix: dynamic sector size (4Kn drives need 4096, not 512)
156 const uint32_t sector_size = reader.logical_sector_size() != 0
157 ? reader.logical_sector_size() : 512u;
158
159 // Primary GPT header is at LBA 1
160 const bool primary_ok = parse_gpt_table(reader, 1, sector_size, out, logs, false);
161 if (primary_ok && !out.empty()) {
162 logs.push_back(trs("GPT: valid header found."));
163 return out;
164 }
165
166 // Bug #9 fix: fall back to backup GPT header at the last sector of the disk.
167 // The backup header itself sits at the very last LBA; the entry table sits
168 // BEFORE it. We just read the header and trust its table_lba field.
169 const uint64_t disk_size = reader.size();
170 if (disk_size < static_cast<uint64_t>(sector_size) * 2) {
171 return out;
172 }
173 const uint64_t backup_header_lba = (disk_size / sector_size) - 1;
174 logs.push_back(trs("GPT: trying backup header at LBA ") + std::to_string(backup_header_lba));
175
176 std::vector<ScanResult> backup_out;
177 const bool backup_ok = parse_gpt_table(reader, backup_header_lba, sector_size,
178 backup_out, logs, true);
179 if (backup_ok && !backup_out.empty()) {
180 logs.push_back(trs("GPT: recovered partitions from backup header."));
181 return backup_out;
182 }
183
184 return out;
185}
uint32_t logical_sector_size() const
bool read_at(uint64_t offset, void *buffer, size_t len)
uint64_t size() const
static std::vector< ScanResult > parse_mbr(DiskReader &reader, std::vector< std::string > &logs)
static std::vector< ScanResult > parse_gpt(DiskReader &reader, std::vector< std::string > &logs)
std::string trs(const char *msgid)
Definition i18n.cpp:22
@ Partition
Definition models.hpp:8
uint64_t le64(const unsigned char *p)
bool parse_gpt_table(DiskReader &reader, uint64_t header_lba, uint32_t sector_size, std::vector< ScanResult > &out, std::vector< std::string > &logs, bool is_backup)
uint32_t le32(const unsigned char *p)