ovl: Validate verity xattr when resolving lowerdata
authorAlexander Larsson <alexl@redhat.com>
Wed, 21 Jun 2023 08:44:27 +0000 (10:44 +0200)
committerAmir Goldstein <amir73il@gmail.com>
Sat, 12 Aug 2023 16:02:38 +0000 (19:02 +0300)
The new digest field in the metacopy xattr is used during lookup to
record whether the header contained a digest in the OVL_HAS_DIGEST
flags.

When accessing file data the first time, if OVL_HAS_DIGEST is set, we
reload the metadata and check that the source lowerdata inode matches
the specified digest in it (according to the enabled verity
options). If the verity check passes we store this info in the inode
flags as OVL_VERIFIED_DIGEST, so that we can avoid doing it again if
the inode remains in memory.

The verification is done in ovl_maybe_validate_verity() which needs to
be called in the same places as ovl_maybe_lookup_lowerdata(), so there
is a new ovl_verify_lowerdata() helper that calls these in the right
order, and all current callers of ovl_maybe_lookup_lowerdata() are
changed to call it instead.

Signed-off-by: Alexander Larsson <alexl@redhat.com>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
fs/overlayfs/copy_up.c
fs/overlayfs/file.c
fs/overlayfs/namei.c
fs/overlayfs/overlayfs.h
fs/overlayfs/super.c
fs/overlayfs/util.c

index 568f743a5584a5c666277d9121f8bc7768ed46c0..68f01fd7f211eea2c8a0b8981a168cf9cebe4aab 100644 (file)
@@ -1078,7 +1078,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;
 
index 21245b00722af97e7dd04f652733f96bffacad4b..176683ea128bf255f385da3a1965958d63db0dbd 100644 (file)
@@ -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;
 
index 3dd4802537103229efc3c5f9ef8de56ae32585db..d00ec43f2376f0f60ceda8ad20e58eee3a9e1695 100644 (file)
@@ -889,8 +889,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 = dentry->d_sb->s_fs_info;
+       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,6 +985,17 @@ 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)
 {
@@ -955,6 +1016,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,
@@ -999,6 +1061,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 +1139,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
@@ -1215,6 +1281,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
                if (err < 0)
                        goto out_free_oe;
                uppermetacopy = err;
+               metacopy_size = err;
        }
 
        if (upperdentry || ctr) {
@@ -1236,6 +1303,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));
index 2e659b355f61d7c2a673d42289ff72ab27ae4cff..02ce60048457b7fc347484bad35e1a35b39acc2f 100644 (file)
@@ -50,6 +50,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 {
@@ -481,8 +483,15 @@ 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,
                             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_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_sync_status(struct ovl_fs *ofs);
 
 static inline void ovl_set_flag(unsigned long flag, struct inode *inode)
@@ -602,7 +611,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);
index cc8977498c483771451525fb6cac282c87994574..9ed21f335adf78f8b323a839dfb428fc98284ea3 100644 (file)
@@ -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;
index 921747223991c809cb5108e1710ccb1f48b71cd9..1df6230dde7ce67996f23c6cc9758f608d31d6a5 100644 (file)
@@ -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>
@@ -1112,6 +1113,18 @@ out:
        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);
@@ -1174,6 +1187,81 @@ err_free:
        return ERR_PTR(res);
 }
 
+/* Call with mounter creds as it may open the file */
+static 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;
+}
+
 /*
  * ovl_sync_status() - Check fs sync status for volatile mounts
  *