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:
Linus Torvalds 2023-08-30 11:54:09 -07:00
commit 63580f669d
13 changed files with 654 additions and 68 deletions

View File

@ -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 FS_IOC_GETFLAGS and FS_IOC_MEASURE_VERITY because it doesn't require
opening the file, and opening verity files can be expensive. opening the file, and opening verity files can be expensive.
.. _accessing_verity_files:
Accessing verity files Accessing verity files
====================== ======================

View File

@ -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. 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 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 is changed. This is only applicable if all lower/upper/work directories are on
the same filesystem, otherwise it will fallback to normal behaviour. 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 Volatile mount
-------------- --------------

View File

@ -124,3 +124,12 @@ config OVERLAY_FS_METACOPY
that doesn't support this feature will have unexpected results. that doesn't support this feature will have unexpected results.
If unsure, say N. 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.

View File

@ -416,7 +416,7 @@ struct ovl_fh *ovl_encode_real_fh(struct ovl_fs *ofs, struct dentry *real,
if (is_upper) if (is_upper)
fh->fb.flags |= OVL_FH_FLAG_PATH_UPPER; fh->fb.flags |= OVL_FH_FLAG_PATH_UPPER;
fh->fb.len = sizeof(fh->fb) + buflen; fh->fb.len = sizeof(fh->fb) + buflen;
if (ofs->config.uuid) if (ovl_origin_uuid(ofs))
fh->fb.uuid = *uuid; fh->fb.uuid = *uuid;
return fh; return fh;
@ -544,6 +544,7 @@ struct ovl_copy_up_ctx {
bool origin; bool origin;
bool indexed; bool indexed;
bool metacopy; bool metacopy;
bool metacopy_digest;
}; };
static int ovl_link_up(struct ovl_copy_up_ctx *c) 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) { if (c->metacopy) {
err = ovl_check_setxattr(ofs, temp, OVL_XATTR_METACOPY, struct path lowerdatapath;
NULL, 0, -EOPNOTSUPP); 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) if (err)
return err; return err;
} }
@ -751,9 +764,15 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
if (err) if (err)
goto cleanup; goto cleanup;
if (!c->metacopy)
ovl_set_upperdata(d_inode(c->dentry));
inode = 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); ovl_inode_update(inode, temp);
if (S_ISDIR(inode->i_mode)) if (S_ISDIR(inode->i_mode))
ovl_set_flag(OVL_WHITEOUTS, inode); ovl_set_flag(OVL_WHITEOUTS, inode);
@ -813,6 +832,12 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
if (err) if (err)
goto out_fput; 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) if (!c->metacopy)
ovl_set_upperdata(d_inode(c->dentry)); ovl_set_upperdata(d_inode(c->dentry));
ovl_inode_update(d_inode(c->dentry), dget(temp)); 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, static bool ovl_need_meta_copy_up(struct dentry *dentry, umode_t mode,
int flags) 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) if (!ofs->config.metacopy)
return false; 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))) if (flags && ((OPEN_FMODE(flags) & FMODE_WRITE) || (flags & O_TRUNC)))
return false; 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; return true;
} }
@ -984,6 +1022,8 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c)
if (err) if (err)
goto out_free; 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)); ovl_set_upperdata(d_inode(c->dentry));
out_free: out_free:
kfree(capability); 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 * 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(). * 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) if (err)
return err; return err;

View File

@ -174,28 +174,37 @@ static int ovl_connect_layer(struct dentry *dentry)
* U = upper file handle * U = upper file handle
* L = lower 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. * 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 * 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. * Return 0 for upper file handle, > 0 for lower file handle or < 0 on error.
*/ */
static int ovl_check_encode_origin(struct dentry *dentry) 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 */ /* Upper file handle for pure upper */
if (!ovl_dentry_lower(dentry)) if (!ovl_dentry_lower(dentry))
return 0; return 0;
/* /*
* Upper file handle for non-indexed upper.
*
* Root is never indexed, so if there's an upper layer, encode upper for * Root is never indexed, so if there's an upper layer, encode upper for
* root. * 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))) !ovl_test_flag(OVL_INDEX, d_inode(dentry)))
return 0; 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 * ovl_connect_layer() will try to make origin's layer "connected" by
* copying up a "connectable" ancestor. * 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); return ovl_connect_layer(dentry);
/* Lower file handle for indexed and non-upper dir/non-dir */ /* 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, struct dentry *real,
const struct ovl_layer *layer) 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 *index = NULL;
struct dentry *this = NULL; struct dentry *this = NULL;
struct inode *inode; struct inode *inode;
@ -656,7 +665,7 @@ static struct dentry *ovl_get_dentry(struct super_block *sb,
struct ovl_path *lowerpath, struct ovl_path *lowerpath,
struct dentry *index) 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; const struct ovl_layer *layer = upper ? &ofs->layers[0] : lowerpath->layer;
struct dentry *real = upper ?: (index ?: lowerpath->dentry); 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, static struct dentry *ovl_upper_fh_to_d(struct super_block *sb,
struct ovl_fh *fh) struct ovl_fh *fh)
{ {
struct ovl_fs *ofs = sb->s_fs_info; struct ovl_fs *ofs = OVL_FS(sb);
struct dentry *dentry; struct dentry *dentry;
struct dentry *upper; 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, static struct dentry *ovl_lower_fh_to_d(struct super_block *sb,
struct ovl_fh *fh) 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 origin = { };
struct ovl_path *stack = &origin; struct ovl_path *stack = &origin;
struct dentry *dentry = NULL; struct dentry *dentry = NULL;
@ -876,3 +885,8 @@ const struct export_operations ovl_export_operations = {
.get_name = ovl_get_name, .get_name = ovl_get_name,
.get_parent = ovl_get_parent, .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,
};

View File

@ -115,8 +115,8 @@ static int ovl_real_fdget_meta(const struct file *file, struct fd *real,
if (allow_meta) { if (allow_meta) {
ovl_path_real(dentry, &realpath); ovl_path_real(dentry, &realpath);
} else { } else {
/* lazy lookup of lowerdata */ /* lazy lookup and verify of lowerdata */
err = ovl_maybe_lookup_lowerdata(dentry); err = ovl_verify_lowerdata(dentry);
if (err) if (err)
return err; return err;
@ -159,8 +159,8 @@ static int ovl_open(struct inode *inode, struct file *file)
struct path realpath; struct path realpath;
int err; int err;
/* lazy lookup of lowerdata */ /* lazy lookup and verify lowerdata */
err = ovl_maybe_lookup_lowerdata(dentry); err = ovl_verify_lowerdata(dentry);
if (err) if (err)
return err; return err;

View File

@ -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) 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) if (ofs->config.userxattr)
return strncmp(name, OVL_XATTR_USER_PREFIX, 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) int ovl_update_time(struct inode *inode, int flags)
{ {
if (flags & S_ATIME) { 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 = { struct path upperpath = {
.mnt = ovl_upper_mnt(ofs), .mnt = ovl_upper_mnt(ofs),
.dentry = ovl_upperdentry_dereference(OVL_I(inode)), .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, static bool ovl_hash_bylower(struct super_block *sb, struct dentry *upper,
struct dentry *lower, bool index) struct dentry *lower, bool index)
{ {
struct ovl_fs *ofs = sb->s_fs_info; struct ovl_fs *ofs = OVL_FS(sb);
/* No, if pure upper */ /* No, if pure upper */
if (!lower) if (!lower)
@ -1311,7 +1311,7 @@ static bool ovl_hash_bylower(struct super_block *sb, struct dentry *upper,
return false; return false;
/* No, if non-indexed upper with NFS export */ /* No, if non-indexed upper with NFS export */
if (sb->s_export_op && upper) if (ofs->config.nfs_export && upper)
return false; return false;
/* Otherwise, hash by lower inode for fsnotify */ /* Otherwise, hash by lower inode for fsnotify */

View File

@ -25,7 +25,7 @@ struct ovl_lookup_data {
bool stop; bool stop;
bool last; bool last;
char *redirect; char *redirect;
bool metacopy; int metacopy;
/* Referring to last redirect xattr */ /* Referring to last redirect xattr */
bool absolute_redirect; 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. * layer where file handle will be decoded.
* In case of uuid=off option just make sure that stored uuid is null. * 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) : if (ovl_origin_uuid(ofs) ?
!uuid_is_null(&fh->fb.uuid)) !uuid_equal(&fh->fb.uuid, &mnt->mnt_sb->s_uuid) :
!uuid_is_null(&fh->fb.uuid))
return NULL; return NULL;
bytes = (fh->fb.len - offsetof(struct ovl_fb, fid)); 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; d->stop = true;
goto put_and_out; 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) if (err < 0)
goto out_err; goto out_err;
@ -889,8 +890,58 @@ static int ovl_fix_origin(struct ovl_fs *ofs, struct dentry *dentry,
return err; 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 */ /* 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); struct inode *inode = d_inode(dentry);
const char *redirect = ovl_lowerdata_redirect(inode); const char *redirect = ovl_lowerdata_redirect(inode);
@ -935,12 +986,23 @@ out_err:
goto out; 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, struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags) unsigned int flags)
{ {
struct ovl_entry *oe = NULL; struct ovl_entry *oe = NULL;
const struct cred *old_cred; 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 *poe = OVL_E(dentry->d_parent);
struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root); struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root);
struct ovl_path *stack = NULL, *origin_path = NULL; 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; unsigned int i;
int err; int err;
bool uppermetacopy = false; bool uppermetacopy = false;
int metacopy_size = 0;
struct ovl_lookup_data d = { struct ovl_lookup_data d = {
.sb = dentry->d_sb, .sb = dentry->d_sb,
.name = dentry->d_name, .name = dentry->d_name,
@ -963,7 +1026,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
.stop = false, .stop = false,
.last = ovl_redirect_follow(ofs) ? false : !ovl_numlower(poe), .last = ovl_redirect_follow(ofs) ? false : !ovl_numlower(poe),
.redirect = NULL, .redirect = NULL,
.metacopy = false, .metacopy = 0,
}; };
if (dentry->d_name.len > ofs->namelen) if (dentry->d_name.len > ofs->namelen)
@ -999,6 +1062,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
if (d.metacopy) if (d.metacopy)
uppermetacopy = true; uppermetacopy = true;
metacopy_size = d.metacopy;
} }
if (d.redirect) { if (d.redirect) {
@ -1076,6 +1140,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
origin = this; origin = this;
} }
if (!upperdentry && !d.is_dir && !ctr && d.metacopy)
metacopy_size = d.metacopy;
if (d.metacopy && ctr) { if (d.metacopy && ctr) {
/* /*
* Do not store intermediate metacopy dentries in * 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 */ /* Defer lookup of lowerdata in data-only layers to first access */
if (d.metacopy && ctr && ofs->numdatalayer && d.absolute_redirect) { if (d.metacopy && ctr && ofs->numdatalayer && d.absolute_redirect) {
d.metacopy = false; d.metacopy = 0;
ctr++; ctr++;
} }
@ -1211,10 +1278,11 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
upperredirect = NULL; upperredirect = NULL;
goto out_free_oe; goto out_free_oe;
} }
err = ovl_check_metacopy_xattr(ofs, &upperpath); err = ovl_check_metacopy_xattr(ofs, &upperpath, NULL);
if (err < 0) if (err < 0)
goto out_free_oe; goto out_free_oe;
uppermetacopy = err; uppermetacopy = err;
metacopy_size = err;
} }
if (upperdentry || ctr) { if (upperdentry || ctr) {
@ -1236,6 +1304,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
goto out_free_oe; goto out_free_oe;
if (upperdentry && !uppermetacopy) if (upperdentry && !uppermetacopy)
ovl_set_flag(OVL_UPPERDATA, inode); 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)); ovl_dentry_init_reval(dentry, upperdentry, OVL_I_E(inode));

View File

@ -7,6 +7,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/uuid.h> #include <linux/uuid.h>
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/fsverity.h>
#include <linux/namei.h> #include <linux/namei.h>
#include <linux/posix_acl.h> #include <linux/posix_acl.h>
#include <linux/posix_acl_xattr.h> #include <linux/posix_acl_xattr.h>
@ -36,6 +37,7 @@ enum ovl_xattr {
OVL_XATTR_IMPURE, OVL_XATTR_IMPURE,
OVL_XATTR_NLINK, OVL_XATTR_NLINK,
OVL_XATTR_UPPER, OVL_XATTR_UPPER,
OVL_XATTR_UUID,
OVL_XATTR_METACOPY, OVL_XATTR_METACOPY,
OVL_XATTR_PROTATTR, OVL_XATTR_PROTATTR,
}; };
@ -49,6 +51,8 @@ enum ovl_inode_flag {
OVL_UPPERDATA, OVL_UPPERDATA,
/* Inode number will remain constant over copy up. */ /* Inode number will remain constant over copy up. */
OVL_CONST_INO, OVL_CONST_INO,
OVL_HAS_DIGEST,
OVL_VERIFIED_DIGEST,
}; };
enum ovl_entry_flag { enum ovl_entry_flag {
@ -64,12 +68,25 @@ enum {
OVL_REDIRECT_ON, OVL_REDIRECT_ON,
}; };
enum {
OVL_UUID_OFF,
OVL_UUID_NULL,
OVL_UUID_AUTO,
OVL_UUID_ON,
};
enum { enum {
OVL_XINO_OFF, OVL_XINO_OFF,
OVL_XINO_AUTO, OVL_XINO_AUTO,
OVL_XINO_ON, 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, * The tuple (fh,uuid) is a universal unique identifier for a copy up origin,
* where: * where:
@ -126,6 +143,26 @@ struct ovl_fh {
#define OVL_FH_FID_OFFSET (OVL_FH_WIRE_OFFSET + \ #define OVL_FH_FID_OFFSET (OVL_FH_WIRE_OFFSET + \
offsetof(struct ovl_fb, fid)) 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]; extern const char *const ovl_xattr_table[][2];
static inline const char *ovl_xattr(struct ovl_fs *ofs, enum ovl_xattr ox) 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, bool ovl_path_check_dir_xattr(struct ovl_fs *ofs, const struct path *path,
enum ovl_xattr ox); enum ovl_xattr ox);
bool ovl_path_check_origin_xattr(struct ovl_fs *ofs, const struct path *path); 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, static inline bool ovl_check_origin_xattr(struct ovl_fs *ofs,
struct dentry *upperdentry) struct dentry *upperdentry)
@ -452,9 +491,20 @@ bool ovl_need_index(struct dentry *dentry);
int ovl_nlink_start(struct dentry *dentry); int ovl_nlink_start(struct dentry *dentry);
void ovl_nlink_end(struct dentry *dentry); void ovl_nlink_end(struct dentry *dentry);
int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir); 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); bool ovl_is_metacopy_dentry(struct dentry *dentry);
char *ovl_get_redirect_xattr(struct ovl_fs *ofs, const struct path *path, int padding); 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); int ovl_sync_status(struct ovl_fs *ofs);
static inline void ovl_set_flag(unsigned long flag, struct inode *inode) 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; 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 * With xino=auto, we do best effort to keep all inodes on same st_dev and
* d_ino consistent with st_ino. * 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 *ovl_lookup_index(struct ovl_fs *ofs, struct dentry *upper,
struct dentry *origin, bool verify); struct dentry *origin, bool verify);
int ovl_path_next(int idx, struct dentry *dentry, struct path *path); 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, struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags); unsigned int flags);
bool ovl_lower_positive(struct dentry *dentry); bool ovl_lower_positive(struct dentry *dentry);
@ -759,6 +820,7 @@ int ovl_set_origin(struct ovl_fs *ofs, struct dentry *lower,
/* export.c */ /* export.c */
extern const struct export_operations ovl_export_operations; extern const struct export_operations ovl_export_operations;
extern const struct export_operations ovl_export_fid_operations;
/* super.c */ /* super.c */
int ovl_fill_super(struct super_block *sb, struct fs_context *fc); int ovl_fill_super(struct super_block *sb, struct fs_context *fc);

View File

@ -10,8 +10,9 @@ struct ovl_config {
char *workdir; char *workdir;
bool default_permissions; bool default_permissions;
int redirect_mode; int redirect_mode;
int verity_mode;
bool index; bool index;
bool uuid; int uuid;
bool nfs_export; bool nfs_export;
int xino; int xino;
bool metacopy; bool metacopy;
@ -81,6 +82,7 @@ struct ovl_fs {
const struct cred *creator_cred; const struct cred *creator_cred;
bool tmpfile; bool tmpfile;
bool noxattr; bool noxattr;
bool nofh;
/* Did we take the inuse lock? */ /* Did we take the inuse lock? */
bool upperdir_locked; bool upperdir_locked;
bool workdir_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)); 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) 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; return (struct ovl_fs *)sb->s_fs_info;
} }

View File

@ -55,6 +55,7 @@ enum {
Opt_userxattr, Opt_userxattr,
Opt_xino, Opt_xino,
Opt_metacopy, Opt_metacopy,
Opt_verity,
Opt_volatile, 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[] = { static const struct constant_table ovl_parameter_xino[] = {
{ "off", OVL_XINO_OFF }, { "off", OVL_XINO_OFF },
{ "auto", OVL_XINO_AUTO }, { "auto", OVL_XINO_AUTO },
@ -101,6 +120,23 @@ static int ovl_redirect_mode_def(void)
OVL_REDIRECT_NOFOLLOW; 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) \ #define fsparam_string_empty(NAME, OPT) \
__fsparam(fs_param_is_string, NAME, OPT, fs_param_can_be_empty, NULL) __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_flag("default_permissions", Opt_default_permissions),
fsparam_enum("redirect_dir", Opt_redirect_dir, ovl_parameter_redirect_dir), fsparam_enum("redirect_dir", Opt_redirect_dir, ovl_parameter_redirect_dir),
fsparam_enum("index", Opt_index, ovl_parameter_bool), 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_enum("nfs_export", Opt_nfs_export, ovl_parameter_bool),
fsparam_flag("userxattr", Opt_userxattr), fsparam_flag("userxattr", Opt_userxattr),
fsparam_enum("xino", Opt_xino, ovl_parameter_xino), fsparam_enum("xino", Opt_xino, ovl_parameter_xino),
fsparam_enum("metacopy", Opt_metacopy, ovl_parameter_bool), fsparam_enum("metacopy", Opt_metacopy, ovl_parameter_bool),
fsparam_enum("verity", Opt_verity, ovl_parameter_verity),
fsparam_flag("volatile", Opt_volatile), 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; config->metacopy = result.uint_32;
ctx->set.metacopy = true; ctx->set.metacopy = true;
break; break;
case Opt_verity:
config->verity_mode = result.uint_32;
break;
case Opt_volatile: case Opt_volatile:
config->ovl_volatile = true; config->ovl_volatile = true;
break; break;
@ -622,7 +662,7 @@ static void ovl_free(struct fs_context *fc)
static int ovl_reconfigure(struct fs_context *fc) static int ovl_reconfigure(struct fs_context *fc)
{ {
struct super_block *sb = fc->root->d_sb; 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; struct super_block *upper_sb;
int ret = 0; 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.redirect_mode = ovl_redirect_mode_def();
ofs->config.index = ovl_index_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.nfs_export = ovl_nfs_export_def;
ofs->config.xino = ovl_xino_def(); ofs->config.xino = ovl_xino_def();
ofs->config.metacopy = ovl_metacopy_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; 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 * This is to make the logic below simpler. It doesn't make any other
* difference, since redirect_dir=on is only used for upper. * 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) if (!config->upperdir && config->redirect_mode == OVL_REDIRECT_FOLLOW)
config->redirect_mode = OVL_REDIRECT_ON; 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 (config->metacopy && config->redirect_mode != OVL_REDIRECT_ON) {
if (set.metacopy && set.redirect) { if (set.metacopy && set.redirect) {
pr_err("conflicting options: metacopy=on,redirect_dir=%s\n", pr_err("conflicting options: metacopy=on,redirect_dir=%s\n",
ovl_redirect_mode(config)); ovl_redirect_mode(config));
return -EINVAL; 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) { if (set.redirect) {
/* /*
* There was an explicit redirect_dir=... that resulted * 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 (config->nfs_export && config->metacopy) {
if (set.nfs_export && set.metacopy) { if (set.nfs_export && set.metacopy) {
pr_err("conflicting options: nfs_export=on,metacopy=on\n"); 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"); pr_info("disabling nfs_export due to metacopy=on\n");
config->nfs_export = false; 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 { } else {
/* /*
* There was an explicit nfs_export=on that resulted * 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 (config->userxattr) {
if (set.redirect && if (set.redirect &&
config->redirect_mode != OVL_REDIRECT_NOFOLLOW) { 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"); pr_err("conflicting options: userxattr,metacopy=on\n");
return -EINVAL; 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. * Silently disable default setting of redirect and metacopy.
* This shall be the default in the future as well: these * 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) int ovl_show_options(struct seq_file *m, struct dentry *dentry)
{ {
struct super_block *sb = dentry->d_sb; 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; size_t nr, nr_merged_lower = ofs->numlayer - ofs->numdatalayer;
const struct ovl_layer *data_layers = &ofs->layers[nr_merged_lower]; 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)); ovl_redirect_mode(&ofs->config));
if (ofs->config.index != ovl_index_def) if (ofs->config.index != ovl_index_def)
seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off"); seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off");
if (!ofs->config.uuid) if (ofs->config.uuid != ovl_uuid_def())
seq_puts(m, ",uuid=off"); seq_printf(m, ",uuid=%s", ovl_uuid_mode(&ofs->config));
if (ofs->config.nfs_export != ovl_nfs_export_def) if (ofs->config.nfs_export != ovl_nfs_export_def)
seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ? seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ?
"on" : "off"); "on" : "off");
@ -909,5 +984,8 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
seq_puts(m, ",volatile"); seq_puts(m, ",volatile");
if (ofs->config.userxattr) if (ofs->config.userxattr)
seq_puts(m, ",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; return 0;
} }

View File

@ -32,6 +32,7 @@ static struct dentry *ovl_d_real(struct dentry *dentry,
const struct inode *inode) const struct inode *inode)
{ {
struct dentry *real = NULL, *lower; struct dentry *real = NULL, *lower;
int err;
/* It's an overlay file */ /* It's an overlay file */
if (inode && d_inode(dentry) == inode) 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 * uprobes on offset within the file, so lowerdata should be available
* when setting the uprobe. * when setting the uprobe.
*/ */
ovl_maybe_lookup_lowerdata(dentry); err = ovl_verify_lowerdata(dentry);
if (err)
goto bug;
lower = ovl_dentry_lowerdata(dentry); lower = ovl_dentry_lowerdata(dentry);
if (!lower) if (!lower)
goto bug; goto bug;
@ -182,7 +185,7 @@ static void ovl_destroy_inode(struct inode *inode)
static void ovl_put_super(struct super_block *sb) 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) if (ofs)
ovl_free_fs(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) */ /* 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 = OVL_FS(sb);
struct super_block *upper_sb; struct super_block *upper_sb;
int ret; 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) static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf)
{ {
struct ovl_fs *ofs = dentry->d_sb->s_fs_info; struct super_block *sb = dentry->d_sb;
struct dentry *root_dentry = dentry->d_sb->s_root; struct ovl_fs *ofs = OVL_FS(sb);
struct dentry *root_dentry = sb->s_root;
struct path path; struct path path;
int err; int err;
@ -250,6 +254,8 @@ static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf)
if (!err) { if (!err) {
buf->f_namelen = ofs->namelen; buf->f_namelen = ofs->namelen;
buf->f_type = OVERLAYFS_SUPER_MAGIC; buf->f_type = OVERLAYFS_SUPER_MAGIC;
if (ovl_has_fsid(ofs))
buf->f_fsid = uuid_to_fsid(sb->s_uuid.b);
} }
return err; 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", pr_warn("fs on '%s' does not support file handles, falling back to index=off,nfs_export=off.\n",
name); name);
} }
ofs->nofh |= !fh_type;
/* /*
* Decoding origin file handle is required for persistent st_ino. * Decoding origin file handle is required for persistent st_ino.
* Without persistent st_ino, xino=auto falls back to xino=off. * 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; ofs->config.index = false;
pr_warn("...falling back to index=off.\n"); 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. * xattr support is required for persistent st_ino.
* Without persistent st_ino, xino=auto falls back to xino=off. * 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; ofs->config.index = false;
pr_warn("upper fs does not support file handles, falling back to index=off.\n"); 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 */ /* Check if upper fs has 32bit inode numbers */
if (fh_type != FILEID_INO32_GEN) 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)) if (!ovl_upper_mnt(ofs))
sb->s_flags |= SB_RDONLY; sb->s_flags |= SB_RDONLY;
if (!ofs->config.uuid && ofs->numfs > 1) { 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=on.\n"); pr_warn("The uuid=off requires a single fs for lower and upper, falling back to uuid=null.\n");
ofs->config.uuid = true; 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) { 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; 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) if (ofs->config.nfs_export)
sb->s_export_op = &ovl_export_operations; 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 */ /* Never override disk quota limits or use reserved space */
cap_lower(cred->cap_effective, CAP_SYS_RESOURCE); cap_lower(cred->cap_effective, CAP_SYS_RESOURCE);
@ -1479,7 +1501,7 @@ out_err:
return err; return err;
} }
static struct file_system_type ovl_fs_type = { struct file_system_type ovl_fs_type = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.name = "overlay", .name = "overlay",
.init_fs_context = ovl_init_fs_context, .init_fs_context = ovl_init_fs_context,

View File

@ -10,6 +10,7 @@
#include <linux/cred.h> #include <linux/cred.h>
#include <linux/xattr.h> #include <linux/xattr.h>
#include <linux/exportfs.h> #include <linux/exportfs.h>
#include <linux/file.h>
#include <linux/fileattr.h> #include <linux/fileattr.h>
#include <linux/uuid.h> #include <linux/uuid.h>
#include <linux/namei.h> #include <linux/namei.h>
@ -18,25 +19,25 @@
int ovl_want_write(struct dentry *dentry) 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)); return mnt_want_write(ovl_upper_mnt(ofs));
} }
void ovl_drop_write(struct dentry *dentry) 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)); mnt_drop_write(ovl_upper_mnt(ofs));
} }
struct dentry *ovl_workdir(struct dentry *dentry) 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; return ofs->workdir;
} }
const struct cred *ovl_override_creds(struct super_block *sb) 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); 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 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; 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 */ /* Index all files on copy up. For now only enabled for NFS export */
bool ovl_index_all(struct super_block *sb) 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; 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 */ /* Verify lower origin on lookup. For now only enabled for NFS export */
bool ovl_verify_lower(struct super_block *sb) 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; 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) 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->mnt = ovl_upper_mnt(ofs);
path->dentry = ovl_dentry_upper(dentry); 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; 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, bool ovl_path_check_dir_xattr(struct ovl_fs *ofs, const struct path *path,
enum ovl_xattr ox) 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_IMPURE_POSTFIX "impure"
#define OVL_XATTR_NLINK_POSTFIX "nlink" #define OVL_XATTR_NLINK_POSTFIX "nlink"
#define OVL_XATTR_UPPER_POSTFIX "upper" #define OVL_XATTR_UPPER_POSTFIX "upper"
#define OVL_XATTR_UUID_POSTFIX "uuid"
#define OVL_XATTR_METACOPY_POSTFIX "metacopy" #define OVL_XATTR_METACOPY_POSTFIX "metacopy"
#define OVL_XATTR_PROTATTR_POSTFIX "protattr" #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_IMPURE),
OVL_XATTR_TAB_ENTRY(OVL_XATTR_NLINK), OVL_XATTR_TAB_ENTRY(OVL_XATTR_NLINK),
OVL_XATTR_TAB_ENTRY(OVL_XATTR_UPPER), 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_METACOPY),
OVL_XATTR_TAB_ENTRY(OVL_XATTR_PROTATTR), OVL_XATTR_TAB_ENTRY(OVL_XATTR_PROTATTR),
}; };
@ -1054,8 +1116,12 @@ err:
return -EIO; 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; 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)) if (!S_ISREG(d_inode(path->dentry)->i_mode))
return 0; 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 < 0) {
if (res == -ENODATA || res == -EOPNOTSUPP) if (res == -ENODATA || res == -EOPNOTSUPP)
return 0; return 0;
@ -1077,12 +1144,48 @@ int ovl_check_metacopy_xattr(struct ovl_fs *ofs, const struct path *path)
goto out; 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: out:
pr_warn_ratelimited("failed to get metacopy (%i)\n", res); pr_warn_ratelimited("failed to get metacopy (%i)\n", res);
return 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) bool ovl_is_metacopy_dentry(struct dentry *dentry)
{ {
struct ovl_entry *oe = OVL_E(dentry); struct ovl_entry *oe = OVL_E(dentry);
@ -1145,6 +1248,112 @@ err_free:
return ERR_PTR(res); 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 * ovl_sync_status() - Check fs sync status for volatile mounts
* *