]>
Commit | Line | Data |
---|---|---|
f6e195e4 GKH |
1 | From 6b06cdee81d68a8a829ad8e8d0f31d6836744af9 Mon Sep 17 00:00:00 2001 |
2 | From: Eric Biggers <ebiggers@google.com> | |
3 | Date: Mon, 24 Apr 2017 10:00:09 -0700 | |
4 | Subject: fscrypt: avoid collisions when presenting long encrypted filenames | |
5 | ||
6 | From: Eric Biggers <ebiggers@google.com> | |
7 | ||
8 | commit 6b06cdee81d68a8a829ad8e8d0f31d6836744af9 upstream. | |
9 | ||
10 | When accessing an encrypted directory without the key, userspace must | |
11 | operate on filenames derived from the ciphertext names, which contain | |
12 | arbitrary bytes. Since we must support filenames as long as NAME_MAX, | |
13 | we can't always just base64-encode the ciphertext, since that may make | |
14 | it too long. Currently, this is solved by presenting long names in an | |
15 | abbreviated form containing any needed filesystem-specific hashes (e.g. | |
16 | to identify a directory block), then the last 16 bytes of ciphertext. | |
17 | This needs to be sufficient to identify the actual name on lookup. | |
18 | ||
19 | However, there is a bug. It seems to have been assumed that due to the | |
20 | use of a CBC (ciphertext block chaining)-based encryption mode, the last | |
21 | 16 bytes (i.e. the AES block size) of ciphertext would depend on the | |
22 | full plaintext, preventing collisions. However, we actually use CBC | |
23 | with ciphertext stealing (CTS), which handles the last two blocks | |
24 | specially, causing them to appear "flipped". Thus, it's actually the | |
25 | second-to-last block which depends on the full plaintext. | |
26 | ||
27 | This caused long filenames that differ only near the end of their | |
28 | plaintexts to, when observed without the key, point to the wrong inode | |
29 | and be undeletable. For example, with ext4: | |
30 | ||
31 | # echo pass | e4crypt add_key -p 16 edir/ | |
32 | # seq -f "edir/abcdefghijklmnopqrstuvwxyz012345%.0f" 100000 | xargs touch | |
33 | # find edir/ -type f | xargs stat -c %i | sort | uniq | wc -l | |
34 | 100000 | |
35 | # sync | |
36 | # echo 3 > /proc/sys/vm/drop_caches | |
37 | # keyctl new_session | |
38 | # find edir/ -type f | xargs stat -c %i | sort | uniq | wc -l | |
39 | 2004 | |
40 | # rm -rf edir/ | |
41 | rm: cannot remove 'edir/_A7nNFi3rhkEQlJ6P,hdzluhODKOeWx5V': Structure needs cleaning | |
42 | ... | |
43 | ||
44 | To fix this, when presenting long encrypted filenames, encode the | |
45 | second-to-last block of ciphertext rather than the last 16 bytes. | |
46 | ||
47 | Although it would be nice to solve this without depending on a specific | |
48 | encryption mode, that would mean doing a cryptographic hash like SHA-256 | |
49 | which would be much less efficient. This way is sufficient for now, and | |
50 | it's still compatible with encryption modes like HEH which are strong | |
51 | pseudorandom permutations. Also, changing the presented names is still | |
52 | allowed at any time because they are only provided to allow applications | |
53 | to do things like delete encrypted directories. They're not designed to | |
54 | be used to persistently identify files --- which would be hard to do | |
55 | anyway, given that they're encrypted after all. | |
56 | ||
57 | For ease of backports, this patch only makes the minimal fix to both | |
58 | ext4 and f2fs. It leaves ubifs as-is, since ubifs doesn't compare the | |
59 | ciphertext block yet. Follow-on patches will clean things up properly | |
60 | and make the filesystems use a shared helper function. | |
61 | ||
62 | Fixes: 5de0b4d0cd15 ("ext4 crypto: simplify and speed up filename encryption") | |
63 | Reported-by: Gwendal Grignou <gwendal@chromium.org> | |
64 | Signed-off-by: Eric Biggers <ebiggers@google.com> | |
65 | Signed-off-by: Theodore Ts'o <tytso@mit.edu> | |
66 | Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> | |
67 | ||
68 | --- | |
69 | fs/crypto/fname.c | 2 +- | |
70 | fs/ext4/namei.c | 4 ++-- | |
71 | fs/f2fs/dir.c | 4 ++-- | |
72 | 3 files changed, 5 insertions(+), 5 deletions(-) | |
73 | ||
74 | --- a/fs/crypto/fname.c | |
75 | +++ b/fs/crypto/fname.c | |
76 | @@ -300,7 +300,7 @@ int fscrypt_fname_disk_to_usr(struct ino | |
77 | } else { | |
78 | memset(buf, 0, 8); | |
79 | } | |
80 | - memcpy(buf + 8, iname->name + iname->len - 16, 16); | |
81 | + memcpy(buf + 8, iname->name + ((iname->len - 17) & ~15), 16); | |
82 | oname->name[0] = '_'; | |
83 | oname->len = 1 + digest_encode(buf, 24, oname->name + 1); | |
84 | return 0; | |
85 | --- a/fs/ext4/namei.c | |
86 | +++ b/fs/ext4/namei.c | |
87 | @@ -1255,9 +1255,9 @@ static inline int ext4_match(struct ext4 | |
88 | if (unlikely(!name)) { | |
89 | if (fname->usr_fname->name[0] == '_') { | |
90 | int ret; | |
91 | - if (de->name_len < 16) | |
92 | + if (de->name_len <= 32) | |
93 | return 0; | |
94 | - ret = memcmp(de->name + de->name_len - 16, | |
95 | + ret = memcmp(de->name + ((de->name_len - 17) & ~15), | |
96 | fname->crypto_buf.name + 8, 16); | |
97 | return (ret == 0) ? 1 : 0; | |
98 | } | |
99 | --- a/fs/f2fs/dir.c | |
100 | +++ b/fs/f2fs/dir.c | |
101 | @@ -139,8 +139,8 @@ struct f2fs_dir_entry *find_target_dentr | |
102 | #ifdef CONFIG_F2FS_FS_ENCRYPTION | |
103 | if (unlikely(!name->name)) { | |
104 | if (fname->usr_fname->name[0] == '_') { | |
105 | - if (de_name.len >= 16 && | |
106 | - !memcmp(de_name.name + de_name.len - 16, | |
107 | + if (de_name.len > 32 && | |
108 | + !memcmp(de_name.name + ((de_name.len - 17) & ~15), | |
109 | fname->crypto_buf.name + 8, 16)) | |
110 | goto found; | |
111 | goto not_match; |