Received: by 2002:a05:6a10:206:0:0:0:0 with SMTP id 6csp2466765pxj; Mon, 10 May 2021 03:47:50 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxOwklnIAsMBJyZnwOoXFCFTbOROidkGjYkAu5Evb/70exnOOc4vW08FPdvBcslnKmnFYqz X-Received: by 2002:a05:6638:cad:: with SMTP id x13mr21292218jad.40.1620643670269; Mon, 10 May 2021 03:47:50 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1620643670; cv=none; d=google.com; s=arc-20160816; b=EEx0rgPsmwn09g873sn5yuDGCO5Mr5/BlxcVEz+s2F1cpm5dTwEeyQsaN/wUxXMBKr eGr6EIljpyzXsyEPMY1CyPCI2CU+7XrBcAUZdXJoSxGd9fU3ZNKKoDJimfrzJSx5GKW2 wVTQAMJK3q5zp8hhXrMizWpyFa+d6DS7kmEu/+XnkV3/AnwAiwMneVHo6DWesOe5aNBE A19q3VvUYn7Ajcp/HZkv18YO8/XqXF8aEMurfjptfc1Wh24CxtpLIeJ4lkFaWBDHF3xU dKeFk6Ms3n09qR1A7z7qCdW0zaek+KE6Oy9SR4EoZLqwzHOpRHop34YWISyvQDaHkTNy lWtw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :user-agent:references:in-reply-to:message-id:date:subject:cc:to :from:dkim-signature; bh=YX2eNQZ/nbbiufBze3+U7WZ2jlP1rNid8VceqdV7WII=; b=efrzF7VNO9mbtlfXoYYGsNt6Pt/ODIfkqfpNvIroXzg6kqOe9JEnXabwZZD3+Hcb/u 21PRakWyQlJmo2TJa9kZPIKjjorty3LDB5QBOxPwjks9kxTuME9nO4puy08NvLydxEKM lR+4KELvlcZ2NnmXM6bA5ulPpUt4UcW480PHOeQkssnPt5MUSHkoxN9cJ2Z1aDTsYgcL ypGw9aXSb0fpCQdkyORyGAdcfMa3OwxrnCh1pxknKVSqUwRqTWuYg4jQ9s5O7ubN4BXL GAJ828p70XJsr5NaMrkJRz/3r5lsigqLdYrYAAv1EAnO54ESo+FSBFCCAGwMPIkA6a7X BfeQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linuxfoundation.org header.s=korg header.b=l1QTjQFT; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linuxfoundation.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id b3si10307253ilq.147.2021.05.10.03.47.37; Mon, 10 May 2021 03:47:50 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@linuxfoundation.org header.s=korg header.b=l1QTjQFT; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linuxfoundation.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232244AbhEJKqH (ORCPT + 99 others); Mon, 10 May 2021 06:46:07 -0400 Received: from mail.kernel.org ([198.145.29.99]:48506 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230319AbhEJKh4 (ORCPT ); Mon, 10 May 2021 06:37:56 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id BE8876162D; Mon, 10 May 2021 10:29:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1620642592; bh=WTKI2LemnozNo8YCF8jCtSU3mH0J0X5GiG/cMWcmcJA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=l1QTjQFTs5nJXYmOmK+B/jGWT6qA2Pab6fmyv8+LYmmTHc/EFHbddFDGR7xpOBQZg RnrUeXt9wmba7AfJfiIwOQlnwAsTVE0Q088mCRPRQTXMuUoek4P8Fb4mykzujErRJY fUxjNCxnRen8L9aimg0+ra0woUbsmaJasp81i+jQ= From: Greg Kroah-Hartman To: linux-kernel@vger.kernel.org Cc: Greg Kroah-Hartman , stable@vger.kernel.org, Qian Cai , Vivek Goyal , Miklos Szeredi Subject: [PATCH 5.4 156/184] fuse: fix write deadlock Date: Mon, 10 May 2021 12:20:50 +0200 Message-Id: <20210510101955.229171886@linuxfoundation.org> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210510101950.200777181@linuxfoundation.org> References: <20210510101950.200777181@linuxfoundation.org> User-Agent: quilt/0.66 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Vivek Goyal commit 4f06dd92b5d0a6f8eec6a34b8d6ef3e1f4ac1e10 upstream. There are two modes for write(2) and friends in fuse: a) write through (update page cache, send sync WRITE request to userspace) b) buffered write (update page cache, async writeout later) The write through method kept all the page cache pages locked that were used for the request. Keeping more than one page locked is deadlock prone and Qian Cai demonstrated this with trinity fuzzing. The reason for keeping the pages locked is that concurrent mapped reads shouldn't try to pull possibly stale data into the page cache. For full page writes, the easy way to fix this is to make the cached page be the authoritative source by marking the page PG_uptodate immediately. After this the page can be safely unlocked, since mapped/cached reads will take the written data from the cache. Concurrent mapped writes will now cause data in the original WRITE request to be updated; this however doesn't cause any data inconsistency and this scenario should be exceedingly rare anyway. If the WRITE request returns with an error in the above case, currently the page is not marked uptodate; this means that a concurrent read will always read consistent data. After this patch the page is uptodate between writing to the cache and receiving the error: there's window where a cached read will read the wrong data. While theoretically this could be a regression, it is unlikely to be one in practice, since this is normal for buffered writes. In case of a partial page write to an already uptodate page the locking is also unnecessary, with the above caveats. Partial write of a not uptodate page still needs to be handled. One way would be to read the complete page before doing the write. This is not possible, since it might break filesystems that don't expect any READ requests when the file was opened O_WRONLY. The other solution is to serialize the synchronous write with reads from the partial pages. The easiest way to do this is to keep the partial pages locked. The problem is that a write() may involve two such pages (one head and one tail). This patch fixes it by only locking the partial tail page. If there's a partial head page as well, then split that off as a separate WRITE request. Reported-by: Qian Cai Link: https://lore.kernel.org/linux-fsdevel/4794a3fa3742a5e84fb0f934944204b55730829b.camel@lca.pw/ Fixes: ea9b9907b82a ("fuse: implement perform_write") Cc: # v2.6.26 Signed-off-by: Vivek Goyal Signed-off-by: Miklos Szeredi Signed-off-by: Greg Kroah-Hartman --- fs/fuse/file.c | 41 +++++++++++++++++++++++++++++------------ fs/fuse/fuse_i.h | 1 + 2 files changed, 30 insertions(+), 12 deletions(-) --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1108,6 +1108,7 @@ static ssize_t fuse_send_write_pages(str struct fuse_file *ff = file->private_data; struct fuse_conn *fc = ff->fc; unsigned int offset, i; + bool short_write; int err; for (i = 0; i < ap->num_pages; i++) @@ -1120,32 +1121,38 @@ static ssize_t fuse_send_write_pages(str if (!err && ia->write.out.size > count) err = -EIO; + short_write = ia->write.out.size < count; offset = ap->descs[0].offset; count = ia->write.out.size; for (i = 0; i < ap->num_pages; i++) { struct page *page = ap->pages[i]; - if (!err && !offset && count >= PAGE_SIZE) - SetPageUptodate(page); - - if (count > PAGE_SIZE - offset) - count -= PAGE_SIZE - offset; - else - count = 0; - offset = 0; - - unlock_page(page); + if (err) { + ClearPageUptodate(page); + } else { + if (count >= PAGE_SIZE - offset) + count -= PAGE_SIZE - offset; + else { + if (short_write) + ClearPageUptodate(page); + count = 0; + } + offset = 0; + } + if (ia->write.page_locked && (i == ap->num_pages - 1)) + unlock_page(page); put_page(page); } return err; } -static ssize_t fuse_fill_write_pages(struct fuse_args_pages *ap, +static ssize_t fuse_fill_write_pages(struct fuse_io_args *ia, struct address_space *mapping, struct iov_iter *ii, loff_t pos, unsigned int max_pages) { + struct fuse_args_pages *ap = &ia->ap; struct fuse_conn *fc = get_fuse_conn(mapping->host); unsigned offset = pos & (PAGE_SIZE - 1); size_t count = 0; @@ -1198,6 +1205,16 @@ static ssize_t fuse_fill_write_pages(str if (offset == PAGE_SIZE) offset = 0; + /* If we copied full page, mark it uptodate */ + if (tmp == PAGE_SIZE) + SetPageUptodate(page); + + if (PageUptodate(page)) { + unlock_page(page); + } else { + ia->write.page_locked = true; + break; + } if (!fc->big_writes) break; } while (iov_iter_count(ii) && count < fc->max_write && @@ -1241,7 +1258,7 @@ static ssize_t fuse_perform_write(struct break; } - count = fuse_fill_write_pages(ap, mapping, ii, pos, nr_pages); + count = fuse_fill_write_pages(&ia, mapping, ii, pos, nr_pages); if (count <= 0) { err = count; } else { --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -845,6 +845,7 @@ struct fuse_io_args { struct { struct fuse_write_in in; struct fuse_write_out out; + bool page_locked; } write; }; struct fuse_args_pages ap;