ecryptfs opens the lower file read-write unless the lower inode is IS_RDONLY(). But a filesystem whose superblock is read-write can still refuse to open a file for writing. A read-only mount underneath does that with EROFS, and so does ext4 after an error since commit d3476f3dad4a ("ext4: don't set SB_RDONLY after filesystem errors"). The retry through the kthread opens read-write as well and fails the same way. Then every lookup of a regular file fails with EROFS because ecryptfs can't read the header, and nothing can be read through ecryptfs anymore. The filesystems that still set SB_RDONLY after an error are about to stop doing that as well. So far ecryptfs opened their files read-only after an error. Fall back to that on EROFS. Opening an ecryptfs file for writing then fails with EPERM like it does on top of a read-only superblock. Reproducer: sig=$(printf x | ecryptfs-add-passphrase - | sed -n 's/.*\[\(.*\)\].*/\1/p') mount --bind -o ro /lower /lower mount -i -t ecryptfs -o ecryptfs_sig=$sig,ecryptfs_cipher=aes,ecryptfs_key_bytes=16,ecryptfs_passthrough /lower /upper cat /upper/f # before: EROFS, after: works Signed-off-by: Christian Brauner (Amutable) --- fs/ecryptfs/kthread.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/ecryptfs/kthread.c b/fs/ecryptfs/kthread.c index ae4cb4e2e134..ba94b304edf3 100644 --- a/fs/ecryptfs/kthread.c +++ b/fs/ecryptfs/kthread.c @@ -30,6 +30,21 @@ static struct ecryptfs_kthread_ctl { static struct task_struct *ecryptfs_kthread; +/* + * A filesystem can refuse writes while its superblock stays read-write, e.g. + * after an error, and a read-only mount refuses them as well. Reading still + * works so fall back to that. + */ +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 +74,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 +147,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) { -- 2.53.0