Merge branch 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
Pull overlayfs fixes from Miklos Szeredi: - fix incomplete syncing of filesystem - fix regression in readdir on ovl over 9p - only follow redirects when needed - misc fixes and cleanups * 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: ovl: fix overlay: warning prefix ovl: Use PTR_ERR_OR_ZERO() ovl: Sync upper dirty data when syncing overlayfs ovl: update ctx->pos on impure dir iteration ovl: Pass ovl_get_nlink() parameters in right order ovl: don't follow redirects if redirect_dir=off
This commit is contained in:
commit
227701e0e7
|
@ -156,6 +156,40 @@ handle it in two different ways:
|
||||||
root of the overlay. Finally the directory is moved to the new
|
root of the overlay. Finally the directory is moved to the new
|
||||||
location.
|
location.
|
||||||
|
|
||||||
|
There are several ways to tune the "redirect_dir" feature.
|
||||||
|
|
||||||
|
Kernel config options:
|
||||||
|
|
||||||
|
- OVERLAY_FS_REDIRECT_DIR:
|
||||||
|
If this is enabled, then redirect_dir is turned on by default.
|
||||||
|
- OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW:
|
||||||
|
If this is enabled, then redirects are always followed by default. Enabling
|
||||||
|
this results in a less secure configuration. Enable this option only when
|
||||||
|
worried about backward compatibility with kernels that have the redirect_dir
|
||||||
|
feature and follow redirects even if turned off.
|
||||||
|
|
||||||
|
Module options (can also be changed through /sys/module/overlay/parameters/*):
|
||||||
|
|
||||||
|
- "redirect_dir=BOOL":
|
||||||
|
See OVERLAY_FS_REDIRECT_DIR kernel config option above.
|
||||||
|
- "redirect_always_follow=BOOL":
|
||||||
|
See OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW kernel config option above.
|
||||||
|
- "redirect_max=NUM":
|
||||||
|
The maximum number of bytes in an absolute redirect (default is 256).
|
||||||
|
|
||||||
|
Mount options:
|
||||||
|
|
||||||
|
- "redirect_dir=on":
|
||||||
|
Redirects are enabled.
|
||||||
|
- "redirect_dir=follow":
|
||||||
|
Redirects are not created, but followed.
|
||||||
|
- "redirect_dir=off":
|
||||||
|
Redirects are not created and only followed if "redirect_always_follow"
|
||||||
|
feature is enabled in the kernel/module config.
|
||||||
|
- "redirect_dir=nofollow":
|
||||||
|
Redirects are not created and not followed (equivalent to "redirect_dir=off"
|
||||||
|
if "redirect_always_follow" feature is not enabled).
|
||||||
|
|
||||||
Non-directories
|
Non-directories
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,16 @@ config OVERLAY_FS_REDIRECT_DIR
|
||||||
an overlay which has redirects on a kernel that doesn't support this
|
an overlay which has redirects on a kernel that doesn't support this
|
||||||
feature will have unexpected results.
|
feature will have unexpected results.
|
||||||
|
|
||||||
|
config OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW
|
||||||
|
bool "Overlayfs: follow redirects even if redirects are turned off"
|
||||||
|
default y
|
||||||
|
depends on OVERLAY_FS
|
||||||
|
help
|
||||||
|
Disable this to get a possibly more secure configuration, but that
|
||||||
|
might not be backward compatible with previous kernels.
|
||||||
|
|
||||||
|
For more information, see Documentation/filesystems/overlayfs.txt
|
||||||
|
|
||||||
config OVERLAY_FS_INDEX
|
config OVERLAY_FS_INDEX
|
||||||
bool "Overlayfs: turn on inodes index feature by default"
|
bool "Overlayfs: turn on inodes index feature by default"
|
||||||
depends on OVERLAY_FS
|
depends on OVERLAY_FS
|
||||||
|
|
|
@ -887,7 +887,8 @@ static int ovl_set_redirect(struct dentry *dentry, bool samedir)
|
||||||
spin_unlock(&dentry->d_lock);
|
spin_unlock(&dentry->d_lock);
|
||||||
} else {
|
} else {
|
||||||
kfree(redirect);
|
kfree(redirect);
|
||||||
pr_warn_ratelimited("overlay: failed to set redirect (%i)\n", err);
|
pr_warn_ratelimited("overlayfs: failed to set redirect (%i)\n",
|
||||||
|
err);
|
||||||
/* Fall back to userspace copy-up */
|
/* Fall back to userspace copy-up */
|
||||||
err = -EXDEV;
|
err = -EXDEV;
|
||||||
}
|
}
|
||||||
|
|
|
@ -435,7 +435,7 @@ int ovl_verify_index(struct dentry *index, struct ovl_path *lower,
|
||||||
|
|
||||||
/* Check if index is orphan and don't warn before cleaning it */
|
/* Check if index is orphan and don't warn before cleaning it */
|
||||||
if (d_inode(index)->i_nlink == 1 &&
|
if (d_inode(index)->i_nlink == 1 &&
|
||||||
ovl_get_nlink(index, origin.dentry, 0) == 0)
|
ovl_get_nlink(origin.dentry, index, 0) == 0)
|
||||||
err = -ENOENT;
|
err = -ENOENT;
|
||||||
|
|
||||||
dput(origin.dentry);
|
dput(origin.dentry);
|
||||||
|
@ -681,6 +681,22 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||||
if (d.stop)
|
if (d.stop)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Following redirects can have security consequences: it's like
|
||||||
|
* a symlink into the lower layer without the permission checks.
|
||||||
|
* This is only a problem if the upper layer is untrusted (e.g
|
||||||
|
* comes from an USB drive). This can allow a non-readable file
|
||||||
|
* or directory to become readable.
|
||||||
|
*
|
||||||
|
* Only following redirects when redirects are enabled disables
|
||||||
|
* this attack vector when not necessary.
|
||||||
|
*/
|
||||||
|
err = -EPERM;
|
||||||
|
if (d.redirect && !ofs->config.redirect_follow) {
|
||||||
|
pr_warn_ratelimited("overlay: refusing to follow redirect for (%pd2)\n", dentry);
|
||||||
|
goto out_put;
|
||||||
|
}
|
||||||
|
|
||||||
if (d.redirect && d.redirect[0] == '/' && poe != roe) {
|
if (d.redirect && d.redirect[0] == '/' && poe != roe) {
|
||||||
poe = roe;
|
poe = roe;
|
||||||
|
|
||||||
|
|
|
@ -180,7 +180,7 @@ static inline int ovl_do_whiteout(struct inode *dir, struct dentry *dentry)
|
||||||
static inline struct dentry *ovl_do_tmpfile(struct dentry *dentry, umode_t mode)
|
static inline struct dentry *ovl_do_tmpfile(struct dentry *dentry, umode_t mode)
|
||||||
{
|
{
|
||||||
struct dentry *ret = vfs_tmpfile(dentry, mode, 0);
|
struct dentry *ret = vfs_tmpfile(dentry, mode, 0);
|
||||||
int err = IS_ERR(ret) ? PTR_ERR(ret) : 0;
|
int err = PTR_ERR_OR_ZERO(ret);
|
||||||
|
|
||||||
pr_debug("tmpfile(%pd2, 0%o) = %i\n", dentry, mode, err);
|
pr_debug("tmpfile(%pd2, 0%o) = %i\n", dentry, mode, err);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -14,6 +14,8 @@ struct ovl_config {
|
||||||
char *workdir;
|
char *workdir;
|
||||||
bool default_permissions;
|
bool default_permissions;
|
||||||
bool redirect_dir;
|
bool redirect_dir;
|
||||||
|
bool redirect_follow;
|
||||||
|
const char *redirect_mode;
|
||||||
bool index;
|
bool index;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -499,7 +499,7 @@ static int ovl_cache_update_ino(struct path *path, struct ovl_cache_entry *p)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
pr_warn_ratelimited("overlay: failed to look up (%s) for ino (%i)\n",
|
pr_warn_ratelimited("overlayfs: failed to look up (%s) for ino (%i)\n",
|
||||||
p->name, err);
|
p->name, err);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -663,7 +663,10 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
|
||||||
return PTR_ERR(rdt.cache);
|
return PTR_ERR(rdt.cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
return iterate_dir(od->realfile, &rdt.ctx);
|
err = iterate_dir(od->realfile, &rdt.ctx);
|
||||||
|
ctx->pos = rdt.ctx.pos;
|
||||||
|
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,13 @@ module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644);
|
||||||
MODULE_PARM_DESC(ovl_redirect_dir_def,
|
MODULE_PARM_DESC(ovl_redirect_dir_def,
|
||||||
"Default to on or off for the redirect_dir feature");
|
"Default to on or off for the redirect_dir feature");
|
||||||
|
|
||||||
|
static bool ovl_redirect_always_follow =
|
||||||
|
IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW);
|
||||||
|
module_param_named(redirect_always_follow, ovl_redirect_always_follow,
|
||||||
|
bool, 0644);
|
||||||
|
MODULE_PARM_DESC(ovl_redirect_always_follow,
|
||||||
|
"Follow redirects even if redirect_dir feature is turned off");
|
||||||
|
|
||||||
static bool ovl_index_def = IS_ENABLED(CONFIG_OVERLAY_FS_INDEX);
|
static bool ovl_index_def = IS_ENABLED(CONFIG_OVERLAY_FS_INDEX);
|
||||||
module_param_named(index, ovl_index_def, bool, 0644);
|
module_param_named(index, ovl_index_def, bool, 0644);
|
||||||
MODULE_PARM_DESC(ovl_index_def,
|
MODULE_PARM_DESC(ovl_index_def,
|
||||||
|
@ -232,6 +239,7 @@ static void ovl_free_fs(struct ovl_fs *ofs)
|
||||||
kfree(ofs->config.lowerdir);
|
kfree(ofs->config.lowerdir);
|
||||||
kfree(ofs->config.upperdir);
|
kfree(ofs->config.upperdir);
|
||||||
kfree(ofs->config.workdir);
|
kfree(ofs->config.workdir);
|
||||||
|
kfree(ofs->config.redirect_mode);
|
||||||
if (ofs->creator_cred)
|
if (ofs->creator_cred)
|
||||||
put_cred(ofs->creator_cred);
|
put_cred(ofs->creator_cred);
|
||||||
kfree(ofs);
|
kfree(ofs);
|
||||||
|
@ -244,6 +252,7 @@ static void ovl_put_super(struct super_block *sb)
|
||||||
ovl_free_fs(ofs);
|
ovl_free_fs(ofs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sync real dirty inodes in upper filesystem (if it exists) */
|
||||||
static int ovl_sync_fs(struct super_block *sb, int wait)
|
static int ovl_sync_fs(struct super_block *sb, int wait)
|
||||||
{
|
{
|
||||||
struct ovl_fs *ofs = sb->s_fs_info;
|
struct ovl_fs *ofs = sb->s_fs_info;
|
||||||
|
@ -252,14 +261,24 @@ static int ovl_sync_fs(struct super_block *sb, int wait)
|
||||||
|
|
||||||
if (!ofs->upper_mnt)
|
if (!ofs->upper_mnt)
|
||||||
return 0;
|
return 0;
|
||||||
upper_sb = ofs->upper_mnt->mnt_sb;
|
|
||||||
if (!upper_sb->s_op->sync_fs)
|
/*
|
||||||
|
* If this is a sync(2) call or an emergency sync, all the super blocks
|
||||||
|
* will be iterated, including upper_sb, so no need to do anything.
|
||||||
|
*
|
||||||
|
* If this is a syncfs(2) call, then we do need to call
|
||||||
|
* sync_filesystem() on upper_sb, but enough if we do it when being
|
||||||
|
* called with wait == 1.
|
||||||
|
*/
|
||||||
|
if (!wait)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* real inodes have already been synced by sync_filesystem(ovl_sb) */
|
upper_sb = ofs->upper_mnt->mnt_sb;
|
||||||
|
|
||||||
down_read(&upper_sb->s_umount);
|
down_read(&upper_sb->s_umount);
|
||||||
ret = upper_sb->s_op->sync_fs(upper_sb, wait);
|
ret = sync_filesystem(upper_sb);
|
||||||
up_read(&upper_sb->s_umount);
|
up_read(&upper_sb->s_umount);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,6 +314,11 @@ static bool ovl_force_readonly(struct ovl_fs *ofs)
|
||||||
return (!ofs->upper_mnt || !ofs->workdir);
|
return (!ofs->upper_mnt || !ofs->workdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *ovl_redirect_mode_def(void)
|
||||||
|
{
|
||||||
|
return ovl_redirect_dir_def ? "on" : "off";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ovl_show_options
|
* ovl_show_options
|
||||||
*
|
*
|
||||||
|
@ -313,12 +337,10 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
|
||||||
}
|
}
|
||||||
if (ofs->config.default_permissions)
|
if (ofs->config.default_permissions)
|
||||||
seq_puts(m, ",default_permissions");
|
seq_puts(m, ",default_permissions");
|
||||||
if (ofs->config.redirect_dir != ovl_redirect_dir_def)
|
if (strcmp(ofs->config.redirect_mode, ovl_redirect_mode_def()) != 0)
|
||||||
seq_printf(m, ",redirect_dir=%s",
|
seq_printf(m, ",redirect_dir=%s", ofs->config.redirect_mode);
|
||||||
ofs->config.redirect_dir ? "on" : "off");
|
|
||||||
if (ofs->config.index != ovl_index_def)
|
if (ofs->config.index != ovl_index_def)
|
||||||
seq_printf(m, ",index=%s",
|
seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off");
|
||||||
ofs->config.index ? "on" : "off");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,8 +370,7 @@ enum {
|
||||||
OPT_UPPERDIR,
|
OPT_UPPERDIR,
|
||||||
OPT_WORKDIR,
|
OPT_WORKDIR,
|
||||||
OPT_DEFAULT_PERMISSIONS,
|
OPT_DEFAULT_PERMISSIONS,
|
||||||
OPT_REDIRECT_DIR_ON,
|
OPT_REDIRECT_DIR,
|
||||||
OPT_REDIRECT_DIR_OFF,
|
|
||||||
OPT_INDEX_ON,
|
OPT_INDEX_ON,
|
||||||
OPT_INDEX_OFF,
|
OPT_INDEX_OFF,
|
||||||
OPT_ERR,
|
OPT_ERR,
|
||||||
|
@ -360,8 +381,7 @@ static const match_table_t ovl_tokens = {
|
||||||
{OPT_UPPERDIR, "upperdir=%s"},
|
{OPT_UPPERDIR, "upperdir=%s"},
|
||||||
{OPT_WORKDIR, "workdir=%s"},
|
{OPT_WORKDIR, "workdir=%s"},
|
||||||
{OPT_DEFAULT_PERMISSIONS, "default_permissions"},
|
{OPT_DEFAULT_PERMISSIONS, "default_permissions"},
|
||||||
{OPT_REDIRECT_DIR_ON, "redirect_dir=on"},
|
{OPT_REDIRECT_DIR, "redirect_dir=%s"},
|
||||||
{OPT_REDIRECT_DIR_OFF, "redirect_dir=off"},
|
|
||||||
{OPT_INDEX_ON, "index=on"},
|
{OPT_INDEX_ON, "index=on"},
|
||||||
{OPT_INDEX_OFF, "index=off"},
|
{OPT_INDEX_OFF, "index=off"},
|
||||||
{OPT_ERR, NULL}
|
{OPT_ERR, NULL}
|
||||||
|
@ -390,10 +410,37 @@ static char *ovl_next_opt(char **s)
|
||||||
return sbegin;
|
return sbegin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ovl_parse_redirect_mode(struct ovl_config *config, const char *mode)
|
||||||
|
{
|
||||||
|
if (strcmp(mode, "on") == 0) {
|
||||||
|
config->redirect_dir = true;
|
||||||
|
/*
|
||||||
|
* Does not make sense to have redirect creation without
|
||||||
|
* redirect following.
|
||||||
|
*/
|
||||||
|
config->redirect_follow = true;
|
||||||
|
} else if (strcmp(mode, "follow") == 0) {
|
||||||
|
config->redirect_follow = true;
|
||||||
|
} else if (strcmp(mode, "off") == 0) {
|
||||||
|
if (ovl_redirect_always_follow)
|
||||||
|
config->redirect_follow = true;
|
||||||
|
} else if (strcmp(mode, "nofollow") != 0) {
|
||||||
|
pr_err("overlayfs: bad mount option \"redirect_dir=%s\"\n",
|
||||||
|
mode);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int ovl_parse_opt(char *opt, struct ovl_config *config)
|
static int ovl_parse_opt(char *opt, struct ovl_config *config)
|
||||||
{
|
{
|
||||||
char *p;
|
char *p;
|
||||||
|
|
||||||
|
config->redirect_mode = kstrdup(ovl_redirect_mode_def(), GFP_KERNEL);
|
||||||
|
if (!config->redirect_mode)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
while ((p = ovl_next_opt(&opt)) != NULL) {
|
while ((p = ovl_next_opt(&opt)) != NULL) {
|
||||||
int token;
|
int token;
|
||||||
substring_t args[MAX_OPT_ARGS];
|
substring_t args[MAX_OPT_ARGS];
|
||||||
|
@ -428,12 +475,11 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
|
||||||
config->default_permissions = true;
|
config->default_permissions = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPT_REDIRECT_DIR_ON:
|
case OPT_REDIRECT_DIR:
|
||||||
config->redirect_dir = true;
|
kfree(config->redirect_mode);
|
||||||
break;
|
config->redirect_mode = match_strdup(&args[0]);
|
||||||
|
if (!config->redirect_mode)
|
||||||
case OPT_REDIRECT_DIR_OFF:
|
return -ENOMEM;
|
||||||
config->redirect_dir = false;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPT_INDEX_ON:
|
case OPT_INDEX_ON:
|
||||||
|
@ -458,7 +504,7 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
|
||||||
config->workdir = NULL;
|
config->workdir = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return ovl_parse_redirect_mode(config, config->redirect_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define OVL_WORKDIR_NAME "work"
|
#define OVL_WORKDIR_NAME "work"
|
||||||
|
@ -1160,7 +1206,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
||||||
if (!cred)
|
if (!cred)
|
||||||
goto out_err;
|
goto out_err;
|
||||||
|
|
||||||
ofs->config.redirect_dir = ovl_redirect_dir_def;
|
|
||||||
ofs->config.index = ovl_index_def;
|
ofs->config.index = ovl_index_def;
|
||||||
err = ovl_parse_opt((char *) data, &ofs->config);
|
err = ovl_parse_opt((char *) data, &ofs->config);
|
||||||
if (err)
|
if (err)
|
||||||
|
|
Loading…
Reference in New Issue