From patchwork Mon Apr 24 17:00:09 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Biggers X-Patchwork-Id: 9696793 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 7B462603F3 for ; Mon, 24 Apr 2017 17:02:45 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 682E52817F for ; Mon, 24 Apr 2017 17:02:45 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 5CE94283FE; Mon, 24 Apr 2017 17:02:45 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A7F5B28408 for ; Mon, 24 Apr 2017 17:02:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S975305AbdDXRCm (ORCPT ); Mon, 24 Apr 2017 13:02:42 -0400 Received: from mail-pg0-f67.google.com ([74.125.83.67]:32819 "EHLO mail-pg0-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S975302AbdDXRCT (ORCPT ); Mon, 24 Apr 2017 13:02:19 -0400 Received: by mail-pg0-f67.google.com with SMTP id 63so5632038pgh.0; Mon, 24 Apr 2017 10:02:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=Un7uz5+8eIkeZK9v81af6fuRXzvg0hxbPi+XVdXKBiw=; b=HCkVobLm+awmQpnye+J+7MjYktskOW76ukE8cmjLgZ2i3UmAHHobLA4Tls0y5CMDqj NSm2LEbTJEjyA1DVkyl5LQxld/n2qhw92HQNOiAg3/iFxYbKYkM9eKhGFHsaY9Ude+dj zBcK68Imh61SOjnuj+0WQiXhUmhd2C4SflkOZCjLKJpamKugzp9nqrlhqu6DvoP6W/zY 5u2EtXz0DC8WDz7BRFFfWPFRyQixSMMlzKp9I2ipZDzv4QyJlkvkYOlBw3gHkHc5Nv/a 8tPGk6Wi8KYe4IgJcqY+CKRZ0aj7NmOOvkhQR7Gni+gX8at01foXAgJB5LRhU4RfDTyT f6lQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=Un7uz5+8eIkeZK9v81af6fuRXzvg0hxbPi+XVdXKBiw=; b=hLk9dCwg/L1cbwy71fMiqUhtMuTLXmWAOinb4PIHiVdpYuCWTZ+yAWb/HEx5E8ZfSe zBHcskNnY+SHIWeeQ6QM71qVsHVN++CeJEYmhEh5tdTp7zixyBFgcXQ7GVgSaYjOhI4z zWzkuebyqQQUdKC9vZWIt+/uMXUcpB3qsI4GHuX29WQn4HkljWNfdEnDlaM+RWXF9crO grdWtYzB200nXPI2VPvuOMsuJtlOn6Gpf4+8QskDoTXwC19swDLu55vcsJ9hZgoBBRg5 +7a34Ya2ExOaXl2xug4OXjXyGU6spO/LI8vTOux74HC3V/3nAWjaLmQs3yaa32LVd7By GzfA== X-Gm-Message-State: AN3rC/60JoUPw8iLj0kjhd8dYogPxWbnBziOjd/Cggq1ySCnH5DpcZfl LEf+knHXawf4LA== X-Received: by 10.99.139.195 with SMTP id j186mr25516925pge.125.1493053332222; Mon, 24 Apr 2017 10:02:12 -0700 (PDT) Received: from ebiggers-linuxstation.kir.corp.google.com ([100.119.30.131]) by smtp.gmail.com with ESMTPSA id s89sm31927267pfi.79.2017.04.24.10.02.11 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 24 Apr 2017 10:02:11 -0700 (PDT) From: Eric Biggers To: linux-fscrypt@vger.kernel.org Cc: "Theodore Y . Ts'o" , Jaegeuk Kim , linux-f2fs-devel@lists.sourceforge.net, linux-ext4@vger.kernel.org, linux-mtd@lists.infradead.org, Gwendal Grignou , hashimoto@chromium.org, kinaba@chromium.org, Eric Biggers , stable@vger.kernel.org Subject: [PATCH 2/6] fscrypt: avoid collisions when presenting long encrypted filenames Date: Mon, 24 Apr 2017 10:00:09 -0700 Message-Id: <20170424170013.85175-3-ebiggers3@gmail.com> X-Mailer: git-send-email 2.12.2.816.g2cccc81164-goog In-Reply-To: <20170424170013.85175-1-ebiggers3@gmail.com> References: <20170424170013.85175-1-ebiggers3@gmail.com> Sender: linux-fscrypt-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fscrypt@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Eric Biggers 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 Cc: stable@vger.kernel.org Signed-off-by: Eric Biggers --- fs/crypto/fname.c | 2 +- fs/ext4/namei.c | 4 ++-- fs/f2fs/dir.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c index 13052b85c393..932881f27f2f 100644 --- a/fs/crypto/fname.c +++ b/fs/crypto/fname.c @@ -300,7 +300,7 @@ int fscrypt_fname_disk_to_usr(struct inode *inode, } 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; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 6ad612c576fc..e6301b6933fc 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -1255,9 +1255,9 @@ static inline int ext4_match(struct ext4_filename *fname, 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; } diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index 374e4b8f9b70..5df3596a667a 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -139,8 +139,8 @@ struct f2fs_dir_entry *find_target_dentry(struct fscrypt_name *fname, #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;