2018-06-18 15:43:41

by Mark Salyzyn

[permalink] [raw]
Subject: overlayfs: caller_credentials option bypass creator_cred

All accesses to the lower filesystems reference the creator (mount)
and not the source context. This is a security issue.

A module bool parameter and mount option caller_credentials is added
to set the default, and to act as a presence check for this feature.
The module parameter is used to change the default behavior because
the 'normal' behavior of overlayfs is a _security_ violation
providing the privileges of the creator to the user when accessing
the files in the filesystems. But since I can not break user API,
I have to preserve the original behavior as default.

On MAC security model expect to set the caller_credentials to Y as
part of early initialization to preserve security model when the
option of iether caller_credentials or creator_credentials is not
explicitly specified in the mount.

Signed-off-by: Mark Salyzyn <[email protected]>
Cc: Miklos Szeredi <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
---
Documentation/filesystems/overlayfs.txt | 7 +++++++
fs/overlayfs/copy_up.c | 3 ++-
fs/overlayfs/dir.c | 12 ++++++++----
fs/overlayfs/inode.c | 24 ++++++++++++++++--------
fs/overlayfs/namei.c | 9 ++++++---
fs/overlayfs/ovl_entry.h | 1 +
fs/overlayfs/readdir.c | 6 ++++--
fs/overlayfs/super.c | 22 ++++++++++++++++++++++
fs/overlayfs/util.c | 8 ++++++--
9 files changed, 72 insertions(+), 20 deletions(-)

diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt
index 72615a2c0752..4328be5cdaa9 100644
--- a/Documentation/filesystems/overlayfs.txt
+++ b/Documentation/filesystems/overlayfs.txt
@@ -106,6 +106,13 @@ Only the lists of names from directories are merged. Other content
such as metadata and extended attributes are reported for the upper
directory only. These attributes of the lower directory are hidden.

+credentials
+-----------
+
+All access to the upper, lower and work directories is the creator's
+credentials. If caller_credentials is set, then the access caller's
+instance credentials will be used.
+
whiteouts and opaque directories
--------------------------------

diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index ddaddb4ce4c3..abc21844f712 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -790,7 +790,8 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags)
dput(parent);
dput(next);
}
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);

return err;
}
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index f480b1a2cd2e..97473bb2ee60 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -561,7 +561,8 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
override_cred->fsgid = inode->i_gid;
if (!attr->hardlink) {
err = security_dentry_create_files_as(dentry,
- attr->mode, &dentry->d_name, old_cred,
+ stat->mode, &dentry->d_name,
+ old_cred ? old_cred : current_cred(),
override_cred);
if (err) {
put_cred(override_cred);
@@ -577,7 +578,8 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
err = ovl_create_over_whiteout(dentry, inode, attr);
}
out_revert_creds:
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
return err;
}

@@ -824,7 +826,8 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir)
err = ovl_remove_upper(dentry, is_dir, &list);
else
err = ovl_remove_and_whiteout(dentry, &list);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
if (!err) {
if (is_dir)
clear_nlink(dentry->d_inode);
@@ -1150,7 +1153,8 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
out_unlock:
unlock_rename(new_upperdir, old_upperdir);
out_revert_creds:
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
ovl_nlink_end(new, locked);
out_drop_write:
ovl_drop_write(old);
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index ed16a898caeb..222678b6e67e 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -49,7 +49,8 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr)
inode_lock(upperdentry->d_inode);
old_cred = ovl_override_creds(dentry->d_sb);
err = notify_change(upperdentry, attr, NULL);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
if (!err)
ovl_copyattr(upperdentry->d_inode, dentry->d_inode);
inode_unlock(upperdentry->d_inode);
@@ -208,7 +209,8 @@ int ovl_getattr(const struct path *path, struct kstat *stat,
stat->nlink = dentry->d_inode->i_nlink;

out:
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);

return err;
}
@@ -242,7 +244,8 @@ int ovl_permission(struct inode *inode, int mask)
mask |= MAY_READ;
}
err = inode_permission(realinode, mask);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);

return err;
}
@@ -259,7 +262,8 @@ static const char *ovl_get_link(struct dentry *dentry,

old_cred = ovl_override_creds(dentry->d_sb);
p = vfs_get_link(ovl_dentry_real(dentry), done);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
return p;
}

@@ -302,7 +306,8 @@ int ovl_xattr_set(struct dentry *dentry, struct inode *inode, const char *name,
WARN_ON(flags != XATTR_REPLACE);
err = vfs_removexattr(realdentry, name);
}
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);

out_drop_write:
ovl_drop_write(dentry);
@@ -320,7 +325,8 @@ int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char *name,

old_cred = ovl_override_creds(dentry->d_sb);
res = vfs_getxattr(realdentry, name, value, size);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
return res;
}

@@ -344,7 +350,8 @@ ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size)

old_cred = ovl_override_creds(dentry->d_sb);
res = vfs_listxattr(realdentry, list, size);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
if (res <= 0 || size == 0)
return res;

@@ -379,7 +386,8 @@ struct posix_acl *ovl_get_acl(struct inode *inode, int type)

old_cred = ovl_override_creds(inode->i_sb);
acl = get_acl(realinode, type);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);

return acl;
}
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
index c993dd8db739..c8b84d262ec2 100644
--- a/fs/overlayfs/namei.c
+++ b/fs/overlayfs/namei.c
@@ -1024,7 +1024,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
OVL_I(inode)->redirect = upperredirect;
}

- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
dput(index);
kfree(stack);
kfree(d.redirect);
@@ -1043,7 +1044,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
kfree(upperredirect);
out:
kfree(d.redirect);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
return ERR_PTR(err);
}

@@ -1097,7 +1099,8 @@ bool ovl_lower_positive(struct dentry *dentry)
dput(this);
}
}
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);

return positive;
}
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
index 41655a7d6894..7e17db561a04 100644
--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -18,6 +18,7 @@ struct ovl_config {
const char *redirect_mode;
bool index;
bool nfs_export;
+ bool caller_credentials;
int xino;
};

diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index ef1fe42ff7bb..af3874d589ad 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -289,7 +289,8 @@ static int ovl_check_whiteouts(struct dentry *dir, struct ovl_readdir_data *rdd)
}
inode_unlock(dir->d_inode);
}
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);

return err;
}
@@ -906,7 +907,8 @@ int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)

old_cred = ovl_override_creds(dentry->d_sb);
err = ovl_dir_read_merged(dentry, list, &root);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
if (err)
return err;

diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 704b37311467..184688ab35cb 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -56,6 +56,11 @@ module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644);
MODULE_PARM_DESC(ovl_xino_auto_def,
"Auto enable xino feature");

+static bool __read_mostly ovl_caller_credentials;
+module_param_named(caller_credentials, ovl_caller_credentials, bool, 0644);
+MODULE_PARM_DESC(ovl_caller_credentials,
+ "Use caller credentials rather than creator credentials for accesses");
+
static void ovl_entry_stack_free(struct ovl_entry *oe)
{
unsigned int i;
@@ -376,6 +381,10 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
"on" : "off");
if (ofs->config.xino != ovl_xino_def())
seq_printf(m, ",xino=%s", ovl_xino_str[ofs->config.xino]);
+ if (ofs->config.caller_credentials)
+ seq_puts(m, ",caller_credentials");
+ else
+ seq_puts(m, ",creator_credentials");
return 0;
}

@@ -413,6 +422,8 @@ enum {
OPT_XINO_ON,
OPT_XINO_OFF,
OPT_XINO_AUTO,
+ OPT_CREATOR_CREDENTIALS,
+ OPT_CALLER_CREDENTIALS,
OPT_ERR,
};

@@ -429,6 +440,8 @@ static const match_table_t ovl_tokens = {
{OPT_XINO_ON, "xino=on"},
{OPT_XINO_OFF, "xino=off"},
{OPT_XINO_AUTO, "xino=auto"},
+ {OPT_CREATOR_CREDENTIALS, "creator_credentials"},
+ {OPT_CALLER_CREDENTIALS, "caller_credentials"},
{OPT_ERR, NULL}
};

@@ -486,6 +499,7 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
if (!config->redirect_mode)
return -ENOMEM;

+ config->caller_credentials = ovl_caller_credentials;
while ((p = ovl_next_opt(&opt)) != NULL) {
int token;
substring_t args[MAX_OPT_ARGS];
@@ -555,6 +569,14 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
config->xino = OVL_XINO_AUTO;
break;

+ case OPT_CREATOR_CREDENTIALS:
+ config->caller_credentials = false;
+ break;
+
+ case OPT_CALLER_CREDENTIALS:
+ config->caller_credentials = true;
+ break;
+
default:
pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p);
return -EINVAL;
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 6f1078028c66..538802289511 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -40,6 +40,8 @@ const struct cred *ovl_override_creds(struct super_block *sb)
{
struct ovl_fs *ofs = sb->s_fs_info;

+ if (ofs->config.caller_credentials)
+ return NULL;
return override_creds(ofs->creator_cred);
}

@@ -630,7 +632,8 @@ int ovl_nlink_start(struct dentry *dentry, bool *locked)
* value relative to the upper inode nlink in an upper inode xattr.
*/
err = ovl_set_nlink_upper(dentry);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);

out:
if (err)
@@ -650,7 +653,8 @@ void ovl_nlink_end(struct dentry *dentry, bool locked)

old_cred = ovl_override_creds(dentry->d_sb);
ovl_cleanup_index(dentry);
- revert_creds(old_cred);
+ if (old_cred)
+ revert_creds(old_cred);
}

mutex_unlock(&OVL_I(d_inode(dentry))->lock);
--
2.18.0.rc1.244.gcf134e6275-goog



2018-06-18 18:55:48

by Vivek Goyal

[permalink] [raw]
Subject: Re: overlayfs: caller_credentials option bypass creator_cred

On Mon, Jun 18, 2018 at 08:42:15AM -0700, Mark Salyzyn wrote:
> All accesses to the lower filesystems reference the creator (mount)
> and not the source context. This is a security issue.

Can you elaborate with an example that how this is a security issue.
mounter's check is in addition to caller's check. So we have two
checks in ovl_permission(). overlay inode gets the credentials from
underlying inode and we first check if caller is allowed to the
operation and if that's allowed, then we check if mounter is allowed
to do the operation.

IOW, mounter's check happen in addition to caller's checks. I am
wondering how did it end up being a security issue.

Vivek

>
> A module bool parameter and mount option caller_credentials is added
> to set the default, and to act as a presence check for this feature.
> The module parameter is used to change the default behavior because
> the 'normal' behavior of overlayfs is a _security_ violation
> providing the privileges of the creator to the user when accessing
> the files in the filesystems. But since I can not break user API,
> I have to preserve the original behavior as default.
>
> On MAC security model expect to set the caller_credentials to Y as
> part of early initialization to preserve security model when the
> option of iether caller_credentials or creator_credentials is not
> explicitly specified in the mount.
>
> Signed-off-by: Mark Salyzyn <[email protected]>
> Cc: Miklos Szeredi <[email protected]>
> Cc: Jonathan Corbet <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> Cc: [email protected]
> ---
> Documentation/filesystems/overlayfs.txt | 7 +++++++
> fs/overlayfs/copy_up.c | 3 ++-
> fs/overlayfs/dir.c | 12 ++++++++----
> fs/overlayfs/inode.c | 24 ++++++++++++++++--------
> fs/overlayfs/namei.c | 9 ++++++---
> fs/overlayfs/ovl_entry.h | 1 +
> fs/overlayfs/readdir.c | 6 ++++--
> fs/overlayfs/super.c | 22 ++++++++++++++++++++++
> fs/overlayfs/util.c | 8 ++++++--
> 9 files changed, 72 insertions(+), 20 deletions(-)
>
> diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt
> index 72615a2c0752..4328be5cdaa9 100644
> --- a/Documentation/filesystems/overlayfs.txt
> +++ b/Documentation/filesystems/overlayfs.txt
> @@ -106,6 +106,13 @@ Only the lists of names from directories are merged. Other content
> such as metadata and extended attributes are reported for the upper
> directory only. These attributes of the lower directory are hidden.
>
> +credentials
> +-----------
> +
> +All access to the upper, lower and work directories is the creator's
> +credentials. If caller_credentials is set, then the access caller's
> +instance credentials will be used.
> +
> whiteouts and opaque directories
> --------------------------------
>
> diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
> index ddaddb4ce4c3..abc21844f712 100644
> --- a/fs/overlayfs/copy_up.c
> +++ b/fs/overlayfs/copy_up.c
> @@ -790,7 +790,8 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags)
> dput(parent);
> dput(next);
> }
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
>
> return err;
> }
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index f480b1a2cd2e..97473bb2ee60 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -561,7 +561,8 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
> override_cred->fsgid = inode->i_gid;
> if (!attr->hardlink) {
> err = security_dentry_create_files_as(dentry,
> - attr->mode, &dentry->d_name, old_cred,
> + stat->mode, &dentry->d_name,
> + old_cred ? old_cred : current_cred(),
> override_cred);
> if (err) {
> put_cred(override_cred);
> @@ -577,7 +578,8 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
> err = ovl_create_over_whiteout(dentry, inode, attr);
> }
> out_revert_creds:
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> return err;
> }
>
> @@ -824,7 +826,8 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir)
> err = ovl_remove_upper(dentry, is_dir, &list);
> else
> err = ovl_remove_and_whiteout(dentry, &list);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> if (!err) {
> if (is_dir)
> clear_nlink(dentry->d_inode);
> @@ -1150,7 +1153,8 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
> out_unlock:
> unlock_rename(new_upperdir, old_upperdir);
> out_revert_creds:
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> ovl_nlink_end(new, locked);
> out_drop_write:
> ovl_drop_write(old);
> diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
> index ed16a898caeb..222678b6e67e 100644
> --- a/fs/overlayfs/inode.c
> +++ b/fs/overlayfs/inode.c
> @@ -49,7 +49,8 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr)
> inode_lock(upperdentry->d_inode);
> old_cred = ovl_override_creds(dentry->d_sb);
> err = notify_change(upperdentry, attr, NULL);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> if (!err)
> ovl_copyattr(upperdentry->d_inode, dentry->d_inode);
> inode_unlock(upperdentry->d_inode);
> @@ -208,7 +209,8 @@ int ovl_getattr(const struct path *path, struct kstat *stat,
> stat->nlink = dentry->d_inode->i_nlink;
>
> out:
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
>
> return err;
> }
> @@ -242,7 +244,8 @@ int ovl_permission(struct inode *inode, int mask)
> mask |= MAY_READ;
> }
> err = inode_permission(realinode, mask);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
>
> return err;
> }
> @@ -259,7 +262,8 @@ static const char *ovl_get_link(struct dentry *dentry,
>
> old_cred = ovl_override_creds(dentry->d_sb);
> p = vfs_get_link(ovl_dentry_real(dentry), done);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> return p;
> }
>
> @@ -302,7 +306,8 @@ int ovl_xattr_set(struct dentry *dentry, struct inode *inode, const char *name,
> WARN_ON(flags != XATTR_REPLACE);
> err = vfs_removexattr(realdentry, name);
> }
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
>
> out_drop_write:
> ovl_drop_write(dentry);
> @@ -320,7 +325,8 @@ int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char *name,
>
> old_cred = ovl_override_creds(dentry->d_sb);
> res = vfs_getxattr(realdentry, name, value, size);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> return res;
> }
>
> @@ -344,7 +350,8 @@ ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size)
>
> old_cred = ovl_override_creds(dentry->d_sb);
> res = vfs_listxattr(realdentry, list, size);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> if (res <= 0 || size == 0)
> return res;
>
> @@ -379,7 +386,8 @@ struct posix_acl *ovl_get_acl(struct inode *inode, int type)
>
> old_cred = ovl_override_creds(inode->i_sb);
> acl = get_acl(realinode, type);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
>
> return acl;
> }
> diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
> index c993dd8db739..c8b84d262ec2 100644
> --- a/fs/overlayfs/namei.c
> +++ b/fs/overlayfs/namei.c
> @@ -1024,7 +1024,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
> OVL_I(inode)->redirect = upperredirect;
> }
>
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> dput(index);
> kfree(stack);
> kfree(d.redirect);
> @@ -1043,7 +1044,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
> kfree(upperredirect);
> out:
> kfree(d.redirect);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> return ERR_PTR(err);
> }
>
> @@ -1097,7 +1099,8 @@ bool ovl_lower_positive(struct dentry *dentry)
> dput(this);
> }
> }
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
>
> return positive;
> }
> diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
> index 41655a7d6894..7e17db561a04 100644
> --- a/fs/overlayfs/ovl_entry.h
> +++ b/fs/overlayfs/ovl_entry.h
> @@ -18,6 +18,7 @@ struct ovl_config {
> const char *redirect_mode;
> bool index;
> bool nfs_export;
> + bool caller_credentials;
> int xino;
> };
>
> diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
> index ef1fe42ff7bb..af3874d589ad 100644
> --- a/fs/overlayfs/readdir.c
> +++ b/fs/overlayfs/readdir.c
> @@ -289,7 +289,8 @@ static int ovl_check_whiteouts(struct dentry *dir, struct ovl_readdir_data *rdd)
> }
> inode_unlock(dir->d_inode);
> }
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
>
> return err;
> }
> @@ -906,7 +907,8 @@ int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)
>
> old_cred = ovl_override_creds(dentry->d_sb);
> err = ovl_dir_read_merged(dentry, list, &root);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> if (err)
> return err;
>
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 704b37311467..184688ab35cb 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -56,6 +56,11 @@ module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644);
> MODULE_PARM_DESC(ovl_xino_auto_def,
> "Auto enable xino feature");
>
> +static bool __read_mostly ovl_caller_credentials;
> +module_param_named(caller_credentials, ovl_caller_credentials, bool, 0644);
> +MODULE_PARM_DESC(ovl_caller_credentials,
> + "Use caller credentials rather than creator credentials for accesses");
> +
> static void ovl_entry_stack_free(struct ovl_entry *oe)
> {
> unsigned int i;
> @@ -376,6 +381,10 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
> "on" : "off");
> if (ofs->config.xino != ovl_xino_def())
> seq_printf(m, ",xino=%s", ovl_xino_str[ofs->config.xino]);
> + if (ofs->config.caller_credentials)
> + seq_puts(m, ",caller_credentials");
> + else
> + seq_puts(m, ",creator_credentials");
> return 0;
> }
>
> @@ -413,6 +422,8 @@ enum {
> OPT_XINO_ON,
> OPT_XINO_OFF,
> OPT_XINO_AUTO,
> + OPT_CREATOR_CREDENTIALS,
> + OPT_CALLER_CREDENTIALS,
> OPT_ERR,
> };
>
> @@ -429,6 +440,8 @@ static const match_table_t ovl_tokens = {
> {OPT_XINO_ON, "xino=on"},
> {OPT_XINO_OFF, "xino=off"},
> {OPT_XINO_AUTO, "xino=auto"},
> + {OPT_CREATOR_CREDENTIALS, "creator_credentials"},
> + {OPT_CALLER_CREDENTIALS, "caller_credentials"},
> {OPT_ERR, NULL}
> };
>
> @@ -486,6 +499,7 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
> if (!config->redirect_mode)
> return -ENOMEM;
>
> + config->caller_credentials = ovl_caller_credentials;
> while ((p = ovl_next_opt(&opt)) != NULL) {
> int token;
> substring_t args[MAX_OPT_ARGS];
> @@ -555,6 +569,14 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
> config->xino = OVL_XINO_AUTO;
> break;
>
> + case OPT_CREATOR_CREDENTIALS:
> + config->caller_credentials = false;
> + break;
> +
> + case OPT_CALLER_CREDENTIALS:
> + config->caller_credentials = true;
> + break;
> +
> default:
> pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p);
> return -EINVAL;
> diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
> index 6f1078028c66..538802289511 100644
> --- a/fs/overlayfs/util.c
> +++ b/fs/overlayfs/util.c
> @@ -40,6 +40,8 @@ const struct cred *ovl_override_creds(struct super_block *sb)
> {
> struct ovl_fs *ofs = sb->s_fs_info;
>
> + if (ofs->config.caller_credentials)
> + return NULL;
> return override_creds(ofs->creator_cred);
> }
>
> @@ -630,7 +632,8 @@ int ovl_nlink_start(struct dentry *dentry, bool *locked)
> * value relative to the upper inode nlink in an upper inode xattr.
> */
> err = ovl_set_nlink_upper(dentry);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
>
> out:
> if (err)
> @@ -650,7 +653,8 @@ void ovl_nlink_end(struct dentry *dentry, bool locked)
>
> old_cred = ovl_override_creds(dentry->d_sb);
> ovl_cleanup_index(dentry);
> - revert_creds(old_cred);
> + if (old_cred)
> + revert_creds(old_cred);
> }
>
> mutex_unlock(&OVL_I(d_inode(dentry))->lock);
> --
> 2.18.0.rc1.244.gcf134e6275-goog
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-unionfs" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2018-06-18 18:59:39

by kernel test robot

[permalink] [raw]
Subject: Re: overlayfs: caller_credentials option bypass creator_cred

Hi Mark,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on linus/master]
[also build test ERROR on v4.18-rc1]
[cannot apply to miklos-vfs/overlayfs-next next-20180618]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url: https://github.com/0day-ci/linux/commits/Mark-Salyzyn/overlayfs-caller_credentials-option-bypass-creator_cred/20180619-012937
config: i386-randconfig-x014-201824 (attached as .config)
compiler: gcc-7 (Debian 7.3.0-16) 7.3.0
reproduce:
# save the attached .config to linux build tree
make ARCH=i386

All errors (new ones prefixed by >>):

fs//overlayfs/dir.c: In function 'ovl_create_or_link':
>> fs//overlayfs/dir.c:564:6: error: 'stat' undeclared (first use in this function)
stat->mode, &dentry->d_name,
^~~~
fs//overlayfs/dir.c:564:6: note: each undeclared identifier is reported only once for each function it appears in

vim +/stat +564 fs//overlayfs/dir.c

532
533 static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
534 struct ovl_cattr *attr, bool origin)
535 {
536 int err;
537 const struct cred *old_cred;
538 struct cred *override_cred;
539 struct dentry *parent = dentry->d_parent;
540
541 err = ovl_copy_up(parent);
542 if (err)
543 return err;
544
545 old_cred = ovl_override_creds(dentry->d_sb);
546
547 /*
548 * When linking a file with copy up origin into a new parent, mark the
549 * new parent dir "impure".
550 */
551 if (origin) {
552 err = ovl_set_impure(parent, ovl_dentry_upper(parent));
553 if (err)
554 goto out_revert_creds;
555 }
556
557 err = -ENOMEM;
558 override_cred = prepare_creds();
559 if (override_cred) {
560 override_cred->fsuid = inode->i_uid;
561 override_cred->fsgid = inode->i_gid;
562 if (!attr->hardlink) {
563 err = security_dentry_create_files_as(dentry,
> 564 stat->mode, &dentry->d_name,
565 old_cred ? old_cred : current_cred(),
566 override_cred);
567 if (err) {
568 put_cred(override_cred);
569 goto out_revert_creds;
570 }
571 }
572 put_cred(override_creds(override_cred));
573 put_cred(override_cred);
574
575 if (!ovl_dentry_is_whiteout(dentry))
576 err = ovl_create_upper(dentry, inode, attr);
577 else
578 err = ovl_create_over_whiteout(dentry, inode, attr);
579 }
580 out_revert_creds:
581 if (old_cred)
582 revert_creds(old_cred);
583 return err;
584 }
585

---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation


Attachments:
(No filename) (2.82 kB)
.config.gz (26.83 kB)
Download all attachments

2018-06-18 19:20:28

by Mark Salyzyn

[permalink] [raw]
Subject: Re: overlayfs: caller_credentials option bypass creator_cred

On 06/18/2018 11:54 AM, Vivek Goyal wrote:
> On Mon, Jun 18, 2018 at 08:42:15AM -0700, Mark Salyzyn wrote:
>> All accesses to the lower filesystems reference the creator (mount)
>> and not the source context. This is a security issue.
> Can you elaborate with an example that how this is a security issue.
> mounter's check is in addition to caller's check. So we have two
> checks in ovl_permission(). overlay inode gets the credentials from
> underlying inode and we first check if caller is allowed to the
> operation and if that's allowed, then we check if mounter is allowed
> to do the operation.
init which does the mount and represents the creator_cred which is
granted a restricted MAC to do just what it needs to do, eg mount, but
not be able to access the files. The caller comes in and is rejected
because init domain is not allowed, even though the caller's domain is.
MAC does not require overlap in privileges between the creator and the user.

-- Mark

2018-06-18 19:44:48

by Vivek Goyal

[permalink] [raw]
Subject: Re: overlayfs: caller_credentials option bypass creator_cred

On Mon, Jun 18, 2018 at 12:18:25PM -0700, Mark Salyzyn wrote:
> On 06/18/2018 11:54 AM, Vivek Goyal wrote:
> > On Mon, Jun 18, 2018 at 08:42:15AM -0700, Mark Salyzyn wrote:
> > > All accesses to the lower filesystems reference the creator (mount)
> > > and not the source context. This is a security issue.
> > Can you elaborate with an example that how this is a security issue.
> > mounter's check is in addition to caller's check. So we have two
> > checks in ovl_permission(). overlay inode gets the credentials from
> > underlying inode and we first check if caller is allowed to the
> > operation and if that's allowed, then we check if mounter is allowed
> > to do the operation.
> init which does the mount and represents the creator_cred which is granted a
> restricted MAC to do just what it needs to do, eg mount, but not be able to
> access the files. The caller comes in and is rejected because init domain is
> not allowed, even though the caller's domain is. MAC does not require
> overlap in privileges between the creator and the user.

[ CC dan walsh, stephen smalley ]

Ok. So this is not about security risk as such. It is more about that
it does not work in particular configuration where caller is allowed to
something but the mounter is not allowed to do that operation, so
operation fails.

(IIUC, recently this was raised as an issue if NFS is lower layer and
server is doing root squashing. Then root on client can do mount but might
not have access to a file while caller does have).

Will it be acceptable to write security policies in such a way so that
mounter has access as well.

Current model does assume that mounter has privileges on underlying files.

I think this works reasonably well with SELinux model of doing context
mounts. So if a "context=foo_label" mount is done, that changes labels
on overlay inodes (and ignore lables on disk). Mounter is the one allowing
this overriding. Now if we mounter allows this, then mounter should have
atleast access to underlying files. Otherwise this becomes equivalent
of that as long as mounter can do mount then it is providing access to
all files on disk/dir (with context mount). And mounter itself might not
be able to do those operations.

Core idea is, trying to make sure mounter of overlay does not provide
more priviliges to caller through a mount point if mounter itself does
not have privilege to do certain operations.

Another place it works well is that one does not have to provide
read/write labels on lower file. So in case of containers, SELinux can
just provide read labels to files in shared image dir, and as long
as mounter has read permission, it can copy up file and provide a writable
copy to the client. If we don't do mounter's check, that means caller
needs to have write permission to lower layer as well. And if caller
breaks out of container, it could directly write lower layer and attcking
another containers. So this was another use case in mind while coming
up with this model.

I am not saying that current model is the best way of doing things. Just
giving some context about why we thought it is a good idea.

Thanks
Vivek

2018-06-18 22:02:02

by Mark Salyzyn

[permalink] [raw]
Subject: Re: overlayfs: caller_credentials option bypass creator_cred

On 06/18/2018 12:43 PM, Vivek Goyal wrote:
> Will it be acceptable to write security policies in such a way so that
> mounter has access as well.
Unfortunately No. Policy of minimizing attack surface for a contained
root service (init in this case). Just because it can mount, does not
mean it can modify critical content; an attacker could use this to open
a hole.

> Current model does assume that mounter has privileges on underlying files.

Only ones it appears to need is the workdir AFAIK, had to add ability to
create in the <wordir> xattr in order to enable r/w mounts later.
Although not all corners were tested, I did not see any copy_up issues
b/c the caller had the privs in the Android security model when mounted
with this new flag.

-- Mark

2018-06-19 14:37:23

by Vivek Goyal

[permalink] [raw]
Subject: Re: overlayfs: caller_credentials option bypass creator_cred

On Mon, Jun 18, 2018 at 02:59:50PM -0700, Mark Salyzyn wrote:
> On 06/18/2018 12:43 PM, Vivek Goyal wrote:
> > Will it be acceptable to write security policies in such a way so that
> > mounter has access as well.
> Unfortunately No. Policy of minimizing attack surface for a contained root
> service (init in this case). Just because it can mount, does not mean it can
> modify critical content; an attacker could use this to open a hole.
>
> > Current model does assume that mounter has privileges on underlying files.
>
> Only ones it appears to need is the workdir AFAIK, had to add ability to
> create in the <wordir> xattr in order to enable r/w mounts later. Although
> not all corners were tested, I did not see any copy_up issues b/c the caller
> had the privs in the Android security model when mounted with this new flag.

So in this system all callers are priviliged and have the capability to
mknod and set trusted xattrs. (Amir mentioned the reason why we switch
creds). If not, then file unlink (Should do mknod), lower non-empty directory
rename (should set trusted REDIRECT) and bunch of other operations should fail.

Thanks
Vivek

2018-06-20 15:29:25

by Mark Salyzyn

[permalink] [raw]
Subject: Re: overlayfs: caller_credentials option bypass creator_cred

On 06/19/2018 07:36 AM, Vivek Goyal wrote:
> On Mon, Jun 18, 2018 at 02:59:50PM -0700, Mark Salyzyn wrote:
> So in this system all callers are priviliged and have the capability to
> mknod and set trusted xattrs.
This is true of the callers that make adjustments (in Android's Case
this is an su context provided to the adb tool for sync and push). More
importantly the large variety of callers have the passive/read MAC
credentials for their domain set of files; where the mounter/creator
does not.
> (Amir mentioned the reason why we switch
> creds). If not, then file unlink (Should do mknod), lower non-empty directory
> rename (should set trusted REDIRECT) and bunch of other operations should fail.

Hmmm, neither was part of my test plan b/c these operations are more
esoteric for development ... need to add them and address them.

Thanks all (You, Eric, Amir and private) for your comments, will
regroup, test and address concerns!

-- Mark