btrfs-progs: optionally enforce chroot for btrfs receive
authorLauri Võsandi <lauri.vosandi@gmail.com>
Sun, 19 Apr 2015 11:46:28 +0000 (14:46 +0300)
committerDavid Sterba <dsterba@suse.cz>
Fri, 24 Apr 2015 13:42:05 +0000 (15:42 +0200)
This patch forces btrfs receive to issue chroot before
parsing the btrfs stream using command-line flag -C
to confine the process and minimize damage that could
be done via malicious btrfs stream.

Signed-off-by: Lauri Võsandi <lauri.vosandi@gmail.com>
[added long option variant, added docs]
Signed-off-by: David Sterba <dsterba@suse.cz>
Documentation/btrfs-receive.asciidoc
cmds-receive.c

index ed1e247..bdbabdf 100644 (file)
@@ -7,7 +7,7 @@ btrfs-receive - receive subvolumes from stdin/file.
 
 SYNOPSIS
 --------
-*btrfs receive* [-ve] [-f <infile>] [--max-errors <N>] <mount>
+*btrfs receive* [options] <mount>
 
 DESCRIPTION
 -----------
@@ -34,6 +34,8 @@ verbose level more.
 -f <infile>::
 By default, btrfs receive uses stdin to receive the subvolumes.
 Use this option to specify a file to use instead.
+-C|--chroot::
+Confine the process to <mount> using chroot.
 -e::
 Terminate after receiving an <end cmd> in the data stream.
 Without this option, the receiver terminates only if an error is recognized
index 919dc9e..3505c76 100644 (file)
@@ -61,6 +61,7 @@ struct btrfs_receive
        char *root_path;
        char *dest_dir_path; /* relative to root_path */
        char *full_subvol_path;
+       int dest_dir_chroot;
 
        struct subvol_info *cur_subvol;
 
@@ -858,14 +859,38 @@ static int do_receive(struct btrfs_receive *r, const char *tomnt, int r_fd,
                goto out;
        }
 
-       /*
-        * find_mount_root returns a root_path that is a subpath of
-        * dest_dir_full_path. Now get the other part of root_path,
-        * which is the destination dir relative to root_path.
-        */
-       r->dest_dir_path = dest_dir_full_path + strlen(r->root_path);
-       while (r->dest_dir_path[0] == '/')
-               r->dest_dir_path++;
+       if (r->dest_dir_chroot) {
+               if (chroot(dest_dir_full_path)) {
+                       ret = -errno;
+                       fprintf(stderr,
+                               "ERROR: failed to chroot to %s, %s\n",
+                               dest_dir_full_path,
+                               strerror(-ret));
+                       goto out;
+               }
+               if (chdir("/")) {
+                       ret = -errno;
+                       fprintf(stderr,
+                               "ERROR: failed to chdir to /, %s\n",
+                               strerror(-ret));
+                       goto out;
+               }
+               if (g_verbose >= 1) {
+                       fprintf(stderr, "chrooted to %s\n",
+                               dest_dir_full_path);
+               }
+               r->root_path = strdup("/");
+               r->dest_dir_path = r->root_path;
+       } else {
+               /*
+                * find_mount_root returns a root_path that is a subpath of
+                * dest_dir_full_path. Now get the other part of root_path,
+                * which is the destination dir relative to root_path.
+                */
+               r->dest_dir_path = dest_dir_full_path + strlen(r->root_path);
+               while (r->dest_dir_path[0] == '/')
+                       r->dest_dir_path++;
+       }
 
        ret = subvol_uuid_search_init(r->mnt_fd, &r->sus);
        if (ret < 0)
@@ -930,15 +955,17 @@ int cmd_receive(int argc, char **argv)
        r.mnt_fd = -1;
        r.write_fd = -1;
        r.dest_dir_fd = -1;
+       r.dest_dir_chroot = 0;
 
        while (1) {
                int c;
                static const struct option long_opts[] = {
                        { "max-errors", required_argument, NULL, 'E' },
+                       { "chroot", no_argument, NULL, 'C' },
                        { NULL, 0, NULL, 0 }
                };
 
-               c = getopt_long(argc, argv, "evf:", long_opts, NULL);
+               c = getopt_long(argc, argv, "Cevf:", long_opts, NULL);
                if (c < 0)
                        break;
 
@@ -952,6 +979,9 @@ int cmd_receive(int argc, char **argv)
                case 'e':
                        r.honor_end_cmd = 1;
                        break;
+               case 'C':
+                       r.dest_dir_chroot = 1;
+                       break;
                case 'E':
                        max_errors = arg_strtou64(optarg);
                        break;
@@ -1001,6 +1031,7 @@ const char * const cmd_receive_usage[] = {
        "                 in the data stream. Without this option,",
        "                 the receiver terminates only if an error",
        "                 is recognized or on EOF.",
+       "-C|--chroot      confine the process to <mount> using chroot",
        "--max-errors <N> Terminate as soon as N errors happened while",
        "                 processing commands from the send stream.",
        "                 Default value is 1. A value of 0 means no limit.",