]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/blame - releases/4.9.30/fscrypt-avoid-collisions-when-presenting-long-encrypted-filenames.patch
Linux 4.14.95
[thirdparty/kernel/stable-queue.git] / releases / 4.9.30 / fscrypt-avoid-collisions-when-presenting-long-encrypted-filenames.patch
CommitLineData
f6e195e4
GKH
1From 6b06cdee81d68a8a829ad8e8d0f31d6836744af9 Mon Sep 17 00:00:00 2001
2From: Eric Biggers <ebiggers@google.com>
3Date: Mon, 24 Apr 2017 10:00:09 -0700
4Subject: fscrypt: avoid collisions when presenting long encrypted filenames
5
6From: Eric Biggers <ebiggers@google.com>
7
8commit 6b06cdee81d68a8a829ad8e8d0f31d6836744af9 upstream.
9
10When accessing an encrypted directory without the key, userspace must
11operate on filenames derived from the ciphertext names, which contain
12arbitrary bytes. Since we must support filenames as long as NAME_MAX,
13we can't always just base64-encode the ciphertext, since that may make
14it too long. Currently, this is solved by presenting long names in an
15abbreviated form containing any needed filesystem-specific hashes (e.g.
16to identify a directory block), then the last 16 bytes of ciphertext.
17This needs to be sufficient to identify the actual name on lookup.
18
19However, there is a bug. It seems to have been assumed that due to the
20use of a CBC (ciphertext block chaining)-based encryption mode, the last
2116 bytes (i.e. the AES block size) of ciphertext would depend on the
22full plaintext, preventing collisions. However, we actually use CBC
23with ciphertext stealing (CTS), which handles the last two blocks
24specially, causing them to appear "flipped". Thus, it's actually the
25second-to-last block which depends on the full plaintext.
26
27This caused long filenames that differ only near the end of their
28plaintexts to, when observed without the key, point to the wrong inode
29and 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
44To fix this, when presenting long encrypted filenames, encode the
45second-to-last block of ciphertext rather than the last 16 bytes.
46
47Although it would be nice to solve this without depending on a specific
48encryption mode, that would mean doing a cryptographic hash like SHA-256
49which would be much less efficient. This way is sufficient for now, and
50it's still compatible with encryption modes like HEH which are strong
51pseudorandom permutations. Also, changing the presented names is still
52allowed at any time because they are only provided to allow applications
53to do things like delete encrypted directories. They're not designed to
54be used to persistently identify files --- which would be hard to do
55anyway, given that they're encrypted after all.
56
57For ease of backports, this patch only makes the minimal fix to both
58ext4 and f2fs. It leaves ubifs as-is, since ubifs doesn't compare the
59ciphertext block yet. Follow-on patches will clean things up properly
60and make the filesystems use a shared helper function.
61
62Fixes: 5de0b4d0cd15 ("ext4 crypto: simplify and speed up filename encryption")
63Reported-by: Gwendal Grignou <gwendal@chromium.org>
64Signed-off-by: Eric Biggers <ebiggers@google.com>
65Signed-off-by: Theodore Ts'o <tytso@mit.edu>
66Signed-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;