Skip to content
API Documentation
BastionGuard™
hfs::anonymous_namespace{hfs_btree.cpp} Namespace Reference

Functions

bool descend (uint32_t node_index, uint16_t expected_height, const HeaderRec &header, const NodeReader &read_node, const LeafRecordCallback &on_leaf, std::set< uint32_t > &visited, size_t &nodes_visited, std::vector< std::string > &logs, std::string &error, bool &callback_stopped)

Variables

constexpr size_t MAX_NODES_VISITED = 1'000'000

Function Documentation

◆ descend()

bool hfs::anonymous_namespace{hfs_btree.cpp}::descend ( uint32_t node_index,
uint16_t expected_height,
const HeaderRec & header,
const NodeReader & read_node,
const LeafRecordCallback & on_leaf,
std::set< uint32_t > & visited,
size_t & nodes_visited,
std::vector< std::string > & logs,
std::string & error,
bool & callback_stopped )

Definition at line 166 of file hfs_btree.cpp.

174 {
175 if (callback_stopped) return true;
176 if (node_index == 0) {
177 // Node 0 is always the header node; descending into it from an
178 // index pointer means corruption.
179 return true;
180 }
181 if (++nodes_visited > MAX_NODES_VISITED) {
182 error = "HFS+ B-tree: traversal cap reached (possible loop)";
183 return false;
184 }
185 if (!visited.insert(node_index).second) {
186 std::ostringstream m;
187 m << "HFS+ B-tree: cycle detected at node " << node_index << ", skip";
188 logs.push_back(m.str());
189 return true;
190 }
191
192 std::vector<unsigned char> node;
193 if (!read_node(node_index, node)) {
194 std::ostringstream m;
195 m << "HFS+ B-tree: read failed for node " << node_index;
196 logs.push_back(m.str());
197 return true; // not fatal; just lose this branch
198 }
199 if (node.size() < header.nodeSize) {
200 std::ostringstream m;
201 m << "HFS+ B-tree: short read for node " << node_index;
202 logs.push_back(m.str());
203 return true;
204 }
205
206 NodeDescriptor nd;
207 if (!parse_node_descriptor(node.data(), nd)) {
208 std::ostringstream m;
209 m << "HFS+ B-tree: bad descriptor at node " << node_index;
210 logs.push_back(m.str());
211 return true;
212 }
213 // height check is informational; HFS+ headers historically populate
214 // height in different ways across implementations. Don't enforce.
215 (void)expected_height;
216
217 if (nd.kind != kBTLeafNode && nd.kind != kBTIndexNode) {
218 std::ostringstream m;
219 m << "HFS+ B-tree: unexpected kind=" << static_cast<int>(nd.kind)
220 << " at node " << node_index;
221 logs.push_back(m.str());
222 return true;
223 }
224
225 for (uint16_t i = 0; i < nd.numRecords; ++i) {
226 const uint16_t rec_off = node_record_offset(node.data(),
227 header.nodeSize,
228 nd.numRecords, i);
229 const uint16_t rec_len = node_record_length(node.data(),
230 header.nodeSize,
231 nd.numRecords, i);
232 if (rec_off == 0 || rec_len == 0) continue;
233 if (rec_off + rec_len > header.nodeSize) continue;
234
235 const unsigned char* recp = node.data() + rec_off;
236
237 if (nd.kind == kBTLeafNode) {
238 if (!on_leaf(recp, rec_len)) {
239 callback_stopped = true;
240 return true;
241 }
242 } else {
243 // Index record: [catalog key][__be32 child node pointer]
244 const uint32_t body_off = catalog_record_body_offset(recp, rec_len);
245 if (body_off == 0 || body_off + 4 > rec_len) continue;
246 const uint32_t child = be32(recp + body_off);
247 if (child == 0 || child >= header.totalNodes) {
248 std::ostringstream m;
249 m << "HFS+ B-tree: index node " << node_index
250 << " has implausible child pointer " << child;
251 logs.push_back(m.str());
252 continue;
253 }
254 if (!descend(child,
255 (expected_height > 0) ? expected_height - 1 : 0,
256 header, read_node, on_leaf,
257 visited, nodes_visited, logs, error,
258 callback_stopped)) {
259 return false;
260 }
261 if (callback_stopped) return true;
262 }
263 }
264 return true;
265}
bool descend(uint32_t node_index, uint16_t expected_height, const HeaderRec &header, const NodeReader &read_node, const LeafRecordCallback &on_leaf, std::set< uint32_t > &visited, size_t &nodes_visited, std::vector< std::string > &logs, std::string &error, bool &callback_stopped)
bool parse_node_descriptor(const unsigned char *buf, NodeDescriptor &out)
Definition hfs_btree.cpp:17
constexpr int8_t kBTIndexNode
Definition hfs_btree.hpp:33
uint32_t be32(const unsigned char *p)
uint16_t node_record_length(const unsigned char *node_buf, uint16_t nodeSize, uint16_t numRecords, uint16_t i)
Definition hfs_btree.cpp:88
uint16_t node_record_offset(const unsigned char *node_buf, uint16_t nodeSize, uint16_t numRecords, uint16_t i)
Definition hfs_btree.cpp:70
uint32_t catalog_record_body_offset(const unsigned char *record_buf, uint32_t record_len)
constexpr int8_t kBTLeafNode
Definition hfs_btree.hpp:32
uint16_t nodeSize
Definition hfs_btree.hpp:69
uint32_t totalNodes
Definition hfs_btree.hpp:71

References hfs::be32(), hfs::catalog_record_body_offset(), descend(), hfs::kBTIndexNode, hfs::kBTLeafNode, hfs::NodeDescriptor::kind, MAX_NODES_VISITED, hfs::node_record_length(), hfs::node_record_offset(), hfs::HeaderRec::nodeSize, hfs::NodeDescriptor::numRecords, hfs::parse_node_descriptor(), and hfs::HeaderRec::totalNodes.

Referenced by descend().

Variable Documentation

◆ MAX_NODES_VISITED

size_t hfs::anonymous_namespace{hfs_btree.cpp}::MAX_NODES_VISITED = 1'000'000
constexpr

Definition at line 164 of file hfs_btree.cpp.

Referenced by descend().