]> git.ipfire.org Git - thirdparty/lxc.git/commitdiff
use pivot_root instead of chroot
authorMichael Holtz <lxc@my.fqdn.org>
Fri, 8 Jan 2010 13:34:13 +0000 (14:34 +0100)
committerDaniel Lezcano <dlezcano@fr.ibm.com>
Fri, 8 Jan 2010 13:34:13 +0000 (14:34 +0100)
lxc currently does a chroot into the target rootfs. chroot is insecure and
can easily be broken, as demonstrated here:

| root@synergy:~# touch /this_is_the_realrootfs_ouch
| # touch /container/webhost/this_is_the_container
| # lxc-start -n webhost /bin/sh
| # ls this*
| this_is_the_container
| # ./breakchroot
| # ls this*
| this_is_the_realrootfs_ouch

code to break chroot taken from
http://www.bpfh.net/simes/computing/chroot-break.html

Now this can be fixed. As our container has his own mount namespace, we can
easily pivot_root into the rootfs and then unmount all old mounts. The patch
attached add a new config keyword which contains the path to a temporary
mount for the old rootfs (inside the container). This stops the chroot break
method shown before.

Example:

| root@synergy:~# grep pivotdir /var/lib/lxc/webhost/config
| lxc.pivotdir = /oldrootfs
| root@synergy:~# ls -lad /container/webhost/oldrootfs
| drwxr-xr-x 2 root root 4096 2010-01-02 03:59 /container/webhost/oldrootfs
| root@synergy:~# lxc-start -n webhost /bin/sh
| # mount -t proc proc /proc
| # cat /proc/mounts
| rootfs / rootfs rw 0 0
| /dev/root / ext3 rw,relatime,errors=remount-ro,data=writeback 0 0
| devpts /dev/console devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
| proc /proc proc rw,relatime 0 0
| # ls this*
| this_is_the_container
| # ./breakchroot
| # ls this*
| this_is_the_container

Signed-off-by: Daniel Lezcano <dlezcano@fr.ibm.com>
Signed-off-by: Michael Holtz <lxc@my.fqdn.org>
src/lxc/conf.c
src/lxc/conf.h
src/lxc/confile.c

index b56e880e594f03144281443f22cdbfde295fc886..96c1868d9cb98636768085e9715271644ca0fffa 100644 (file)
@@ -65,6 +65,8 @@ lxc_log_define(lxc_conf, lxc);
 #define MS_REC 16384
 #endif
 
+extern int pivot_root(const char * new_root, const char * put_old);
+
 typedef int (*instanciate_cb)(struct lxc_netdev *);
 
 struct mount_opt {
@@ -336,7 +338,183 @@ static int setup_tty(const char *rootfs, const struct lxc_tty_info *tty_info)
        return 0;
 }
 
-static int setup_rootfs(const char *rootfs)
+static int setup_rootfs_pivot_root_cb(void *buffer, void *data)
+{
+       struct lxc_list *mountlist, *listentry, *iterator;
+       char *pivotdir, *mountpoint, *mountentry;
+       int found;
+       void **cbparm;
+
+       mountentry = buffer;
+       cbparm = (void **)data;
+
+       mountlist = cbparm[0];
+       pivotdir  = cbparm[1];
+
+       /* parse entry, first field is mountname, ignore */
+       mountpoint = strtok(mountentry, " ");
+       if (!mountpoint)
+               return -1;
+
+       /* second field is mountpoint */
+       mountpoint = strtok(NULL, " ");
+       if (!mountpoint)
+               return -1;
+
+       /* only consider mountpoints below old root fs */
+       if (strncmp(mountpoint, pivotdir, strlen(pivotdir)))
+               return 0;
+
+       /* filter duplicate mountpoints */
+       found = 0;
+       lxc_list_for_each(iterator, mountlist) {
+               if (!strcmp(iterator->elem, mountpoint)) {
+                       found = 1;
+                       break;
+               }
+       }
+       if (found)
+               return 0;
+
+       /* add entry to list */
+       listentry = malloc(sizeof(*listentry));
+       if (!listentry) {
+               SYSERROR("malloc for mountpoint listentry failed");
+               return -1;
+       }
+
+       listentry->elem = strdup(mountpoint);
+       if (!listentry->elem) {
+               SYSERROR("strdup failed");
+               return -1;
+       }
+       lxc_list_add_tail(mountlist, listentry);
+
+       return 0;
+}
+
+
+static int setup_rootfs_pivot_root(const char *rootfs, const char *pivotdir)
+{
+       char path[MAXPATHLEN], buffer[MAXPATHLEN];
+       void *cbparm[2];
+       struct lxc_list mountlist, *iterator;
+       int ok, still_mounted, last_still_mounted;
+       int pivotdir_is_temp = 0;
+
+       /* change into new root fs */
+       if (chdir(rootfs)) {
+               SYSERROR("can't chroot to new rootfs '%s'", rootfs);
+               return -1;
+       }
+
+       /* create temporary mountpoint if none specified */
+       if (!pivotdir) {
+
+               snprintf(path, sizeof(path), "./lxc-oldrootfs-XXXXXX" );
+               if (!mkdtemp(path)) {
+                       SYSERROR("can't make temporary mountpoint");
+                       return -1;
+               }
+
+               pivotdir = strdup(&path[1]); /* get rid of leading dot */
+               if (!pivotdir) {
+                       SYSERROR("strdup failed");
+                       return -1;
+               }
+
+               pivotdir_is_temp = 1;
+       }
+       else {
+               snprintf(path, sizeof(path), ".%s", pivotdir);
+       }
+
+       DEBUG("temporary mountpoint for old rootfs is '%s'", path);
+
+       /* pivot_root into our new root fs */
+
+       if (pivot_root(".", path)) {
+               SYSERROR("pivot_root syscall failed");
+               return -1;
+       }
+
+       if (chdir("/")) {
+               SYSERROR("can't chroot to / after pivot_root");
+               return -1;
+       }
+
+       DEBUG("pivot_root syscall to '%s' successful", pivotdir);
+
+       /* read and parse /proc/mounts in old root fs */
+       lxc_list_init(&mountlist);
+
+       snprintf(path, sizeof(path), "%s/", pivotdir);
+       cbparm[0] = &mountlist;
+       cbparm[1] = strdup(path);
+
+       if (!cbparm[1]) {
+               SYSERROR("strdup failed");
+               return -1;
+       }
+
+       snprintf(path, sizeof(path), "/%s/proc/mounts", pivotdir);
+       ok = lxc_file_for_each_line(path,
+                              setup_rootfs_pivot_root_cb,
+                              buffer, sizeof(buffer), &cbparm);
+       if (ok < 0) {
+               SYSERROR("failed to read or parse mount list '%s'", path);
+               return -1;
+       }
+
+       /* umount filesystems until none left or list no longer shrinks */
+       still_mounted = 0;
+       do {
+               last_still_mounted = still_mounted;
+               still_mounted = 0;
+
+               lxc_list_for_each(iterator, &mountlist) {
+
+                       if (!umount(iterator->elem)) {
+                               DEBUG("umounted '%s'", (char *)iterator->elem);
+                               lxc_list_del(iterator);
+                               continue;
+                       }
+
+                       if (errno != EBUSY) {
+                               SYSERROR("failed to umount '%s'", (char *)iterator->elem);
+                               return -1;
+                       }
+
+                       still_mounted++;
+               }
+       } while (still_mounted > 0 && still_mounted != last_still_mounted);
+
+       if (still_mounted) {
+               ERROR("could not umount %d mounts", still_mounted);
+               return -1;
+       }
+
+       /* umount old root fs */
+       if (umount(pivotdir)) {
+               SYSERROR("could not unmount old rootfs");
+               return -1;
+       }
+       DEBUG("umounted '%s'", pivotdir);
+
+       /* remove temporary mount point */
+       if (pivotdir_is_temp) {
+               if (rmdir(pivotdir)) {
+                       SYSERROR("can't remove temporary mountpoint");
+                       return -1;
+               }
+
+       }
+
+       INFO("pivoted to '%s'", rootfs);
+       return 0;
+}
+
+static int setup_rootfs(const char *rootfs, const char *pivotdir)
 {
        char *tmpname;
        int ret = -1;
@@ -360,18 +538,11 @@ static int setup_rootfs(const char *rootfs)
                goto out;
        }
 
-       if (chroot(tmpname)) {
-               SYSERROR("failed to set chroot %s", tmpname);
+       if (setup_rootfs_pivot_root(tmpname, pivotdir)) {
+               ERROR("failed to pivot_root to '%s'", rootfs);
                goto out;
        }
 
-       if (chdir(getenv("HOME")) && chdir("/")) {
-               SYSERROR("failed to change to home directory");
-               goto out;
-       }
-
-       INFO("chrooted to '%s'", rootfs);
-
        ret = 0;
 out:
        rmdir(tmpname);
@@ -827,6 +998,7 @@ struct lxc_conf *lxc_conf_init(void)
        memset(new, 0, sizeof(*new));
 
        new->rootfs = NULL;
+       new->pivotdir = NULL;
        new->fstab = NULL;
        new->utsname = NULL;
        new->tty = 0;
@@ -1128,7 +1300,7 @@ int lxc_setup(const char *name, struct lxc_conf *lxc_conf)
                return -1;
        }
 
-       if (setup_rootfs(lxc_conf->rootfs)) {
+       if (setup_rootfs(lxc_conf->rootfs, lxc_conf->pivotdir)) {
                ERROR("failed to set rootfs for '%s'", name);
                return -1;
        }
index c4e5deb6121aa84225629d903e7fd78e0c3b6424..c9657c9054e1c34c1ef2cf110183539a10279d7e 100644 (file)
@@ -157,6 +157,7 @@ struct lxc_tty_info {
  */
 struct lxc_conf {
        char *rootfs;
+       char *pivotdir;
        char *fstab;
        int tty;
        int pts;
@@ -191,6 +192,7 @@ extern int conf_has(const char *name, const char *info);
 
 #define conf_has_fstab(__name)   conf_has(__name, "fstab")
 #define conf_has_rootfs(__name)  conf_has(__name, "rootfs")
+#define conf_has_pivotdir(__name) conf_has(__name, "pivotdir")
 #define conf_has_utsname(__name) conf_has(__name, "utsname")
 #define conf_has_network(__name) conf_has(__name, "network")
 #define conf_has_console(__name) conf_has(__name, "console")
index a5b626378e7178e49dcc4728103c773582a33456..6e15d09026bc7da4e13b12e905d7a55dd9296aaf 100644 (file)
@@ -45,6 +45,7 @@ static int config_tty(const char *, char *, struct lxc_conf *);
 static int config_cgroup(const char *, char *, struct lxc_conf *);
 static int config_mount(const char *, char *, struct lxc_conf *);
 static int config_rootfs(const char *, char *, struct lxc_conf *);
+static int config_pivotdir(const char *, char *, struct lxc_conf *);
 static int config_utsname(const char *, char *, struct lxc_conf *);
 static int config_network_type(const char *, char *, struct lxc_conf *);
 static int config_network_flags(const char *, char *, struct lxc_conf *);
@@ -72,6 +73,7 @@ static struct config config[] = {
        { "lxc.cgroup",               config_cgroup               },
        { "lxc.mount",                config_mount                },
        { "lxc.rootfs",               config_rootfs               },
+       { "lxc.pivotdir",             config_pivotdir             },
        { "lxc.utsname",              config_utsname              },
        { "lxc.network.type",         config_network_type         },
        { "lxc.network.flags",        config_network_flags        },
@@ -580,6 +582,22 @@ static int config_rootfs(const char *key, char *value, struct lxc_conf *lxc_conf
        return 0;
 }
 
+static int config_pivotdir(const char *key, char *value, struct lxc_conf *lxc_conf)
+{
+       if (strlen(value) >= MAXPATHLEN) {
+               ERROR("%s path is too long", value);
+               return -1;
+       }
+
+       lxc_conf->pivotdir = strdup(value);
+       if (!lxc_conf->pivotdir) {
+               SYSERROR("failed to duplicate string %s", value);
+               return -1;
+       }
+
+       return 0;
+}
+
 static int config_utsname(const char *key, char *value, struct lxc_conf *lxc_conf)
 {
        struct utsname *utsname;