/src/sleuthkit/tsk/fs/apfs.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * The Sleuth Kit |
3 | | * |
4 | | * Brian Carrier [carrier <at> sleuthkit [dot] org] |
5 | | * Copyright (c) 2019-2020 Brian Carrier. All Rights reserved |
6 | | * Copyright (c) 2018-2019 BlackBag Technologies. All Rights reserved |
7 | | * |
8 | | * This software is distributed under the Common Public License 1.0 |
9 | | */ |
10 | | #include "tsk/util/crypto.hpp" |
11 | | #include "apfs_fs.hpp" |
12 | | #include "tsk_apfs.hpp" |
13 | | |
14 | | #include <cstring> |
15 | | |
16 | | // MSVC doesn't define ffs/ffsll. |
17 | | #ifdef _MSC_VER |
18 | | #include <intrin.h> |
19 | | |
20 | | #ifdef _M_X64 // 64-bit |
21 | | #pragma intrinsic(_BitScanForward64) |
22 | | static __forceinline int lsbset(unsigned __int64 x) { |
23 | | unsigned long i; |
24 | | |
25 | | if (_BitScanForward64(&i, x)) { |
26 | | return i + 1; |
27 | | } |
28 | | |
29 | | return 0; |
30 | | } |
31 | | #else // 32-bit |
32 | | #pragma intrinsic(_BitScanForward) |
33 | | static __forceinline int lsbset(long x) { |
34 | | unsigned long i; |
35 | | |
36 | | if (_BitScanForward(&i, x)) { |
37 | | return i + 1; |
38 | | } |
39 | | return 0; |
40 | | } |
41 | | #endif // _M_X64 |
42 | | |
43 | | #else // gcc or clang |
44 | | |
45 | | #ifdef __x86_64__ |
46 | 0 | #define lsbset(x) __builtin_ffsll(x) |
47 | | #else // 32-bit |
48 | | #define lsbset(x) __builtin_ffs(x) |
49 | | #endif // __x86_64__ |
50 | | |
51 | | #endif // _MSC_VER |
52 | | |
53 | | class wrapped_key_parser { |
54 | | // TODO(JTS): This code assume a well-formed input. It needs some sanity |
55 | | // checking! |
56 | | |
57 | | using tag = uint8_t; |
58 | | using view = span<const uint8_t>; |
59 | | |
60 | | const uint8_t* _data; |
61 | | |
62 | 0 | size_t get_length(const uint8_t** pos) const noexcept { |
63 | 0 | auto data = *pos; |
64 | |
|
65 | 0 | size_t len = *data++; |
66 | |
|
67 | 0 | if (len & 0x80) { |
68 | 0 | len = 0; |
69 | 0 | auto enc_len = len & 0x7F; |
70 | 0 | while (enc_len--) { |
71 | 0 | len <<= 8; |
72 | 0 | len |= *data++; |
73 | 0 | } |
74 | 0 | } |
75 | |
|
76 | 0 | *pos = data; |
77 | 0 | return len; |
78 | 0 | } |
79 | | |
80 | 0 | const view get_tag(tag t) const noexcept { |
81 | 0 | auto data = _data; |
82 | |
|
83 | 0 | while (true) { |
84 | 0 | const auto tag = *data++; |
85 | 0 | const auto len = get_length(&data); |
86 | |
|
87 | 0 | if (tag == t) { |
88 | 0 | return {data, len}; |
89 | 0 | } |
90 | | |
91 | 0 | data += len; |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | // Needed for the recursive variadic to compile, but should never be |
96 | | // called. TODO(JTS): Use constexpr if when we enforce C++17 |
97 | 0 | const view get_data(void) const { |
98 | 0 | throw std::logic_error("this should be unreachable"); |
99 | 0 | } |
100 | | |
101 | | public: |
102 | 0 | wrapped_key_parser(const void* data) noexcept : _data{(const uint8_t*)data} {} |
103 | | |
104 | | template <typename... Args> |
105 | 0 | const view get_data(tag t, Args... args) const noexcept { |
106 | 0 | const auto data = get_tag(t); |
107 | |
|
108 | 0 | if (sizeof...(args) == 0 || !data.valid()) { |
109 | 0 | return data; |
110 | 0 | } |
111 | | |
112 | 0 | return wrapped_key_parser{data.data()}.get_data(args...); |
113 | 0 | } Unexecuted instantiation: span<unsigned char const> const wrapped_key_parser::get_data<int, int>(unsigned char, int, int) const Unexecuted instantiation: span<unsigned char const> const wrapped_key_parser::get_data<int>(unsigned char, int) const Unexecuted instantiation: span<unsigned char const> const wrapped_key_parser::get_data<>(unsigned char) const |
114 | | |
115 | | template <typename... Args> |
116 | 0 | uint64_t get_number(tag t, Args... args) const noexcept { |
117 | 0 | const auto data = get_data(t, args...); |
118 | |
|
119 | 0 | uint64_t n = 0; |
120 | 0 | for (auto p = data.data(); p < data.data() + data.count(); p++) { |
121 | 0 | n <<= 8; |
122 | 0 | n |= *p; |
123 | 0 | } |
124 | |
|
125 | 0 | return n; |
126 | 0 | } |
127 | | }; |
128 | | |
129 | | APFSBlock::APFSBlock(const APFSPool& pool, const apfs_block_num block_num) |
130 | 5.62k | : _storage{}, _pool{pool}, _block_num{block_num} { |
131 | 5.62k | const auto sz = |
132 | 5.62k | pool.read(block_num * APFS_BLOCK_SIZE, _storage.data(), APFS_BLOCK_SIZE); |
133 | 5.62k | if (sz != APFS_BLOCK_SIZE) { |
134 | 23 | throw std::runtime_error("could not read APFSBlock"); |
135 | 23 | } |
136 | 5.62k | } |
137 | | |
138 | 0 | void APFSBlock::decrypt(const uint8_t* key, const uint8_t* key2) noexcept { |
139 | | #ifdef HAVE_LIBCRYPTO |
140 | | // If the data is encrypted via the T2 chip, we can't decrypt it. This means |
141 | | // that if the data wasn't decrypted at acquisition time, then processing will |
142 | | // likely fail. Either way, there is no need to decrypt. |
143 | | if (_pool.hardware_crypto()) { |
144 | | return; |
145 | | } |
146 | | |
147 | | aes_xts_decryptor dec{ aes_xts_decryptor::AES_128, key, key2, |
148 | | APFS_CRYPTO_SW_BLKSIZE }; |
149 | | |
150 | | dec.decrypt_buffer(_storage.data(), _storage.size(), |
151 | | _block_num * APFS_BLOCK_SIZE); |
152 | | #endif |
153 | | // TODO: what is the intended behavior here if there is no crypto support. |
154 | 0 | } |
155 | | |
156 | 0 | void APFSBlock::dump() const noexcept { |
157 | | // Dump contents of block to stdout for debugging |
158 | 0 | for (const auto byte : _storage) { |
159 | 0 | putchar(byte); |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | 1.12k | bool APFSObject::validate_checksum() const noexcept { |
164 | 1.12k | if (obj()->cksum == std::numeric_limits<uint64_t>::max()) { |
165 | 1 | return false; |
166 | 1 | } |
167 | | |
168 | | // Calculate the checksum using the modified fletcher's algorithm |
169 | 1.12k | const auto checksum = [&]() -> uint64_t { |
170 | 1.12k | const auto data = |
171 | 1.12k | reinterpret_cast<const uint32_t*>(_storage.data() + sizeof(uint64_t)); |
172 | 1.12k | const auto len = (_storage.size() - sizeof(uint64_t)) / sizeof(uint32_t); |
173 | | |
174 | 1.12k | constexpr uint64_t mod = std::numeric_limits<uint32_t>::max(); |
175 | | |
176 | 1.12k | uint64_t sum1{0}; |
177 | 1.12k | uint64_t sum2{0}; |
178 | | |
179 | 1.14M | for (size_t i = 0; i < len; i++) { |
180 | 1.14M | sum1 = (sum1 + data[i]) % mod; |
181 | 1.14M | sum2 = (sum2 + sum1) % mod; |
182 | 1.14M | } |
183 | | |
184 | 1.12k | const auto ck_low = mod - ((sum1 + sum2) % mod); |
185 | 1.12k | const auto ck_high = mod - ((sum1 + ck_low) % mod); |
186 | | |
187 | 1.12k | return (ck_high << 32) | ck_low; |
188 | 1.12k | }(); |
189 | | |
190 | | // Compare calculated checksum with the value in the object header |
191 | 1.12k | return (checksum == obj()->cksum); |
192 | 1.12k | } |
193 | | |
194 | | APFSSuperblock::APFSSuperblock(const APFSPool& pool, |
195 | | const apfs_block_num block_num) |
196 | 93 | : APFSObject(pool, block_num), _spaceman{} { |
197 | 93 | if (obj_type() != APFS_OBJ_TYPE_SUPERBLOCK) { |
198 | 8 | throw std::runtime_error("APFSSuperblock: invalid object type"); |
199 | 8 | } |
200 | | |
201 | 85 | if (sb()->magic != APFS_NXSUPERBLOCK_MAGIC) { |
202 | 0 | throw std::runtime_error("APFSSuperblock: invalid magic"); |
203 | 0 | } |
204 | | |
205 | 85 | if (bit_is_set(sb()->incompatible_features, APFS_NXSB_INCOMPAT_VERSION1)) { |
206 | 0 | throw std::runtime_error( |
207 | 0 | "APFSSuperblock: Pre-release versions of APFS are not supported"); |
208 | 0 | } |
209 | | |
210 | 85 | if (bit_is_set(sb()->incompatible_features, APFS_NXSB_INCOMPAT_FUSION)) { |
211 | 2 | if (tsk_verbose) { |
212 | 0 | tsk_fprintf(stderr, |
213 | 0 | "WARNING: APFS fusion drives may not be fully supported\n"); |
214 | 0 | } |
215 | 2 | } |
216 | | |
217 | 85 | if (block_size() != APFS_BLOCK_SIZE) { |
218 | 0 | throw std::runtime_error( |
219 | 0 | "APFSSuperblock: invalid or unsupported block size"); |
220 | 0 | } |
221 | 85 | } |
222 | | |
223 | 16 | const std::vector<apfs_block_num> APFSSuperblock::volume_blocks() const { |
224 | 16 | std::vector<apfs_block_num> vec{}; |
225 | | |
226 | 16 | const auto root = omap().root<APFSObjectBtreeNode>(); |
227 | | |
228 | 16 | for (const auto& e : root.entries()) { |
229 | 2 | vec.emplace_back(e.value->paddr); |
230 | 2 | } |
231 | | |
232 | 16 | return vec; |
233 | 16 | } |
234 | | |
235 | 0 | const std::vector<apfs_block_num> APFSSuperblock::sm_bitmap_blocks() const { |
236 | 0 | const auto entries = spaceman().bm_entries(); |
237 | |
|
238 | 0 | std::vector<apfs_block_num> v{}; |
239 | 0 | v.reserve(entries.size()); |
240 | |
|
241 | 0 | for (const auto& entry : entries) { |
242 | 0 | if (entry.bm_block != 0) { |
243 | 0 | v.emplace_back(entry.bm_block); |
244 | 0 | } |
245 | 0 | } |
246 | |
|
247 | 0 | return v; |
248 | 0 | } |
249 | | |
250 | 0 | const std::vector<uint64_t> APFSSuperblock::volume_oids() const { |
251 | 0 | std::vector<uint64_t> v{}; |
252 | |
|
253 | 0 | for (auto i = 0U; i < sb()->max_fs_count; i++) { |
254 | 0 | const auto oid = sb()->fs_oids[i]; |
255 | |
|
256 | 0 | if (oid == 0) { |
257 | 0 | break; |
258 | 0 | } |
259 | | |
260 | 0 | v.emplace_back(oid); |
261 | 0 | } |
262 | |
|
263 | 0 | return v; |
264 | 0 | } |
265 | | |
266 | 0 | apfs_block_num APFSSuperblock::checkpoint_desc_block() const { |
267 | 0 | for (auto i = 0U; i < sb()->chkpt_desc_block_count; i++) { |
268 | 0 | const auto block_num = sb()->chkpt_desc_base_addr + i; |
269 | 0 | const auto block = APFSObject(_pool, block_num); |
270 | |
|
271 | 0 | if (!block.validate_checksum()) { |
272 | 0 | if (tsk_verbose) { |
273 | 0 | tsk_fprintf(stderr, |
274 | 0 | "APFSSuperblock::checkpoint_desc_block: Block %lld did not " |
275 | 0 | "validate.\n", |
276 | 0 | block_num); |
277 | 0 | } |
278 | 0 | continue; |
279 | 0 | } |
280 | | |
281 | 0 | if (block.xid() == xid() && |
282 | 0 | block.obj_type() == APFS_OBJ_TYPE_CHECKPOINT_DESC) { |
283 | 0 | return block_num; |
284 | 0 | } |
285 | 0 | } |
286 | | |
287 | | // We didn't find anything so return 0; |
288 | 0 | return 0; |
289 | 0 | } |
290 | | |
291 | 0 | const APFSSpaceman& APFSSuperblock::spaceman() const { |
292 | 0 | if (_spaceman != nullptr) { |
293 | 0 | return *_spaceman; |
294 | 0 | } |
295 | | |
296 | 0 | #ifdef TSK_MULTITHREAD_LIB |
297 | | // Since this function is const, and const methods generally are assumed to be |
298 | | // thread safe, we ideally want to it be thread safe so multiple threads |
299 | | // aren't trying to initialize at the same time. |
300 | 0 | std::lock_guard<std::mutex> lock{_spaceman_init_lock}; |
301 | | |
302 | | // Check again to make sure someone else didn't already beat us to this. |
303 | 0 | if (_spaceman != nullptr) { |
304 | 0 | return *_spaceman; |
305 | 0 | } |
306 | 0 | #endif |
307 | | |
308 | 0 | const APFSCheckpointMap cd{_pool, checkpoint_desc_block()}; |
309 | |
|
310 | 0 | _spaceman = std::make_unique<APFSSpaceman>( |
311 | 0 | _pool, cd.get_object_block(sb()->spaceman_oid, APFS_OBJ_TYPE_SPACEMAN)); |
312 | |
|
313 | 0 | return *_spaceman; |
314 | 0 | } |
315 | | |
316 | 0 | APFSSuperblock::Keybag APFSSuperblock::keybag() const { |
317 | 0 | if (sb()->keylocker.start_paddr == 0) { |
318 | 0 | throw std::runtime_error("no keybag found"); |
319 | 0 | } |
320 | | |
321 | 0 | return {(*this)}; |
322 | 0 | } |
323 | | |
324 | | APFSOmap::APFSOmap(const APFSPool& pool, const apfs_block_num block_num) |
325 | 17 | : APFSObject(pool, block_num) { |
326 | 17 | if (obj_type() != APFS_OBJ_TYPE_OMAP) { |
327 | 7 | throw std::runtime_error("APFSOmap: invalid object type"); |
328 | 7 | } |
329 | 17 | } |
330 | | |
331 | | APFSFileSystem::APFSFileSystem(const APFSPool& pool, |
332 | | const apfs_block_num block_num) |
333 | 6 | : APFSObject(pool, block_num) { |
334 | 6 | if (obj_type() != APFS_OBJ_TYPE_FS) { |
335 | 1 | throw std::runtime_error("APFSFileSystem: invalid object type"); |
336 | 1 | } |
337 | | |
338 | 5 | if (fs()->magic != APFS_FS_MAGIC) { |
339 | 0 | throw std::runtime_error("APFSFileSystem: invalid magic"); |
340 | 0 | } |
341 | | |
342 | 5 | if (encrypted() && pool.hardware_crypto() == false) { |
343 | 0 | init_crypto_info(); |
344 | 0 | } |
345 | 5 | } |
346 | | |
347 | | APFSFileSystem::wrapped_kek::wrapped_kek(TSKGuid&& id, |
348 | | const std::unique_ptr<uint8_t[]>& kp) |
349 | 0 | : uuid{std::forward<TSKGuid>(id)} { |
350 | | // Parse KEK |
351 | 0 | wrapped_key_parser wp{kp.get()}; |
352 | | |
353 | | // Get flags |
354 | 0 | flags = wp.get_number(0x30, 0xA3, 0x82); |
355 | | |
356 | | // Get wrapped KEK |
357 | 0 | auto kek_data = wp.get_data(0x30, 0xA3, 0x83); |
358 | 0 | if (kek_data.count() != sizeof(data)) { |
359 | 0 | throw std::runtime_error("invalid KEK size"); |
360 | 0 | } |
361 | 0 | std::memcpy(data, kek_data.data(), sizeof(data)); |
362 | | |
363 | | // Get iterations |
364 | 0 | iterations = wp.get_number(0x30, 0xA3, 0x84); |
365 | | |
366 | | // Get salt |
367 | 0 | kek_data = wp.get_data(0x30, 0xA3, 0x85); |
368 | 0 | if (kek_data.count() != sizeof(salt)) { |
369 | 0 | throw std::runtime_error("invalid salt size"); |
370 | 0 | } |
371 | 0 | std::memcpy(salt, kek_data.data(), sizeof(salt)); |
372 | 0 | } |
373 | | |
374 | | APFSFileSystem::APFSFileSystem(const APFSPool& pool, |
375 | | const apfs_block_num block_num, |
376 | | const std::string& password) |
377 | 2 | : APFSFileSystem(pool, block_num) { |
378 | 2 | if (encrypted()) { |
379 | 0 | unlock(password); |
380 | 0 | } |
381 | 2 | } |
382 | | |
383 | | // These are the known special recovery UUIDs. The ones that are commented out |
384 | | // are currently supported. |
385 | | static const auto unsupported_recovery_keys = { |
386 | | TSKGuid{"c064ebc6-0000-11aa-aa11-00306543ecac"}, // Institutional Recovery |
387 | | TSKGuid{"2fa31400-baff-4de7-ae2a-c3aa6e1fd340"}, // Institutional User |
388 | | // TSKGuid{"ebc6C064-0000-11aa-aa11-00306543ecac"}, // Personal Recovery |
389 | | TSKGuid{"64c0c6eb-0000-11aa-aa11-00306543ecac"}, // iCould Recovery |
390 | | TSKGuid{"ec1c2ad9-b618-4ed6-bd8d-50f361c27507"}, // iCloud User |
391 | | }; |
392 | | |
393 | 0 | void APFSFileSystem::init_crypto_info() { |
394 | 0 | try { |
395 | | |
396 | | // Get container keybag |
397 | 0 | const auto container_kb = _pool.nx()->keybag(); |
398 | |
|
399 | 0 | auto data = container_kb.get_key(uuid(), APFS_KB_TYPE_VOLUME_KEY); |
400 | 0 | if (data == nullptr) { |
401 | 0 | throw std::runtime_error( |
402 | 0 | "APFSFileSystem: can not find volume encryption key"); |
403 | 0 | } |
404 | | |
405 | 0 | wrapped_key_parser wp{ data.get() }; |
406 | | |
407 | | // Get Wrapped VEK |
408 | 0 | auto kek_data = wp.get_data(0x30, 0xA3, 0x83); |
409 | 0 | if (kek_data.count() != sizeof(_crypto.wrapped_vek)) { |
410 | 0 | throw std::runtime_error("invalid VEK size"); |
411 | 0 | } |
412 | 0 | std::memcpy(_crypto.wrapped_vek, kek_data.data(), |
413 | 0 | sizeof(_crypto.wrapped_vek)); |
414 | | |
415 | | // Get VEK Flags |
416 | 0 | _crypto.vek_flags = wp.get_number(0x30, 0xA3, 0x82); |
417 | | |
418 | | // Get VEK UUID |
419 | 0 | kek_data = wp.get_data(0x30, 0xA3, 0x81); |
420 | 0 | if (kek_data.count() != sizeof(_crypto.vek_uuid)) { |
421 | 0 | throw std::runtime_error("invalid UUID size"); |
422 | 0 | } |
423 | 0 | std::memcpy(_crypto.vek_uuid, kek_data.data(), sizeof(_crypto.vek_uuid)); |
424 | |
|
425 | 0 | data = container_kb.get_key(uuid(), APFS_KB_TYPE_UNLOCK_RECORDS); |
426 | 0 | if (data == nullptr) { |
427 | 0 | throw std::runtime_error( |
428 | 0 | "APFSFileSystem: can not find volume recovery key"); |
429 | 0 | } |
430 | | |
431 | 0 | const auto rec = |
432 | 0 | reinterpret_cast<const apfs_volrec_keybag_value*>(data.get()); |
433 | |
|
434 | 0 | if (rec->num_blocks != 1) { |
435 | 0 | throw std::runtime_error( |
436 | 0 | "only single block keybags are currently supported"); |
437 | 0 | } |
438 | | |
439 | 0 | _crypto.recs_block_num = rec->start_block; |
440 | |
|
441 | 0 | Keybag recs{ (*this), _crypto.recs_block_num }; |
442 | |
|
443 | 0 | data = recs.get_key(uuid(), APFS_KB_TYPE_PASSPHRASE_HINT); |
444 | |
|
445 | 0 | if (data != nullptr) { |
446 | 0 | _crypto.password_hint = std::string((const char*)data.get()); |
447 | 0 | } |
448 | | |
449 | | // Get KEKs |
450 | 0 | auto keks = recs.get_keys(); |
451 | 0 | if (keks.empty()) { |
452 | 0 | throw std::runtime_error("could not find any KEKs"); |
453 | 0 | } |
454 | | |
455 | 0 | for (auto& k : keks) { |
456 | 0 | if (k.type != APFS_KB_TYPE_UNLOCK_RECORDS) { |
457 | 0 | continue; |
458 | 0 | } |
459 | | |
460 | 0 | if (std::find(unsupported_recovery_keys.begin(), |
461 | 0 | unsupported_recovery_keys.end(), |
462 | 0 | k.uuid) != unsupported_recovery_keys.end()) { |
463 | | // Skip unparsable recovery KEKs |
464 | 0 | if (tsk_verbose) { |
465 | 0 | tsk_fprintf(stderr, "apfs: skipping unsupported KEK type: %s\n", |
466 | 0 | k.uuid.str().c_str()); |
467 | 0 | } |
468 | 0 | continue; |
469 | 0 | } |
470 | | |
471 | 0 | _crypto.wrapped_keks.emplace_back(wrapped_kek{ std::move(k.uuid), k.data }); |
472 | 0 | } |
473 | 0 | } |
474 | 0 | catch (std::exception& e) { |
475 | 0 | if (tsk_verbose) { |
476 | 0 | tsk_fprintf(stderr, "APFSFileSystem::init_crypto_info: %s", e.what()); |
477 | 0 | } |
478 | 0 | } |
479 | 0 | } |
480 | | |
481 | 0 | bool APFSFileSystem::unlock(const std::string& password) noexcept { |
482 | | #ifdef HAVE_LIBCRYPTO |
483 | | if (_crypto.unlocked) { |
484 | | // Already unlocked |
485 | | return true; |
486 | | } |
487 | | |
488 | | // TODO(JTS): If bits 32:16 are set to 1, some other sort of KEK decryption is |
489 | | // used (see _fv_decrypt_vek in AppleKeyStore). |
490 | | if (_crypto.unk16()) { |
491 | | if (tsk_verbose) { |
492 | | tsk_fprintf(stderr, |
493 | | "apfs: UNK16 is set in VEK. Decryption will likely fail.\n"); |
494 | | } |
495 | | } |
496 | | |
497 | | // Check the password against all possible KEKs |
498 | | for (const auto& wk : _crypto.wrapped_keks) { |
499 | | // If the 57th bit of the KEK flags is set, then the kek is a CoreStorage |
500 | | // KEK |
501 | | const auto kek_len = (wk.cs()) ? 0x10 : 0x20; |
502 | | |
503 | | // TODO(JTS): If the 56th bit of the KEK flags is set, some sort of hardware |
504 | | // decryption is needed |
505 | | if (wk.hw_crypt()) { |
506 | | if (tsk_verbose) { |
507 | | tsk_fprintf( |
508 | | stderr, |
509 | | "apfs: hardware decryption is not yet supported. KEK decryption " |
510 | | "will likely fail\n"); |
511 | | } |
512 | | } |
513 | | |
514 | | const auto user_key = pbkdf2_hmac_sha256(password, wk.salt, sizeof(wk.salt), |
515 | | wk.iterations, kek_len); |
516 | | if (user_key == nullptr) { |
517 | | if (tsk_verbose) { |
518 | | tsk_fprintf(stderr, "apfs: can not generate user key\n"); |
519 | | } |
520 | | continue; |
521 | | } |
522 | | |
523 | | const auto kek = |
524 | | rfc3394_key_unwrap(user_key.get(), kek_len, wk.data, kek_len + 8); |
525 | | if (kek == nullptr) { |
526 | | if (tsk_verbose) { |
527 | | tsk_fprintf(stderr, |
528 | | "apfs: KEK %s can not be unwrapped with given password\n", |
529 | | wk.uuid.str().c_str()); |
530 | | } |
531 | | continue; |
532 | | } |
533 | | |
534 | | // If the 57th bit of the VEK flags is set, then the VEK is a |
535 | | // CoreStorage VEK |
536 | | const auto vek_len = (_crypto.cs()) ? 0x10 : 0x20; |
537 | | |
538 | | // If a 128 bit VEK is wrapped with a 256 bit KEK then only the first 128 |
539 | | // bits of the KEK are used. |
540 | | const auto vek = rfc3394_key_unwrap(kek.get(), std::min(kek_len, vek_len), |
541 | | _crypto.wrapped_vek, vek_len + 8); |
542 | | if (vek == nullptr) { |
543 | | if (tsk_verbose) { |
544 | | tsk_fprintf(stderr, "apfs: failed to unwrap VEK\n"); |
545 | | } |
546 | | continue; |
547 | | } |
548 | | |
549 | | _crypto.password = password; |
550 | | std::memcpy(_crypto.vek, vek.get(), vek_len); |
551 | | |
552 | | if (_crypto.cs()) { |
553 | | // For volumes that were converted from CoreStorage, the tweak is the |
554 | | // first 128-bits of SHA256(vek + vekuuid) |
555 | | std::memcpy(_crypto.vek + 0x10, _crypto.vek_uuid, |
556 | | sizeof(_crypto.vek_uuid)); |
557 | | |
558 | | const auto hash = hash_buffer_sha256(_crypto.vek, sizeof(_crypto.vek)); |
559 | | |
560 | | std::memcpy(_crypto.vek + 0x10, hash.get(), 0x10); |
561 | | } |
562 | | |
563 | | _crypto.unlocked = true; |
564 | | |
565 | | return true; |
566 | | } |
567 | | |
568 | | return false; |
569 | | #else |
570 | 0 | if (tsk_verbose) { |
571 | 0 | tsk_fprintf(stderr, "apfs: crypto library not loaded\n"); |
572 | 0 | } |
573 | 0 | return false; |
574 | 0 | #endif |
575 | 0 | } |
576 | | |
577 | | const std::vector<APFSFileSystem::unmount_log_t> APFSFileSystem::unmount_log() |
578 | 0 | const { |
579 | 0 | std::vector<unmount_log_t> v{}; |
580 | |
|
581 | 0 | for (auto i = 0; i < 8; i++) { |
582 | 0 | const auto& log = fs()->unmount_logs[i]; |
583 | |
|
584 | 0 | if (log.timestamp == 0) { |
585 | 0 | return v; |
586 | 0 | } |
587 | | |
588 | 0 | v.emplace_back( |
589 | 0 | unmount_log_t{log.timestamp, log.kext_ver_str, log.last_xid}); |
590 | 0 | } |
591 | | |
592 | 0 | return v; |
593 | 0 | } |
594 | | |
595 | | const std::vector<APFSFileSystem::snapshot_t> APFSFileSystem::snapshots() |
596 | 0 | const { |
597 | 0 | std::vector<snapshot_t> v{}; |
598 | |
|
599 | 0 | const APFSSnapshotMetaBtreeNode snap_tree{_pool, fs()->snap_meta_tree_oid}; |
600 | |
|
601 | 0 | struct key_type { |
602 | 0 | uint64_t xid_and_type; |
603 | |
|
604 | 0 | inline uint64_t snap_xid() const noexcept { |
605 | 0 | return bitfield_value(xid_and_type, 60, 0); |
606 | 0 | } |
607 | |
|
608 | 0 | inline uint64_t type() const noexcept { |
609 | 0 | return bitfield_value(xid_and_type, 4, 60); |
610 | 0 | } |
611 | 0 | }; |
612 | |
|
613 | 0 | using value_type = apfs_snap_metadata; |
614 | |
|
615 | 0 | std::for_each(snap_tree.begin(), snap_tree.end(), [&](const auto& entry) { |
616 | 0 | const auto key = entry.key.template as<key_type>(); |
617 | 0 | const auto value = entry.value.template as<value_type>(); |
618 | |
|
619 | 0 | if (key->type() != APFS_JOBJTYPE_SNAP_METADATA) { |
620 | 0 | return; |
621 | 0 | } |
622 | | |
623 | 0 | v.emplace_back(snapshot_t{ |
624 | 0 | {value->name, value->name_length - 1U}, // name |
625 | 0 | value->create_time, // timestamp |
626 | 0 | key->snap_xid(), // snap_xid |
627 | 0 | (value->extentref_tree_oid == 0), // dataless |
628 | 0 | }); |
629 | 0 | }); |
630 | |
|
631 | 0 | return v; |
632 | 0 | } |
633 | | |
634 | 0 | APFSJObjTree APFSFileSystem::root_jobj_tree() const { |
635 | 0 | return {_pool, omap_root(), rdo(), crypto_info()}; |
636 | 0 | } |
637 | | |
638 | 0 | apfs_block_num APFSFileSystem::omap_root() const { |
639 | 0 | return APFSOmap{_pool, fs()->omap_oid}.root_block(); |
640 | 0 | } |
641 | | |
642 | | APFSJObjBtreeNode::APFSJObjBtreeNode(const APFSObjectBtreeNode* obj_root, |
643 | | apfs_block_num block_num, |
644 | | const uint8_t* key) |
645 | | #ifdef HAVE_LIBCRYPTO |
646 | | : APFSBtreeNode(obj_root->pool(), block_num, key), _obj_root{obj_root} { |
647 | | #else |
648 | 2 | : APFSBtreeNode(obj_root->pool(), block_num, nullptr), _obj_root{ obj_root } { |
649 | 2 | #endif |
650 | 2 | if (subtype() != APFS_OBJ_TYPE_FSTREE) { |
651 | 0 | throw std::runtime_error("APFSJObjBtreeNode: invalid subtype"); |
652 | 0 | } |
653 | 2 | } |
654 | | |
655 | | |
656 | 1 | APFSObjectBtreeNode::iterator APFSObjectBtreeNode::find(uint64_t oid) const { |
657 | 1 | return APFSBtreeNode::find( |
658 | 1 | oid, [xid = this->_xid](const auto& key, |
659 | 1 | const auto oid) noexcept->int64_t { |
660 | 1 | if ((key->oid == oid) && (key->xid > xid)) { |
661 | 0 | return key->xid - xid; |
662 | 0 | } |
663 | | |
664 | 1 | return (key->oid - oid); |
665 | 1 | }); |
666 | 1 | } |
667 | | |
668 | | APFSObjectBtreeNode::APFSObjectBtreeNode(const APFSPool& pool, |
669 | | apfs_block_num block_num) |
670 | 10 | : APFSBtreeNode(pool, block_num), _xid{xid()} { |
671 | 10 | if (subtype() != APFS_OBJ_TYPE_OMAP) { |
672 | 0 | throw std::runtime_error("APFSObjectBtreeNode: invalid subtype"); |
673 | 0 | } |
674 | 10 | } |
675 | | |
676 | | APFSObjectBtreeNode::APFSObjectBtreeNode(const APFSPool& pool, |
677 | | apfs_block_num block_num, |
678 | | uint64_t snap_xid) |
679 | 0 | : APFSBtreeNode(pool, block_num), _xid{snap_xid} { |
680 | 0 | if (subtype() != APFS_OBJ_TYPE_OMAP) { |
681 | 0 | throw std::runtime_error("APFSObjectBtreeNode: invalid subtype"); |
682 | 0 | } |
683 | 0 | } |
684 | | |
685 | | APFSSnapshotMetaBtreeNode::APFSSnapshotMetaBtreeNode(const APFSPool& pool, |
686 | | apfs_block_num block_num) |
687 | 0 | : APFSBtreeNode(pool, block_num) { |
688 | 0 | if (subtype() != APFS_OBJ_TYPE_SNAPMETATREE) { |
689 | 0 | throw std::runtime_error("APFSSnapshotMetaBtreeNode: invalid subtype"); |
690 | 0 | } |
691 | 0 | } |
692 | | |
693 | | APFSExtentRefBtreeNode::APFSExtentRefBtreeNode(const APFSPool& pool, |
694 | | apfs_block_num block_num) |
695 | 0 | : APFSBtreeNode(pool, block_num) { |
696 | 0 | if (subtype() != APFS_OBJ_TYPE_BLOCKREFTREE) { |
697 | 0 | throw std::runtime_error("APFSExtentRefBtreeNode: invalid subtype"); |
698 | 0 | } |
699 | 0 | } |
700 | | |
701 | | APFSCheckpointMap::APFSCheckpointMap(const APFSPool& pool, |
702 | | const apfs_block_num block_num) |
703 | 0 | : APFSObject(pool, block_num) { |
704 | 0 | if (obj_type() != APFS_OBJ_TYPE_CHECKPOINT_DESC) { |
705 | 0 | throw std::runtime_error("APFSCheckpointMap: invalid object type"); |
706 | 0 | } |
707 | 0 | } |
708 | | |
709 | | apfs_block_num APFSCheckpointMap::get_object_block( |
710 | 0 | uint64_t oid, APFS_OBJ_TYPE_ENUM type) const { |
711 | 0 | const auto entries = map()->entries; |
712 | |
|
713 | 0 | for (auto i = 0U; i < map()->count; i++) { |
714 | 0 | const auto& entry = entries[i]; |
715 | |
|
716 | 0 | if (entry.oid == oid && entry.type == type) { |
717 | 0 | return entry.paddr; |
718 | 0 | } |
719 | 0 | } |
720 | | |
721 | | // Not found |
722 | 0 | throw std::runtime_error( |
723 | 0 | "APFSCheckpointMap::get_object_block: object not found"); |
724 | 0 | } |
725 | | |
726 | | APFSSpaceman::APFSSpaceman(const APFSPool& pool, const apfs_block_num block_num) |
727 | 0 | : APFSObject(pool, block_num), _bm_entries{} { |
728 | 0 | if (obj_type() != APFS_OBJ_TYPE_SPACEMAN) { |
729 | 0 | throw std::runtime_error("APFSSpaceman: invalid object type"); |
730 | 0 | } |
731 | 0 | } |
732 | | |
733 | 0 | const std::vector<APFSSpacemanCIB::bm_entry>& APFSSpaceman::bm_entries() const { |
734 | 0 | if (!_bm_entries.empty()) { |
735 | 0 | return _bm_entries; |
736 | 0 | } |
737 | | |
738 | 0 | #ifdef TSK_MULTITHREAD_LIB |
739 | | // Since this function is const, and const methods generally are assumed to be |
740 | | // thread safe, we ideally want to it be thread safe so multiple threads |
741 | | // aren't trying to initialize at the same time. |
742 | 0 | std::lock_guard<std::mutex> lock{_bm_entries_init_lock}; |
743 | | |
744 | | // Check again to make sure someone else didn't already beat us to this. |
745 | 0 | if (!_bm_entries.empty()) { |
746 | 0 | return _bm_entries; |
747 | 0 | } |
748 | | |
749 | | // Our above checks would not prevent someone from accessing the member while |
750 | | // the initialization is in progress, so let's initialize a temporary and them |
751 | | // move it into the member instead. |
752 | 0 | decltype(_bm_entries) bm_entries{}; |
753 | | #else |
754 | | // There's no possibility for contention, so let's just initialize the member |
755 | | // directly so that we can save the move. |
756 | | auto& bm_entries = _bm_entries; |
757 | | #endif |
758 | |
|
759 | 0 | bm_entries.reserve(sm()->devs[APFS_SD_MAIN].cib_count); |
760 | |
|
761 | 0 | const auto cib_blocks = [&] { |
762 | 0 | std::vector<apfs_block_num> v{}; |
763 | 0 | v.reserve(sm()->devs[APFS_SD_MAIN].cib_count); |
764 | |
|
765 | 0 | const auto entries = this->entries(); |
766 | | |
767 | | // Is the next level cib? |
768 | 0 | if (sm()->devs[APFS_SD_MAIN].cab_count == 0) { |
769 | | // Our entires contain the cib blocks |
770 | 0 | for (auto i = 0U; i < sm()->devs[APFS_SD_MAIN].cib_count; i++) { |
771 | 0 | v.emplace_back(entries[i]); |
772 | 0 | } |
773 | |
|
774 | 0 | return v; |
775 | 0 | } |
776 | | |
777 | | // The next level is cab, not cib so we need to recurse them |
778 | 0 | for (auto i = 0U; i < sm()->devs[APFS_SD_MAIN].cab_count; i++) { |
779 | 0 | const APFSSpacemanCAB cab(_pool, entries[i]); |
780 | 0 | const auto cab_entries = cab.cib_blocks(); |
781 | | |
782 | | // Append the blocks to the vector |
783 | 0 | std::copy(cab_entries.begin(), cab_entries.end(), std::back_inserter(v)); |
784 | 0 | } |
785 | |
|
786 | 0 | return v; |
787 | 0 | }(); |
788 | |
|
789 | 0 | for (const auto block : cib_blocks) { |
790 | 0 | const APFSSpacemanCIB cib(_pool, block); |
791 | |
|
792 | 0 | const auto entries = cib.bm_entries(); |
793 | | |
794 | | // Append the entries to the vector |
795 | 0 | std::copy(entries.begin(), entries.end(), std::back_inserter(bm_entries)); |
796 | 0 | } |
797 | | |
798 | | // Sort the entries by offset |
799 | 0 | std::sort(bm_entries.begin(), bm_entries.end(), |
800 | 0 | [](const auto& a, const auto& b) { return (a.offset < b.offset); }); |
801 | |
|
802 | 0 | #ifdef TSK_MULTITHREAD_LIB |
803 | | // Now that we're fully initialized we can now move our initialized vector |
804 | | // into the member to signal that we're ready for access. |
805 | 0 | _bm_entries = std::move(bm_entries); |
806 | 0 | #endif |
807 | |
|
808 | 0 | return _bm_entries; |
809 | 0 | } |
810 | | |
811 | | const std::vector<APFSSpaceman::range> APFSSpaceman::unallocated_ranges() |
812 | 0 | const { |
813 | 0 | std::vector<range> v{}; |
814 | |
|
815 | 0 | for (const auto& entry : bm_entries()) { |
816 | 0 | if (entry.free_blocks == 0) { |
817 | | // No free ranges to add |
818 | 0 | continue; |
819 | 0 | } |
820 | | |
821 | 0 | if (entry.total_blocks == entry.free_blocks) { |
822 | | // The entire bitmap block is free |
823 | 0 | if (!v.empty() && |
824 | 0 | v.back().start_block + v.back().num_blocks == entry.offset) { |
825 | | // We're within the same range as the last one, so just update the |
826 | | // count |
827 | 0 | v.back().num_blocks += entry.free_blocks; |
828 | 0 | } else { |
829 | | // We're not contiguous with the last range, so add a new one |
830 | 0 | v.emplace_back(range{entry.offset, entry.free_blocks}); |
831 | 0 | } |
832 | 0 | continue; |
833 | 0 | } |
834 | | |
835 | | // We've got to enumerate the bitmap block for it's ranges |
836 | 0 | const auto ranges = APFSBitmapBlock{_pool, entry}.unallocated_ranges(); |
837 | | |
838 | | // TODO(JTS): We could possibly de-duplicate the first range if it's |
839 | | // contiguous with the last range, but the overhead might outweigh the |
840 | | // convenience |
841 | 0 | std::copy(ranges.begin(), ranges.end(), std::back_inserter(v)); |
842 | 0 | } |
843 | |
|
844 | 0 | return v; |
845 | 0 | } |
846 | | |
847 | | APFSSpacemanCIB::APFSSpacemanCIB(const APFSPool& pool, |
848 | | const apfs_block_num block_num) |
849 | 0 | : APFSObject(pool, block_num) { |
850 | 0 | if (obj_type() != APFS_OBJ_TYPE_SPACEMAN_CIB) { |
851 | 0 | throw std::runtime_error("APFSSpacemanCIB: invalid object type"); |
852 | 0 | } |
853 | 0 | } |
854 | | |
855 | | const std::vector<APFSSpacemanCIB::bm_entry> APFSSpacemanCIB::bm_entries() |
856 | 0 | const { |
857 | 0 | std::vector<bm_entry> v{}; |
858 | 0 | v.reserve(cib()->entry_count); |
859 | |
|
860 | 0 | const auto entries = cib()->entries; |
861 | 0 | for (auto i = 0U; i < cib()->entry_count; i++) { |
862 | 0 | const auto& entry = entries[i]; |
863 | 0 | v.emplace_back(bm_entry{entry.addr, entry.block_count, entry.free_count, |
864 | 0 | entry.bm_addr}); |
865 | 0 | } |
866 | |
|
867 | 0 | return v; |
868 | 0 | } |
869 | | |
870 | | APFSSpacemanCAB::APFSSpacemanCAB(const APFSPool& pool, |
871 | | const apfs_block_num block_num) |
872 | 0 | : APFSObject(pool, block_num) { |
873 | 0 | if (obj_type() != APFS_OBJ_TYPE_SPACEMAN_CAB) { |
874 | 0 | throw std::runtime_error("APFSSpacemanCAB: invalid object type"); |
875 | 0 | } |
876 | 0 | } |
877 | | |
878 | 0 | const std::vector<apfs_block_num> APFSSpacemanCAB::cib_blocks() const { |
879 | 0 | std::vector<apfs_block_num> v{}; |
880 | 0 | v.reserve(cib_count()); |
881 | |
|
882 | 0 | const auto entries = cab()->cib_blocks; |
883 | |
|
884 | 0 | for (auto i = 0U; i < cib_count(); i++) { |
885 | 0 | v.emplace_back(entries[i]); |
886 | 0 | } |
887 | |
|
888 | 0 | return v; |
889 | 0 | } |
890 | | |
891 | | APFSBitmapBlock::APFSBitmapBlock(const APFSPool& pool, |
892 | | const APFSSpacemanCIB::bm_entry& entry) |
893 | 0 | : APFSBlock(pool, entry.bm_block), _entry{entry} {} |
894 | | |
895 | 0 | uint32_t APFSBitmapBlock::next() noexcept { |
896 | 0 | while (!done()) { |
897 | | // Calculate the index of the bit to be evaluated. |
898 | 0 | const auto i = _hint % cached_bits; |
899 | | |
900 | | // If we're evaluating the first bit then we need to cache the next set |
901 | | // from the array. |
902 | 0 | if (i == 0) { |
903 | 0 | cache_next(); |
904 | | |
905 | | // If there are no set bits then there's nothing to scan for, so let's |
906 | | // try again with the next set of bits. |
907 | 0 | if (_cache == 0) { |
908 | 0 | _hint += cached_bits; |
909 | 0 | continue; |
910 | 0 | } |
911 | 0 | } |
912 | | |
913 | | // Mask the fetched value and count the number of trailing zero bits. |
914 | 0 | const auto c = lsbset((_cache >> i) << i); |
915 | | |
916 | | // If c is non-zero then there are set bits. |
917 | 0 | if (c != 0) { |
918 | | // There are set bits. We just need to make sure that they're within |
919 | | // the range we're scanning for. |
920 | | |
921 | | // Adjust the hint for the next call |
922 | 0 | _hint += c - i; |
923 | | |
924 | | // Check to see if we're still in range |
925 | 0 | if (_hint - 1 < _entry.total_blocks) { |
926 | 0 | return _hint - 1; |
927 | 0 | } |
928 | | |
929 | | // The hit is outside of our scanned range |
930 | 0 | return no_bits_left; |
931 | 0 | } |
932 | | |
933 | | // There are no set bits, so we need to adjust the hint to the next set of |
934 | | // bits and try again. |
935 | 0 | _hint += cached_bits - i; |
936 | 0 | } |
937 | | |
938 | 0 | return no_bits_left; |
939 | 0 | } |
940 | | |
941 | 0 | const std::vector<APFSSpaceman::range> APFSBitmapBlock::unallocated_ranges() { |
942 | | // Check for special case where all blocks are allocated |
943 | 0 | if (_entry.free_blocks == 0) { |
944 | 0 | return {}; |
945 | 0 | } |
946 | | |
947 | | // Check for special cases where all blocks are free |
948 | 0 | if (_entry.free_blocks == _entry.total_blocks) { |
949 | 0 | return {{_entry.offset, _entry.total_blocks}}; |
950 | 0 | } |
951 | | |
952 | 0 | reset(); |
953 | 0 | _mode = mode::unset; |
954 | |
|
955 | 0 | std::vector<APFSSpaceman::range> v{}; |
956 | |
|
957 | 0 | while (!done()) { |
958 | | // Get the start of the range. |
959 | 0 | const auto s = next(); |
960 | | |
961 | | // If there's no start then we're done. |
962 | 0 | if (s == no_bits_left) { |
963 | 0 | break; |
964 | 0 | } |
965 | | |
966 | | // Toggle the scan mode to look for the next type of bit. |
967 | 0 | toggle_mode(); |
968 | | |
969 | | // Get the end of the range. |
970 | 0 | auto e = next(); |
971 | | |
972 | | // If there's no end then we set the end of the range to the end of the |
973 | | // bitmap. |
974 | 0 | if (e == no_bits_left) { |
975 | 0 | e = _entry.total_blocks; |
976 | 0 | } |
977 | | |
978 | | // Add the range description to the vector. |
979 | 0 | v.emplace_back(APFSSpaceman::range{s + _entry.offset, e - s}); |
980 | | |
981 | | // Toggle the scan mode for the next scan. |
982 | 0 | toggle_mode(); |
983 | 0 | } |
984 | |
|
985 | 0 | return v; |
986 | 0 | } |
987 | | |
988 | | APFSKeybag::APFSKeybag(const APFSPool& pool, const apfs_block_num block_num, |
989 | | const uint8_t* key, const uint8_t* key2) |
990 | 0 | : APFSObject(pool, block_num) { |
991 | 0 | decrypt(key, key2); |
992 | |
|
993 | 0 | if (!validate_checksum()) { |
994 | 0 | throw std::runtime_error("keybag did not decrypt properly"); |
995 | 0 | } |
996 | | |
997 | 0 | if (kb()->version != 2) { |
998 | 0 | throw std::runtime_error("keybag version not supported"); |
999 | 0 | } |
1000 | 0 | } |
1001 | | |
1002 | | std::unique_ptr<uint8_t[]> APFSKeybag::get_key(const TSKGuid& uuid, |
1003 | 0 | uint16_t type) const { |
1004 | 0 | if (kb()->num_entries == 0) { |
1005 | 0 | return nullptr; |
1006 | 0 | } |
1007 | | |
1008 | | // First key is immediately after the header |
1009 | 0 | auto next_key = kb()->first_key; |
1010 | |
|
1011 | 0 | for (auto i = 0U; i < kb()->num_entries; i++) { |
1012 | 0 | if (next_key->type == type && |
1013 | 0 | std::memcmp(next_key->uuid, uuid.bytes().data(), 16) == 0) { |
1014 | | // We've found a matching key. Copy it's data to a pointer and return it. |
1015 | 0 | const auto data = reinterpret_cast<const uint8_t*>(next_key + 1); |
1016 | | |
1017 | | // We're padding the data with an extra byte so we can null-terminate |
1018 | | // any data strings. There might be a better way. |
1019 | 0 | auto dp = std::make_unique<uint8_t[]>(next_key->length + 1); |
1020 | |
|
1021 | 0 | std::memcpy(dp.get(), data, next_key->length); |
1022 | |
|
1023 | 0 | return dp; |
1024 | 0 | } |
1025 | | |
1026 | | // Calculate address of next key (ensuring alignment) |
1027 | | |
1028 | 0 | const auto nk_addr = |
1029 | 0 | (uintptr_t)next_key + |
1030 | 0 | ((sizeof(*next_key) + next_key->length + 0x0F) & ~0x0FULL); |
1031 | |
|
1032 | 0 | next_key = reinterpret_cast<const apfs_keybag_key*>(nk_addr); |
1033 | 0 | } |
1034 | | |
1035 | | // Not Found |
1036 | 0 | return nullptr; |
1037 | 0 | } |
1038 | | |
1039 | 0 | std::vector<APFSKeybag::key> APFSKeybag::get_keys() const { |
1040 | 0 | std::vector<key> keys; |
1041 | | |
1042 | | // First key is immediately after the header |
1043 | 0 | auto next_key = kb()->first_key; |
1044 | |
|
1045 | 0 | for (auto i = 0U; i < kb()->num_entries; i++) { |
1046 | 0 | const auto data = reinterpret_cast<const uint8_t*>(next_key + 1); |
1047 | | |
1048 | | // We're padding the data with an extra byte so we can null-terminate |
1049 | | // any data strings. There might be a better way. |
1050 | 0 | auto dp = std::make_unique<uint8_t[]>(next_key->length + 1); |
1051 | |
|
1052 | 0 | std::memcpy(dp.get(), data, next_key->length); |
1053 | |
|
1054 | 0 | keys.emplace_back(key{{next_key->uuid}, std::move(dp), next_key->type}); |
1055 | | |
1056 | | // Calculate address of next key (ensuring alignment) |
1057 | 0 | const auto nk_addr = |
1058 | 0 | (uintptr_t)next_key + |
1059 | 0 | ((sizeof(*next_key) + next_key->length + 0x0F) & ~0x0FULL); |
1060 | |
|
1061 | 0 | next_key = reinterpret_cast<const apfs_keybag_key*>(nk_addr); |
1062 | 0 | } |
1063 | |
|
1064 | 0 | return keys; |
1065 | 0 | } |
1066 | | |
1067 | | APFSSuperblock::Keybag::Keybag(const APFSSuperblock& sb) |
1068 | 0 | : APFSKeybag(sb.pool(), sb.sb()->keylocker.start_paddr, sb.sb()->uuid, |
1069 | 0 | sb.sb()->uuid) { |
1070 | 0 | if (obj_type_and_flags() != APFS_OBJ_TYPE_CONTAINER_KEYBAG) { |
1071 | 0 | throw std::runtime_error("APFSSuperblock::Keybag: invalid object type"); |
1072 | 0 | } |
1073 | | |
1074 | 0 | if (sb.sb()->keylocker.block_count != 1) { |
1075 | 0 | throw std::runtime_error("only single block keybags are supported"); |
1076 | 0 | } |
1077 | 0 | } |
1078 | | |
1079 | | APFSExtentRefBtreeNode::iterator APFSExtentRefBtreeNode::find( |
1080 | 0 | apfs_block_num block) const { |
1081 | 0 | return APFSBtreeNode::find( |
1082 | 0 | block, [](const auto& key, const auto block) noexcept->int64_t { |
1083 | 0 | return key.template as<APFSPhysicalExtentKey>()->start_block() - block; |
1084 | 0 | }); |
1085 | 0 | } |
1086 | | |
1087 | | APFSFileSystem::Keybag::Keybag(const APFSFileSystem& vol, |
1088 | | apfs_block_num block_num) |
1089 | 0 | : APFSKeybag(vol.pool(), block_num, vol.fs()->uuid, vol.fs()->uuid) { |
1090 | 0 | if (obj_type_and_flags() != APFS_OBJ_TYPE_VOLUME_RECOVERY_KEYBAG) { |
1091 | 0 | throw std::runtime_error("APFSFileSystem::Keybag: invalid object type"); |
1092 | 0 | } |
1093 | 0 | } |