/src/sleuthkit/tsk/fs/apfs_fs.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 <cstring> |
11 | | |
12 | | #include "apfs_fs.hpp" |
13 | | |
14 | | APFSJObjTree::APFSJObjTree(const APFSPool& pool, apfs_block_num obj_omap, |
15 | | uint64_t root_tree_oid, |
16 | | const APFSFileSystem::crypto_info_t& crypto) |
17 | 1 | : _crypto{crypto}, |
18 | 1 | _obj_root{pool, obj_omap}, |
19 | 1 | _jobj_root{&_obj_root, _obj_root.find(root_tree_oid)->value->paddr, |
20 | 1 | _crypto.key.get()}, |
21 | 1 | _root_tree_oid{root_tree_oid} {} |
22 | | |
23 | | APFSJObjTree::APFSJObjTree(const APFSFileSystem& vol) |
24 | 1 | : APFSJObjTree{vol.pool(), |
25 | 1 | APFSOmap{vol.pool(), vol.fs()->omap_oid}.root_block(), |
26 | 1 | vol.rdo(), vol.crypto_info()} {} |
27 | | |
28 | 0 | void APFSJObjTree::set_snapshot(uint64_t snap_xid) { |
29 | 0 | _obj_root.snapshot(snap_xid); |
30 | | |
31 | | // This type isn't copyable or moveable, so we have to use in-place allocation |
32 | | // TODO(JTS): Refactor APFSObjects so that they can be move assigned |
33 | 0 | _jobj_root.~APFSJObjBtreeNode(); |
34 | | #ifdef HAVE_LIBCRYPTO |
35 | | new (&_jobj_root) APFSJObjBtreeNode( |
36 | | &_obj_root, _obj_root.find(_root_tree_oid)->value->paddr, |
37 | | _crypto.key.get()); |
38 | | #else |
39 | 0 | new (&_jobj_root) APFSJObjBtreeNode( |
40 | 0 | &_obj_root, _obj_root.find(_root_tree_oid)->value->paddr, nullptr); |
41 | 0 | #endif |
42 | 0 | } |
43 | | |
44 | 1 | APFSJObjTree::crypto::crypto(const APFSFileSystem::crypto_info_t& crypto) { |
45 | 1 | if (crypto.unlocked) { |
46 | 0 | key = std::make_unique<uint8_t[]>(0x20); |
47 | 0 | std::memcpy(key.get(), crypto.vek, 0x20); |
48 | 0 | password = crypto.password; |
49 | |
|
50 | | #ifdef HAVE_LIBCRYPTO |
51 | | decryptor = std::make_unique<aes_xts_decryptor>( |
52 | | aes_xts_decryptor::AES_128, key.get(), nullptr, APFS_CRYPTO_SW_BLKSIZE); |
53 | | #endif |
54 | 0 | } |
55 | 1 | } |
56 | | |
57 | | APFSJObject::APFSJObject(const std::pair<jit, jit>& jobjs) |
58 | 1 | : APFSJObject(jobjs.first, jobjs.second) {} |
59 | | |
60 | 1 | APFSJObject::APFSJObject(const jit& start, const jit& end) { |
61 | 1 | std::for_each(start, end, [this](const auto& it) { this->add_entry(it); }); |
62 | 1 | } |
63 | | |
64 | 0 | void APFSJObject::add_entry(const jit::value_type& e) { |
65 | 0 | const auto key = e.key.template as<key_type>(); |
66 | |
|
67 | 0 | switch (key->type()) { |
68 | | // Inode records |
69 | 0 | case APFS_JOBJTYPE_INODE: { |
70 | 0 | const auto value = e.value.template as<apfs_inode>(); |
71 | 0 | _inode = *value; |
72 | | |
73 | | // If the private_id is not the same as the oid then we're a clone |
74 | 0 | _is_clone = (_inode.private_id != key->oid()); |
75 | | |
76 | | // If there's more data than the size of the inode then we have xdata |
77 | 0 | size_t e_offset = sizeof(apfs_inode); |
78 | 0 | size_t e_size = e.value.count(); |
79 | | // Need at least 4 bytes for start of extended fields (xf_blob_t) |
80 | 0 | if (e_size > sizeof(apfs_inode) + 4) { |
81 | | // The xfield headers are right after the inode |
82 | 0 | const auto xfield = reinterpret_cast<const apfs_xfield*>(value + 1); |
83 | |
|
84 | 0 | e_offset += 4; |
85 | | |
86 | | // Need at least 4 bytes for each x_field_t |
87 | 0 | if (xfield->num_exts < (e_size - e_offset) / 4) { |
88 | | // sizeof(xf_blob_t) + number of extenteded fields * sizeof(x_field_t) |
89 | 0 | e_offset += xfield->num_exts * 4; |
90 | | |
91 | | // The xfield data is after all of the xfield headers |
92 | 0 | auto xfield_data = |
93 | 0 | reinterpret_cast<const char*>(&xfield->entries[xfield->num_exts]); |
94 | |
|
95 | 0 | for (auto i = 0U; i < xfield->num_exts; i++) { |
96 | 0 | const auto& ext = xfield->entries[i]; |
97 | |
|
98 | 0 | switch (ext.type) { |
99 | 0 | case APFS_XFIELD_TYPE_NAME: |
100 | 0 | if((ext.len < 1) || (ext.len > e_size) || (e_offset > e_size - ext.len)) { |
101 | 0 | break; |
102 | 0 | } |
103 | 0 | _name = std::string(xfield_data, ext.len - 1); |
104 | 0 | break; |
105 | | |
106 | 0 | case APFS_XFIELD_TYPE_DSTREAM: { |
107 | 0 | const auto ds = |
108 | 0 | reinterpret_cast<const apfs_dstream*>(xfield_data); |
109 | |
|
110 | 0 | _size = ds->size; |
111 | 0 | _size_on_disk = ds->alloced_size; |
112 | 0 | break; |
113 | 0 | } |
114 | 0 | } |
115 | | |
116 | | // The next data needs to be aligned properly |
117 | 0 | xfield_data += (ext.len + 7) & 0xFFF8; |
118 | 0 | e_offset += (ext.len + 7) & 0xFFF8; |
119 | 0 | } |
120 | 0 | } |
121 | 0 | } |
122 | 0 | break; |
123 | 0 | } |
124 | | |
125 | | // Directory records |
126 | 0 | case APFS_JOBJTYPE_DIR_RECORD: { |
127 | 0 | #pragma pack(push, 1) |
128 | 0 | struct dir_record_key : key_type { |
129 | 0 | uint32_t namelen_and_hash; |
130 | 0 | char name[0]; |
131 | |
|
132 | 0 | inline uint32_t name_len() const noexcept { |
133 | 0 | return bitfield_value(namelen_and_hash, 10, 0); |
134 | 0 | } |
135 | |
|
136 | 0 | inline uint32_t hash() const noexcept { |
137 | 0 | return bitfield_value(namelen_and_hash, 22, 10); |
138 | 0 | } |
139 | 0 | }; |
140 | 0 | #pragma pack(pop) |
141 | 0 | static_assert(sizeof(dir_record_key) == 0x0C, "invalid struct padding"); |
142 | |
|
143 | 0 | const auto k = e.key.template as<dir_record_key>(); |
144 | 0 | const auto value = e.value.template as<apfs_dir_record>(); |
145 | |
|
146 | 0 | _children.emplace_back( |
147 | 0 | child_entry{std::string(k->name, k->name_len() - 1U), *value}); |
148 | 0 | break; |
149 | 0 | } |
150 | | |
151 | | // File extents |
152 | 0 | case APFS_JOBJTYPE_FILE_EXTENT: { |
153 | 0 | struct file_extent_key : key_type { |
154 | 0 | uint64_t offset; |
155 | 0 | }; |
156 | |
|
157 | 0 | const auto k = e.key.template as<file_extent_key>(); |
158 | 0 | const auto value = e.value.template as<apfs_file_extent>(); |
159 | 0 | const auto len = |
160 | 0 | bitfield_value(value->len_and_flags, APFS_FILE_EXTENT_LEN_BITS, |
161 | 0 | APFS_FILE_EXTENT_LEN_SHIFT); |
162 | |
|
163 | 0 | _extents.emplace_back(extent{k->offset, value->phys, len, value->crypto}); |
164 | |
|
165 | 0 | break; |
166 | 0 | } |
167 | | |
168 | | // Extended Attributes |
169 | 0 | case APFS_JOBJTYPE_XATTR: { |
170 | 0 | struct xattr_key : key_type { |
171 | 0 | uint16_t name_len; |
172 | 0 | char name[0]; |
173 | 0 | }; |
174 | |
|
175 | 0 | const auto k = e.key.template as<xattr_key>(); |
176 | 0 | const auto value = e.value.template as<apfs_xattr>(); |
177 | |
|
178 | 0 | if (value->flags & APFS_XATTR_FLAG_INLINE) { |
179 | 0 | #pragma pack(push, 1) |
180 | 0 | struct ixattr : apfs_xattr { |
181 | 0 | char data[0]; |
182 | 0 | }; |
183 | 0 | #pragma pack(pop) |
184 | |
|
185 | 0 | const auto ix = e.value.template as<ixattr>(); |
186 | 0 | _inline_xattrs.emplace_back(inline_xattr{{k->name, k->name_len - 1U}, |
187 | 0 | {ix->data, ix->xdata_len}}); |
188 | 0 | break; |
189 | 0 | } |
190 | | |
191 | | // Non-Resident XATTRs |
192 | 0 | #pragma pack(push, 1) |
193 | 0 | struct nrattr : apfs_xattr { |
194 | 0 | uint64_t xattr_obj_id; |
195 | 0 | apfs_dstream dstream; |
196 | 0 | }; |
197 | 0 | #pragma pack(pop) |
198 | 0 | static_assert(sizeof(nrattr) == 0x34, "misaligned structure"); |
199 | |
|
200 | 0 | const auto nrx = e.value.template as<nrattr>(); |
201 | |
|
202 | 0 | _nonres_xattrs.emplace_back(nonres_xattr{{k->name, k->name_len - 1U}, |
203 | 0 | nrx->xattr_obj_id, |
204 | 0 | nrx->dstream.size, |
205 | 0 | nrx->dstream.alloced_size, |
206 | 0 | nrx->dstream.default_crypto_id}); |
207 | |
|
208 | 0 | break; |
209 | 0 | } |
210 | 0 | }; |
211 | 0 | } |
212 | | |
213 | 0 | APFSJObjTree::iterator APFSJObjTree::begin() const { |
214 | 0 | return {this, APFS_ROOT_INODE_NUM}; |
215 | 0 | } |
216 | | |
217 | 0 | APFSJObjTree::iterator APFSJObjTree::end() const { return {this}; } |
218 | | |
219 | | APFSJObjTree::iterator::iterator(const APFSJObjTree* tree, uint64_t oid) |
220 | 0 | : _tree{tree} { |
221 | 0 | auto range = tree->jobjs(oid); |
222 | 0 | _jobj = {range}; |
223 | 0 | _next = std::move(range.second); |
224 | 0 | } |
225 | | |
226 | | APFSJObjTree::iterator::iterator(const APFSJObjTree* tree) noexcept |
227 | 0 | : _tree{tree} {} |
228 | | |
229 | 0 | APFSJObjTree::iterator& APFSJObjTree::iterator::operator++() { |
230 | 0 | if (_next == _tree->_jobj_root.end()) { |
231 | 0 | _next = {}; |
232 | 0 | _jobj = {}; |
233 | 0 | return (*this); |
234 | 0 | } |
235 | | |
236 | 0 | const auto key = _next->key.template as<APFSJObject::key_type>(); |
237 | |
|
238 | 0 | auto end = std::find_if( |
239 | 0 | _next, |
240 | 0 | _tree->_jobj_root.end(), [oid = key->oid()](const auto& it) noexcept { |
241 | 0 | const auto key = it.key.template as<APFSJObject::key_type>(); |
242 | 0 | return key->oid() > oid; |
243 | 0 | }); |
244 | |
|
245 | 0 | _jobj = {_next, end}; |
246 | 0 | _next = std::move(end); |
247 | |
|
248 | 0 | return (*this); |
249 | 0 | } |