From: Liu Bo Date: Mon, 27 Feb 2017 06:21:18 +0000 (-0800) Subject: Btrfs: fix crash on fsync when using overlayfs v4 X-Git-Tag: v4.1.12-93~3^2 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=9bce26c80ccde0dd80a7e57bc21519ed7311dbbb;p=users%2Fjedix%2Flinux-maple.git Btrfs: fix crash on fsync when using overlayfs v4 Orabug: 25512309 If the lower or upper directory of an overlayfs mount belongs to a btrfs file system and we fsync the file through the overlayfs' merged directory we ended up accessing an inode that didn't belong to btrfs as if it were a btrfs inode at btrfs_sync_file() resulting in a crash. This fixes the problem by getting the inode from file_inode() and getting the real dentry for recursively checking up parent directory. Note that the upstream fix is commit de17e793b104d690e1d007dfc5cb6b4f649598ca ("btrfs: fix crash/invalid memory access on fsync when using overlayfs"). But we are not able to use it since it uses a new helper file_dentry() and the commit that adds the helper would cause KABI breakage. Signed-off-by: Liu Bo --- diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index 24be5c12b66e1..ed3d8f6316266 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -1848,6 +1848,77 @@ static int start_ordered_ops(struct inode *inode, loff_t start, loff_t end) return ret; } +/* In order to avoid KABI breakage */ +/* private information held for every overlayfs dentry */ +struct ovl_entry { + struct dentry *__upperdentry; + void *cache; /* struct ovl_dir_cache */ + union { + struct { + u64 version; + const char *redirect; + bool opaque; + }; + struct rcu_head rcu; + }; + unsigned numlower; + struct path lowerstack[]; +}; + +static struct dentry *ovl_dentry_upper(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + return lockless_dereference(oe->__upperdentry); +} + +static struct dentry *ovl_dentry_lower(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + return oe->numlower ? (oe->lowerstack[0].dentry) : NULL; +} + +static struct dentry *btrfs_d_real(struct dentry *dentry, struct inode *inode) +{ + struct dentry *real; + + if (!d_is_reg(dentry)) { + if (!inode || inode == d_inode(dentry)) + return dentry; + goto bug; + } + + if (d_is_negative(dentry)) + return dentry; + + real = ovl_dentry_upper(dentry); + if (real && (!inode || inode == d_inode(real))) + return real; + + real = ovl_dentry_lower(dentry); + if (!real) + goto bug; + + real = btrfs_d_real(real, inode); + if (!inode || inode == d_inode(real)) + return real; +bug: + WARN(1, "ovl_d_real(%pd4, %s:%lu): real dentry not found\n", dentry, + inode ? inode->i_sb->s_id : "NULL", inode ? inode->i_ino : 0); + return dentry; +} + +static struct dentry *btrfs_file_dentry(struct file *file) +{ + struct dentry *dentry = file->f_path.dentry; + + if (unlikely(dentry->d_flags & DCACHE_OP_SELECT_INODE)) + return btrfs_d_real(dentry, file_inode(file)); + else + return dentry; +} + /* * fsync call for both files and directories. This logs the inode into * the tree log instead of forcing full commits whenever possible. @@ -1861,8 +1932,8 @@ static int start_ordered_ops(struct inode *inode, loff_t start, loff_t end) */ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) { - struct dentry *dentry = file->f_path.dentry; - struct inode *inode = d_inode(dentry); + struct dentry *dentry = btrfs_file_dentry(file); + struct inode *inode = file_inode(file); struct btrfs_root *root = BTRFS_I(inode)->root; struct btrfs_trans_handle *trans; struct btrfs_log_ctx ctx; @@ -2008,9 +2079,17 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) btrfs_init_log_ctx(&ctx); - ret = btrfs_log_dentry_safe(trans, root, dentry, start, end, &ctx); - if (ret < 0) { - /* Fallthrough and commit/free transaction. */ + if (inode->i_sb == dentry->d_sb) { + ret = btrfs_log_dentry_safe(trans, root, dentry, start, end, &ctx); + if (ret < 0) { + /* Fallthrough and commit/free transaction. */ + ret = 1; + } + } else { + /* + * the above btrfs_file_dentry didn't get a btrfs dentry, lets + * go to commit transaction + */ ret = 1; }