This repository has been archived by the owner on Jun 29, 2021. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathnode.c
422 lines (366 loc) · 11.9 KB
/
node.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/apfs/node.c
*
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
*/
#include <linux/slab.h>
#include <linux/buffer_head.h>
#include "apfs.h"
#include "btree.h"
#include "key.h"
#include "message.h"
#include "node.h"
#include "object.h"
#include "super.h"
/**
* apfs_node_is_valid - Check basic sanity of the node index
* @sb: filesystem superblock
* @node: node to check
*
* Verifies that the node index fits in a single block, and that the number
* of records fits in the index. Without this check a crafted filesystem could
* pretend to have too many records, and calls to apfs_node_locate_key() and
* apfs_node_locate_data() would read beyond the limits of the node.
*/
static bool apfs_node_is_valid(struct super_block *sb,
struct apfs_node *node)
{
int records = node->records;
int index_size = node->key - sizeof(struct apfs_btree_node_phys);
int entry_size;
if (!records) /* Empty nodes could keep a multiple query spinning */
return false;
if (node->key > sb->s_blocksize)
return false;
entry_size = (apfs_node_has_fixed_kv_size(node)) ?
sizeof(struct apfs_kvoff) : sizeof(struct apfs_kvloc);
return records * entry_size <= index_size;
}
static void apfs_node_release(struct kref *kref)
{
struct apfs_node *node =
container_of(kref, struct apfs_node, refcount);
brelse(node->object.bh);
kfree(node);
}
void apfs_node_get(struct apfs_node *node)
{
kref_get(&node->refcount);
}
void apfs_node_put(struct apfs_node *node)
{
kref_put(&node->refcount, apfs_node_release);
}
/**
* apfs_read_node - Read a node header from disk
* @sb: filesystem superblock
* @block: number of the block where the node is stored
*
* Returns ERR_PTR in case of failure, otherwise return a pointer to the
* resulting apfs_node structure with the initial reference taken.
*
* For now we assume the node has not been read before.
*/
struct apfs_node *apfs_read_node(struct super_block *sb, u64 block)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct buffer_head *bh;
struct apfs_btree_node_phys *raw;
struct apfs_node *node;
bh = sb_bread(sb, block);
if (!bh) {
apfs_err(sb, "unable to read node");
return ERR_PTR(-EINVAL);
}
raw = (struct apfs_btree_node_phys *) bh->b_data;
node = kmalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
brelse(bh);
return ERR_PTR(-ENOMEM);
}
node->flags = le16_to_cpu(raw->btn_flags);
node->records = le32_to_cpu(raw->btn_nkeys);
node->key = sizeof(*raw) + le16_to_cpu(raw->btn_table_space.off)
+ le16_to_cpu(raw->btn_table_space.len);
node->free = node->key + le16_to_cpu(raw->btn_free_space.off);
node->data = node->free + le16_to_cpu(raw->btn_free_space.len);
node->object.sb = sb;
node->object.block_nr = block;
node->object.oid = le64_to_cpu(raw->btn_o.o_oid);
node->object.bh = bh;
kref_init(&node->refcount);
if (sbi->s_flags & APFS_CHECK_NODES &&
!apfs_obj_verify_csum(sb, &raw->btn_o)) {
apfs_alert(sb, "bad checksum for node in block 0x%llx", block);
apfs_node_put(node);
return ERR_PTR(-EFSBADCRC);
}
if (!apfs_node_is_valid(sb, node)) {
apfs_alert(sb, "bad node in block 0x%llx", block);
apfs_node_put(node);
return ERR_PTR(-EFSCORRUPTED);
}
return node;
}
/**
* apfs_node_locate_key - Locate the key of a node record
* @node: node to be searched
* @index: number of the entry to locate
* @off: on return will hold the offset in the block
*
* Returns the length of the key, or 0 in case of failure. The function checks
* that this length fits within the block; callers must use the returned value
* to make sure they never operate outside its bounds.
*/
static int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
{
struct super_block *sb = node->object.sb;
struct apfs_btree_node_phys *raw;
int len;
if (index >= node->records)
return 0;
raw = (struct apfs_btree_node_phys *)node->object.bh->b_data;
if (apfs_node_has_fixed_kv_size(node)) {
struct apfs_kvoff *entry;
entry = (struct apfs_kvoff *)raw->btn_data + index;
len = 16;
/* Translate offset in key area to offset in block */
*off = node->key + le16_to_cpu(entry->k);
} else {
/* These node types have variable length keys and data */
struct apfs_kvloc *entry;
entry = (struct apfs_kvloc *)raw->btn_data + index;
len = le16_to_cpu(entry->k.len);
/* Translate offset in key area to offset in block */
*off = node->key + le16_to_cpu(entry->k.off);
}
if (*off + len > sb->s_blocksize) {
/* Avoid out-of-bounds read if corrupted */
return 0;
}
return len;
}
/**
* apfs_node_locate_data - Locate the data of a node record
* @node: node to be searched
* @index: number of the entry to locate
* @off: on return will hold the offset in the block
*
* Returns the length of the data, or 0 in case of failure. The function checks
* that this length fits within the block; callers must use the returned value
* to make sure they never operate outside its bounds.
*/
static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
{
struct super_block *sb = node->object.sb;
struct apfs_btree_node_phys *raw;
int len;
if (index >= node->records)
return 0;
raw = (struct apfs_btree_node_phys *)node->object.bh->b_data;
if (apfs_node_has_fixed_kv_size(node)) {
/* These node types have fixed length keys and data */
struct apfs_kvoff *entry;
entry = (struct apfs_kvoff *)raw->btn_data + index;
/* Node type decides length */
len = apfs_node_is_leaf(node) ? 16 : 8;
/*
* Data offsets are counted backwards from the end of the
* block, or from the beginning of the footer when it exists
*/
if (apfs_node_is_root(node)) /* has footer */
*off = sb->s_blocksize - sizeof(struct apfs_btree_info)
- le16_to_cpu(entry->v);
else
*off = sb->s_blocksize - le16_to_cpu(entry->v);
} else {
/* These node types have variable length keys and data */
struct apfs_kvloc *entry;
entry = (struct apfs_kvloc *)raw->btn_data + index;
len = le16_to_cpu(entry->v.len);
/*
* Data offsets are counted backwards from the end of the
* block, or from the beginning of the footer when it exists
*/
if (apfs_node_is_root(node)) /* has footer */
*off = sb->s_blocksize - sizeof(struct apfs_btree_info)
- le16_to_cpu(entry->v.off);
else
*off = sb->s_blocksize - le16_to_cpu(entry->v.off);
}
if (*off < 0 || *off + len > sb->s_blocksize) {
/* Avoid out-of-bounds read if corrupted */
return 0;
}
return len;
}
/**
* apfs_key_from_query - Read the current key from a query structure
* @query: the query, with @query->key_off and @query->key_len already set
* @key: return parameter for the key
*
* Reads the key into @key and performs some basic sanity checks as a
* protection against crafted filesystems. Returns 0 on success or a
* negative error code otherwise.
*/
static int apfs_key_from_query(struct apfs_query *query, struct apfs_key *key)
{
struct super_block *sb = query->node->object.sb;
char *raw = query->node->object.bh->b_data;
void *raw_key = (void *)(raw + query->key_off);
int err = 0;
switch (query->flags & APFS_QUERY_TREE_MASK) {
case APFS_QUERY_CAT:
err = apfs_read_cat_key(raw_key, query->key_len, key);
break;
case APFS_QUERY_OMAP:
err = apfs_read_omap_key(raw_key, query->key_len, key);
break;
default:
/* Not implemented yet */
err = -EINVAL;
break;
}
if (err) {
apfs_alert(sb, "bad node key in block 0x%llx",
query->node->object.block_nr);
}
/* A multiple query must ignore some of these fields */
if (query->flags & APFS_QUERY_ANY_NAME)
key->name = NULL;
if (query->flags & APFS_QUERY_ANY_NUMBER)
key->number = 0;
return err;
}
/**
* apfs_node_next - Find the next matching record in the current node
* @sb: filesystem superblock
* @query: multiple query in execution
*
* Returns 0 on success, -EAGAIN if the next record is in another node,
* -ENODATA if no more matching records exist, or another negative error
* code in case of failure.
*/
static int apfs_node_next(struct super_block *sb, struct apfs_query *query)
{
struct apfs_node *node = query->node;
struct apfs_key curr_key;
int cmp, err;
if (query->flags & APFS_QUERY_DONE)
/* Nothing left to search; the query failed */
return -ENODATA;
if (!query->index) /* The next record may be in another node */
return -EAGAIN;
--query->index;
query->key_len = apfs_node_locate_key(node, query->index,
&query->key_off);
err = apfs_key_from_query(query, &curr_key);
if (err)
return err;
cmp = apfs_keycmp(sb, &curr_key, query->key);
if (cmp > 0) /* Records are out of order */
return -EFSCORRUPTED;
if (cmp != 0 && apfs_node_is_leaf(node) &&
query->flags & APFS_QUERY_EXACT)
return -ENODATA;
query->len = apfs_node_locate_data(node, query->index, &query->off);
if (query->len == 0)
return -EFSCORRUPTED;
if (cmp != 0) {
/*
* This is the last entry that can be relevant in this node.
* Keep searching the children, but don't return to this level.
*/
query->flags |= APFS_QUERY_DONE;
}
return 0;
}
/**
* apfs_node_query - Execute a query on a single node
* @sb: filesystem superblock
* @query: the query to execute
*
* The search will start at index @query->index, looking for the key that comes
* right before @query->key, according to the order given by apfs_keycmp().
*
* The @query->index will be updated to the last index checked. This is
* important when searching for multiple entries, since the query may need
* to remember where it was on this level. If we are done with this node, the
* query will be flagged as APFS_QUERY_DONE, and the search will end in failure
* as soon as we return to this level. The function may also return -EAGAIN,
* to signal that the search should go on in a different branch.
*
* On success returns 0; the offset of the data within the block will be saved
* in @query->off, and its length in @query->len. The function checks that this
* length fits within the block; callers must use the returned value to make
* sure they never operate outside its bounds.
*
* -ENODATA will be returned if no appropriate entry was found, -EFSCORRUPTED
* in case of corruption.
*/
int apfs_node_query(struct super_block *sb, struct apfs_query *query)
{
struct apfs_node *node = query->node;
int left, right;
int cmp;
int err;
if (query->flags & APFS_QUERY_NEXT)
return apfs_node_next(sb, query);
/* Search by bisection */
cmp = 1;
left = 0;
do {
struct apfs_key curr_key;
if (cmp > 0) {
right = query->index - 1;
if (right < left)
return -ENODATA;
query->index = (left + right) / 2;
} else {
left = query->index;
query->index = DIV_ROUND_UP(left + right, 2);
}
query->key_len = apfs_node_locate_key(node, query->index,
&query->key_off);
err = apfs_key_from_query(query, &curr_key);
if (err)
return err;
cmp = apfs_keycmp(sb, &curr_key, query->key);
if (cmp == 0 && !(query->flags & APFS_QUERY_MULTIPLE))
break;
} while (left != right);
if (cmp > 0)
return -ENODATA;
if (cmp != 0 && apfs_node_is_leaf(query->node) &&
query->flags & APFS_QUERY_EXACT)
return -ENODATA;
if (query->flags & APFS_QUERY_MULTIPLE) {
if (cmp != 0) /* Last relevant entry in level */
query->flags |= APFS_QUERY_DONE;
query->flags |= APFS_QUERY_NEXT;
}
query->len = apfs_node_locate_data(node, query->index, &query->off);
if (query->len == 0)
return -EFSCORRUPTED;
return 0;
}
/**
* apfs_bno_from_query - Read the block number found by a successful omap query
* @query: the query that found the record
* @bno: Return parameter. The block number found.
*
* Reads the block number in the omap record into @bno and performs a basic
* sanity check as a protection against crafted filesystems. Returns 0 on
* success or -EFSCORRUPTED otherwise.
*/
int apfs_bno_from_query(struct apfs_query *query, u64 *bno)
{
struct apfs_omap_val *omap_val;
char *raw = query->node->object.bh->b_data;
if (query->len != sizeof(*omap_val))
return -EFSCORRUPTED;
omap_val = (struct apfs_omap_val *)(raw + query->off);
*bno = le64_to_cpu(omap_val->ov_paddr);
return 0;
}