ecryptfs opens the lower file read-write unless the lower inode is IS_RDONLY(). A read-only mount underneath refuses that and so does ext4 after an error since commit d3476f3dad4a ("ext4: don't set SB_RDONLY after filesystem errors"). Then every lookup of a regular file fails with EROFS. The other filesystems are about to behave like ext4, so fall back to opening the lower file read-only. Opening an ecryptfs file for writing then fails with EPERM like on top of a read-only superblock. truncate(2), creating a file and opening an empty file still write to the lower file and would trip the WARN_ON_ONCE() in __kernel_write_iter(). So let ecryptfs_write_lower() and __ecryptfs_truncate() refuse that with EROFS. Note that ecryptfs never takes write access on the lower mount. On a read-only bind mount that was only a problem for directories so far. With this regular files can be unlinked and changed through it too. Signed-off-by: Christian Brauner (Amutable) --- fs/ecryptfs/inode.c | 8 ++++++++ fs/ecryptfs/kthread.c | 15 +++++++++++++-- fs/ecryptfs/read_write.c | 3 +++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 3a966348a578..35afb94143b8 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -741,6 +741,7 @@ static int __ecryptfs_truncate(struct dentry *dentry, const struct iattr *ia) loff_t i_size = i_size_read(inode); loff_t lower_size_before_truncate; loff_t lower_size_after_truncate; + struct file *lower_file; struct iattr lower_ia; size_t num_zeros; int rc; @@ -771,6 +772,13 @@ static int __ecryptfs_truncate(struct dentry *dentry, const struct iattr *ia) if (rc) return rc; + /* Fail before zeroing the page cache, see ecryptfs_write_lower(). */ + lower_file = ecryptfs_inode_to_private(inode)->lower_file; + if (!(lower_file->f_mode & FMODE_WRITE)) { + rc = -EROFS; + goto out; + } + if (ia->ia_size > i_size) { char zero[] = { 0x00 }; diff --git a/fs/ecryptfs/kthread.c b/fs/ecryptfs/kthread.c index ae4cb4e2e134..d381d56baa90 100644 --- a/fs/ecryptfs/kthread.c +++ b/fs/ecryptfs/kthread.c @@ -30,6 +30,17 @@ static struct ecryptfs_kthread_ctl { static struct task_struct *ecryptfs_kthread; +/* A read-only mount or a filesystem that hit an error refuses O_RDWR. */ +static struct file *ecryptfs_open_lower(const struct path *path, int flags, + const struct cred *cred) +{ + struct file *file = dentry_open(path, flags, cred); + + if (PTR_ERR_OR_ZERO(file) == -EROFS && (flags & O_ACCMODE) == O_RDWR) + file = dentry_open(path, (flags & ~O_ACCMODE) | O_RDONLY, cred); + return file; +} + /** * ecryptfs_threadfn * @ignored: ignored @@ -59,7 +70,7 @@ static int ecryptfs_threadfn(void *ignored) struct ecryptfs_open_req, kthread_ctl_list); list_del(&req->kthread_ctl_list); - *req->lower_file = dentry_open(&req->path, + *req->lower_file = ecryptfs_open_lower(&req->path, (O_RDWR | O_LARGEFILE), current_cred()); complete(&req->done); } @@ -132,7 +143,7 @@ int ecryptfs_privileged_open(struct file **lower_file, * lower file is fput() when all eCryptfs files for the inode are * released. */ flags |= IS_RDONLY(d_inode(lower_dentry)) ? O_RDONLY : O_RDWR; - (*lower_file) = dentry_open(&req.path, flags, cred); + (*lower_file) = ecryptfs_open_lower(&req.path, flags, cred); if (!IS_ERR(*lower_file)) goto out; if ((flags & O_ACCMODE) == O_RDONLY) { diff --git a/fs/ecryptfs/read_write.c b/fs/ecryptfs/read_write.c index b3b451c2b941..b3956ae50ff6 100644 --- a/fs/ecryptfs/read_write.c +++ b/fs/ecryptfs/read_write.c @@ -33,6 +33,9 @@ int ecryptfs_write_lower(struct inode *ecryptfs_inode, char *data, lower_file = ecryptfs_inode_to_private(ecryptfs_inode)->lower_file; if (!lower_file) return -EIO; + /* ecryptfs_open_lower() may have fallen back to a read-only file. */ + if (!(lower_file->f_mode & FMODE_WRITE)) + return -EROFS; rc = kernel_write(lower_file, data, size, &offset); mark_inode_dirty_sync(ecryptfs_inode); return rc; -- 2.53.0