From: Greg Kroah-Hartman Date: Tue, 23 May 2017 10:23:23 +0000 (+0200) Subject: 4.9-stable patches X-Git-Tag: v3.18.55~40 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f6e195e47b381ad9ffb66d062cc07887b00e474f;p=thirdparty%2Fkernel%2Fstable-queue.git 4.9-stable patches added patches: f2fs-check-entire-encrypted-bigname-when-finding-a-dentry.patch fscrypt-avoid-collisions-when-presenting-long-encrypted-filenames.patch --- diff --git a/queue-4.9/f2fs-check-entire-encrypted-bigname-when-finding-a-dentry.patch b/queue-4.9/f2fs-check-entire-encrypted-bigname-when-finding-a-dentry.patch new file mode 100644 index 00000000000..b7798195f96 --- /dev/null +++ b/queue-4.9/f2fs-check-entire-encrypted-bigname-when-finding-a-dentry.patch @@ -0,0 +1,159 @@ +From 6332cd32c8290a80e929fc044dc5bdba77396e33 Mon Sep 17 00:00:00 2001 +From: Jaegeuk Kim +Date: Mon, 24 Apr 2017 10:00:08 -0700 +Subject: f2fs: check entire encrypted bigname when finding a dentry + +From: Jaegeuk Kim + +commit 6332cd32c8290a80e929fc044dc5bdba77396e33 upstream. + +If user has no key under an encrypted dir, fscrypt gives digested dentries. +Previously, when looking up a dentry, f2fs only checks its hash value with +first 4 bytes of the digested dentry, which didn't handle hash collisions fully. +This patch enhances to check entire dentry bytes likewise ext4. + +Eric reported how to reproduce this issue by: + + # seq -f "edir/abcdefghijklmnopqrstuvwxyz012345%.0f" 100000 | xargs touch + # find edir -type f | xargs stat -c %i | sort | uniq | wc -l +100000 + # sync + # echo 3 > /proc/sys/vm/drop_caches + # keyctl new_session + # find edir -type f | xargs stat -c %i | sort | uniq | wc -l +99999 + +Cc: +Reported-by: Eric Biggers +Signed-off-by: Jaegeuk Kim +(fixed f2fs_dentry_hash() to work even when the hash is 0) +Signed-off-by: Eric Biggers +Signed-off-by: Theodore Ts'o +Signed-off-by: Greg Kroah-Hartman + +--- + fs/f2fs/dir.c | 37 +++++++++++++++++++++---------------- + fs/f2fs/f2fs.h | 3 ++- + fs/f2fs/hash.c | 7 ++++++- + fs/f2fs/inline.c | 4 ++-- + 4 files changed, 31 insertions(+), 20 deletions(-) + +--- a/fs/f2fs/dir.c ++++ b/fs/f2fs/dir.c +@@ -130,19 +130,29 @@ struct f2fs_dir_entry *find_target_dentr + continue; + } + +- /* encrypted case */ ++ if (de->hash_code != namehash) ++ goto not_match; ++ + de_name.name = d->filename[bit_pos]; + de_name.len = le16_to_cpu(de->name_len); + +- /* show encrypted name */ +- if (fname->hash) { +- if (de->hash_code == fname->hash) +- goto found; +- } else if (de_name.len == name->len && +- de->hash_code == namehash && +- !memcmp(de_name.name, name->name, name->len)) ++#ifdef CONFIG_F2FS_FS_ENCRYPTION ++ if (unlikely(!name->name)) { ++ if (fname->usr_fname->name[0] == '_') { ++ if (de_name.len >= 16 && ++ !memcmp(de_name.name + de_name.len - 16, ++ fname->crypto_buf.name + 8, 16)) ++ goto found; ++ goto not_match; ++ } ++ name->name = fname->crypto_buf.name; ++ name->len = fname->crypto_buf.len; ++ } ++#endif ++ if (de_name.len == name->len && ++ !memcmp(de_name.name, name->name, name->len)) + goto found; +- ++not_match: + if (max_slots && max_len > *max_slots) + *max_slots = max_len; + max_len = 0; +@@ -170,12 +180,7 @@ static struct f2fs_dir_entry *find_in_le + struct f2fs_dir_entry *de = NULL; + bool room = false; + int max_slots; +- f2fs_hash_t namehash; +- +- if(fname->hash) +- namehash = cpu_to_le32(fname->hash); +- else +- namehash = f2fs_dentry_hash(&name); ++ f2fs_hash_t namehash = f2fs_dentry_hash(&name, fname); + + nbucket = dir_buckets(level, F2FS_I(dir)->i_dir_level); + nblock = bucket_blocks(level); +@@ -539,7 +544,7 @@ int f2fs_add_regular_entry(struct inode + + level = 0; + slots = GET_DENTRY_SLOTS(new_name->len); +- dentry_hash = f2fs_dentry_hash(new_name); ++ dentry_hash = f2fs_dentry_hash(new_name, NULL); + + current_depth = F2FS_I(dir)->i_current_depth; + if (F2FS_I(dir)->chash == dentry_hash) { +--- a/fs/f2fs/f2fs.h ++++ b/fs/f2fs/f2fs.h +@@ -2016,7 +2016,8 @@ int sanity_check_ckpt(struct f2fs_sb_inf + /* + * hash.c + */ +-f2fs_hash_t f2fs_dentry_hash(const struct qstr *); ++f2fs_hash_t f2fs_dentry_hash(const struct qstr *name_info, ++ struct fscrypt_name *fname); + + /* + * node.c +--- a/fs/f2fs/hash.c ++++ b/fs/f2fs/hash.c +@@ -70,7 +70,8 @@ static void str2hashbuf(const unsigned c + *buf++ = pad; + } + +-f2fs_hash_t f2fs_dentry_hash(const struct qstr *name_info) ++f2fs_hash_t f2fs_dentry_hash(const struct qstr *name_info, ++ struct fscrypt_name *fname) + { + __u32 hash; + f2fs_hash_t f2fs_hash; +@@ -79,6 +80,10 @@ f2fs_hash_t f2fs_dentry_hash(const struc + const unsigned char *name = name_info->name; + size_t len = name_info->len; + ++ /* encrypted bigname case */ ++ if (fname && !fname->disk_name.name) ++ return cpu_to_le32(fname->hash); ++ + if (is_dot_dotdot(name_info)) + return 0; + +--- a/fs/f2fs/inline.c ++++ b/fs/f2fs/inline.c +@@ -294,7 +294,7 @@ struct f2fs_dir_entry *find_in_inline_di + return NULL; + } + +- namehash = f2fs_dentry_hash(&name); ++ namehash = f2fs_dentry_hash(&name, fname); + + inline_dentry = inline_data_addr(ipage); + +@@ -531,7 +531,7 @@ int f2fs_add_inline_entry(struct inode * + + f2fs_wait_on_page_writeback(ipage, NODE, true); + +- name_hash = f2fs_dentry_hash(new_name); ++ name_hash = f2fs_dentry_hash(new_name, NULL); + make_dentry_ptr(NULL, &d, (void *)dentry_blk, 2); + f2fs_update_dentry(ino, mode, &d, new_name, name_hash, bit_pos); + diff --git a/queue-4.9/fscrypt-avoid-collisions-when-presenting-long-encrypted-filenames.patch b/queue-4.9/fscrypt-avoid-collisions-when-presenting-long-encrypted-filenames.patch new file mode 100644 index 00000000000..a0d036d5668 --- /dev/null +++ b/queue-4.9/fscrypt-avoid-collisions-when-presenting-long-encrypted-filenames.patch @@ -0,0 +1,111 @@ +From 6b06cdee81d68a8a829ad8e8d0f31d6836744af9 Mon Sep 17 00:00:00 2001 +From: Eric Biggers +Date: Mon, 24 Apr 2017 10:00:09 -0700 +Subject: fscrypt: avoid collisions when presenting long encrypted filenames + +From: Eric Biggers + +commit 6b06cdee81d68a8a829ad8e8d0f31d6836744af9 upstream. + +When accessing an encrypted directory without the key, userspace must +operate on filenames derived from the ciphertext names, which contain +arbitrary bytes. Since we must support filenames as long as NAME_MAX, +we can't always just base64-encode the ciphertext, since that may make +it too long. Currently, this is solved by presenting long names in an +abbreviated form containing any needed filesystem-specific hashes (e.g. +to identify a directory block), then the last 16 bytes of ciphertext. +This needs to be sufficient to identify the actual name on lookup. + +However, there is a bug. It seems to have been assumed that due to the +use of a CBC (ciphertext block chaining)-based encryption mode, the last +16 bytes (i.e. the AES block size) of ciphertext would depend on the +full plaintext, preventing collisions. However, we actually use CBC +with ciphertext stealing (CTS), which handles the last two blocks +specially, causing them to appear "flipped". Thus, it's actually the +second-to-last block which depends on the full plaintext. + +This caused long filenames that differ only near the end of their +plaintexts to, when observed without the key, point to the wrong inode +and be undeletable. For example, with ext4: + + # echo pass | e4crypt add_key -p 16 edir/ + # seq -f "edir/abcdefghijklmnopqrstuvwxyz012345%.0f" 100000 | xargs touch + # find edir/ -type f | xargs stat -c %i | sort | uniq | wc -l + 100000 + # sync + # echo 3 > /proc/sys/vm/drop_caches + # keyctl new_session + # find edir/ -type f | xargs stat -c %i | sort | uniq | wc -l + 2004 + # rm -rf edir/ + rm: cannot remove 'edir/_A7nNFi3rhkEQlJ6P,hdzluhODKOeWx5V': Structure needs cleaning + ... + +To fix this, when presenting long encrypted filenames, encode the +second-to-last block of ciphertext rather than the last 16 bytes. + +Although it would be nice to solve this without depending on a specific +encryption mode, that would mean doing a cryptographic hash like SHA-256 +which would be much less efficient. This way is sufficient for now, and +it's still compatible with encryption modes like HEH which are strong +pseudorandom permutations. Also, changing the presented names is still +allowed at any time because they are only provided to allow applications +to do things like delete encrypted directories. They're not designed to +be used to persistently identify files --- which would be hard to do +anyway, given that they're encrypted after all. + +For ease of backports, this patch only makes the minimal fix to both +ext4 and f2fs. It leaves ubifs as-is, since ubifs doesn't compare the +ciphertext block yet. Follow-on patches will clean things up properly +and make the filesystems use a shared helper function. + +Fixes: 5de0b4d0cd15 ("ext4 crypto: simplify and speed up filename encryption") +Reported-by: Gwendal Grignou +Signed-off-by: Eric Biggers +Signed-off-by: Theodore Ts'o +Signed-off-by: Greg Kroah-Hartman + +--- + fs/crypto/fname.c | 2 +- + fs/ext4/namei.c | 4 ++-- + fs/f2fs/dir.c | 4 ++-- + 3 files changed, 5 insertions(+), 5 deletions(-) + +--- a/fs/crypto/fname.c ++++ b/fs/crypto/fname.c +@@ -300,7 +300,7 @@ int fscrypt_fname_disk_to_usr(struct ino + } else { + memset(buf, 0, 8); + } +- memcpy(buf + 8, iname->name + iname->len - 16, 16); ++ memcpy(buf + 8, iname->name + ((iname->len - 17) & ~15), 16); + oname->name[0] = '_'; + oname->len = 1 + digest_encode(buf, 24, oname->name + 1); + return 0; +--- a/fs/ext4/namei.c ++++ b/fs/ext4/namei.c +@@ -1255,9 +1255,9 @@ static inline int ext4_match(struct ext4 + if (unlikely(!name)) { + if (fname->usr_fname->name[0] == '_') { + int ret; +- if (de->name_len < 16) ++ if (de->name_len <= 32) + return 0; +- ret = memcmp(de->name + de->name_len - 16, ++ ret = memcmp(de->name + ((de->name_len - 17) & ~15), + fname->crypto_buf.name + 8, 16); + return (ret == 0) ? 1 : 0; + } +--- a/fs/f2fs/dir.c ++++ b/fs/f2fs/dir.c +@@ -139,8 +139,8 @@ struct f2fs_dir_entry *find_target_dentr + #ifdef CONFIG_F2FS_FS_ENCRYPTION + if (unlikely(!name->name)) { + if (fname->usr_fname->name[0] == '_') { +- if (de_name.len >= 16 && +- !memcmp(de_name.name + de_name.len - 16, ++ if (de_name.len > 32 && ++ !memcmp(de_name.name + ((de_name.len - 17) & ~15), + fname->crypto_buf.name + 8, 16)) + goto found; + goto not_match; diff --git a/queue-4.9/series b/queue-4.9/series index 484fd114e49..bdf4bdcf412 100644 --- a/queue-4.9/series +++ b/queue-4.9/series @@ -72,3 +72,5 @@ pid_ns-fix-race-between-setns-ed-fork-and-zap_pid_ns_processes.patch usb-serial-ftdi_sio-fix-setting-latency-for-unprivileged-users.patch usb-serial-ftdi_sio-add-olimex-arm-usb-tiny-h-pids.patch usb-chaoskey-fix-alea-quirk-on-big-endian-hosts.patch +f2fs-check-entire-encrypted-bigname-when-finding-a-dentry.patch +fscrypt-avoid-collisions-when-presenting-long-encrypted-filenames.patch