Skip to content
API Documentation
BastionGuard™
hfs_format.cpp
Go to the documentation of this file.
1// hfs_format.cpp -- HFS+ / HFSX on-disk parsers.
2//
3// All parsers are pure: they read raw bytes and fill structs. They do
4// NOT perform I/O. Sanity checks reject obviously wrong values
5// (signature, block size, total blocks).
6
7#include "hfs_format.hpp"
8
9#include <algorithm>
10
11namespace hfs {
12
13bool parse_fork_data(const unsigned char* buf, ForkData& out) {
14 if (buf == nullptr) return false;
15 out = {};
16 out.logical_size = be64(buf + 0x00);
17 out.clump_size = be32(buf + 0x08);
18 out.total_blocks = be32(buf + 0x0C);
19
20 // 8 extent descriptors, each 8 bytes (start_block + block_count)
21 for (size_t i = 0; i < 8; ++i) {
22 const unsigned char* p = buf + 0x10 + i * 8;
23 out.extents[i].start_block = be32(p + 0);
24 out.extents[i].block_count = be32(p + 4);
25 }
26 return true;
27}
28
29bool parse_volume_header(const unsigned char* buf, VolumeHeader& out) {
30 if (buf == nullptr) return false;
31
32 out = {};
33 out.signature = be16(buf + 0x00);
35 return false;
36 }
37 out.version = be16(buf + 0x02);
38 // For HFS+ classic the version must be 4; for HFSX it must be 5.
39 if (out.is_hfsplus() && out.version != HFSPLUS_VERSION) return false;
40 if (out.is_hfsx() && out.version != HFSX_VERSION) return false;
41
42 out.attributes = be32(buf + 0x04);
43 out.last_mounted_version = be32(buf + 0x08);
44 out.journal_info_block = be32(buf + 0x0C);
45 out.create_date = be32(buf + 0x10);
46 out.modify_date = be32(buf + 0x14);
47 out.backup_date = be32(buf + 0x18);
48 out.checked_date = be32(buf + 0x1C);
49 out.file_count = be32(buf + 0x20);
50 out.folder_count = be32(buf + 0x24);
51 out.block_size = be32(buf + 0x28);
52 out.total_blocks = be32(buf + 0x2C);
53 out.free_blocks = be32(buf + 0x30);
54 out.next_allocation = be32(buf + 0x34);
55 out.rsrc_clump_size = be32(buf + 0x38);
56 out.data_clump_size = be32(buf + 0x3C);
57 out.next_catalog_id = be32(buf + 0x40);
58 out.write_count = be32(buf + 0x44);
59 out.encodings_bitmap = be64(buf + 0x48);
60
61 // finderInfo is 32 bytes at offset 0x50 (8 * uint32 in raw form, but
62 // we treat it opaquely)
63 std::copy(buf + 0x50, buf + 0x70, out.finder_info.begin());
64
65 // Five fork-data structs, each 80 bytes, starting at offset 0x70:
66 // allocationFile @ 0x070
67 // extentsFile @ 0x0C0
68 // catalogFile @ 0x110
69 // attributesFile @ 0x160
70 // startupFile @ 0x1B0
71 // End: 0x1B0 + 0x50 = 0x200 (= 512 bytes total).
72 if (!parse_fork_data(buf + 0x070, out.allocation_file)) return false;
73 if (!parse_fork_data(buf + 0x0C0, out.extents_file)) return false;
74 if (!parse_fork_data(buf + 0x110, out.catalog_file)) return false;
75 if (!parse_fork_data(buf + 0x160, out.attributes_file)) return false;
76 if (!parse_fork_data(buf + 0x1B0, out.startup_file)) return false;
77
78 // Sanity checks
79 // block_size must be a power of 2 in [512, 65536]
80 if (out.block_size < 512 || out.block_size > 65536 ||
81 (out.block_size & (out.block_size - 1)) != 0) {
82 return false;
83 }
84 // total_blocks must be plausible: at least the volume header occupies
85 // some blocks; reject zero. Also reject absurdly large values.
86 if (out.total_blocks == 0) return false;
87
88 // The catalog file must have a non-zero size (every HFS+ volume has
89 // at least the root folder and root thread record).
90 if (out.catalog_file.logical_size == 0) return false;
91 if (out.catalog_file.total_blocks == 0) return false;
92
93 // Sanity: the first extent of each special file must point INTO the
94 // volume range. Extent start_block + block_count <= total_blocks.
95 auto fork_in_range = [&](const ForkData& f) -> bool {
96 for (const auto& e : f.extents) {
97 if (e.block_count == 0) continue;
98 // Defensive: prevent overflow
99 if (e.start_block > out.total_blocks) return false;
100 const uint64_t end = static_cast<uint64_t>(e.start_block)
101 + e.block_count;
102 if (end > out.total_blocks) return false;
103 }
104 return true;
105 };
106 if (!fork_in_range(out.allocation_file)) return false;
107 if (!fork_in_range(out.extents_file)) return false;
108 if (!fork_in_range(out.catalog_file)) return false;
109 if (!fork_in_range(out.attributes_file)) return false;
110 if (!fork_in_range(out.startup_file)) return false;
111
112 return true;
113}
114
115} // namespace hfs
bool parse_volume_header(const unsigned char *buf, VolumeHeader &out)
constexpr uint16_t HFSX_SIGNATURE
uint64_t be64(const unsigned char *p)
bool parse_fork_data(const unsigned char *buf, ForkData &out)
constexpr uint16_t HFSPLUS_SIGNATURE
uint32_t be32(const unsigned char *p)
constexpr uint16_t HFSX_VERSION
constexpr uint16_t HFSPLUS_VERSION
uint16_t be16(const unsigned char *p)
uint64_t logical_size
std::array< ForkExtent, 8 > extents
uint32_t total_blocks
uint32_t clump_size
bool is_hfsx() const
uint32_t next_allocation
ForkData attributes_file
uint64_t encodings_bitmap
uint32_t data_clump_size
uint32_t next_catalog_id
uint32_t last_mounted_version
uint32_t rsrc_clump_size
uint32_t journal_info_block
ForkData allocation_file
bool is_hfsplus() const
std::array< uint8_t, 32 > finder_info