Received: by 2002:a17:90a:9103:0:0:0:0 with SMTP id k3csp11804662pjo; Thu, 2 Jan 2020 14:34:34 -0800 (PST) X-Google-Smtp-Source: APXvYqwpgiUEbuc8mI0cFSO/9IBmbiajk9djBHPGijr+XAvJYzzPyRkyfpIQCHgNDWx471tSCJDE X-Received: by 2002:a9d:750b:: with SMTP id r11mr90995481otk.209.1578004474614; Thu, 02 Jan 2020 14:34:34 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1578004474; cv=none; d=google.com; s=arc-20160816; b=rGtwRHq/8x4AYCgtUye0snSIlD9CzJmClVCup+u3jbiIa9heCVpkk9Cj+OXp37mRQ3 Ec6dRMIeO25t0SnlhWaR9G4lBdZcrnxzw0X/sP0ryfPptbQKcaxzcPKfyxMKIQNkHMQ3 20erlxfHcTlvzpcW3zFaRRLFN03mm3DE1ADffSPDQaqAY6Kpc+r0Yi0dQr76ALx86ztL FUkabGDsvCNCsrp/uDkbijirt+R4Nq7+mrlBxi00bLO6wON2ueAoborQiSqSRa2Zgm0L YYtqGVuCXcUyWfeNlYMeSnOJmyva9IMPFOrNM8C3TaPdoMakYy1uhHnJACCfcOFrs4BH l9AA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :user-agent:references:in-reply-to:message-id:date:subject:cc:to :from:dkim-signature; bh=lUclAF2pjn6u4lk1hw5VyLLvCPNYUM6xEaK/AQxV6HE=; b=Qy3VNsgTGcHJ9yisLjRKr00/+7pMbvKtGFxzuPtrybIt64DH8f4pMzDbAiwiYKctW1 MLXhzmRutDWU8TkZb4IDQyyStOceQNR6NQ+JM6/5raDtJYO8OnCiQEqoB2qEY6rAwZ9p PfOx3TWw1EH9d82I3p6wFEDFAYiIJmGVZi86iFjQ+IUnVGaeEd8wgfO9Nm3APwl+vKXh oHq7bzqCHzUiW7J++g+3Sz34lo3YCMF21WxEWzWngFieQdU0xLcRMuFyLa+X7LzLj6Gh mjzqa5IG0BiqnuH+qI36t3w6qIg9H8Sdj68hFqa3R36xVedUZSO4jDeveZcnLlDKBAOV W9Lw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=NRX8PopO; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id h203si26710471oif.3.2020.01.02.14.34.23; Thu, 02 Jan 2020 14:34:34 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=NRX8PopO; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730173AbgABWds (ORCPT + 99 others); Thu, 2 Jan 2020 17:33:48 -0500 Received: from mail.kernel.org ([198.145.29.99]:41558 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1730320AbgABWdr (ORCPT ); Thu, 2 Jan 2020 17:33:47 -0500 Received: from localhost (83-86-89-107.cable.dynamic.v4.ziggo.nl [83.86.89.107]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPSA id C9F4120866; Thu, 2 Jan 2020 22:33:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1578004426; bh=5WK5Lvi1zvF6yFk+wD1iaLyN3/vKf4+AP3pnlwernI4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NRX8PopOeaAlACmzK3PTM9i/l8OjGezZWvurjogOqzYUFw0uLSEQf6d/RdAtsVmXc R0pkXCVILHfkY+jbnGhO2gmqWbl7V7JDO1kDoxldJ29//YxV5dHnR9ATYULONk5Ah5 MK8T0g+HoVt7O61w/ydsmYSyV5NVF9om7+ieu+Hw= From: Greg Kroah-Hartman To: linux-kernel@vger.kernel.org Cc: Greg Kroah-Hartman , stable@vger.kernel.org, Alexander Viro , Jann Horn , "Eric W. Biederman" , Linus Torvalds , Siddharth Chandrasekaran Subject: [PATCH 4.9 156/171] Make filldir[64]() verify the directory entry filename is valid Date: Thu, 2 Jan 2020 23:08:07 +0100 Message-Id: <20200102220608.429623475@linuxfoundation.org> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200102220546.960200039@linuxfoundation.org> References: <20200102220546.960200039@linuxfoundation.org> User-Agent: quilt/0.66 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Linus Torvalds commit 8a23eb804ca4f2be909e372cf5a9e7b30ae476cd upstream. This has been discussed several times, and now filesystem people are talking about doing it individually at the filesystem layer, so head that off at the pass and just do it in getdents{64}(). This is partially based on a patch by Jann Horn, but checks for NUL bytes as well, and somewhat simplified. There's also commentary about how it might be better if invalid names due to filesystem corruption don't cause an immediate failure, but only an error at the end of the readdir(), so that people can still see the filenames that are ok. There's also been discussion about just how much POSIX strictly speaking requires this since it's about filesystem corruption. It's really more "protect user space from bad behavior" as pointed out by Jann. But since Eric Biederman looked up the POSIX wording, here it is for context: "From readdir: The readdir() function shall return a pointer to a structure representing the directory entry at the current position in the directory stream specified by the argument dirp, and position the directory stream at the next entry. It shall return a null pointer upon reaching the end of the directory stream. The structure dirent defined in the header describes a directory entry. From definitions: 3.129 Directory Entry (or Link) An object that associates a filename with a file. Several directory entries can associate names with the same file. ... 3.169 Filename A name consisting of 1 to {NAME_MAX} bytes used to name a file. The characters composing the name may be selected from the set of all character values excluding the slash character and the null byte. The filenames dot and dot-dot have special meaning. A filename is sometimes referred to as a 'pathname component'." Note that I didn't bother adding the checks to any legacy interfaces that nobody uses. Also note that if this ends up being noticeable as a performance regression, we can fix that to do a much more optimized model that checks for both NUL and '/' at the same time one word at a time. We haven't really tended to optimize 'memchr()', and it only checks for one pattern at a time anyway, and we really _should_ check for NUL too (but see the comment about "soft errors" in the code about why it currently only checks for '/') See the CONFIG_DCACHE_WORD_ACCESS case of hash_name() for how the name lookup code looks for pathname terminating characters in parallel. Link: https://lore.kernel.org/lkml/20190118161440.220134-2-jannh@google.com/ Cc: Alexander Viro Cc: Jann Horn Cc: Eric W. Biederman Signed-off-by: Linus Torvalds Signed-off-by: Siddharth Chandrasekaran Signed-off-by: Greg Kroah-Hartman --- fs/readdir.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) --- a/fs/readdir.c +++ b/fs/readdir.c @@ -64,6 +64,40 @@ out: EXPORT_SYMBOL(iterate_dir); /* + * POSIX says that a dirent name cannot contain NULL or a '/'. + * + * It's not 100% clear what we should really do in this case. + * The filesystem is clearly corrupted, but returning a hard + * error means that you now don't see any of the other names + * either, so that isn't a perfect alternative. + * + * And if you return an error, what error do you use? Several + * filesystems seem to have decided on EUCLEAN being the error + * code for EFSCORRUPTED, and that may be the error to use. Or + * just EIO, which is perhaps more obvious to users. + * + * In order to see the other file names in the directory, the + * caller might want to make this a "soft" error: skip the + * entry, and return the error at the end instead. + * + * Note that this should likely do a "memchr(name, 0, len)" + * check too, since that would be filesystem corruption as + * well. However, that case can't actually confuse user space, + * which has to do a strlen() on the name anyway to find the + * filename length, and the above "soft error" worry means + * that it's probably better left alone until we have that + * issue clarified. + */ +static int verify_dirent_name(const char *name, int len) +{ + if (WARN_ON_ONCE(!len)) + return -EIO; + if (WARN_ON_ONCE(memchr(name, '/', len))) + return -EIO; + return 0; +} + +/* * Traditional linux readdir() handling.. * * "count=1" is a special case, meaning that the buffer is one @@ -172,6 +206,9 @@ static int filldir(struct dir_context *c int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2, sizeof(long)); + buf->error = verify_dirent_name(name, namlen); + if (unlikely(buf->error)) + return buf->error; buf->error = -EINVAL; /* only used if we fail.. */ if (reclen > buf->count) return -EINVAL; @@ -258,6 +295,9 @@ static int filldir64(struct dir_context int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1, sizeof(u64)); + buf->error = verify_dirent_name(name, namlen); + if (unlikely(buf->error)) + return buf->error; buf->error = -EINVAL; /* only used if we fail.. */ if (reclen > buf->count) return -EINVAL;