summaryrefslogtreecommitdiffstats
path: root/e2fsck/encrypted_files.c
blob: 16be2d6d8cce58c30842e9870e70092337f334c4 (plain)
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
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
/*
 * encrypted_files.c --- save information about encrypted files
 *
 * Copyright 2019 Google LLC
 *
 * %Begin-Header%
 * This file may be redistributed under the terms of the GNU Public
 * License.
 * %End-Header%
 */

/*
 * e2fsck pass 1 (inode table scan) creates a map from inode number to
 * encryption policy for all encrypted inodes.  But it's optimized so that the
 * full xattrs aren't saved but rather only 32-bit "policy IDs", since usually
 * many inodes share the same encryption policy.  This requires also maintaining
 * a second map, from policy to policy ID.  See add_encrypted_file().
 *
 * We also use run-length encoding to save memory when many adjacent inodes
 * share the same encryption policy, which is often the case too.
 *
 * e2fsck pass 2 (directory structure check) uses the inode => policy ID map to
 * verify that all regular files, directories, and symlinks in encrypted
 * directories use the directory's encryption policy.
 */

#include "config.h"

#include "e2fsck.h"
#include "problem.h"
#include "ext2fs/rbtree.h"

#define FSCRYPT_KEY_DESCRIPTOR_SIZE	8
#define FSCRYPT_KEY_IDENTIFIER_SIZE	16
#define FS_KEY_DERIVATION_NONCE_SIZE	16

struct fscrypt_context_v1 {
	__u8 version;
	__u8 contents_encryption_mode;
	__u8 filenames_encryption_mode;
	__u8 flags;
	__u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
	__u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE];
};

struct fscrypt_context_v2 {
	__u8 version;
	__u8 contents_encryption_mode;
	__u8 filenames_encryption_mode;
	__u8 flags;
	__u8 __reserved[4];
	__u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
	__u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE];
};

/* On-disk format of encryption xattr */
union fscrypt_context {
	__u8 version;
	struct fscrypt_context_v1 v1;
	struct fscrypt_context_v2 v2;
};

struct fscrypt_policy_v1 {
	__u8 version;
	__u8 contents_encryption_mode;
	__u8 filenames_encryption_mode;
	__u8 flags;
	__u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
};

struct fscrypt_policy_v2 {
	__u8 version;
	__u8 contents_encryption_mode;
	__u8 filenames_encryption_mode;
	__u8 flags;
	__u8 __reserved[4];
	__u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
};

/* The encryption "policy" is the fscrypt_context excluding the nonce. */
union fscrypt_policy {
	__u8 version;
	struct fscrypt_policy_v1 v1;
	struct fscrypt_policy_v2 v2;
};

/* A range of inodes which share the same encryption policy */
struct encrypted_file_range {
	ext2_ino_t		first_ino;
	ext2_ino_t		last_ino;
	__u32			policy_id;
};

/* Information about the encrypted files which have been seen so far */
struct encrypted_file_info {
	/*
	 * Map from inode number to encryption policy ID, implemented as a
	 * sorted array of inode ranges, each of which shares the same policy.
	 * Inodes are added in order of increasing inode number.
	 *
	 * Freed after pass 2.
	 */
	struct encrypted_file_range	*file_ranges;
	size_t				file_ranges_count;
	size_t				file_ranges_capacity;

	/*
	 * Map from encryption policy to encryption policy ID, for the unique
	 * encryption policies that have been seen so far.  next_policy_id is
	 * the next available ID, starting at 0.
	 *
	 * Freed after pass 1.
	 */
	struct rb_root		policies;
	__u32			next_policy_id;
};

/* Entry in encrypted_file_info::policies */
struct policy_map_entry {
	union fscrypt_policy	policy;
	__u32			policy_id;
	struct rb_node		node;
};

static int cmp_fscrypt_policies(e2fsck_t ctx, const union fscrypt_policy *a,
				const union fscrypt_policy *b)
{
	if (a->version != b->version)
		return (int)a->version - (int)b->version;

	switch (a->version) {
	case 1:
		return memcmp(a, b, sizeof(a->v1));
	case 2:
		return memcmp(a, b, sizeof(a->v2));
	}
	fatal_error(ctx, "Unhandled encryption policy version");
	return 0;
}

/* Read an inode's encryption xattr. */
static errcode_t read_encryption_xattr(e2fsck_t ctx, ext2_ino_t ino,
				       void **value, size_t *value_len)
{
	struct ext2_xattr_handle *h;
	errcode_t retval;

	retval = ext2fs_xattrs_open(ctx->fs, ino, &h);
	if (retval)
		return retval;

	retval = ext2fs_xattrs_read(h);
	if (retval == 0)
		retval = ext2fs_xattr_get(h, "c", value, value_len);

	ext2fs_xattrs_close(&h);
	return retval;
}

/*
 * Convert an fscrypt_context to an fscrypt_policy.  Returns 0,
 * CORRUPT_ENCRYPTION_POLICY, or UNRECOGNIZED_ENCRYPTION_POLICY.
 */
static __u32 fscrypt_context_to_policy(const void *xattr, size_t xattr_size,
				       union fscrypt_policy *policy_u)
{
	const union fscrypt_context *ctx_u = xattr;

	if (xattr_size < 1)
		return CORRUPT_ENCRYPTION_POLICY;
	switch (ctx_u->version) {
	case 0:
		return CORRUPT_ENCRYPTION_POLICY;
	case 1: {
		struct fscrypt_policy_v1 *policy = &policy_u->v1;
		const struct fscrypt_context_v1 *ctx = &ctx_u->v1;

		if (xattr_size != sizeof(*ctx))
			return CORRUPT_ENCRYPTION_POLICY;
		policy->version = ctx->version;
		policy->contents_encryption_mode =
			ctx->contents_encryption_mode;
		policy->filenames_encryption_mode =
			ctx->filenames_encryption_mode;
		policy->flags = ctx->flags;
		memcpy(policy->master_key_descriptor,
		       ctx->master_key_descriptor,
		       sizeof(policy->master_key_descriptor));
		return 0;
	}
	case 2: {
		struct fscrypt_policy_v2 *policy = &policy_u->v2;
		const struct fscrypt_context_v2 *ctx = &ctx_u->v2;

		if (xattr_size != sizeof(*ctx))
			return CORRUPT_ENCRYPTION_POLICY;
		policy->version = ctx->version;
		policy->contents_encryption_mode =
			ctx->contents_encryption_mode;
		policy->filenames_encryption_mode =
			ctx->filenames_encryption_mode;
		policy->flags = ctx->flags;
		memcpy(policy->__reserved, ctx->__reserved,
		       sizeof(policy->__reserved));
		memcpy(policy->master_key_identifier,
		       ctx->master_key_identifier,
		       sizeof(policy->master_key_identifier));
		return 0;
	}
	}
	return UNRECOGNIZED_ENCRYPTION_POLICY;
}

/*
 * Read an inode's encryption xattr and get/allocate its encryption policy ID,
 * or alternatively use one of the special IDs NO_ENCRYPTION_POLICY,
 * CORRUPT_ENCRYPTION_POLICY, or UNRECOGNIZED_ENCRYPTION_POLICY.
 *
 * Returns nonzero only if out of memory.
 */
static errcode_t get_encryption_policy_id(e2fsck_t ctx, ext2_ino_t ino,
					  __u32 *policy_id_ret)
{
	struct encrypted_file_info *info = ctx->encrypted_files;
	struct rb_node **new = &info->policies.rb_node;
	struct rb_node *parent = NULL;
	void *xattr;
	size_t xattr_size;
	union fscrypt_policy policy;
	__u32 policy_id;
	struct policy_map_entry *entry;
	errcode_t retval;

	retval = read_encryption_xattr(ctx, ino, &xattr, &xattr_size);
	if (retval == EXT2_ET_NO_MEMORY)
		return retval;
	if (retval) {
		*policy_id_ret = NO_ENCRYPTION_POLICY;
		return 0;
	}

	/* Translate the xattr to an fscrypt_policy, if possible. */
	policy_id = fscrypt_context_to_policy(xattr, xattr_size, &policy);
	ext2fs_free_mem(&xattr);
	if (policy_id != 0)
		goto out;

	/* Check if the policy was already seen. */
	while (*new) {
		int res;

		parent = *new;
		entry = ext2fs_rb_entry(parent, struct policy_map_entry, node);
		res = cmp_fscrypt_policies(ctx, &policy, &entry->policy);
		if (res < 0) {
			new = &parent->rb_left;
		} else if (res > 0) {
			new = &parent->rb_right;
		} else {
			/* Policy already seen.  Use existing ID. */
			policy_id = entry->policy_id;
			goto out;
		}
	}

	/* First time seeing this policy.  Allocate a new policy ID. */
	retval = ext2fs_get_mem(sizeof(*entry), &entry);
	if (retval)
		goto out;
	policy_id = info->next_policy_id++;
	entry->policy_id = policy_id;
	entry->policy = policy;
	ext2fs_rb_link_node(&entry->node, parent, new);
	ext2fs_rb_insert_color(&entry->node, &info->policies);
out:
	*policy_id_ret = policy_id;
	return retval;
}

static int handle_nomem(e2fsck_t ctx, struct problem_context *pctx,
			size_t size_needed)
{
	pctx->num = size_needed;
	fix_problem(ctx, PR_1_ALLOCATE_ENCRYPTED_INODE_LIST, pctx);
	/* Should never get here */
	ctx->flags |= E2F_FLAG_ABORT;
	return 0;
}

static int append_ino_and_policy_id(e2fsck_t ctx, struct problem_context *pctx,
				    ext2_ino_t ino, __u32 policy_id)
{
	struct encrypted_file_info *info = ctx->encrypted_files;
	struct encrypted_file_range *range;

	/* See if we can just extend the last range. */
	if (info->file_ranges_count > 0) {
		range = &info->file_ranges[info->file_ranges_count - 1];

		if (ino <= range->last_ino) {
			/* Should never get here */
			fatal_error(ctx,
				    "Encrypted inodes processed out of order");
		}

		if (ino == range->last_ino + 1 &&
		    policy_id == range->policy_id) {
			range->last_ino++;
			return 0;
		}
	}
	/* Nope, a new range is needed. */

	if (info->file_ranges_count == info->file_ranges_capacity) {
		/* Double the capacity by default. */
		size_t new_capacity = info->file_ranges_capacity * 2;

		/* ... but go from 0 to 128 right away. */
		if (new_capacity < 128)
			new_capacity = 128;

		/* We won't need more than the filesystem's inode count. */
		if (new_capacity > ctx->fs->super->s_inodes_count)
			new_capacity = ctx->fs->super->s_inodes_count;

		/* To be safe, ensure the capacity really increases. */
		if (new_capacity < info->file_ranges_capacity + 1)
			new_capacity = info->file_ranges_capacity + 1;

		if (ext2fs_resize_mem(info->file_ranges_capacity *
					sizeof(*range),
				      new_capacity * sizeof(*range),
				      &info->file_ranges) != 0)
			return handle_nomem(ctx, pctx,
					    new_capacity * sizeof(*range));

		info->file_ranges_capacity = new_capacity;
	}
	range = &info->file_ranges[info->file_ranges_count++];
	range->first_ino = ino;
	range->last_ino = ino;
	range->policy_id = policy_id;
	return 0;
}

/*
 * Handle an inode that has EXT4_ENCRYPT_FL set during pass 1.  Normally this
 * just finds the unique ID that identifies the inode's encryption policy
 * (allocating a new ID if needed), and adds the inode number and its policy ID
 * to the encrypted_file_info so that it's available in pass 2.
 *
 * But this also handles:
 * - If the inode doesn't have an encryption xattr at all, offer to clear the
 *   encrypt flag.
 * - If the encryption xattr is clearly corrupt, tell the caller that the whole
 *   inode should be cleared.
 * - To be future-proof: if the encryption xattr has an unrecognized version
 *   number, it *might* be valid, so we don't consider it invalid.  But we can't
 *   do much with it, so give all such policies the same ID,
 *   UNRECOGNIZED_ENCRYPTION_POLICY.
 *
 * Returns -1 if the inode should be cleared, otherwise 0.
 */
int add_encrypted_file(e2fsck_t ctx, struct problem_context *pctx)
{
	struct encrypted_file_info *info = ctx->encrypted_files;
	ext2_ino_t ino = pctx->ino;
	__u32 policy_id;

	/* Allocate the encrypted_file_info if needed. */
	if (info == NULL) {
		if (ext2fs_get_memzero(sizeof(*info), &info) != 0)
			return handle_nomem(ctx, pctx, sizeof(*info));
		ctx->encrypted_files = info;
	}

	/* Get a unique ID for this inode's encryption policy. */
	if (get_encryption_policy_id(ctx, ino, &policy_id) != 0)
		return handle_nomem(ctx, pctx, 0 /* unknown size */);
	if (policy_id == NO_ENCRYPTION_POLICY) {
		if (fix_problem(ctx, PR_1_MISSING_ENCRYPTION_XATTR, pctx)) {
			pctx->inode->i_flags &= ~EXT4_ENCRYPT_FL;
			e2fsck_write_inode(ctx, ino, pctx->inode, "pass1");
		}
		return 0;
	} else if (policy_id == CORRUPT_ENCRYPTION_POLICY) {
		if (fix_problem(ctx, PR_1_CORRUPT_ENCRYPTION_XATTR, pctx))
			return -1;
		return 0;
	}

	/* Store this ino => policy_id mapping in the encrypted_file_info. */
	return append_ino_and_policy_id(ctx, pctx, ino, policy_id);
}

/*
 * Find the ID of an inode's encryption policy, using the information saved
 * earlier.
 *
 * If the inode is encrypted, returns the policy ID or
 * UNRECOGNIZED_ENCRYPTION_POLICY.  Else, returns NO_ENCRYPTION_POLICY.
 */
__u32 find_encryption_policy(e2fsck_t ctx, ext2_ino_t ino)
{
	const struct encrypted_file_info *info = ctx->encrypted_files;
	size_t l, r;

	if (info == NULL)
		return NO_ENCRYPTION_POLICY;
	l = 0;
	r = info->file_ranges_count;
	while (l < r) {
		size_t m = l + (r - l) / 2;
		const struct encrypted_file_range *range =
			&info->file_ranges[m];

		if (ino < range->first_ino)
			r = m;
		else if (ino > range->last_ino)
			l = m + 1;
		else
			return range->policy_id;
	}
	return NO_ENCRYPTION_POLICY;
}

/* Destroy ctx->encrypted_files->policies */
void destroy_encryption_policy_map(e2fsck_t ctx)
{
	struct encrypted_file_info *info = ctx->encrypted_files;

	if (info) {
		struct rb_root *policies = &info->policies;

		while (!ext2fs_rb_empty_root(policies)) {
			struct policy_map_entry *entry;

			entry = ext2fs_rb_entry(policies->rb_node,
						struct policy_map_entry, node);
			ext2fs_rb_erase(&entry->node, policies);
			ext2fs_free_mem(&entry);
		}
		info->next_policy_id = 0;
	}
}

/* Destroy ctx->encrypted_files */
void destroy_encrypted_file_info(e2fsck_t ctx)
{
	struct encrypted_file_info *info = ctx->encrypted_files;

	if (info) {
		destroy_encryption_policy_map(ctx);
		ext2fs_free_mem(&info->file_ranges);
		ext2fs_free_mem(&info);
		ctx->encrypted_files = NULL;
	}
}