diff mbox series

[v1] btrfs: send: Emit file capabilities after chown

Message ID 20200507201804.1623-1-marcos@mpdesouza.com (mailing list archive)
State New, archived
Headers show
Series [v1] btrfs: send: Emit file capabilities after chown | expand

Commit Message

Marcos Paulo de Souza May 7, 2020, 8:18 p.m. UTC
From: Marcos Paulo de Souza <mpdesouza@suse.com>

[PROBLEM]
Whenever a chown is executed, all capabilities of the file being touched are
lost.  When doing incremental send with a file with capabilities, there is a
situation where the capability can be lost in the receiving side. The
sequence of actions bellow shows the problem:

$ mount /dev/sda fs1
$ mount /dev/sdb fs2

$ touch fs1/foo.bar
$ setcap cap_sys_nice+ep fs1/foo.bar
$ btrfs subvol snap -r fs1 fs1/snap_init
$ btrfs send fs1/snap_init | btrfs receive fs2

$ chgrp adm fs1/foo.bar
$ setcap cap_sys_nice+ep fs1/foo.bar

$ btrfs subvol snap -r fs1 fs1/snap_complete
$ btrfs subvol snap -r fs1 fs1/snap_incremental

$ btrfs send fs1/snap_complete | btrfs receive fs2
$ btrfs send -p fs1/snap_init fs1/snap_incremental | btrfs receive fs2

At this point, only a chown was emitted by "btrfs send" since only the group
was changed. This makes the cap_sys_nice capability to be dropped from
fs2/snap_incremental/foo.bar

[FIX]
Only emit capabilities after chown is emitted. The current code
first checks for xattrs that are new/changed, emits them, and later emit
the chown. Now, __process_new_xattr skips capabilities, letting only
finish_inode_if_needed to emit them, if they exist, for the inode being
processed.

Also, this patch also fixes a longstanding problem that was issuing an xattr
_before_ chown. This behavior was being worked around in "btrfs receive"
side by caching the capability and only applying it after chown. Now,
xattrs are only emmited _after_ chown, making that hack not needed
anymore.

Link: https://github.com/kdave/btrfs-progs/issues/202
Suggested-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Marcos Paulo de Souza <mpdesouza@suse.com>
---
 The first version of the patch was an RFC

 Changes from RFC:
 * Explained about chown + drop capabilities problem in the commit message (suggested
   by Filipe and David)
 * Changed the commit message to show describe the fix (suggested by Filipe)
 * Skip the xattr in __process_new_xattr if it's a capability, since it'll be
   handled in finish_inode_if_needed now (suggested by Filipe).
 * Created function send_capabilities to query if the inode has caps, and if
   yes, emit them.
 * Call send_capabilities in finish_inode_if_needed _after_ the needs_chown
   check. (suggested by Filipe)

 fs/btrfs/send.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

Comments

Filipe Manana May 8, 2020, 9:57 a.m. UTC | #1
On Thu, May 7, 2020 at 9:16 PM Marcos Paulo de Souza
<marcos@mpdesouza.com> wrote:
>
> From: Marcos Paulo de Souza <mpdesouza@suse.com>
>
> [PROBLEM]
> Whenever a chown is executed, all capabilities of the file being touched are
> lost.  When doing incremental send with a file with capabilities, there is a
> situation where the capability can be lost in the receiving side. The
> sequence of actions bellow shows the problem:
>
> $ mount /dev/sda fs1
> $ mount /dev/sdb fs2
>
> $ touch fs1/foo.bar
> $ setcap cap_sys_nice+ep fs1/foo.bar
> $ btrfs subvol snap -r fs1 fs1/snap_init
> $ btrfs send fs1/snap_init | btrfs receive fs2
>
> $ chgrp adm fs1/foo.bar
> $ setcap cap_sys_nice+ep fs1/foo.bar
>
> $ btrfs subvol snap -r fs1 fs1/snap_complete
> $ btrfs subvol snap -r fs1 fs1/snap_incremental
>
> $ btrfs send fs1/snap_complete | btrfs receive fs2
> $ btrfs send -p fs1/snap_init fs1/snap_incremental | btrfs receive fs2
>
> At this point, only a chown was emitted by "btrfs send" since only the group
> was changed. This makes the cap_sys_nice capability to be dropped from
> fs2/snap_incremental/foo.bar
>
> [FIX]
> Only emit capabilities after chown is emitted. The current code
> first checks for xattrs that are new/changed, emits them, and later emit
> the chown. Now, __process_new_xattr skips capabilities, letting only
> finish_inode_if_needed to emit them, if they exist, for the inode being
> processed.
>
> Also, this patch also fixes a longstanding problem that was issuing an xattr
> _before_ chown.

This sentence seems an unnecessary duplication of what the previous
paragraph said.

> This behavior was being worked around in "btrfs receive"
> side by caching the capability and only applying it after chown. Now,
> xattrs are only emmited _after_ chown, making that hack not needed
> anymore.

Right, lets add:

CC: stable@vger.kernel.org

With that and after a few kernels releases with the fix (lets in 6
months maybe),
we can remove the btrfs-progs stuff.

Also please send the test case for fstests, to test that particular
scenario in the changelog and others for which the btrfs-progs hack
worked but we never got any test case:

1) test that after a full send of a snapshot with a file with
capabilities, the receiver still has the capabilities;
2) test that if we add capabilities to a file which had none in the
parent snapshot, the receiver gets the capabilities after an
incremental send;
3) test that if the capabilities and owner/group change in the second
snapshot for a file, after an incremental send the receiver has the
correct capabilities;
4) the case for the changelog - if the onwer/group changes but the
capabilities remain the same, after an incremental send the
capabilities aren't lost

>
> Link: https://github.com/kdave/btrfs-progs/issues/202
> Suggested-by: Filipe Manana <fdmanana@suse.com>
> Signed-off-by: Marcos Paulo de Souza <mpdesouza@suse.com>

Reviewed-by: Filipe Manana <fdmanana@suse.com>

> ---
>  The first version of the patch was an RFC
>
>  Changes from RFC:
>  * Explained about chown + drop capabilities problem in the commit message (suggested
>    by Filipe and David)
>  * Changed the commit message to show describe the fix (suggested by Filipe)
>  * Skip the xattr in __process_new_xattr if it's a capability, since it'll be
>    handled in finish_inode_if_needed now (suggested by Filipe).
>  * Created function send_capabilities to query if the inode has caps, and if
>    yes, emit them.
>  * Call send_capabilities in finish_inode_if_needed _after_ the needs_chown
>    check. (suggested by Filipe)
>
>  fs/btrfs/send.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 69 insertions(+)
>
> diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
> index 6b86841315be..4f19965bdd82 100644
> --- a/fs/btrfs/send.c
> +++ b/fs/btrfs/send.c
> @@ -23,6 +23,7 @@
>  #include "btrfs_inode.h"
>  #include "transaction.h"
>  #include "compression.h"
> +#include "xattr.h"
>
>  /*
>   * Maximum number of references an extent can have in order for us to attempt to
> @@ -4545,6 +4546,10 @@ static int __process_new_xattr(int num, struct btrfs_key *di_key,
>         struct fs_path *p;
>         struct posix_acl_xattr_header dummy_acl;
>
> +       /* capabilities are emitted by finish_inode_if_needed */
> +       if (!strncmp(name, XATTR_NAME_CAPS, name_len))
> +               return 0;
> +
>         p = fs_path_alloc();
>         if (!p)
>                 return -ENOMEM;
> @@ -5107,6 +5112,66 @@ static int send_extent_data(struct send_ctx *sctx,
>         return 0;
>  }
>
> +/*
> + * Search for a capability xattr related to sctx->cur_ino. If the capability if
> + * found, call send_set_xattr function to emit it.
> + *
> + * Return %0 if there isn't a capability, or when the capability was emitted
> + * successfully, or < %0 if an error occurred.
> + */
> +static int send_capabilities(struct send_ctx *sctx)
> +{
> +       struct fs_path *fspath = NULL;
> +       struct btrfs_path *path;
> +       struct btrfs_dir_item *di;
> +       struct extent_buffer *leaf;
> +       unsigned long data_ptr;
> +       char *name = XATTR_NAME_CAPS;

Can be "const", or get rid of name and use  XATTR_NAME_CAPS directly everywhere.

Anyway, it's more a subjective stylistic detail, not that relevant.

Thanks, good work!


> +       char *buf = NULL;
> +       int buf_len;
> +       int ret = 0;
> +
> +       path = btrfs_alloc_path();
> +       if (!path)
> +               return -ENOMEM;
> +
> +       di = btrfs_lookup_xattr(NULL, sctx->send_root, path, sctx->cur_ino,
> +                               name, strlen(name), 0);
> +       if (!di) {
> +               /* there is no xattr for this inode */
> +               goto out;
> +       } else if (IS_ERR(di)) {
> +               ret = PTR_ERR(di);
> +               goto out;
> +       }
> +
> +       leaf = path->nodes[0];
> +       buf_len = btrfs_dir_data_len(leaf, di);
> +
> +       fspath = fs_path_alloc();
> +       buf = kmalloc(buf_len, GFP_KERNEL);
> +       if (!fspath || !buf) {
> +               ret = -ENOMEM;
> +               goto out;
> +       }
> +
> +       ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, fspath);
> +       if (ret < 0)
> +               goto out;
> +
> +       data_ptr = (unsigned long)((char *)(di + 1) +
> +                                  btrfs_dir_name_len(leaf, di));
> +       read_extent_buffer(leaf, buf, data_ptr,
> +                          btrfs_dir_data_len(leaf, di));
> +
> +       ret = send_set_xattr(sctx, fspath, name, strlen(name), buf, buf_len);
> +out:
> +       kfree(buf);
> +       fs_path_free(fspath);
> +       btrfs_free_path(path);
> +       return ret;
> +}
> +
>  static int clone_range(struct send_ctx *sctx,
>                        struct clone_root *clone_root,
>                        const u64 disk_byte,
> @@ -6010,6 +6075,10 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
>                         goto out;
>         }
>
> +       ret = send_capabilities(sctx);
> +       if (ret < 0)
> +               goto out;
> +
>         /*
>          * If other directory inodes depended on our current directory
>          * inode's move/rename, now do their move/rename operations.
> --
> 2.25.1
>
Filipe Manana May 8, 2020, 12:12 p.m. UTC | #2
On Thu, May 7, 2020 at 9:16 PM Marcos Paulo de Souza
<marcos@mpdesouza.com> wrote:
>
> From: Marcos Paulo de Souza <mpdesouza@suse.com>
>
> [PROBLEM]
> Whenever a chown is executed, all capabilities of the file being touched are
> lost.  When doing incremental send with a file with capabilities, there is a
> situation where the capability can be lost in the receiving side. The
> sequence of actions bellow shows the problem:
>
> $ mount /dev/sda fs1
> $ mount /dev/sdb fs2
>
> $ touch fs1/foo.bar
> $ setcap cap_sys_nice+ep fs1/foo.bar
> $ btrfs subvol snap -r fs1 fs1/snap_init
> $ btrfs send fs1/snap_init | btrfs receive fs2
>
> $ chgrp adm fs1/foo.bar
> $ setcap cap_sys_nice+ep fs1/foo.bar
>
> $ btrfs subvol snap -r fs1 fs1/snap_complete
> $ btrfs subvol snap -r fs1 fs1/snap_incremental
>
> $ btrfs send fs1/snap_complete | btrfs receive fs2
> $ btrfs send -p fs1/snap_init fs1/snap_incremental | btrfs receive fs2
>
> At this point, only a chown was emitted by "btrfs send" since only the group
> was changed. This makes the cap_sys_nice capability to be dropped from
> fs2/snap_incremental/foo.bar
>
> [FIX]
> Only emit capabilities after chown is emitted. The current code
> first checks for xattrs that are new/changed, emits them, and later emit
> the chown. Now, __process_new_xattr skips capabilities, letting only
> finish_inode_if_needed to emit them, if they exist, for the inode being
> processed.
>
> Also, this patch also fixes a longstanding problem that was issuing an xattr
> _before_ chown. This behavior was being worked around in "btrfs receive"
> side by caching the capability and only applying it after chown. Now,
> xattrs are only emmited _after_ chown, making that hack not needed
> anymore.
>
> Link: https://github.com/kdave/btrfs-progs/issues/202
> Suggested-by: Filipe Manana <fdmanana@suse.com>
> Signed-off-by: Marcos Paulo de Souza <mpdesouza@suse.com>
> ---
>  The first version of the patch was an RFC
>
>  Changes from RFC:
>  * Explained about chown + drop capabilities problem in the commit message (suggested
>    by Filipe and David)
>  * Changed the commit message to show describe the fix (suggested by Filipe)
>  * Skip the xattr in __process_new_xattr if it's a capability, since it'll be
>    handled in finish_inode_if_needed now (suggested by Filipe).
>  * Created function send_capabilities to query if the inode has caps, and if
>    yes, emit them.
>  * Call send_capabilities in finish_inode_if_needed _after_ the needs_chown
>    check. (suggested by Filipe)
>
>  fs/btrfs/send.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 69 insertions(+)
>
> diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
> index 6b86841315be..4f19965bdd82 100644
> --- a/fs/btrfs/send.c
> +++ b/fs/btrfs/send.c
> @@ -23,6 +23,7 @@
>  #include "btrfs_inode.h"
>  #include "transaction.h"
>  #include "compression.h"
> +#include "xattr.h"
>
>  /*
>   * Maximum number of references an extent can have in order for us to attempt to
> @@ -4545,6 +4546,10 @@ static int __process_new_xattr(int num, struct btrfs_key *di_key,
>         struct fs_path *p;
>         struct posix_acl_xattr_header dummy_acl;
>
> +       /* capabilities are emitted by finish_inode_if_needed */
> +       if (!strncmp(name, XATTR_NAME_CAPS, name_len))
> +               return 0;
> +
>         p = fs_path_alloc();
>         if (!p)
>                 return -ENOMEM;
> @@ -5107,6 +5112,66 @@ static int send_extent_data(struct send_ctx *sctx,
>         return 0;
>  }
>
> +/*
> + * Search for a capability xattr related to sctx->cur_ino. If the capability if
> + * found, call send_set_xattr function to emit it.
> + *
> + * Return %0 if there isn't a capability, or when the capability was emitted
> + * successfully, or < %0 if an error occurred.
> + */
> +static int send_capabilities(struct send_ctx *sctx)
> +{
> +       struct fs_path *fspath = NULL;
> +       struct btrfs_path *path;
> +       struct btrfs_dir_item *di;
> +       struct extent_buffer *leaf;
> +       unsigned long data_ptr;
> +       char *name = XATTR_NAME_CAPS;
> +       char *buf = NULL;
> +       int buf_len;
> +       int ret = 0;
> +
> +       path = btrfs_alloc_path();

So, I forgot this: use alloc_path_for_send() instead, we want a path
that uses the commit root. Path allocations in send should always use
that helper.

Thanks.

> +       if (!path)
> +               return -ENOMEM;
> +
> +       di = btrfs_lookup_xattr(NULL, sctx->send_root, path, sctx->cur_ino,
> +                               name, strlen(name), 0);
> +       if (!di) {
> +               /* there is no xattr for this inode */
> +               goto out;
> +       } else if (IS_ERR(di)) {
> +               ret = PTR_ERR(di);
> +               goto out;
> +       }
> +
> +       leaf = path->nodes[0];
> +       buf_len = btrfs_dir_data_len(leaf, di);
> +
> +       fspath = fs_path_alloc();
> +       buf = kmalloc(buf_len, GFP_KERNEL);
> +       if (!fspath || !buf) {
> +               ret = -ENOMEM;
> +               goto out;
> +       }
> +
> +       ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, fspath);
> +       if (ret < 0)
> +               goto out;
> +
> +       data_ptr = (unsigned long)((char *)(di + 1) +
> +                                  btrfs_dir_name_len(leaf, di));
> +       read_extent_buffer(leaf, buf, data_ptr,
> +                          btrfs_dir_data_len(leaf, di));
> +
> +       ret = send_set_xattr(sctx, fspath, name, strlen(name), buf, buf_len);
> +out:
> +       kfree(buf);
> +       fs_path_free(fspath);
> +       btrfs_free_path(path);
> +       return ret;
> +}
> +
>  static int clone_range(struct send_ctx *sctx,
>                        struct clone_root *clone_root,
>                        const u64 disk_byte,
> @@ -6010,6 +6075,10 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
>                         goto out;
>         }
>
> +       ret = send_capabilities(sctx);
> +       if (ret < 0)
> +               goto out;
> +
>         /*
>          * If other directory inodes depended on our current directory
>          * inode's move/rename, now do their move/rename operations.
> --
> 2.25.1
>
diff mbox series

Patch

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index 6b86841315be..4f19965bdd82 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -23,6 +23,7 @@ 
 #include "btrfs_inode.h"
 #include "transaction.h"
 #include "compression.h"
+#include "xattr.h"
 
 /*
  * Maximum number of references an extent can have in order for us to attempt to
@@ -4545,6 +4546,10 @@  static int __process_new_xattr(int num, struct btrfs_key *di_key,
 	struct fs_path *p;
 	struct posix_acl_xattr_header dummy_acl;
 
+	/* capabilities are emitted by finish_inode_if_needed */
+	if (!strncmp(name, XATTR_NAME_CAPS, name_len))
+		return 0;
+
 	p = fs_path_alloc();
 	if (!p)
 		return -ENOMEM;
@@ -5107,6 +5112,66 @@  static int send_extent_data(struct send_ctx *sctx,
 	return 0;
 }
 
+/*
+ * Search for a capability xattr related to sctx->cur_ino. If the capability if
+ * found, call send_set_xattr function to emit it.
+ *
+ * Return %0 if there isn't a capability, or when the capability was emitted
+ * successfully, or < %0 if an error occurred.
+ */
+static int send_capabilities(struct send_ctx *sctx)
+{
+	struct fs_path *fspath = NULL;
+	struct btrfs_path *path;
+	struct btrfs_dir_item *di;
+	struct extent_buffer *leaf;
+	unsigned long data_ptr;
+	char *name = XATTR_NAME_CAPS;
+	char *buf = NULL;
+	int buf_len;
+	int ret = 0;
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+
+	di = btrfs_lookup_xattr(NULL, sctx->send_root, path, sctx->cur_ino,
+				name, strlen(name), 0);
+	if (!di) {
+		/* there is no xattr for this inode */
+		goto out;
+	} else if (IS_ERR(di)) {
+		ret = PTR_ERR(di);
+		goto out;
+	}
+
+	leaf = path->nodes[0];
+	buf_len = btrfs_dir_data_len(leaf, di);
+
+	fspath = fs_path_alloc();
+	buf = kmalloc(buf_len, GFP_KERNEL);
+	if (!fspath || !buf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, fspath);
+	if (ret < 0)
+		goto out;
+
+	data_ptr = (unsigned long)((char *)(di + 1) +
+				   btrfs_dir_name_len(leaf, di));
+	read_extent_buffer(leaf, buf, data_ptr,
+			   btrfs_dir_data_len(leaf, di));
+
+	ret = send_set_xattr(sctx, fspath, name, strlen(name), buf, buf_len);
+out:
+	kfree(buf);
+	fs_path_free(fspath);
+	btrfs_free_path(path);
+	return ret;
+}
+
 static int clone_range(struct send_ctx *sctx,
 		       struct clone_root *clone_root,
 		       const u64 disk_byte,
@@ -6010,6 +6075,10 @@  static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
 			goto out;
 	}
 
+	ret = send_capabilities(sctx);
+	if (ret < 0)
+		goto out;
+
 	/*
 	 * If other directory inodes depended on our current directory
 	 * inode's move/rename, now do their move/rename operations.