Coverage Report

Created: 2025-07-18 07:08

/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
}