Commit 598a5976 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull AFS fixes from David Howells:

 - Fix copy_file_range() to an afs file now returning EINVAL if the
   splice_write file op isn't supplied.

 - Fix a deref-before-check in afs_unuse_cell().

 - Fix a use-after-free in afs_xattr_get_acl().

 - Fix afs to not try to clear PG_writeback when laundering a page.

 - Fix afs to take a ref on a page that it sets PG_private on and to
   drop that ref when clearing PG_private. This is done through recently
   added helpers.

 - Fix a page leak if write_begin() fails.

 - Fix afs_write_begin() to not alter the dirty region info stored in
   page->private, but rather do this in afs_write_end() instead when we
   know what we actually changed.

 - Fix afs_invalidatepage() to alter the dirty region info on a page
   when partial page invalidation occurs so that we don't inadvertantly
   include a span of zeros that will get written back if a page gets
   laundered due to a remote 3rd-party induced invalidation.

   We mustn't, however, reduce the dirty region if the page has been
   seen to be mapped (ie. we got called through the page_mkwrite vector)
   as the page might still be mapped and we might lose data if the file
   is extended again.

 - Fix the dirty region info to have a lower resolution if the size of
   the page is too large for this to be encoded (e.g. powerpc32 with 64K
   pages).

   Note that this might not be the ideal way to handle this, since it
   may allow some leakage of undirtied zero bytes to the server's copy
   in the case of a 3rd-party conflict.

To aid the last two fixes, two additional changes:

 - Wrap the manipulations of the dirty region info stored in
   page->private into helper functions.

 - Alter the encoding of the dirty region so that the region bounds can
   be stored with one fewer bit, making a bit available for the
   indication of mappedness.

* tag 'afs-fixes-20201029' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs:
  afs: Fix dirty-region encoding on ppc32 with 64K pages
  afs: Fix afs_invalidatepage to adjust the dirty region
  afs: Alter dirty range encoding in page->private
  afs: Wrap page->private manipulations in inline functions
  afs: Fix where page->private is set during write
  afs: Fix page leak on afs_write_begin() failure
  afs: Fix to take ref on page when PG_private is set
  afs: Fix afs_launder_page to not clear PG_writeback
  afs: Fix a use after free in afs_xattr_get_acl()
  afs: Fix tracing deref-before-check
  afs: Fix copy_file_range()
parents 58130a6c 2d9900f2
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -589,7 +589,7 @@ struct afs_cell *afs_use_cell(struct afs_cell *cell, enum afs_cell_trace reason)
 */
void afs_unuse_cell(struct afs_net *net, struct afs_cell *cell, enum afs_cell_trace reason)
{
	unsigned int debug_id = cell->debug_id;
	unsigned int debug_id;
	time64_t now, expire_delay;
	int u, a;

@@ -604,6 +604,7 @@ void afs_unuse_cell(struct afs_net *net, struct afs_cell *cell, enum afs_cell_tr
	if (cell->vl_servers->nr_servers)
		expire_delay = afs_cell_gc_delay;

	debug_id = cell->debug_id;
	u = atomic_read(&cell->ref);
	a = atomic_dec_return(&cell->active);
	trace_afs_cell(debug_id, u, a, reason);
+4 −8
Original line number Diff line number Diff line
@@ -281,8 +281,7 @@ static struct afs_read *afs_read_dir(struct afs_vnode *dvnode, struct key *key)
			if (ret < 0)
				goto error;

			set_page_private(req->pages[i], 1);
			SetPagePrivate(req->pages[i]);
			attach_page_private(req->pages[i], (void *)1);
			unlock_page(req->pages[i]);
			i++;
		} else {
@@ -1975,8 +1974,7 @@ static int afs_dir_releasepage(struct page *page, gfp_t gfp_flags)

	_enter("{{%llx:%llu}[%lu]}", dvnode->fid.vid, dvnode->fid.vnode, page->index);

	set_page_private(page, 0);
	ClearPagePrivate(page);
	detach_page_private(page);

	/* The directory will need reloading. */
	if (test_and_clear_bit(AFS_VNODE_DIR_VALID, &dvnode->flags))
@@ -2003,8 +2001,6 @@ static void afs_dir_invalidatepage(struct page *page, unsigned int offset,
		afs_stat_v(dvnode, n_inval);

	/* we clean up only if the entire page is being invalidated */
	if (offset == 0 && length == PAGE_SIZE) {
		set_page_private(page, 0);
		ClearPagePrivate(page);
	}
	if (offset == 0 && length == PAGE_SIZE)
		detach_page_private(page);
}
+2 −4
Original line number Diff line number Diff line
@@ -243,10 +243,8 @@ void afs_edit_dir_add(struct afs_vnode *vnode,
						   index, gfp);
			if (!page)
				goto error;
			if (!PagePrivate(page)) {
				set_page_private(page, 1);
				SetPagePrivate(page);
			}
			if (!PagePrivate(page))
				attach_page_private(page, (void *)1);
			dir_page = kmap(page);
		}

+63 −15
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ const struct file_operations afs_file_operations = {
	.write_iter	= afs_file_write,
	.mmap		= afs_file_mmap,
	.splice_read	= generic_file_splice_read,
	.splice_write	= iter_file_splice_write,
	.fsync		= afs_fsync,
	.lock		= afs_lock,
	.flock		= afs_flock,
@@ -600,6 +601,63 @@ static int afs_readpages(struct file *file, struct address_space *mapping,
	return ret;
}

/*
 * Adjust the dirty region of the page on truncation or full invalidation,
 * getting rid of the markers altogether if the region is entirely invalidated.
 */
static void afs_invalidate_dirty(struct page *page, unsigned int offset,
				 unsigned int length)
{
	struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
	unsigned long priv;
	unsigned int f, t, end = offset + length;

	priv = page_private(page);

	/* we clean up only if the entire page is being invalidated */
	if (offset == 0 && length == thp_size(page))
		goto full_invalidate;

	 /* If the page was dirtied by page_mkwrite(), the PTE stays writable
	  * and we don't get another notification to tell us to expand it
	  * again.
	  */
	if (afs_is_page_dirty_mmapped(priv))
		return;

	/* We may need to shorten the dirty region */
	f = afs_page_dirty_from(priv);
	t = afs_page_dirty_to(priv);

	if (t <= offset || f >= end)
		return; /* Doesn't overlap */

	if (f < offset && t > end)
		return; /* Splits the dirty region - just absorb it */

	if (f >= offset && t <= end)
		goto undirty;

	if (f < offset)
		t = offset;
	else
		f = end;
	if (f == t)
		goto undirty;

	priv = afs_page_dirty(f, t);
	set_page_private(page, priv);
	trace_afs_page_dirty(vnode, tracepoint_string("trunc"), page->index, priv);
	return;

undirty:
	trace_afs_page_dirty(vnode, tracepoint_string("undirty"), page->index, priv);
	clear_page_dirty_for_io(page);
full_invalidate:
	priv = (unsigned long)detach_page_private(page);
	trace_afs_page_dirty(vnode, tracepoint_string("inval"), page->index, priv);
}

/*
 * invalidate part or all of a page
 * - release a page and clean up its private data if offset is 0 (indicating
@@ -608,31 +666,23 @@ static int afs_readpages(struct file *file, struct address_space *mapping,
static void afs_invalidatepage(struct page *page, unsigned int offset,
			       unsigned int length)
{
	struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
	unsigned long priv;

	_enter("{%lu},%u,%u", page->index, offset, length);

	BUG_ON(!PageLocked(page));

#ifdef CONFIG_AFS_FSCACHE
	/* we clean up only if the entire page is being invalidated */
	if (offset == 0 && length == PAGE_SIZE) {
#ifdef CONFIG_AFS_FSCACHE
		if (PageFsCache(page)) {
			struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
			fscache_wait_on_page_write(vnode->cache, page);
			fscache_uncache_page(vnode->cache, page);
		}
	}
#endif

		if (PagePrivate(page)) {
			priv = page_private(page);
			trace_afs_page_dirty(vnode, tracepoint_string("inval"),
					     page->index, priv);
			set_page_private(page, 0);
			ClearPagePrivate(page);
		}
	}
	if (PagePrivate(page))
		afs_invalidate_dirty(page, offset, length);

	_leave("");
}
@@ -660,11 +710,9 @@ static int afs_releasepage(struct page *page, gfp_t gfp_flags)
#endif

	if (PagePrivate(page)) {
		priv = page_private(page);
		priv = (unsigned long)detach_page_private(page);
		trace_afs_page_dirty(vnode, tracepoint_string("rel"),
				     page->index, priv);
		set_page_private(page, 0);
		ClearPagePrivate(page);
	}

	/* indicate that the page can be released */
+57 −0
Original line number Diff line number Diff line
@@ -812,6 +812,7 @@ struct afs_operation {
			pgoff_t		last;		/* last page in mapping to deal with */
			unsigned	first_offset;	/* offset into mapping[first] */
			unsigned	last_to;	/* amount of mapping[last] */
			bool		laundering;	/* Laundering page, PG_writeback not set */
		} store;
		struct {
			struct iattr	*attr;
@@ -857,6 +858,62 @@ struct afs_vnode_cache_aux {
	u64			data_version;
} __packed;

/*
 * We use page->private to hold the amount of the page that we've written to,
 * splitting the field into two parts.  However, we need to represent a range
 * 0...PAGE_SIZE, so we reduce the resolution if the size of the page
 * exceeds what we can encode.
 */
#ifdef CONFIG_64BIT
#define __AFS_PAGE_PRIV_MASK	0x7fffffffUL
#define __AFS_PAGE_PRIV_SHIFT	32
#define __AFS_PAGE_PRIV_MMAPPED	0x80000000UL
#else
#define __AFS_PAGE_PRIV_MASK	0x7fffUL
#define __AFS_PAGE_PRIV_SHIFT	16
#define __AFS_PAGE_PRIV_MMAPPED	0x8000UL
#endif

static inline unsigned int afs_page_dirty_resolution(void)
{
	int shift = PAGE_SHIFT - (__AFS_PAGE_PRIV_SHIFT - 1);
	return (shift > 0) ? shift : 0;
}

static inline size_t afs_page_dirty_from(unsigned long priv)
{
	unsigned long x = priv & __AFS_PAGE_PRIV_MASK;

	/* The lower bound is inclusive */
	return x << afs_page_dirty_resolution();
}

static inline size_t afs_page_dirty_to(unsigned long priv)
{
	unsigned long x = (priv >> __AFS_PAGE_PRIV_SHIFT) & __AFS_PAGE_PRIV_MASK;

	/* The upper bound is immediately beyond the region */
	return (x + 1) << afs_page_dirty_resolution();
}

static inline unsigned long afs_page_dirty(size_t from, size_t to)
{
	unsigned int res = afs_page_dirty_resolution();
	from >>= res;
	to = (to - 1) >> res;
	return (to << __AFS_PAGE_PRIV_SHIFT) | from;
}

static inline unsigned long afs_page_dirty_mmapped(unsigned long priv)
{
	return priv | __AFS_PAGE_PRIV_MMAPPED;
}

static inline bool afs_is_page_dirty_mmapped(unsigned long priv)
{
	return priv & __AFS_PAGE_PRIV_MMAPPED;
}

#include <trace/events/afs.h>

/*****************************************************************************/
Loading