Skip to content

Commit 7c6893e

Browse files
author
Miklos Szeredi
committed
ovl: don't allow writing ioctl on lower layer
Problem with ioctl() is that it's a file operation, yet often used as an inode operation (i.e. modify the inode despite the file being opened for read-only). mnt_want_write_file() is used by filesystems in such cases to get write access on an arbitrary open file. Since overlayfs lets filesystems do all file operations, including ioctl, this can lead to mnt_want_write_file() returning OK for a lower file and modification of that lower file. This patch prevents modification by checking if the file is from an overlayfs lower layer and returning EPERM in that case. Need to introduce a mnt_want_write_file_path() variant that still does the old thing for inode operations that can do the copy up + modification correctly in such cases (fchown, fsetxattr, fremovexattr). This does not address the correctness of such ioctls on overlayfs (the correct way would be to copy up and attempt to perform ioctl on upper file). In theory this could be a regression. We very much hope that nobody is relying on such a hack in any sane setup. While this patch meddles in VFS code, it has no effect on non-overlayfs filesystems. Reported-by: "zhangyi (F)" <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]>
1 parent cd91304 commit 7c6893e

File tree

4 files changed

+70
-9
lines changed

4 files changed

+70
-9
lines changed

fs/internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@ extern void __init mnt_init(void);
7171

7272
extern int __mnt_want_write(struct vfsmount *);
7373
extern int __mnt_want_write_file(struct file *);
74+
extern int mnt_want_write_file_path(struct file *);
7475
extern void __mnt_drop_write(struct vfsmount *);
7576
extern void __mnt_drop_write_file(struct file *);
77+
extern void mnt_drop_write_file_path(struct file *);
7678

7779
/*
7880
* fs_struct.c

fs/namespace.c

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,13 +431,18 @@ int __mnt_want_write_file(struct file *file)
431431
}
432432

433433
/**
434-
* mnt_want_write_file - get write access to a file's mount
434+
* mnt_want_write_file_path - get write access to a file's mount
435435
* @file: the file who's mount on which to take a write
436436
*
437437
* This is like mnt_want_write, but it takes a file and can
438438
* do some optimisations if the file is open for write already
439+
*
440+
* Called by the vfs for cases when we have an open file at hand, but will do an
441+
* inode operation on it (important distinction for files opened on overlayfs,
442+
* since the file operations will come from the real underlying file, while
443+
* inode operations come from the overlay).
439444
*/
440-
int mnt_want_write_file(struct file *file)
445+
int mnt_want_write_file_path(struct file *file)
441446
{
442447
int ret;
443448

@@ -447,6 +452,53 @@ int mnt_want_write_file(struct file *file)
447452
sb_end_write(file->f_path.mnt->mnt_sb);
448453
return ret;
449454
}
455+
456+
static inline int may_write_real(struct file *file)
457+
{
458+
struct dentry *dentry = file->f_path.dentry;
459+
struct dentry *upperdentry;
460+
461+
/* Writable file? */
462+
if (file->f_mode & FMODE_WRITER)
463+
return 0;
464+
465+
/* Not overlayfs? */
466+
if (likely(!(dentry->d_flags & DCACHE_OP_REAL)))
467+
return 0;
468+
469+
/* File refers to upper, writable layer? */
470+
upperdentry = d_real(dentry, NULL, 0, D_REAL_UPPER);
471+
if (upperdentry && file_inode(file) == d_inode(upperdentry))
472+
return 0;
473+
474+
/* Lower layer: can't write to real file, sorry... */
475+
return -EPERM;
476+
}
477+
478+
/**
479+
* mnt_want_write_file - get write access to a file's mount
480+
* @file: the file who's mount on which to take a write
481+
*
482+
* This is like mnt_want_write, but it takes a file and can
483+
* do some optimisations if the file is open for write already
484+
*
485+
* Mostly called by filesystems from their ioctl operation before performing
486+
* modification. On overlayfs this needs to check if the file is on a read-only
487+
* lower layer and deny access in that case.
488+
*/
489+
int mnt_want_write_file(struct file *file)
490+
{
491+
int ret;
492+
493+
ret = may_write_real(file);
494+
if (!ret) {
495+
sb_start_write(file_inode(file)->i_sb);
496+
ret = __mnt_want_write_file(file);
497+
if (ret)
498+
sb_end_write(file_inode(file)->i_sb);
499+
}
500+
return ret;
501+
}
450502
EXPORT_SYMBOL_GPL(mnt_want_write_file);
451503

452504
/**
@@ -484,10 +536,16 @@ void __mnt_drop_write_file(struct file *file)
484536
__mnt_drop_write(file->f_path.mnt);
485537
}
486538

487-
void mnt_drop_write_file(struct file *file)
539+
void mnt_drop_write_file_path(struct file *file)
488540
{
489541
mnt_drop_write(file->f_path.mnt);
490542
}
543+
544+
void mnt_drop_write_file(struct file *file)
545+
{
546+
__mnt_drop_write(file->f_path.mnt);
547+
sb_end_write(file_inode(file)->i_sb);
548+
}
491549
EXPORT_SYMBOL(mnt_drop_write_file);
492550

493551
static int mnt_make_readonly(struct mount *mnt)

fs/open.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,12 +670,12 @@ SYSCALL_DEFINE3(fchown, unsigned int, fd, uid_t, user, gid_t, group)
670670
if (!f.file)
671671
goto out;
672672

673-
error = mnt_want_write_file(f.file);
673+
error = mnt_want_write_file_path(f.file);
674674
if (error)
675675
goto out_fput;
676676
audit_file(f.file);
677677
error = chown_common(&f.file->f_path, user, group);
678-
mnt_drop_write_file(f.file);
678+
mnt_drop_write_file_path(f.file);
679679
out_fput:
680680
fdput(f);
681681
out:

fs/xattr.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <linux/posix_acl_xattr.h>
2424

2525
#include <linux/uaccess.h>
26+
#include "internal.h"
2627

2728
static const char *
2829
strcmp_prefix(const char *a, const char *a_prefix)
@@ -496,10 +497,10 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
496497
if (!f.file)
497498
return error;
498499
audit_file(f.file);
499-
error = mnt_want_write_file(f.file);
500+
error = mnt_want_write_file_path(f.file);
500501
if (!error) {
501502
error = setxattr(f.file->f_path.dentry, name, value, size, flags);
502-
mnt_drop_write_file(f.file);
503+
mnt_drop_write_file_path(f.file);
503504
}
504505
fdput(f);
505506
return error;
@@ -728,10 +729,10 @@ SYSCALL_DEFINE2(fremovexattr, int, fd, const char __user *, name)
728729
if (!f.file)
729730
return error;
730731
audit_file(f.file);
731-
error = mnt_want_write_file(f.file);
732+
error = mnt_want_write_file_path(f.file);
732733
if (!error) {
733734
error = removexattr(f.file->f_path.dentry, name);
734-
mnt_drop_write_file(f.file);
735+
mnt_drop_write_file_path(f.file);
735736
}
736737
fdput(f);
737738
return error;

0 commit comments

Comments
 (0)