Skip to content

Commit

Permalink
btrfs: send: Re emit file capabilities when chown is emitted
Browse files Browse the repository at this point in the history
[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, 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]
Emit all capabilities when a chown is being emitted. The current code
first checks for xattrs that are new/changed, emits them, and later emit
the chown. To avoid sending the xattrs duplicated, a new flag is
introduced in send_context.

Link: kdave/btrfs-progs#202

Signed-off-by: Marcos Paulo de Souza <mpdesouza@suse.com>
  • Loading branch information
marcosps committed May 4, 2020
1 parent 022687d commit 62efe5f
Showing 1 changed file with 34 additions and 0 deletions.
34 changes: 34 additions & 0 deletions fs/btrfs/send.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ struct send_ctx {
u64 cur_inode_last_extent;
u64 cur_inode_next_write_offset;
bool ignore_cur_inode;
/*
* send_chown can be called after the xattr is emitted, so this flag
* helps to avoid emitting the capability xattr twice.
*/
bool caps_sent;

u64 send_progress;

Expand Down Expand Up @@ -4565,6 +4570,9 @@ static int __process_new_xattr(int num, struct btrfs_key *di_key,
}
}

if (!strncmp(name, XATTR_NAME_CAPS, name_len))
sctx->caps_sent = true;

ret = get_cur_path(sctx, sctx->cur_ino, sctx->cur_inode_gen, p);
if (ret < 0)
goto out;
Expand Down Expand Up @@ -4600,6 +4608,19 @@ static int __process_deleted_xattr(int num, struct btrfs_key *di_key,
return ret;
}

static int __process_new_caps(int num, struct btrfs_key *di_key,
const char *name, int name_len,
const char *data, int data_len,
u8 type, void *ctx)

{
if (strncmp(name, XATTR_NAME_CAPS, name_len) != 0)
return 0;

return __process_new_xattr(num, di_key, name, name_len, data, data_len,
type, ctx);
}

static int process_new_xattr(struct send_ctx *sctx)
{
int ret = 0;
Expand Down Expand Up @@ -6013,6 +6034,18 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
}

if (need_chown) {
/*
* When chown is emitted, we need to check if the inode has
* capabilities, if yes, reemit them. As this phase can be
* called when at_end is 1, when need to check if the capability
* wasn't already emitted.
*/
if (!sctx->caps_sent) {
ret = process_all_xattrs(sctx, __process_new_caps);
if (ret < 0)
goto out;
}

ret = send_chown(sctx, sctx->cur_ino, sctx->cur_inode_gen,
left_uid, left_gid);
if (ret < 0)
Expand Down Expand Up @@ -6152,6 +6185,7 @@ static int changed_inode(struct send_ctx *sctx,
sctx->cur_inode_last_extent = (u64)-1;
sctx->cur_inode_next_write_offset = 0;
sctx->ignore_cur_inode = false;
sctx->caps_sent = false;

/*
* Set send_progress to current inode. This will tell all get_cur_xxx
Expand Down

0 comments on commit 62efe5f

Please sign in to comment.