From patchwork Fri Dec 15 17:42:11 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Biggers X-Patchwork-Id: 10115693 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 CB6AF6019C for ; Fri, 15 Dec 2017 17:46:57 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B7DDC29EE1 for ; Fri, 15 Dec 2017 17:46:57 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id ACC112A024; Fri, 15 Dec 2017 17:46:57 +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 D64B029EE1 for ; Fri, 15 Dec 2017 17:46:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756088AbdLORq4 (ORCPT ); Fri, 15 Dec 2017 12:46:56 -0500 Received: from mail-pg0-f68.google.com ([74.125.83.68]:41737 "EHLO mail-pg0-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755929AbdLORqt (ORCPT ); Fri, 15 Dec 2017 12:46:49 -0500 Received: by mail-pg0-f68.google.com with SMTP id o2so6250681pgc.8; Fri, 15 Dec 2017 09:46:49 -0800 (PST) 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=ZYm4upuqql1Q1Z43nDxClqmYrgCIyNmSH4lk+YxxQW0=; b=WuMW5h21fGUabQyaGpdFRt41xktN4mb0a6l+aGCdxcShzdLv12dHFAB8z9wkfy6S/M s4D5oTfX/hWRoVjNcOy7UmJlCrKYePGEeevRTJegG3dXLdOwk5d1u3ic8mn1wQfLFUnj AD/DJO6NDUeiHid5hcw85RxORbhVBW35gHcPIlh0A2jmtMAjPMZreBCY2Ij/ekwU7429 Vk7Nx7zL9WnmQHEoJ6bISarkixVPbzrr0OmSLBi6oynufD3mBmif33X580Uy93ZCcZzA HurhoyfpjGrPQ2Y7mMr6a5C0/RmY/MATKGVzyRWVTiyIbVYOQtQ/KAcptixd/FCnSakT 2YxQ== 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=ZYm4upuqql1Q1Z43nDxClqmYrgCIyNmSH4lk+YxxQW0=; b=uHnTPSxS+k+Frg/40PqoZvTkFJhMMjEPobg2EwFdNE32iLj6cyNCCCUh/NtEP42rGu 1HsxS2ZGLMSTHUUEMe5pV2d/IxmbYQ4PXYeYGwQ45ZmLFAPKV1N2Zbt8HPH2CowPeu9d SDp2W0FRC/4C0GBcRh4n3t9428zRmfFbnou+XkEiZAjCYw4FpA6qzw/XJ0IgFyNWHhzE 95x3mZ71FRxOit/+3STmY3Xll5pIqVt0v8vxjpcQyPPA01CAUM+ExNHgU0/GzH0X7YsC CcMTlwQHKOc3IkgILshNlO5MHE1yKzm+YsFtz6gx6/fpd4Ics9oIIZz76hUw3a/9lGGw XBTg== X-Gm-Message-State: AKGB3mL2SJQ8ByUR4GdxLNGpEw4VTefWjiRL8deqcJabdZq6M4cif3m3 xiqJULHw3tltbY7EkpfXRaW8+R9FQBo= X-Google-Smtp-Source: ACJfBouxqTiM9umYrDoQCO9+WTOaMkZNrmOKGy42M6hQ3Q2CC/uapCIOkM9/1lvkAlIsNouLvo43OA== X-Received: by 10.84.218.8 with SMTP id q8mr6810025pli.117.1513360008670; Fri, 15 Dec 2017 09:46:48 -0800 (PST) Received: from zzz.localdomain (c-67-185-97-198.hsd1.wa.comcast.net. [67.185.97.198]) by smtp.gmail.com with ESMTPSA id j62sm12980149pfc.18.2017.12.15.09.46.47 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 15 Dec 2017 09:46:48 -0800 (PST) From: Eric Biggers To: linux-fscrypt@vger.kernel.org Cc: "Theodore Y . Ts'o" , Jaegeuk Kim , linux-ext4@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net, linux-mtd@lists.infradead.org, Eric Biggers Subject: [PATCH 10/24] fscrypt: new helper functions for ->symlink() Date: Fri, 15 Dec 2017 09:42:11 -0800 Message-Id: <20171215174225.31583-11-ebiggers3@gmail.com> X-Mailer: git-send-email 2.15.1 In-Reply-To: <20171215174225.31583-1-ebiggers3@gmail.com> References: <20171215174225.31583-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 Currently, filesystems supporting fscrypt need to implement some tricky logic when creating encrypted symlinks, including handling a peculiar on-disk format (struct fscrypt_symlink_data) and correctly calculating the size of the encrypted symlink. Introduce helper functions to make things a bit easier: - fscrypt_prepare_symlink() computes and validates the size the symlink target will require on-disk. - fscrypt_encrypt_symlink() creates the encrypted target if needed. The new helpers actually fix some subtle bugs. First, when checking whether the symlink target was too long, filesystems didn't account for the fact that the NUL padding is meant to be truncated if it would cause the maximum length to be exceeded, as is done for filenames in directories. Consequently users would receive ENAMETOOLONG when creating symlinks close to what is supposed to be the maximum length. For example, with EXT4 with a 4K block size, the maximum symlink target length in an encrypted directory is supposed to be 4093 bytes (in comparison to 4095 in an unencrypted directory), but in FS_POLICY_FLAGS_PAD_32-mode only up to 4064 bytes were accepted. Second, symlink targets of "." and ".." were not being encrypted, even though they should be, as these names are special in *directory entries* but not in symlink targets. Fortunately, we can fix this simply by starting to encrypt them, as old kernels already accept them in encrypted form. Third, the output string length the filesystems were providing when doing the actual encryption was incorrect, as it was forgotten to exclude 'sizeof(struct fscrypt_symlink_data)'. Fortunately though, this bug didn't make a difference. Signed-off-by: Eric Biggers --- fs/crypto/fname.c | 8 ++-- fs/crypto/fscrypt_private.h | 4 ++ fs/crypto/hooks.c | 86 +++++++++++++++++++++++++++++++++++++++++ include/linux/fscrypt.h | 64 ++++++++++++++++++++++++++++++ include/linux/fscrypt_notsupp.h | 16 ++++++++ include/linux/fscrypt_supp.h | 6 +++ 6 files changed, 181 insertions(+), 3 deletions(-) diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c index 52d4dbe1e8e7..62f13d533439 100644 --- a/fs/crypto/fname.c +++ b/fs/crypto/fname.c @@ -34,8 +34,8 @@ static inline bool fscrypt_is_dot_dotdot(const struct qstr *str) * * Return: 0 on success, -errno on failure */ -static int fname_encrypt(struct inode *inode, - const struct qstr *iname, struct fscrypt_str *oname) +int fname_encrypt(struct inode *inode, + const struct qstr *iname, struct fscrypt_str *oname) { struct skcipher_request *req = NULL; DECLARE_CRYPTO_WAIT(wait); @@ -56,9 +56,11 @@ static int fname_encrypt(struct inode *inode, * Copy the filename to the output buffer for encrypting in-place and * pad it with the needed number of NUL bytes. */ + if (WARN_ON(oname->len < iname->len)) + return -ENOBUFS; cryptlen = max_t(unsigned int, iname->len, FS_CRYPTO_BLOCK_SIZE); cryptlen = round_up(cryptlen, padding); - cryptlen = min(cryptlen, lim); + cryptlen = min3(cryptlen, lim, oname->len); memcpy(oname->name, iname->name, iname->len); memset(oname->name + iname->len, 0, cryptlen - iname->len); diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index 2b848e7c92f0..6995bca5006b 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -98,6 +98,10 @@ extern int fscrypt_do_page_crypto(const struct inode *inode, extern struct page *fscrypt_alloc_bounce_page(struct fscrypt_ctx *ctx, gfp_t gfp_flags); +/* fname.c */ +extern int fname_encrypt(struct inode *inode, + const struct qstr *iname, struct fscrypt_str *oname); + /* keyinfo.c */ extern void __exit fscrypt_essiv_cleanup(void); diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index 9f5fb2eb9cf7..483e87d40f03 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -110,3 +110,89 @@ int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry) return 0; } EXPORT_SYMBOL_GPL(__fscrypt_prepare_lookup); + +int __fscrypt_prepare_symlink(struct inode *dir, unsigned int len, + unsigned int max_len, + struct fscrypt_str *disk_link) +{ + int err; + + /* + * To calculate the size of the encrypted symlink target we need to know + * the amount of NUL padding, which is determined by the flags set in + * the encryption policy which will be inherited from the directory. + * The easiest way to get access to this is to just load the directory's + * fscrypt_info, since we'll need it to create the dir_entry anyway. + */ + err = fscrypt_require_key(dir); + if (err) + return err; + + /* + * Calculate the size of the encrypted symlink and verify it won't + * exceed max_len. Note that for historical reasons, encrypted symlink + * targets are prefixed with the ciphertext length, despite this + * actually being redundant with i_size. This decreases by 2 bytes the + * longest symlink target we can accept. + * + * We could recover 1 byte by not counting a null terminator, but + * counting it (even though it is meaningless for ciphertext) is simpler + * for now since filesystems will assume it is there and subtract it. + */ + if (sizeof(struct fscrypt_symlink_data) + len > max_len) + return -ENAMETOOLONG; + disk_link->len = min_t(unsigned int, + sizeof(struct fscrypt_symlink_data) + + fscrypt_fname_encrypted_size(dir, len), + max_len); + disk_link->name = NULL; + return 0; +} +EXPORT_SYMBOL_GPL(__fscrypt_prepare_symlink); + +int __fscrypt_encrypt_symlink(struct inode *inode, const char *target, + unsigned int len, struct fscrypt_str *disk_link) +{ + int err; + struct qstr iname = { .name = target, .len = len }; + struct fscrypt_symlink_data *sd; + unsigned int ciphertext_len; + struct fscrypt_str oname; + + err = fscrypt_require_key(inode); + if (err) + return err; + + if (disk_link->name) { + /* filesystem-provided buffer */ + sd = (struct fscrypt_symlink_data *)disk_link->name; + } else { + sd = kmalloc(disk_link->len, GFP_NOFS); + if (!sd) + return -ENOMEM; + } + ciphertext_len = disk_link->len - sizeof(*sd); + sd->len = cpu_to_le16(ciphertext_len); + + oname.name = sd->encrypted_path; + oname.len = ciphertext_len; + err = fname_encrypt(inode, &iname, &oname); + if (err) { + if (!disk_link->name) + kfree(sd); + return err; + } + BUG_ON(oname.len != ciphertext_len); + + /* + * Null-terminating the ciphertext doesn't make sense, but we still + * count the null terminator in the length, so we might as well + * initialize it just in case the filesystem writes it out. + */ + sd->encrypted_path[ciphertext_len] = '\0'; + + if (!disk_link->name) + disk_link->name = (unsigned char *)sd; + return 0; +} +EXPORT_SYMBOL_GPL(__fscrypt_encrypt_symlink); diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 071ebabfc287..6a678d0e956a 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -196,4 +196,68 @@ static inline int fscrypt_prepare_setattr(struct dentry *dentry, return 0; } +/** + * fscrypt_prepare_symlink - prepare to create a possibly-encrypted symlink + * @dir: directory in which the symlink is being created + * @target: plaintext symlink target + * @len: length of @target excluding null terminator + * @max_len: space the filesystem has available to store the symlink target + * @disk_link: (out) the on-disk symlink target being prepared + * + * This function computes the size the symlink target will require on-disk, + * stores it in @disk_link->len, and validates it against @max_len. An + * encrypted symlink may be longer than the original. + * + * Additionally, @disk_link->name is set to @target if the symlink will be + * unencrypted, but left NULL if the symlink will be encrypted. For encrypted + * symlinks, the filesystem must call fscrypt_encrypt_symlink() to create the + * on-disk target later. (The reason for the two-step process is that some + * filesystems need to know the size of the symlink target before creating the + * inode, e.g. to determine whether it will be a "fast" or "slow" symlink.) + * + * Return: 0 on success, -ENAMETOOLONG if the symlink target is too long, + * -ENOKEY if the encryption key is missing, or another -errno code if a problem + * occurred while setting up the encryption key. + */ +static inline int fscrypt_prepare_symlink(struct inode *dir, + const char *target, + unsigned int len, + unsigned int max_len, + struct fscrypt_str *disk_link) +{ + if (IS_ENCRYPTED(dir) || fscrypt_dummy_context_enabled(dir)) + return __fscrypt_prepare_symlink(dir, len, max_len, disk_link); + + disk_link->name = (unsigned char *)target; + disk_link->len = len + 1; + if (disk_link->len > max_len) + return -ENAMETOOLONG; + return 0; +} + +/** + * fscrypt_encrypt_symlink - encrypt the symlink target if needed + * @inode: symlink inode + * @target: plaintext symlink target + * @len: length of @target excluding null terminator + * @disk_link: (in/out) the on-disk symlink target being prepared + * + * If the symlink target needs to be encrypted, then this function encrypts it + * into @disk_link->name. fscrypt_prepare_symlink() must have been called + * previously to compute @disk_link->len. If the filesystem did not allocate a + * buffer for @disk_link->name after calling fscrypt_prepare_link(), then one + * will be kmalloc()'ed and the filesystem will be responsible for freeing it. + * + * Return: 0 on success, -errno on failure + */ +static inline int fscrypt_encrypt_symlink(struct inode *inode, + const char *target, + unsigned int len, + struct fscrypt_str *disk_link) +{ + if (IS_ENCRYPTED(inode)) + return __fscrypt_encrypt_symlink(inode, target, len, disk_link); + return 0; +} + #endif /* _LINUX_FSCRYPT_H */ diff --git a/include/linux/fscrypt_notsupp.h b/include/linux/fscrypt_notsupp.h index 81e02201b215..02ec0aa894d8 100644 --- a/include/linux/fscrypt_notsupp.h +++ b/include/linux/fscrypt_notsupp.h @@ -223,4 +223,20 @@ static inline int __fscrypt_prepare_lookup(struct inode *dir, return -EOPNOTSUPP; } +static inline int __fscrypt_prepare_symlink(struct inode *dir, + unsigned int len, + unsigned int max_len, + struct fscrypt_str *disk_link) +{ + return -EOPNOTSUPP; +} + +static inline int __fscrypt_encrypt_symlink(struct inode *inode, + const char *target, + unsigned int len, + struct fscrypt_str *disk_link) +{ + return -EOPNOTSUPP; +} + #endif /* _LINUX_FSCRYPT_NOTSUPP_H */ diff --git a/include/linux/fscrypt_supp.h b/include/linux/fscrypt_supp.h index 562a9bc04560..7e0b67ccd816 100644 --- a/include/linux/fscrypt_supp.h +++ b/include/linux/fscrypt_supp.h @@ -205,5 +205,11 @@ extern int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *new_dentry, unsigned int flags); extern int __fscrypt_prepare_lookup(struct inode *dir, struct dentry *dentry); +extern int __fscrypt_prepare_symlink(struct inode *dir, unsigned int len, + unsigned int max_len, + struct fscrypt_str *disk_link); +extern int __fscrypt_encrypt_symlink(struct inode *inode, const char *target, + unsigned int len, + struct fscrypt_str *disk_link); #endif /* _LINUX_FSCRYPT_SUPP_H */