From 8d3095f4ad47ac409440a0ba1c80e13519ff867d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 12 Oct 2015 17:11:44 +0200 Subject: ovl: default permissions Add mount option "default_permissions" to alter the way permissions are calculated. Without this option and prior to this patch permissions were calculated by underlying lower or upper filesystem. With this option the permissions are calculated by overlayfs based on the file owner, group and mode bits. This has significance for example when a read-only exported NFS filesystem is used as a lower layer. In this case the underlying NFS filesystem will reply with EROFS, in which case all we know is that the filesystem is read-only. But that's not what we are interested in, we are interested in whether the access would be allowed if the filesystem wasn't read-only; the server doesn't tell us that, and would need updating at various levels, which doesn't seem practicable. Signed-off-by: Miklos Szeredi diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index ec0c2a050..7654631 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -98,6 +98,29 @@ int ovl_permission(struct inode *inode, int mask) realdentry = ovl_entry_real(oe, &is_upper); + if (ovl_is_default_permissions(inode)) { + struct kstat stat; + struct path realpath = { .dentry = realdentry }; + + if (mask & MAY_NOT_BLOCK) + return -ECHILD; + + realpath.mnt = ovl_entry_mnt_real(oe, inode, is_upper); + + err = vfs_getattr(&realpath, &stat); + if (err) + return err; + + if ((stat.mode ^ inode->i_mode) & S_IFMT) + return -ESTALE; + + inode->i_mode = stat.mode; + inode->i_uid = stat.uid; + inode->i_gid = stat.gid; + + return generic_permission(inode, mask); + } + /* Careful in RCU walk mode */ realinode = ACCESS_ONCE(realdentry->d_inode); if (!realinode) { diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index ea5a40b..f3e6efe 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -142,7 +142,10 @@ struct dentry *ovl_dentry_upper(struct dentry *dentry); struct dentry *ovl_dentry_lower(struct dentry *dentry); struct dentry *ovl_dentry_real(struct dentry *dentry); struct dentry *ovl_entry_real(struct ovl_entry *oe, bool *is_upper); +struct vfsmount *ovl_entry_mnt_real(struct ovl_entry *oe, struct inode *inode, + bool is_upper); struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry); +bool ovl_is_default_permissions(struct inode *inode); void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); struct dentry *ovl_workdir(struct dentry *dentry); int ovl_want_write(struct dentry *dentry); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 8d04b86..97cacb5 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -30,6 +30,7 @@ struct ovl_config { char *lowerdir; char *upperdir; char *workdir; + bool default_permissions; }; /* private information held for overlayfs's superblock */ @@ -154,6 +155,18 @@ struct dentry *ovl_entry_real(struct ovl_entry *oe, bool *is_upper) return realdentry; } +struct vfsmount *ovl_entry_mnt_real(struct ovl_entry *oe, struct inode *inode, + bool is_upper) +{ + if (is_upper) { + struct ovl_fs *ofs = inode->i_sb->s_fs_info; + + return ofs->upper_mnt; + } else { + return oe->numlower ? oe->lowerstack[0].mnt : NULL; + } +} + struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; @@ -161,6 +174,13 @@ struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry) return oe->cache; } +bool ovl_is_default_permissions(struct inode *inode) +{ + struct ovl_fs *ofs = inode->i_sb->s_fs_info; + + return ofs->config.default_permissions; +} + void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache) { struct ovl_entry *oe = dentry->d_fsdata; @@ -594,6 +614,8 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) seq_printf(m, ",upperdir=%s", ufs->config.upperdir); seq_printf(m, ",workdir=%s", ufs->config.workdir); } + if (ufs->config.default_permissions) + seq_puts(m, ",default_permissions"); return 0; } @@ -618,6 +640,7 @@ enum { OPT_LOWERDIR, OPT_UPPERDIR, OPT_WORKDIR, + OPT_DEFAULT_PERMISSIONS, OPT_ERR, }; @@ -625,6 +648,7 @@ static const match_table_t ovl_tokens = { {OPT_LOWERDIR, "lowerdir=%s"}, {OPT_UPPERDIR, "upperdir=%s"}, {OPT_WORKDIR, "workdir=%s"}, + {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, {OPT_ERR, NULL} }; @@ -685,6 +709,10 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) return -ENOMEM; break; + case OPT_DEFAULT_PERMISSIONS: + config->default_permissions = true; + break; + default: pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p); return -EINVAL; -- cgit v0.10.2 From 97daf8b97ad6f913a34c82515be64dc9ac08d63e Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 10 Nov 2015 17:08:41 +0100 Subject: ovl: allow zero size xattr When ovl_copy_xattr() encountered a zero size xattr no more xattrs were copied and the function returned success. This is clearly not the desired behavior. Signed-off-by: Miklos Szeredi Cc: diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 871fcb6..394e87f 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -54,7 +54,7 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new) for (name = buf; name < (buf + list_size); name += strlen(name) + 1) { size = vfs_getxattr(old, name, value, XATTR_SIZE_MAX); - if (size <= 0) { + if (size < 0) { error = size; goto out_free_value; } -- cgit v0.10.2 From e4ad29fa0d224d05e08b2858e65f112fd8edd4fe Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Sat, 24 Oct 2015 07:19:46 -0500 Subject: ovl: use a minimal buffer in ovl_copy_xattr Rather than always allocating the high-order XATTR_SIZE_MAX buffer which is costly and prone to failure, only allocate what is needed and realloc if necessary. Fixes https://github.com/coreos/bugs/issues/489 Signed-off-by: Miklos Szeredi Cc: diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 394e87f..758012b 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -22,9 +22,9 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new) { - ssize_t list_size, size; - char *buf, *name, *value; - int error; + ssize_t list_size, size, value_size = 0; + char *buf, *name, *value = NULL; + int uninitialized_var(error); if (!old->d_inode->i_op->getxattr || !new->d_inode->i_op->getxattr) @@ -41,29 +41,40 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new) if (!buf) return -ENOMEM; - error = -ENOMEM; - value = kmalloc(XATTR_SIZE_MAX, GFP_KERNEL); - if (!value) - goto out; - list_size = vfs_listxattr(old, buf, list_size); if (list_size <= 0) { error = list_size; - goto out_free_value; + goto out; } for (name = buf; name < (buf + list_size); name += strlen(name) + 1) { - size = vfs_getxattr(old, name, value, XATTR_SIZE_MAX); +retry: + size = vfs_getxattr(old, name, value, value_size); + if (size == -ERANGE) + size = vfs_getxattr(old, name, NULL, 0); + if (size < 0) { error = size; - goto out_free_value; + break; + } + + if (size > value_size) { + void *new; + + new = krealloc(value, size, GFP_KERNEL); + if (!new) { + error = -ENOMEM; + break; + } + value = new; + value_size = size; + goto retry; } + error = vfs_setxattr(new, name, value, size, 0); if (error) - goto out_free_value; + break; } - -out_free_value: kfree(value); out: kfree(buf); -- cgit v0.10.2 From 257f871993474e2bde6c497b54022c362cf398e1 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Wed, 4 Nov 2015 10:59:52 -0800 Subject: ovl: move super block magic number to magic.h The overlayfs file system is not recognized by programs like tail because the magic number is not in standard header location. Move it so that the value will propagate on for the GNU library and utilities. Needs to go in the fstatfs manual page as well. Signed-off-by: Stephen Hemminger Signed-off-by: Miklos Szeredi diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 97cacb5..32f3124 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -24,8 +24,6 @@ MODULE_AUTHOR("Miklos Szeredi "); MODULE_DESCRIPTION("Overlay filesystem"); MODULE_LICENSE("GPL"); -#define OVERLAYFS_SUPER_MAGIC 0x794c7630 - struct ovl_config { char *lowerdir; char *upperdir; diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index 7b1425a..eec4389 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -31,6 +31,7 @@ #define PSTOREFS_MAGIC 0x6165676C #define EFIVARFS_MAGIC 0xde5e81e4 #define HOSTFS_SUPER_MAGIC 0x00c0ffee +#define OVERLAYFS_SUPER_MAGIC 0x794c7630 #define MINIX_SUPER_MAGIC 0x137F /* minix v1 fs, 14 char names */ #define MINIX_SUPER_MAGIC2 0x138F /* minix v1 fs, 30 char names */ -- cgit v0.10.2 From ed06e069775ad9236087594a1c1667367e983fb5 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 9 Dec 2015 16:11:59 +0100 Subject: ovl: root: copy attr We copy i_uid and i_gid of underlying inode into overlayfs inode. Except for the root inode. Fix this omission. Signed-off-by: Miklos Szeredi Cc: diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 32f3124..ec31711 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1079,6 +1079,9 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) root_dentry->d_fsdata = oe; + ovl_copyattr(ovl_dentry_real(root_dentry)->d_inode, + root_dentry->d_inode); + sb->s_magic = OVERLAYFS_SUPER_MAGIC; sb->s_op = &ovl_super_operations; sb->s_root = root_dentry; -- cgit v0.10.2 From cf9a6784f7c1b5ee2b9159a1246e327c331c5697 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 11 Dec 2015 16:30:49 +0100 Subject: ovl: setattr: check permissions before copy-up Without this copy-up of a file can be forced, even without actually being allowed to do anything on the file. [Arnd Bergmann] include for PAGE_CACHE_SIZE (used by MAX_LFS_FILESIZE definition). Signed-off-by: Miklos Szeredi Cc: diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 7654631..213a726 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -45,6 +45,19 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) int err; struct dentry *upperdentry; + /* + * Check for permissions before trying to copy-up. This is redundant + * since it will be rechecked later by ->setattr() on upper dentry. But + * without this, copy-up can be triggered by just about anybody. + * + * We don't initialize inode->size, which just means that + * inode_newsize_ok() will always check against MAX_LFS_FILESIZE and not + * check for a swapfile (which this won't be anyway). + */ + err = inode_change_ok(dentry->d_inode, attr); + if (err) + return err; + err = ovl_want_write(dentry); if (err) goto out; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index ec31711..b08bf4d 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -936,6 +937,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) } sb->s_stack_depth = 0; + sb->s_maxbytes = MAX_LFS_FILESIZE; if (ufs->config.upperdir) { if (!ufs->config.workdir) { pr_err("overlayfs: missing 'workdir'\n"); -- cgit v0.10.2 From 84889d49335627bc770b32787c1ef9ebad1da232 Mon Sep 17 00:00:00 2001 From: Konstantin Khlebnikov Date: Mon, 16 Nov 2015 18:44:11 +0300 Subject: ovl: check dentry positiveness in ovl_cleanup_whiteouts() This patch fixes kernel crash at removing directory which contains whiteouts from lower layers. Cache of directory content passed as "list" contains entries from all layers, including whiteouts from lower layers. So, lookup in upper dir (moved into work at this stage) will return negative entry. Plus this cache is filled long before and we can race with external removal. Example: mkdir -p lower0/dir lower1/dir upper work overlay touch lower0/dir/a lower0/dir/b mknod lower1/dir/a c 0 0 mount -t overlay none overlay -o lowerdir=lower1:lower0,upperdir=upper,workdir=work rm -fr overlay/dir Signed-off-by: Konstantin Khlebnikov Signed-off-by: Miklos Szeredi Cc: # 3.18+ diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 70e9af5..adcb139 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -571,7 +571,8 @@ void ovl_cleanup_whiteouts(struct dentry *upper, struct list_head *list) (int) PTR_ERR(dentry)); continue; } - ovl_cleanup(upper->d_inode, dentry); + if (dentry->d_inode) + ovl_cleanup(upper->d_inode, dentry); dput(dentry); } mutex_unlock(&upper->d_inode->i_mutex); -- cgit v0.10.2