overlayfs update for 6.6
-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEE9zuTYTs0RXF+Ke33EVvVyTe/1WoFAmTu0QoACgkQEVvVyTe/ 1WpbzBAAjIZXzhn8KldDpG0muw9JKaSOxM45uhZE1s/2uKsVCyp4k3lubTbxxYO1 S9rUjhF2gSJFOfuSOK/XXEKXyu4MGT7iy7pKswu0k8+AHDDRBksPXJKA/AkhLPUr vX1pU6aWw2OSn1xdhIgY+F4DveyzYQL/CEoUzFyRPxSB0G/yjktRAjdZ2HL4cAvN eVXPyTj0bd4LVj1ITla4uj8DbgivrqmRJbZ9bKnSRE8GXWBriJhV//M2Q3QRno+W 04TtAvyh+klQeqZFVOQ0reZUFZzYBBZZTmqoFiUzTny7oljWl5F0+JfJOHhRGknG LYZCia34+T6TZPhOnZzT/szTDoXVvNJhEf+vBQCqhaCugqJc/2uJdw9CW8ZcDvA9 ZNOMxEbXE4VgGjJ0HM6MoDMUoIEUiNWEnXWEaKyCAfOPqgYwPy+QeDO4JtBPQpRn fwZx7Xpc1FLpTc9feHxzox9o81S8rPRMycUBg2c3KZB6TFnYNDxWIIo365naMCzz A8IDVGf+gd+S4NaZvh9FUijciIslYfyFgqwQERZmJnpDk1d1NyeUC7Nn7EkmUpyp guRaC+rUcqYP4CpuSHTCPle94qHqiAkbsKSJWebZ2M1j9fjZ+okPw0k83Nih79vu vRhs70Ah51v1lpBb0mlDjsV3vKm3Apv8nMJKZvVuC+Cw6Qiob5s= =F4Hi -----END PGP SIGNATURE----- Merge tag 'ovl-update-6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs Pull overlayfs updates from Amir Goldstein: - add verification feature needed by composefs (Alexander Larsson) - improve integration of overlayfs and fanotify (Amir Goldstein) - fortify some overlayfs code (Andrea Righi) * tag 'ovl-update-6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs: ovl: validate superblock in OVL_FS() ovl: make consistent use of OVL_FS() ovl: Kconfig: introduce CONFIG_OVERLAY_FS_DEBUG ovl: auto generate uuid for new overlay filesystems ovl: store persistent uuid/fsid with uuid=on ovl: add support for unique fsid per instance ovl: support encoding non-decodable file handles ovl: Handle verity during copy-up ovl: Validate verity xattr when resolving lowerdata ovl: Add versioned header for overlay.metacopy xattr ovl: Add framework for verity support
This commit is contained in:
commit
63580f669d
@ -326,6 +326,8 @@ the file has fs-verity enabled. This can perform better than
|
||||
FS_IOC_GETFLAGS and FS_IOC_MEASURE_VERITY because it doesn't require
|
||||
opening the file, and opening verity files can be expensive.
|
||||
|
||||
.. _accessing_verity_files:
|
||||
|
||||
Accessing verity files
|
||||
======================
|
||||
|
||||
|
@ -405,6 +405,53 @@ when a "metacopy" file in one of the lower layers above it, has a "redirect"
|
||||
to the absolute path of the "lower data" file in the "data-only" lower layer.
|
||||
|
||||
|
||||
fs-verity support
|
||||
----------------------
|
||||
|
||||
During metadata copy up of a lower file, if the source file has
|
||||
fs-verity enabled and overlay verity support is enabled, then the
|
||||
digest of the lower file is added to the "trusted.overlay.metacopy"
|
||||
xattr. This is then used to verify the content of the lower file
|
||||
each the time the metacopy file is opened.
|
||||
|
||||
When a layer containing verity xattrs is used, it means that any such
|
||||
metacopy file in the upper layer is guaranteed to match the content
|
||||
that was in the lower at the time of the copy-up. If at any time
|
||||
(during a mount, after a remount, etc) such a file in the lower is
|
||||
replaced or modified in any way, access to the corresponding file in
|
||||
overlayfs will result in EIO errors (either on open, due to overlayfs
|
||||
digest check, or from a later read due to fs-verity) and a detailed
|
||||
error is printed to the kernel logs. For more details of how fs-verity
|
||||
file access works, see :ref:`Documentation/filesystems/fsverity.rst
|
||||
<accessing_verity_files>`.
|
||||
|
||||
Verity can be used as a general robustness check to detect accidental
|
||||
changes in the overlayfs directories in use. But, with additional care
|
||||
it can also give more powerful guarantees. For example, if the upper
|
||||
layer is fully trusted (by using dm-verity or something similar), then
|
||||
an untrusted lower layer can be used to supply validated file content
|
||||
for all metacopy files. If additionally the untrusted lower
|
||||
directories are specified as "Data-only", then they can only supply
|
||||
such file content, and the entire mount can be trusted to match the
|
||||
upper layer.
|
||||
|
||||
This feature is controlled by the "verity" mount option, which
|
||||
supports these values:
|
||||
|
||||
- "off":
|
||||
The metacopy digest is never generated or used. This is the
|
||||
default if verity option is not specified.
|
||||
- "on":
|
||||
Whenever a metacopy files specifies an expected digest, the
|
||||
corresponding data file must match the specified digest. When
|
||||
generating a metacopy file the verity digest will be set in it
|
||||
based on the source file (if it has one).
|
||||
- "require":
|
||||
Same as "on", but additionally all metacopy files must specify a
|
||||
digest (or EIO is returned on open). This means metadata copy up
|
||||
will only be used if the data file has fs-verity enabled,
|
||||
otherwise a full copy-up is used.
|
||||
|
||||
Sharing and copying layers
|
||||
--------------------------
|
||||
|
||||
@ -610,6 +657,31 @@ can be useful in case the underlying disk is copied and the UUID of this copy
|
||||
is changed. This is only applicable if all lower/upper/work directories are on
|
||||
the same filesystem, otherwise it will fallback to normal behaviour.
|
||||
|
||||
|
||||
UUID and fsid
|
||||
-------------
|
||||
|
||||
The UUID of overlayfs instance itself and the fsid reported by statfs(2) are
|
||||
controlled by the "uuid" mount option, which supports these values:
|
||||
|
||||
- "null":
|
||||
UUID of overlayfs is null. fsid is taken from upper most filesystem.
|
||||
- "off":
|
||||
UUID of overlayfs is null. fsid is taken from upper most filesystem.
|
||||
UUID of underlying layers is ignored.
|
||||
- "on":
|
||||
UUID of overlayfs is generated and used to report a unique fsid.
|
||||
UUID is stored in xattr "trusted.overlay.uuid", making overlayfs fsid
|
||||
unique and persistent. This option requires an overlayfs with upper
|
||||
filesystem that supports xattrs.
|
||||
- "auto": (default)
|
||||
UUID is taken from xattr "trusted.overlay.uuid" if it exists.
|
||||
Upgrade to "uuid=on" on first time mount of new overlay filesystem that
|
||||
meets the prerequites.
|
||||
Downgrade to "uuid=null" for existing overlay filesystems that were never
|
||||
mounted with "uuid=on".
|
||||
|
||||
|
||||
Volatile mount
|
||||
--------------
|
||||
|
||||
|
@ -124,3 +124,12 @@ config OVERLAY_FS_METACOPY
|
||||
that doesn't support this feature will have unexpected results.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config OVERLAY_FS_DEBUG
|
||||
bool "Overlayfs: turn on extra debugging checks"
|
||||
default n
|
||||
depends on OVERLAY_FS
|
||||
help
|
||||
Say Y here to enable extra debugging checks in overlayfs.
|
||||
|
||||
If unsure, say N.
|
||||
|
@ -416,7 +416,7 @@ struct ovl_fh *ovl_encode_real_fh(struct ovl_fs *ofs, struct dentry *real,
|
||||
if (is_upper)
|
||||
fh->fb.flags |= OVL_FH_FLAG_PATH_UPPER;
|
||||
fh->fb.len = sizeof(fh->fb) + buflen;
|
||||
if (ofs->config.uuid)
|
||||
if (ovl_origin_uuid(ofs))
|
||||
fh->fb.uuid = *uuid;
|
||||
|
||||
return fh;
|
||||
@ -544,6 +544,7 @@ struct ovl_copy_up_ctx {
|
||||
bool origin;
|
||||
bool indexed;
|
||||
bool metacopy;
|
||||
bool metacopy_digest;
|
||||
};
|
||||
|
||||
static int ovl_link_up(struct ovl_copy_up_ctx *c)
|
||||
@ -641,8 +642,20 @@ static int ovl_copy_up_metadata(struct ovl_copy_up_ctx *c, struct dentry *temp)
|
||||
}
|
||||
|
||||
if (c->metacopy) {
|
||||
err = ovl_check_setxattr(ofs, temp, OVL_XATTR_METACOPY,
|
||||
NULL, 0, -EOPNOTSUPP);
|
||||
struct path lowerdatapath;
|
||||
struct ovl_metacopy metacopy_data = OVL_METACOPY_INIT;
|
||||
|
||||
ovl_path_lowerdata(c->dentry, &lowerdatapath);
|
||||
if (WARN_ON_ONCE(lowerdatapath.dentry == NULL))
|
||||
return -EIO;
|
||||
err = ovl_get_verity_digest(ofs, &lowerdatapath, &metacopy_data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (metacopy_data.digest_algo)
|
||||
c->metacopy_digest = true;
|
||||
|
||||
err = ovl_set_metacopy_xattr(ofs, temp, &metacopy_data);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
@ -751,9 +764,15 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
|
||||
if (err)
|
||||
goto cleanup;
|
||||
|
||||
if (!c->metacopy)
|
||||
ovl_set_upperdata(d_inode(c->dentry));
|
||||
inode = d_inode(c->dentry);
|
||||
if (c->metacopy_digest)
|
||||
ovl_set_flag(OVL_HAS_DIGEST, inode);
|
||||
else
|
||||
ovl_clear_flag(OVL_HAS_DIGEST, inode);
|
||||
ovl_clear_flag(OVL_VERIFIED_DIGEST, inode);
|
||||
|
||||
if (!c->metacopy)
|
||||
ovl_set_upperdata(inode);
|
||||
ovl_inode_update(inode, temp);
|
||||
if (S_ISDIR(inode->i_mode))
|
||||
ovl_set_flag(OVL_WHITEOUTS, inode);
|
||||
@ -813,6 +832,12 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
|
||||
if (err)
|
||||
goto out_fput;
|
||||
|
||||
if (c->metacopy_digest)
|
||||
ovl_set_flag(OVL_HAS_DIGEST, d_inode(c->dentry));
|
||||
else
|
||||
ovl_clear_flag(OVL_HAS_DIGEST, d_inode(c->dentry));
|
||||
ovl_clear_flag(OVL_VERIFIED_DIGEST, d_inode(c->dentry));
|
||||
|
||||
if (!c->metacopy)
|
||||
ovl_set_upperdata(d_inode(c->dentry));
|
||||
ovl_inode_update(d_inode(c->dentry), dget(temp));
|
||||
@ -907,7 +932,7 @@ out:
|
||||
static bool ovl_need_meta_copy_up(struct dentry *dentry, umode_t mode,
|
||||
int flags)
|
||||
{
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||
|
||||
if (!ofs->config.metacopy)
|
||||
return false;
|
||||
@ -918,6 +943,19 @@ static bool ovl_need_meta_copy_up(struct dentry *dentry, umode_t mode,
|
||||
if (flags && ((OPEN_FMODE(flags) & FMODE_WRITE) || (flags & O_TRUNC)))
|
||||
return false;
|
||||
|
||||
/* Fall back to full copy if no fsverity on source data and we require verity */
|
||||
if (ofs->config.verity_mode == OVL_VERITY_REQUIRE) {
|
||||
struct path lowerdata;
|
||||
|
||||
ovl_path_lowerdata(dentry, &lowerdata);
|
||||
|
||||
if (WARN_ON_ONCE(lowerdata.dentry == NULL) ||
|
||||
ovl_ensure_verity_loaded(&lowerdata) ||
|
||||
!fsverity_active(d_inode(lowerdata.dentry))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -984,6 +1022,8 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c)
|
||||
if (err)
|
||||
goto out_free;
|
||||
|
||||
ovl_clear_flag(OVL_HAS_DIGEST, d_inode(c->dentry));
|
||||
ovl_clear_flag(OVL_VERIFIED_DIGEST, d_inode(c->dentry));
|
||||
ovl_set_upperdata(d_inode(c->dentry));
|
||||
out_free:
|
||||
kfree(capability);
|
||||
@ -1078,7 +1118,7 @@ static int ovl_copy_up_flags(struct dentry *dentry, int flags)
|
||||
* not very important to optimize this case, so do lazy lowerdata lookup
|
||||
* before any copy up, so we can do it before taking ovl_inode_lock().
|
||||
*/
|
||||
err = ovl_maybe_lookup_lowerdata(dentry);
|
||||
err = ovl_verify_lowerdata(dentry);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
@ -174,28 +174,37 @@ static int ovl_connect_layer(struct dentry *dentry)
|
||||
* U = upper file handle
|
||||
* L = lower file handle
|
||||
*
|
||||
* (*) Connecting an overlay dir from real lower dentry is not always
|
||||
* (*) Decoding a connected overlay dir from real lower dentry is not always
|
||||
* possible when there are redirects in lower layers and non-indexed merge dirs.
|
||||
* To mitigate those case, we may copy up the lower dir ancestor before encode
|
||||
* a lower dir file handle.
|
||||
* of a decodable file handle for non-upper dir.
|
||||
*
|
||||
* Return 0 for upper file handle, > 0 for lower file handle or < 0 on error.
|
||||
*/
|
||||
static int ovl_check_encode_origin(struct dentry *dentry)
|
||||
{
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||
bool decodable = ofs->config.nfs_export;
|
||||
|
||||
/* Lower file handle for non-upper non-decodable */
|
||||
if (!ovl_dentry_upper(dentry) && !decodable)
|
||||
return 0;
|
||||
|
||||
/* Upper file handle for pure upper */
|
||||
if (!ovl_dentry_lower(dentry))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Upper file handle for non-indexed upper.
|
||||
*
|
||||
* Root is never indexed, so if there's an upper layer, encode upper for
|
||||
* root.
|
||||
*/
|
||||
if (ovl_dentry_upper(dentry) &&
|
||||
if (dentry == dentry->d_sb->s_root)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Upper decodable file handle for non-indexed upper.
|
||||
*/
|
||||
if (ovl_dentry_upper(dentry) && decodable &&
|
||||
!ovl_test_flag(OVL_INDEX, d_inode(dentry)))
|
||||
return 0;
|
||||
|
||||
@ -205,7 +214,7 @@ static int ovl_check_encode_origin(struct dentry *dentry)
|
||||
* ovl_connect_layer() will try to make origin's layer "connected" by
|
||||
* copying up a "connectable" ancestor.
|
||||
*/
|
||||
if (d_is_dir(dentry) && ovl_upper_mnt(ofs))
|
||||
if (d_is_dir(dentry) && ovl_upper_mnt(ofs) && decodable)
|
||||
return ovl_connect_layer(dentry);
|
||||
|
||||
/* Lower file handle for indexed and non-upper dir/non-dir */
|
||||
@ -435,7 +444,7 @@ static struct dentry *ovl_lookup_real_inode(struct super_block *sb,
|
||||
struct dentry *real,
|
||||
const struct ovl_layer *layer)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
struct dentry *index = NULL;
|
||||
struct dentry *this = NULL;
|
||||
struct inode *inode;
|
||||
@ -656,7 +665,7 @@ static struct dentry *ovl_get_dentry(struct super_block *sb,
|
||||
struct ovl_path *lowerpath,
|
||||
struct dentry *index)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
const struct ovl_layer *layer = upper ? &ofs->layers[0] : lowerpath->layer;
|
||||
struct dentry *real = upper ?: (index ?: lowerpath->dentry);
|
||||
|
||||
@ -681,7 +690,7 @@ static struct dentry *ovl_get_dentry(struct super_block *sb,
|
||||
static struct dentry *ovl_upper_fh_to_d(struct super_block *sb,
|
||||
struct ovl_fh *fh)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
struct dentry *dentry;
|
||||
struct dentry *upper;
|
||||
|
||||
@ -701,7 +710,7 @@ static struct dentry *ovl_upper_fh_to_d(struct super_block *sb,
|
||||
static struct dentry *ovl_lower_fh_to_d(struct super_block *sb,
|
||||
struct ovl_fh *fh)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
struct ovl_path origin = { };
|
||||
struct ovl_path *stack = &origin;
|
||||
struct dentry *dentry = NULL;
|
||||
@ -876,3 +885,8 @@ const struct export_operations ovl_export_operations = {
|
||||
.get_name = ovl_get_name,
|
||||
.get_parent = ovl_get_parent,
|
||||
};
|
||||
|
||||
/* encode_fh() encodes non-decodable file handles with nfs_export=off */
|
||||
const struct export_operations ovl_export_fid_operations = {
|
||||
.encode_fh = ovl_encode_fh,
|
||||
};
|
||||
|
@ -115,8 +115,8 @@ static int ovl_real_fdget_meta(const struct file *file, struct fd *real,
|
||||
if (allow_meta) {
|
||||
ovl_path_real(dentry, &realpath);
|
||||
} else {
|
||||
/* lazy lookup of lowerdata */
|
||||
err = ovl_maybe_lookup_lowerdata(dentry);
|
||||
/* lazy lookup and verify of lowerdata */
|
||||
err = ovl_verify_lowerdata(dentry);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@ -159,8 +159,8 @@ static int ovl_open(struct inode *inode, struct file *file)
|
||||
struct path realpath;
|
||||
int err;
|
||||
|
||||
/* lazy lookup of lowerdata */
|
||||
err = ovl_maybe_lookup_lowerdata(dentry);
|
||||
/* lazy lookup and verify lowerdata */
|
||||
err = ovl_verify_lowerdata(dentry);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
@ -341,7 +341,7 @@ static const char *ovl_get_link(struct dentry *dentry,
|
||||
|
||||
bool ovl_is_private_xattr(struct super_block *sb, const char *name)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
|
||||
if (ofs->config.userxattr)
|
||||
return strncmp(name, OVL_XATTR_USER_PREFIX,
|
||||
@ -696,7 +696,7 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
|
||||
int ovl_update_time(struct inode *inode, int flags)
|
||||
{
|
||||
if (flags & S_ATIME) {
|
||||
struct ovl_fs *ofs = inode->i_sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(inode->i_sb);
|
||||
struct path upperpath = {
|
||||
.mnt = ovl_upper_mnt(ofs),
|
||||
.dentry = ovl_upperdentry_dereference(OVL_I(inode)),
|
||||
@ -1291,7 +1291,7 @@ struct inode *ovl_get_trap_inode(struct super_block *sb, struct dentry *dir)
|
||||
static bool ovl_hash_bylower(struct super_block *sb, struct dentry *upper,
|
||||
struct dentry *lower, bool index)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
|
||||
/* No, if pure upper */
|
||||
if (!lower)
|
||||
@ -1311,7 +1311,7 @@ static bool ovl_hash_bylower(struct super_block *sb, struct dentry *upper,
|
||||
return false;
|
||||
|
||||
/* No, if non-indexed upper with NFS export */
|
||||
if (sb->s_export_op && upper)
|
||||
if (ofs->config.nfs_export && upper)
|
||||
return false;
|
||||
|
||||
/* Otherwise, hash by lower inode for fsnotify */
|
||||
|
@ -25,7 +25,7 @@ struct ovl_lookup_data {
|
||||
bool stop;
|
||||
bool last;
|
||||
char *redirect;
|
||||
bool metacopy;
|
||||
int metacopy;
|
||||
/* Referring to last redirect xattr */
|
||||
bool absolute_redirect;
|
||||
};
|
||||
@ -171,8 +171,9 @@ struct dentry *ovl_decode_real_fh(struct ovl_fs *ofs, struct ovl_fh *fh,
|
||||
* layer where file handle will be decoded.
|
||||
* In case of uuid=off option just make sure that stored uuid is null.
|
||||
*/
|
||||
if (ofs->config.uuid ? !uuid_equal(&fh->fb.uuid, &mnt->mnt_sb->s_uuid) :
|
||||
!uuid_is_null(&fh->fb.uuid))
|
||||
if (ovl_origin_uuid(ofs) ?
|
||||
!uuid_equal(&fh->fb.uuid, &mnt->mnt_sb->s_uuid) :
|
||||
!uuid_is_null(&fh->fb.uuid))
|
||||
return NULL;
|
||||
|
||||
bytes = (fh->fb.len - offsetof(struct ovl_fb, fid));
|
||||
@ -270,7 +271,7 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
|
||||
d->stop = true;
|
||||
goto put_and_out;
|
||||
}
|
||||
err = ovl_check_metacopy_xattr(OVL_FS(d->sb), &path);
|
||||
err = ovl_check_metacopy_xattr(OVL_FS(d->sb), &path, NULL);
|
||||
if (err < 0)
|
||||
goto out_err;
|
||||
|
||||
@ -889,8 +890,58 @@ static int ovl_fix_origin(struct ovl_fs *ofs, struct dentry *dentry,
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ovl_maybe_validate_verity(struct dentry *dentry)
|
||||
{
|
||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct path datapath, metapath;
|
||||
int err;
|
||||
|
||||
if (!ofs->config.verity_mode ||
|
||||
!ovl_is_metacopy_dentry(dentry) ||
|
||||
ovl_test_flag(OVL_VERIFIED_DIGEST, inode))
|
||||
return 0;
|
||||
|
||||
if (!ovl_test_flag(OVL_HAS_DIGEST, inode)) {
|
||||
if (ofs->config.verity_mode == OVL_VERITY_REQUIRE) {
|
||||
pr_warn_ratelimited("metacopy file '%pd' has no digest specified\n",
|
||||
dentry);
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ovl_path_lowerdata(dentry, &datapath);
|
||||
if (!datapath.dentry)
|
||||
return -EIO;
|
||||
|
||||
ovl_path_real(dentry, &metapath);
|
||||
if (!metapath.dentry)
|
||||
return -EIO;
|
||||
|
||||
err = ovl_inode_lock_interruptible(inode);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!ovl_test_flag(OVL_VERIFIED_DIGEST, inode)) {
|
||||
const struct cred *old_cred;
|
||||
|
||||
old_cred = ovl_override_creds(dentry->d_sb);
|
||||
|
||||
err = ovl_validate_verity(ofs, &metapath, &datapath);
|
||||
if (err == 0)
|
||||
ovl_set_flag(OVL_VERIFIED_DIGEST, inode);
|
||||
|
||||
revert_creds(old_cred);
|
||||
}
|
||||
|
||||
ovl_inode_unlock(inode);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Lazy lookup of lowerdata */
|
||||
int ovl_maybe_lookup_lowerdata(struct dentry *dentry)
|
||||
static int ovl_maybe_lookup_lowerdata(struct dentry *dentry)
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
const char *redirect = ovl_lowerdata_redirect(inode);
|
||||
@ -935,12 +986,23 @@ out_err:
|
||||
goto out;
|
||||
}
|
||||
|
||||
int ovl_verify_lowerdata(struct dentry *dentry)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = ovl_maybe_lookup_lowerdata(dentry);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return ovl_maybe_validate_verity(dentry);
|
||||
}
|
||||
|
||||
struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
unsigned int flags)
|
||||
{
|
||||
struct ovl_entry *oe = NULL;
|
||||
const struct cred *old_cred;
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||
struct ovl_entry *poe = OVL_E(dentry->d_parent);
|
||||
struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root);
|
||||
struct ovl_path *stack = NULL, *origin_path = NULL;
|
||||
@ -955,6 +1017,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
unsigned int i;
|
||||
int err;
|
||||
bool uppermetacopy = false;
|
||||
int metacopy_size = 0;
|
||||
struct ovl_lookup_data d = {
|
||||
.sb = dentry->d_sb,
|
||||
.name = dentry->d_name,
|
||||
@ -963,7 +1026,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
.stop = false,
|
||||
.last = ovl_redirect_follow(ofs) ? false : !ovl_numlower(poe),
|
||||
.redirect = NULL,
|
||||
.metacopy = false,
|
||||
.metacopy = 0,
|
||||
};
|
||||
|
||||
if (dentry->d_name.len > ofs->namelen)
|
||||
@ -999,6 +1062,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
|
||||
if (d.metacopy)
|
||||
uppermetacopy = true;
|
||||
metacopy_size = d.metacopy;
|
||||
}
|
||||
|
||||
if (d.redirect) {
|
||||
@ -1076,6 +1140,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
origin = this;
|
||||
}
|
||||
|
||||
if (!upperdentry && !d.is_dir && !ctr && d.metacopy)
|
||||
metacopy_size = d.metacopy;
|
||||
|
||||
if (d.metacopy && ctr) {
|
||||
/*
|
||||
* Do not store intermediate metacopy dentries in
|
||||
@ -1120,7 +1187,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
|
||||
/* Defer lookup of lowerdata in data-only layers to first access */
|
||||
if (d.metacopy && ctr && ofs->numdatalayer && d.absolute_redirect) {
|
||||
d.metacopy = false;
|
||||
d.metacopy = 0;
|
||||
ctr++;
|
||||
}
|
||||
|
||||
@ -1211,10 +1278,11 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
upperredirect = NULL;
|
||||
goto out_free_oe;
|
||||
}
|
||||
err = ovl_check_metacopy_xattr(ofs, &upperpath);
|
||||
err = ovl_check_metacopy_xattr(ofs, &upperpath, NULL);
|
||||
if (err < 0)
|
||||
goto out_free_oe;
|
||||
uppermetacopy = err;
|
||||
metacopy_size = err;
|
||||
}
|
||||
|
||||
if (upperdentry || ctr) {
|
||||
@ -1236,6 +1304,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
goto out_free_oe;
|
||||
if (upperdentry && !uppermetacopy)
|
||||
ovl_set_flag(OVL_UPPERDATA, inode);
|
||||
|
||||
if (metacopy_size > OVL_METACOPY_MIN_SIZE)
|
||||
ovl_set_flag(OVL_HAS_DIGEST, inode);
|
||||
}
|
||||
|
||||
ovl_dentry_init_reval(dentry, upperdentry, OVL_I_E(inode));
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/uuid.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fsverity.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/posix_acl.h>
|
||||
#include <linux/posix_acl_xattr.h>
|
||||
@ -36,6 +37,7 @@ enum ovl_xattr {
|
||||
OVL_XATTR_IMPURE,
|
||||
OVL_XATTR_NLINK,
|
||||
OVL_XATTR_UPPER,
|
||||
OVL_XATTR_UUID,
|
||||
OVL_XATTR_METACOPY,
|
||||
OVL_XATTR_PROTATTR,
|
||||
};
|
||||
@ -49,6 +51,8 @@ enum ovl_inode_flag {
|
||||
OVL_UPPERDATA,
|
||||
/* Inode number will remain constant over copy up. */
|
||||
OVL_CONST_INO,
|
||||
OVL_HAS_DIGEST,
|
||||
OVL_VERIFIED_DIGEST,
|
||||
};
|
||||
|
||||
enum ovl_entry_flag {
|
||||
@ -64,12 +68,25 @@ enum {
|
||||
OVL_REDIRECT_ON,
|
||||
};
|
||||
|
||||
enum {
|
||||
OVL_UUID_OFF,
|
||||
OVL_UUID_NULL,
|
||||
OVL_UUID_AUTO,
|
||||
OVL_UUID_ON,
|
||||
};
|
||||
|
||||
enum {
|
||||
OVL_XINO_OFF,
|
||||
OVL_XINO_AUTO,
|
||||
OVL_XINO_ON,
|
||||
};
|
||||
|
||||
enum {
|
||||
OVL_VERITY_OFF,
|
||||
OVL_VERITY_ON,
|
||||
OVL_VERITY_REQUIRE,
|
||||
};
|
||||
|
||||
/*
|
||||
* The tuple (fh,uuid) is a universal unique identifier for a copy up origin,
|
||||
* where:
|
||||
@ -126,6 +143,26 @@ struct ovl_fh {
|
||||
#define OVL_FH_FID_OFFSET (OVL_FH_WIRE_OFFSET + \
|
||||
offsetof(struct ovl_fb, fid))
|
||||
|
||||
/* On-disk format for "metacopy" xattr (if non-zero size) */
|
||||
struct ovl_metacopy {
|
||||
u8 version; /* 0 */
|
||||
u8 len; /* size of this header + used digest bytes */
|
||||
u8 flags;
|
||||
u8 digest_algo; /* FS_VERITY_HASH_ALG_* constant, 0 for no digest */
|
||||
u8 digest[FS_VERITY_MAX_DIGEST_SIZE]; /* Only the used part on disk */
|
||||
} __packed;
|
||||
|
||||
#define OVL_METACOPY_MAX_SIZE (sizeof(struct ovl_metacopy))
|
||||
#define OVL_METACOPY_MIN_SIZE (OVL_METACOPY_MAX_SIZE - FS_VERITY_MAX_DIGEST_SIZE)
|
||||
#define OVL_METACOPY_INIT { 0, OVL_METACOPY_MIN_SIZE }
|
||||
|
||||
static inline int ovl_metadata_digest_size(const struct ovl_metacopy *metacopy)
|
||||
{
|
||||
if (metacopy->len < OVL_METACOPY_MIN_SIZE)
|
||||
return 0;
|
||||
return (int)metacopy->len - OVL_METACOPY_MIN_SIZE;
|
||||
}
|
||||
|
||||
extern const char *const ovl_xattr_table[][2];
|
||||
static inline const char *ovl_xattr(struct ovl_fs *ofs, enum ovl_xattr ox)
|
||||
{
|
||||
@ -430,6 +467,8 @@ bool ovl_already_copied_up(struct dentry *dentry, int flags);
|
||||
bool ovl_path_check_dir_xattr(struct ovl_fs *ofs, const struct path *path,
|
||||
enum ovl_xattr ox);
|
||||
bool ovl_path_check_origin_xattr(struct ovl_fs *ofs, const struct path *path);
|
||||
bool ovl_init_uuid_xattr(struct super_block *sb, struct ovl_fs *ofs,
|
||||
const struct path *upperpath);
|
||||
|
||||
static inline bool ovl_check_origin_xattr(struct ovl_fs *ofs,
|
||||
struct dentry *upperdentry)
|
||||
@ -452,9 +491,20 @@ bool ovl_need_index(struct dentry *dentry);
|
||||
int ovl_nlink_start(struct dentry *dentry);
|
||||
void ovl_nlink_end(struct dentry *dentry);
|
||||
int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir);
|
||||
int ovl_check_metacopy_xattr(struct ovl_fs *ofs, const struct path *path);
|
||||
int ovl_check_metacopy_xattr(struct ovl_fs *ofs, const struct path *path,
|
||||
struct ovl_metacopy *data);
|
||||
int ovl_set_metacopy_xattr(struct ovl_fs *ofs, struct dentry *d,
|
||||
struct ovl_metacopy *metacopy);
|
||||
bool ovl_is_metacopy_dentry(struct dentry *dentry);
|
||||
char *ovl_get_redirect_xattr(struct ovl_fs *ofs, const struct path *path, int padding);
|
||||
int ovl_ensure_verity_loaded(struct path *path);
|
||||
int ovl_get_verity_xattr(struct ovl_fs *ofs, const struct path *path,
|
||||
u8 *digest_buf, int *buf_length);
|
||||
int ovl_validate_verity(struct ovl_fs *ofs,
|
||||
struct path *metapath,
|
||||
struct path *datapath);
|
||||
int ovl_get_verity_digest(struct ovl_fs *ofs, struct path *src,
|
||||
struct ovl_metacopy *metacopy);
|
||||
int ovl_sync_status(struct ovl_fs *ofs);
|
||||
|
||||
static inline void ovl_set_flag(unsigned long flag, struct inode *inode)
|
||||
@ -494,6 +544,17 @@ static inline bool ovl_redirect_dir(struct ovl_fs *ofs)
|
||||
return ofs->config.redirect_mode == OVL_REDIRECT_ON;
|
||||
}
|
||||
|
||||
static inline bool ovl_origin_uuid(struct ovl_fs *ofs)
|
||||
{
|
||||
return ofs->config.uuid != OVL_UUID_OFF;
|
||||
}
|
||||
|
||||
static inline bool ovl_has_fsid(struct ovl_fs *ofs)
|
||||
{
|
||||
return ofs->config.uuid == OVL_UUID_ON ||
|
||||
ofs->config.uuid == OVL_UUID_AUTO;
|
||||
}
|
||||
|
||||
/*
|
||||
* With xino=auto, we do best effort to keep all inodes on same st_dev and
|
||||
* d_ino consistent with st_ino.
|
||||
@ -574,7 +635,7 @@ struct dentry *ovl_get_index_fh(struct ovl_fs *ofs, struct ovl_fh *fh);
|
||||
struct dentry *ovl_lookup_index(struct ovl_fs *ofs, struct dentry *upper,
|
||||
struct dentry *origin, bool verify);
|
||||
int ovl_path_next(int idx, struct dentry *dentry, struct path *path);
|
||||
int ovl_maybe_lookup_lowerdata(struct dentry *dentry);
|
||||
int ovl_verify_lowerdata(struct dentry *dentry);
|
||||
struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||||
unsigned int flags);
|
||||
bool ovl_lower_positive(struct dentry *dentry);
|
||||
@ -759,6 +820,7 @@ int ovl_set_origin(struct ovl_fs *ofs, struct dentry *lower,
|
||||
|
||||
/* export.c */
|
||||
extern const struct export_operations ovl_export_operations;
|
||||
extern const struct export_operations ovl_export_fid_operations;
|
||||
|
||||
/* super.c */
|
||||
int ovl_fill_super(struct super_block *sb, struct fs_context *fc);
|
||||
|
@ -10,8 +10,9 @@ struct ovl_config {
|
||||
char *workdir;
|
||||
bool default_permissions;
|
||||
int redirect_mode;
|
||||
int verity_mode;
|
||||
bool index;
|
||||
bool uuid;
|
||||
int uuid;
|
||||
bool nfs_export;
|
||||
int xino;
|
||||
bool metacopy;
|
||||
@ -81,6 +82,7 @@ struct ovl_fs {
|
||||
const struct cred *creator_cred;
|
||||
bool tmpfile;
|
||||
bool noxattr;
|
||||
bool nofh;
|
||||
/* Did we take the inuse lock? */
|
||||
bool upperdir_locked;
|
||||
bool workdir_locked;
|
||||
@ -115,8 +117,13 @@ static inline struct mnt_idmap *ovl_upper_mnt_idmap(struct ovl_fs *ofs)
|
||||
return mnt_idmap(ovl_upper_mnt(ofs));
|
||||
}
|
||||
|
||||
extern struct file_system_type ovl_fs_type;
|
||||
|
||||
static inline struct ovl_fs *OVL_FS(struct super_block *sb)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_OVERLAY_FS_DEBUG))
|
||||
WARN_ON_ONCE(sb->s_type != &ovl_fs_type);
|
||||
|
||||
return (struct ovl_fs *)sb->s_fs_info;
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ enum {
|
||||
Opt_userxattr,
|
||||
Opt_xino,
|
||||
Opt_metacopy,
|
||||
Opt_verity,
|
||||
Opt_volatile,
|
||||
};
|
||||
|
||||
@ -64,6 +65,24 @@ static const struct constant_table ovl_parameter_bool[] = {
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct constant_table ovl_parameter_uuid[] = {
|
||||
{ "off", OVL_UUID_OFF },
|
||||
{ "null", OVL_UUID_NULL },
|
||||
{ "auto", OVL_UUID_AUTO },
|
||||
{ "on", OVL_UUID_ON },
|
||||
{}
|
||||
};
|
||||
|
||||
static const char *ovl_uuid_mode(struct ovl_config *config)
|
||||
{
|
||||
return ovl_parameter_uuid[config->uuid].name;
|
||||
}
|
||||
|
||||
static int ovl_uuid_def(void)
|
||||
{
|
||||
return OVL_UUID_AUTO;
|
||||
}
|
||||
|
||||
static const struct constant_table ovl_parameter_xino[] = {
|
||||
{ "off", OVL_XINO_OFF },
|
||||
{ "auto", OVL_XINO_AUTO },
|
||||
@ -101,6 +120,23 @@ static int ovl_redirect_mode_def(void)
|
||||
OVL_REDIRECT_NOFOLLOW;
|
||||
}
|
||||
|
||||
static const struct constant_table ovl_parameter_verity[] = {
|
||||
{ "off", OVL_VERITY_OFF },
|
||||
{ "on", OVL_VERITY_ON },
|
||||
{ "require", OVL_VERITY_REQUIRE },
|
||||
{}
|
||||
};
|
||||
|
||||
static const char *ovl_verity_mode(struct ovl_config *config)
|
||||
{
|
||||
return ovl_parameter_verity[config->verity_mode].name;
|
||||
}
|
||||
|
||||
static int ovl_verity_mode_def(void)
|
||||
{
|
||||
return OVL_VERITY_OFF;
|
||||
}
|
||||
|
||||
#define fsparam_string_empty(NAME, OPT) \
|
||||
__fsparam(fs_param_is_string, NAME, OPT, fs_param_can_be_empty, NULL)
|
||||
|
||||
@ -111,11 +147,12 @@ const struct fs_parameter_spec ovl_parameter_spec[] = {
|
||||
fsparam_flag("default_permissions", Opt_default_permissions),
|
||||
fsparam_enum("redirect_dir", Opt_redirect_dir, ovl_parameter_redirect_dir),
|
||||
fsparam_enum("index", Opt_index, ovl_parameter_bool),
|
||||
fsparam_enum("uuid", Opt_uuid, ovl_parameter_bool),
|
||||
fsparam_enum("uuid", Opt_uuid, ovl_parameter_uuid),
|
||||
fsparam_enum("nfs_export", Opt_nfs_export, ovl_parameter_bool),
|
||||
fsparam_flag("userxattr", Opt_userxattr),
|
||||
fsparam_enum("xino", Opt_xino, ovl_parameter_xino),
|
||||
fsparam_enum("metacopy", Opt_metacopy, ovl_parameter_bool),
|
||||
fsparam_enum("verity", Opt_verity, ovl_parameter_verity),
|
||||
fsparam_flag("volatile", Opt_volatile),
|
||||
{}
|
||||
};
|
||||
@ -572,6 +609,9 @@ static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
config->metacopy = result.uint_32;
|
||||
ctx->set.metacopy = true;
|
||||
break;
|
||||
case Opt_verity:
|
||||
config->verity_mode = result.uint_32;
|
||||
break;
|
||||
case Opt_volatile:
|
||||
config->ovl_volatile = true;
|
||||
break;
|
||||
@ -622,7 +662,7 @@ static void ovl_free(struct fs_context *fc)
|
||||
static int ovl_reconfigure(struct fs_context *fc)
|
||||
{
|
||||
struct super_block *sb = fc->root->d_sb;
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
struct super_block *upper_sb;
|
||||
int ret = 0;
|
||||
|
||||
@ -679,7 +719,7 @@ int ovl_init_fs_context(struct fs_context *fc)
|
||||
|
||||
ofs->config.redirect_mode = ovl_redirect_mode_def();
|
||||
ofs->config.index = ovl_index_def;
|
||||
ofs->config.uuid = true;
|
||||
ofs->config.uuid = ovl_uuid_def();
|
||||
ofs->config.nfs_export = ovl_nfs_export_def;
|
||||
ofs->config.xino = ovl_xino_def();
|
||||
ofs->config.metacopy = ovl_metacopy_def;
|
||||
@ -762,6 +802,23 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
|
||||
config->ovl_volatile = false;
|
||||
}
|
||||
|
||||
if (!config->upperdir && config->uuid == OVL_UUID_ON) {
|
||||
pr_info("option \"uuid=on\" requires an upper fs, falling back to uuid=null.\n");
|
||||
config->uuid = OVL_UUID_NULL;
|
||||
}
|
||||
|
||||
/* Resolve verity -> metacopy dependency */
|
||||
if (config->verity_mode && !config->metacopy) {
|
||||
/* Don't allow explicit specified conflicting combinations */
|
||||
if (set.metacopy) {
|
||||
pr_err("conflicting options: metacopy=off,verity=%s\n",
|
||||
ovl_verity_mode(config));
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Otherwise automatically enable metacopy. */
|
||||
config->metacopy = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is to make the logic below simpler. It doesn't make any other
|
||||
* difference, since redirect_dir=on is only used for upper.
|
||||
@ -769,13 +826,18 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
|
||||
if (!config->upperdir && config->redirect_mode == OVL_REDIRECT_FOLLOW)
|
||||
config->redirect_mode = OVL_REDIRECT_ON;
|
||||
|
||||
/* Resolve metacopy -> redirect_dir dependency */
|
||||
/* Resolve verity -> metacopy -> redirect_dir dependency */
|
||||
if (config->metacopy && config->redirect_mode != OVL_REDIRECT_ON) {
|
||||
if (set.metacopy && set.redirect) {
|
||||
pr_err("conflicting options: metacopy=on,redirect_dir=%s\n",
|
||||
ovl_redirect_mode(config));
|
||||
return -EINVAL;
|
||||
}
|
||||
if (config->verity_mode && set.redirect) {
|
||||
pr_err("conflicting options: verity=%s,redirect_dir=%s\n",
|
||||
ovl_verity_mode(config), ovl_redirect_mode(config));
|
||||
return -EINVAL;
|
||||
}
|
||||
if (set.redirect) {
|
||||
/*
|
||||
* There was an explicit redirect_dir=... that resulted
|
||||
@ -812,7 +874,7 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
|
||||
}
|
||||
}
|
||||
|
||||
/* Resolve nfs_export -> !metacopy dependency */
|
||||
/* Resolve nfs_export -> !metacopy && !verity dependency */
|
||||
if (config->nfs_export && config->metacopy) {
|
||||
if (set.nfs_export && set.metacopy) {
|
||||
pr_err("conflicting options: nfs_export=on,metacopy=on\n");
|
||||
@ -825,6 +887,14 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
|
||||
*/
|
||||
pr_info("disabling nfs_export due to metacopy=on\n");
|
||||
config->nfs_export = false;
|
||||
} else if (config->verity_mode) {
|
||||
/*
|
||||
* There was an explicit verity=.. that resulted
|
||||
* in this conflict.
|
||||
*/
|
||||
pr_info("disabling nfs_export due to verity=%s\n",
|
||||
ovl_verity_mode(config));
|
||||
config->nfs_export = false;
|
||||
} else {
|
||||
/*
|
||||
* There was an explicit nfs_export=on that resulted
|
||||
@ -836,7 +906,7 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
|
||||
}
|
||||
|
||||
|
||||
/* Resolve userxattr -> !redirect && !metacopy dependency */
|
||||
/* Resolve userxattr -> !redirect && !metacopy && !verity dependency */
|
||||
if (config->userxattr) {
|
||||
if (set.redirect &&
|
||||
config->redirect_mode != OVL_REDIRECT_NOFOLLOW) {
|
||||
@ -848,6 +918,11 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
|
||||
pr_err("conflicting options: userxattr,metacopy=on\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (config->verity_mode) {
|
||||
pr_err("conflicting options: userxattr,verity=%s\n",
|
||||
ovl_verity_mode(config));
|
||||
return -EINVAL;
|
||||
}
|
||||
/*
|
||||
* Silently disable default setting of redirect and metacopy.
|
||||
* This shall be the default in the future as well: these
|
||||
@ -872,7 +947,7 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
|
||||
int ovl_show_options(struct seq_file *m, struct dentry *dentry)
|
||||
{
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
size_t nr, nr_merged_lower = ofs->numlayer - ofs->numdatalayer;
|
||||
const struct ovl_layer *data_layers = &ofs->layers[nr_merged_lower];
|
||||
|
||||
@ -895,8 +970,8 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
|
||||
ovl_redirect_mode(&ofs->config));
|
||||
if (ofs->config.index != ovl_index_def)
|
||||
seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off");
|
||||
if (!ofs->config.uuid)
|
||||
seq_puts(m, ",uuid=off");
|
||||
if (ofs->config.uuid != ovl_uuid_def())
|
||||
seq_printf(m, ",uuid=%s", ovl_uuid_mode(&ofs->config));
|
||||
if (ofs->config.nfs_export != ovl_nfs_export_def)
|
||||
seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ?
|
||||
"on" : "off");
|
||||
@ -909,5 +984,8 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
|
||||
seq_puts(m, ",volatile");
|
||||
if (ofs->config.userxattr)
|
||||
seq_puts(m, ",userxattr");
|
||||
if (ofs->config.verity_mode != ovl_verity_mode_def())
|
||||
seq_printf(m, ",verity=%s",
|
||||
ovl_verity_mode(&ofs->config));
|
||||
return 0;
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ static struct dentry *ovl_d_real(struct dentry *dentry,
|
||||
const struct inode *inode)
|
||||
{
|
||||
struct dentry *real = NULL, *lower;
|
||||
int err;
|
||||
|
||||
/* It's an overlay file */
|
||||
if (inode && d_inode(dentry) == inode)
|
||||
@ -58,7 +59,9 @@ static struct dentry *ovl_d_real(struct dentry *dentry,
|
||||
* uprobes on offset within the file, so lowerdata should be available
|
||||
* when setting the uprobe.
|
||||
*/
|
||||
ovl_maybe_lookup_lowerdata(dentry);
|
||||
err = ovl_verify_lowerdata(dentry);
|
||||
if (err)
|
||||
goto bug;
|
||||
lower = ovl_dentry_lowerdata(dentry);
|
||||
if (!lower)
|
||||
goto bug;
|
||||
@ -182,7 +185,7 @@ static void ovl_destroy_inode(struct inode *inode)
|
||||
|
||||
static void ovl_put_super(struct super_block *sb)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
|
||||
if (ofs)
|
||||
ovl_free_fs(ofs);
|
||||
@ -191,7 +194,7 @@ static void ovl_put_super(struct super_block *sb)
|
||||
/* Sync real dirty inodes in upper filesystem (if it exists) */
|
||||
static int ovl_sync_fs(struct super_block *sb, int wait)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
struct super_block *upper_sb;
|
||||
int ret;
|
||||
|
||||
@ -239,8 +242,9 @@ static int ovl_sync_fs(struct super_block *sb, int wait)
|
||||
*/
|
||||
static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf)
|
||||
{
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
struct dentry *root_dentry = dentry->d_sb->s_root;
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
struct dentry *root_dentry = sb->s_root;
|
||||
struct path path;
|
||||
int err;
|
||||
|
||||
@ -250,6 +254,8 @@ static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf)
|
||||
if (!err) {
|
||||
buf->f_namelen = ofs->namelen;
|
||||
buf->f_type = OVERLAYFS_SUPER_MAGIC;
|
||||
if (ovl_has_fsid(ofs))
|
||||
buf->f_fsid = uuid_to_fsid(sb->s_uuid.b);
|
||||
}
|
||||
|
||||
return err;
|
||||
@ -397,6 +403,7 @@ static int ovl_lower_dir(const char *name, struct path *path,
|
||||
pr_warn("fs on '%s' does not support file handles, falling back to index=off,nfs_export=off.\n",
|
||||
name);
|
||||
}
|
||||
ofs->nofh |= !fh_type;
|
||||
/*
|
||||
* Decoding origin file handle is required for persistent st_ino.
|
||||
* Without persistent st_ino, xino=auto falls back to xino=off.
|
||||
@ -770,6 +777,10 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
|
||||
ofs->config.index = false;
|
||||
pr_warn("...falling back to index=off.\n");
|
||||
}
|
||||
if (ovl_has_fsid(ofs)) {
|
||||
ofs->config.uuid = OVL_UUID_NULL;
|
||||
pr_warn("...falling back to uuid=null.\n");
|
||||
}
|
||||
/*
|
||||
* xattr support is required for persistent st_ino.
|
||||
* Without persistent st_ino, xino=auto falls back to xino=off.
|
||||
@ -815,6 +826,7 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
|
||||
ofs->config.index = false;
|
||||
pr_warn("upper fs does not support file handles, falling back to index=off.\n");
|
||||
}
|
||||
ofs->nofh |= !fh_type;
|
||||
|
||||
/* Check if upper fs has 32bit inode numbers */
|
||||
if (fh_type != FILEID_INO32_GEN)
|
||||
@ -1416,9 +1428,12 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
if (!ovl_upper_mnt(ofs))
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
|
||||
if (!ofs->config.uuid && ofs->numfs > 1) {
|
||||
pr_warn("The uuid=off requires a single fs for lower and upper, falling back to uuid=on.\n");
|
||||
ofs->config.uuid = true;
|
||||
if (!ovl_origin_uuid(ofs) && ofs->numfs > 1) {
|
||||
pr_warn("The uuid=off requires a single fs for lower and upper, falling back to uuid=null.\n");
|
||||
ofs->config.uuid = OVL_UUID_NULL;
|
||||
} else if (ovl_has_fsid(ofs) && ovl_upper_mnt(ofs)) {
|
||||
/* Use per instance persistent uuid/fsid */
|
||||
ovl_init_uuid_xattr(sb, ofs, &ctx->upper);
|
||||
}
|
||||
|
||||
if (!ovl_force_readonly(ofs) && ofs->config.index) {
|
||||
@ -1449,8 +1464,15 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
ofs->config.nfs_export = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Support encoding decodable file handles with nfs_export=on
|
||||
* and encoding non-decodable file handles with nfs_export=off
|
||||
* if all layers support file handles.
|
||||
*/
|
||||
if (ofs->config.nfs_export)
|
||||
sb->s_export_op = &ovl_export_operations;
|
||||
else if (!ofs->nofh)
|
||||
sb->s_export_op = &ovl_export_fid_operations;
|
||||
|
||||
/* Never override disk quota limits or use reserved space */
|
||||
cap_lower(cred->cap_effective, CAP_SYS_RESOURCE);
|
||||
@ -1479,7 +1501,7 @@ out_err:
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct file_system_type ovl_fs_type = {
|
||||
struct file_system_type ovl_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "overlay",
|
||||
.init_fs_context = ovl_init_fs_context,
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <linux/cred.h>
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/exportfs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fileattr.h>
|
||||
#include <linux/uuid.h>
|
||||
#include <linux/namei.h>
|
||||
@ -18,25 +19,25 @@
|
||||
|
||||
int ovl_want_write(struct dentry *dentry)
|
||||
{
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||
return mnt_want_write(ovl_upper_mnt(ofs));
|
||||
}
|
||||
|
||||
void ovl_drop_write(struct dentry *dentry)
|
||||
{
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||
mnt_drop_write(ovl_upper_mnt(ofs));
|
||||
}
|
||||
|
||||
struct dentry *ovl_workdir(struct dentry *dentry)
|
||||
{
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||
return ofs->workdir;
|
||||
}
|
||||
|
||||
const struct cred *ovl_override_creds(struct super_block *sb)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
|
||||
return override_creds(ofs->creator_cred);
|
||||
}
|
||||
@ -62,7 +63,7 @@ int ovl_can_decode_fh(struct super_block *sb)
|
||||
|
||||
struct dentry *ovl_indexdir(struct super_block *sb)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
|
||||
return ofs->indexdir;
|
||||
}
|
||||
@ -70,7 +71,7 @@ struct dentry *ovl_indexdir(struct super_block *sb)
|
||||
/* Index all files on copy up. For now only enabled for NFS export */
|
||||
bool ovl_index_all(struct super_block *sb)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
|
||||
return ofs->config.nfs_export && ofs->config.index;
|
||||
}
|
||||
@ -78,7 +79,7 @@ bool ovl_index_all(struct super_block *sb)
|
||||
/* Verify lower origin on lookup. For now only enabled for NFS export */
|
||||
bool ovl_verify_lower(struct super_block *sb)
|
||||
{
|
||||
struct ovl_fs *ofs = sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(sb);
|
||||
|
||||
return ofs->config.nfs_export && ofs->config.index;
|
||||
}
|
||||
@ -203,7 +204,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry)
|
||||
|
||||
void ovl_path_upper(struct dentry *dentry, struct path *path)
|
||||
{
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
|
||||
|
||||
path->mnt = ovl_upper_mnt(ofs);
|
||||
path->dentry = ovl_dentry_upper(dentry);
|
||||
@ -675,6 +676,65 @@ bool ovl_path_check_origin_xattr(struct ovl_fs *ofs, const struct path *path)
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load persistent uuid from xattr into s_uuid if found, or store a new
|
||||
* random generated value in s_uuid and in xattr.
|
||||
*/
|
||||
bool ovl_init_uuid_xattr(struct super_block *sb, struct ovl_fs *ofs,
|
||||
const struct path *upperpath)
|
||||
{
|
||||
bool set = false;
|
||||
int res;
|
||||
|
||||
/* Try to load existing persistent uuid */
|
||||
res = ovl_path_getxattr(ofs, upperpath, OVL_XATTR_UUID, sb->s_uuid.b,
|
||||
UUID_SIZE);
|
||||
if (res == UUID_SIZE)
|
||||
return true;
|
||||
|
||||
if (res != -ENODATA)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* With uuid=auto, if uuid xattr is found, it will be used.
|
||||
* If uuid xattrs is not found, generate a persistent uuid only on mount
|
||||
* of new overlays where upper root dir is not yet marked as impure.
|
||||
* An upper dir is marked as impure on copy up or lookup of its subdirs.
|
||||
*/
|
||||
if (ofs->config.uuid == OVL_UUID_AUTO) {
|
||||
res = ovl_path_getxattr(ofs, upperpath, OVL_XATTR_IMPURE, NULL,
|
||||
0);
|
||||
if (res > 0) {
|
||||
/* Any mount of old overlay - downgrade to uuid=null */
|
||||
ofs->config.uuid = OVL_UUID_NULL;
|
||||
return true;
|
||||
} else if (res == -ENODATA) {
|
||||
/* First mount of new overlay - upgrade to uuid=on */
|
||||
ofs->config.uuid = OVL_UUID_ON;
|
||||
} else if (res < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Generate overlay instance uuid */
|
||||
uuid_gen(&sb->s_uuid);
|
||||
|
||||
/* Try to store persistent uuid */
|
||||
set = true;
|
||||
res = ovl_setxattr(ofs, upperpath->dentry, OVL_XATTR_UUID, sb->s_uuid.b,
|
||||
UUID_SIZE);
|
||||
if (res == 0)
|
||||
return true;
|
||||
|
||||
fail:
|
||||
memset(sb->s_uuid.b, 0, UUID_SIZE);
|
||||
ofs->config.uuid = OVL_UUID_NULL;
|
||||
pr_warn("failed to %s uuid (%pd2, err=%i); falling back to uuid=null.\n",
|
||||
set ? "set" : "get", upperpath->dentry, res);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ovl_path_check_dir_xattr(struct ovl_fs *ofs, const struct path *path,
|
||||
enum ovl_xattr ox)
|
||||
{
|
||||
@ -697,6 +757,7 @@ bool ovl_path_check_dir_xattr(struct ovl_fs *ofs, const struct path *path,
|
||||
#define OVL_XATTR_IMPURE_POSTFIX "impure"
|
||||
#define OVL_XATTR_NLINK_POSTFIX "nlink"
|
||||
#define OVL_XATTR_UPPER_POSTFIX "upper"
|
||||
#define OVL_XATTR_UUID_POSTFIX "uuid"
|
||||
#define OVL_XATTR_METACOPY_POSTFIX "metacopy"
|
||||
#define OVL_XATTR_PROTATTR_POSTFIX "protattr"
|
||||
|
||||
@ -711,6 +772,7 @@ const char *const ovl_xattr_table[][2] = {
|
||||
OVL_XATTR_TAB_ENTRY(OVL_XATTR_IMPURE),
|
||||
OVL_XATTR_TAB_ENTRY(OVL_XATTR_NLINK),
|
||||
OVL_XATTR_TAB_ENTRY(OVL_XATTR_UPPER),
|
||||
OVL_XATTR_TAB_ENTRY(OVL_XATTR_UUID),
|
||||
OVL_XATTR_TAB_ENTRY(OVL_XATTR_METACOPY),
|
||||
OVL_XATTR_TAB_ENTRY(OVL_XATTR_PROTATTR),
|
||||
};
|
||||
@ -1054,8 +1116,12 @@ err:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* err < 0, 0 if no metacopy xattr, 1 if metacopy xattr found */
|
||||
int ovl_check_metacopy_xattr(struct ovl_fs *ofs, const struct path *path)
|
||||
/*
|
||||
* err < 0, 0 if no metacopy xattr, metacopy data size if xattr found.
|
||||
* an empty xattr returns OVL_METACOPY_MIN_SIZE to distinguish from no xattr value.
|
||||
*/
|
||||
int ovl_check_metacopy_xattr(struct ovl_fs *ofs, const struct path *path,
|
||||
struct ovl_metacopy *data)
|
||||
{
|
||||
int res;
|
||||
|
||||
@ -1063,7 +1129,8 @@ int ovl_check_metacopy_xattr(struct ovl_fs *ofs, const struct path *path)
|
||||
if (!S_ISREG(d_inode(path->dentry)->i_mode))
|
||||
return 0;
|
||||
|
||||
res = ovl_path_getxattr(ofs, path, OVL_XATTR_METACOPY, NULL, 0);
|
||||
res = ovl_path_getxattr(ofs, path, OVL_XATTR_METACOPY,
|
||||
data, data ? OVL_METACOPY_MAX_SIZE : 0);
|
||||
if (res < 0) {
|
||||
if (res == -ENODATA || res == -EOPNOTSUPP)
|
||||
return 0;
|
||||
@ -1077,12 +1144,48 @@ int ovl_check_metacopy_xattr(struct ovl_fs *ofs, const struct path *path)
|
||||
goto out;
|
||||
}
|
||||
|
||||
return 1;
|
||||
if (res == 0) {
|
||||
/* Emulate empty data for zero size metacopy xattr */
|
||||
res = OVL_METACOPY_MIN_SIZE;
|
||||
if (data) {
|
||||
memset(data, 0, res);
|
||||
data->len = res;
|
||||
}
|
||||
} else if (res < OVL_METACOPY_MIN_SIZE) {
|
||||
pr_warn_ratelimited("metacopy file '%pd' has too small xattr\n",
|
||||
path->dentry);
|
||||
return -EIO;
|
||||
} else if (data) {
|
||||
if (data->version != 0) {
|
||||
pr_warn_ratelimited("metacopy file '%pd' has unsupported version\n",
|
||||
path->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
if (res != data->len) {
|
||||
pr_warn_ratelimited("metacopy file '%pd' has invalid xattr size\n",
|
||||
path->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
out:
|
||||
pr_warn_ratelimited("failed to get metacopy (%i)\n", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int ovl_set_metacopy_xattr(struct ovl_fs *ofs, struct dentry *d, struct ovl_metacopy *metacopy)
|
||||
{
|
||||
size_t len = metacopy->len;
|
||||
|
||||
/* If no flags or digest fall back to empty metacopy file */
|
||||
if (metacopy->version == 0 && metacopy->flags == 0 && metacopy->digest_algo == 0)
|
||||
len = 0;
|
||||
|
||||
return ovl_check_setxattr(ofs, d, OVL_XATTR_METACOPY,
|
||||
metacopy, len, -EOPNOTSUPP);
|
||||
}
|
||||
|
||||
bool ovl_is_metacopy_dentry(struct dentry *dentry)
|
||||
{
|
||||
struct ovl_entry *oe = OVL_E(dentry);
|
||||
@ -1145,6 +1248,112 @@ err_free:
|
||||
return ERR_PTR(res);
|
||||
}
|
||||
|
||||
/* Call with mounter creds as it may open the file */
|
||||
int ovl_ensure_verity_loaded(struct path *datapath)
|
||||
{
|
||||
struct inode *inode = d_inode(datapath->dentry);
|
||||
struct file *filp;
|
||||
|
||||
if (!fsverity_active(inode) && IS_VERITY(inode)) {
|
||||
/*
|
||||
* If this inode was not yet opened, the verity info hasn't been
|
||||
* loaded yet, so we need to do that here to force it into memory.
|
||||
*/
|
||||
filp = kernel_file_open(datapath, O_RDONLY, inode, current_cred());
|
||||
if (IS_ERR(filp))
|
||||
return PTR_ERR(filp);
|
||||
fput(filp);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ovl_validate_verity(struct ovl_fs *ofs,
|
||||
struct path *metapath,
|
||||
struct path *datapath)
|
||||
{
|
||||
struct ovl_metacopy metacopy_data;
|
||||
u8 actual_digest[FS_VERITY_MAX_DIGEST_SIZE];
|
||||
int xattr_digest_size, digest_size;
|
||||
int xattr_size, err;
|
||||
u8 verity_algo;
|
||||
|
||||
if (!ofs->config.verity_mode ||
|
||||
/* Verity only works on regular files */
|
||||
!S_ISREG(d_inode(metapath->dentry)->i_mode))
|
||||
return 0;
|
||||
|
||||
xattr_size = ovl_check_metacopy_xattr(ofs, metapath, &metacopy_data);
|
||||
if (xattr_size < 0)
|
||||
return xattr_size;
|
||||
|
||||
if (!xattr_size || !metacopy_data.digest_algo) {
|
||||
if (ofs->config.verity_mode == OVL_VERITY_REQUIRE) {
|
||||
pr_warn_ratelimited("metacopy file '%pd' has no digest specified\n",
|
||||
metapath->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
xattr_digest_size = ovl_metadata_digest_size(&metacopy_data);
|
||||
|
||||
err = ovl_ensure_verity_loaded(datapath);
|
||||
if (err < 0) {
|
||||
pr_warn_ratelimited("lower file '%pd' failed to load fs-verity info\n",
|
||||
datapath->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
digest_size = fsverity_get_digest(d_inode(datapath->dentry), actual_digest,
|
||||
&verity_algo, NULL);
|
||||
if (digest_size == 0) {
|
||||
pr_warn_ratelimited("lower file '%pd' has no fs-verity digest\n", datapath->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (xattr_digest_size != digest_size ||
|
||||
metacopy_data.digest_algo != verity_algo ||
|
||||
memcmp(metacopy_data.digest, actual_digest, xattr_digest_size) != 0) {
|
||||
pr_warn_ratelimited("lower file '%pd' has the wrong fs-verity digest\n",
|
||||
datapath->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ovl_get_verity_digest(struct ovl_fs *ofs, struct path *src,
|
||||
struct ovl_metacopy *metacopy)
|
||||
{
|
||||
int err, digest_size;
|
||||
|
||||
if (!ofs->config.verity_mode || !S_ISREG(d_inode(src->dentry)->i_mode))
|
||||
return 0;
|
||||
|
||||
err = ovl_ensure_verity_loaded(src);
|
||||
if (err < 0) {
|
||||
pr_warn_ratelimited("lower file '%pd' failed to load fs-verity info\n",
|
||||
src->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
digest_size = fsverity_get_digest(d_inode(src->dentry),
|
||||
metacopy->digest, &metacopy->digest_algo, NULL);
|
||||
if (digest_size == 0 ||
|
||||
WARN_ON_ONCE(digest_size > FS_VERITY_MAX_DIGEST_SIZE)) {
|
||||
if (ofs->config.verity_mode == OVL_VERITY_REQUIRE) {
|
||||
pr_warn_ratelimited("lower file '%pd' has no fs-verity digest\n",
|
||||
src->dentry);
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
metacopy->len += digest_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* ovl_sync_status() - Check fs sync status for volatile mounts
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user