rofiles-fuse: Check fsverity flag for copyup
authorColin Walters <walters@verbum.org>
Thu, 15 Feb 2024 01:33:17 +0000 (20:33 -0500)
committerColin Walters <walters@verbum.org>
Thu, 15 Feb 2024 13:03:16 +0000 (08:03 -0500)
We need to do a copyup if fsverity is enabled.
Sadly to do this we can't just use ostree_break_hardlink
as is.

ci/gh-install.sh
ci/installdeps.sh
src/rofiles-fuse/main.c
tests/test-rofiles-fuse.sh

index 966edb2896b729f42cd4524e498424a89ef8abc2..2260883becd233d1edd503f39c01ce1b73549495 100755 (executable)
@@ -79,6 +79,7 @@ case "$ID" in
             docbook-xsl
             e2fslibs-dev
             elfutils
+            fsverity
             fuse
             gnupg
             gobject-introspection
index 89b7ac1272c04660a2185417825e660f05bacefa..f63e2d4ea6554baff1e5818bde79a18bac0c95a7 100755 (executable)
@@ -19,7 +19,7 @@ pkg_install_buildroot
 pkg_builddep ostree
 pkg_install sudo which attr fuse strace \
     libubsan libasan libtsan redhat-rpm-config \
-    elfutils
+    elfutils fsverity-utils
 if test -n "${CI_PKGS:-}"; then
     pkg_install ${CI_PKGS}
 fi
index dc98898756ca1fe21698664e67de32ea7ee59a7d..a1dda3aadca6918b2f66dc3ed2e5dec2db59380a 100644 (file)
@@ -33,6 +33,7 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/statvfs.h>
+#include <sys/sysmacros.h>
 #include <sys/types.h>
 #include <sys/xattr.h>
 #include <unistd.h>
@@ -243,6 +244,11 @@ can_write_stbuf (const struct statx *stbuf)
    */
   if (!(S_ISREG (stbuf->stx_mode) || S_ISLNK (stbuf->stx_mode)))
     return TRUE;
+#ifdef STATX_ATTR_VERITY
+  /* Can't write to fsverity files */
+  if (stbuf->stx_attributes & STATX_ATTR_VERITY)
+    return FALSE;
+#endif
   /* If the object isn't hardlinked, it's OK to write */
   if (stbuf->stx_nlink <= 1)
     return TRUE;
@@ -275,11 +281,48 @@ gioerror_to_errno (GIOErrorEnum e)
     }
 }
 
+// The libglnx APIs take a stat buffer, so we need to be able to
+// convert from statx.
+static inline void
+statx_to_stat (const struct statx *stxbuf, struct stat *stbuf)
+{
+  stbuf->st_dev = makedev (stxbuf->stx_dev_major, stxbuf->stx_dev_minor);
+  stbuf->st_rdev = makedev (stxbuf->stx_rdev_major, stxbuf->stx_rdev_minor);
+  stbuf->st_ino = stxbuf->stx_ino;
+  stbuf->st_mode = stxbuf->stx_mode;
+  stbuf->st_nlink = stxbuf->stx_nlink;
+  stbuf->st_uid = stxbuf->stx_uid;
+  stbuf->st_gid = stxbuf->stx_gid;
+  stbuf->st_size = stxbuf->stx_size;
+  stbuf->st_blksize = stxbuf->stx_blksize;
+}
+
+// A copy of ostree_break_hardlink but without the check for hardlinks, which
+// is mainly relevant for regular files, where we need to handle verity.
+static gboolean
+copyup (int dfd, const char *path, const struct statx *stxbuf, GError **error)
+{
+  if (S_ISREG (stxbuf->stx_mode))
+    {
+      struct stat stbuf;
+      statx_to_stat (stxbuf, &stbuf);
+      // Note GLNX_FILE_COPY_OVERWRITE always uses O_TMPFILE+rename
+      return glnx_file_copy_at (dfd, path, &stbuf, dfd, path, GLNX_FILE_COPY_OVERWRITE, NULL,
+                                error);
+    }
+  else
+    {
+      // For symlinks, we can just directly call the ostree API.  This avoids
+      // more code duplication because atomically copying symlinks requires
+      // a temp-link dance.
+      return ostree_break_hardlink (dfd, path, FALSE, NULL, error);
+    }
+}
+
 static int
 verify_write_or_copyup (const char *path, const struct statx *stbuf, gboolean *out_did_copyup)
 {
   struct statx stbuf_local;
-
   if (out_did_copyup)
     *out_did_copyup = FALSE;
 
@@ -304,7 +347,7 @@ verify_write_or_copyup (const char *path, const struct statx *stbuf, gboolean *o
       if (opt_copyup)
         {
           g_autoptr (GError) tmp_error = NULL;
-          if (!ostree_break_hardlink (basefd, path, FALSE, NULL, &tmp_error))
+          if (!copyup (basefd, path, stbuf, &tmp_error))
             return -gioerror_to_errno ((GIOErrorEnum)tmp_error->code);
           if (out_did_copyup)
             *out_did_copyup = TRUE;
@@ -417,53 +460,23 @@ do_open (const char *path, mode_t mode, struct fuse_file_info *finfo)
   else
     {
       /* Write */
-
-      /* We need to specially handle O_TRUNC */
-      fd = openat (basefd, path, finfo->flags & ~O_TRUNC, mode);
-      if (fd == -1)
-        return -errno;
-
-      if (statx (fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS, &stbuf) == -1)
-        {
-          (void)close (fd);
-          return -errno;
-        }
-
-      gboolean did_copyup;
-      int r = verify_write_or_copyup (path, &stbuf, &did_copyup);
-      if (r != 0)
+      if (statx (basefd, path, AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, STATX_BASIC_STATS, &stbuf)
+          == -1)
         {
-          (void)close (fd);
-          return r;
-        }
-
-      /* In the copyup case, we need to re-open */
-      if (did_copyup)
-        {
-          (void)close (fd);
-          /* Note that unlike the initial open, we will pass through
-           * O_TRUNC.  More ideally in this copyup case we'd avoid copying
-           * the whole file in the first place, but eh.  It's not like we're
-           * high performance anyways.
-           */
-          fd = openat (basefd, path, finfo->flags & ~(O_EXCL | O_CREAT), mode);
-          if (fd == -1)
+          if (errno != ENOENT)
             return -errno;
         }
       else
         {
-          /* In the non-copyup case we handle O_TRUNC here, after we've verified
-           * the hardlink state above with verify_write_or_copyup().
-           */
-          if (finfo->flags & O_TRUNC)
-            {
-              if (ftruncate (fd, 0) == -1)
-                {
-                  (void)close (fd);
-                  return -errno;
-                }
-            }
+          gboolean did_copyup;
+          int r = verify_write_or_copyup (path, &stbuf, &did_copyup);
+          if (r != 0)
+            return r;
         }
+
+      fd = openat (basefd, path, finfo->flags, mode);
+      if (fd == -1)
+        return -errno;
     }
 
   finfo->fh = fd;
index a56a76c6d3ca599c3a16938e11afbc0452389328..0ac55764f3ffbe192b8384065f07380a962bc719 100755 (executable)
@@ -26,7 +26,7 @@ skip_without_user_xattrs
 
 setup_test_repository "bare"
 
-echo "1..12"
+echo "1..13"
 
 cd ${test_tmpdir}
 mkdir mnt
@@ -192,3 +192,21 @@ sed -i -e s,first,second, mnt/firstfile
 assert_file_has_content_literal mnt/firstfile "second"
 
 echo "ok copyup"
+
+copyup_reset
+echo nonhardlinked > checkout-test2/nonhardlinked
+if fsverity enable checkout-test2/nonhardlinked 2>err.txt; then
+    orig_inode=$(stat -c %i checkout-test2/nonhardlinked)
+    echo "updated content" > mnt/nonhardlinked
+    new_inode=$(stat -c %i checkout-test2/nonhardlinked)
+    assert_not_streq "${orig_inode}" "${new_inode}"
+    # And via chmod
+    fsverity enable checkout-test2/nonhardlinked
+    orig_inode=$(stat -c %i checkout-test2/nonhardlinked)
+    chmod 0700 mnt/nonhardlinked
+    new_inode=$(stat -c %i checkout-test2/nonhardlinked)
+    assert_not_streq "${orig_inode}" "${new_inode}"
+    echo "ok copyup fsverity"
+else
+    skip "no fsverity support: $(cat err.txt)"
+fi
\ No newline at end of file