Skip to content

Commit 592fd47

Browse files
hallynstgraber
authored andcommitted
CVE-2015-1335: Protect container mounts against symlinks
When a container starts up, lxc sets up the container's inital fstree by doing a bunch of mounting, guided by the container configuration file. The container config is owned by the admin or user on the host, so we do not try to guard against bad entries. However, since the mount target is in the container, it's possible that the container admin could divert the mount with symbolic links. This could bypass proper container startup (i.e. confinement of a root-owned container by the restrictive apparmor policy, by diverting the required write to /proc/self/attr/current), or bypass the (path-based) apparmor policy by diverting, say, /proc to /mnt in the container. To prevent this, 1. do not allow mounts to paths containing symbolic links 2. do not allow bind mounts from relative paths containing symbolic links. Details: Define safe_mount which ensures that the container has not inserted any symbolic links into any mount targets for mounts to be done during container setup. The host's mount path may contain symbolic links. As it is under the control of the administrator, that's ok. So safe_mount begins the check for symbolic links after the rootfs->mount, by opening that directory. It opens each directory along the path using openat() relative to the parent directory using O_NOFOLLOW. When the target is reached, it mounts onto /proc/self/fd/<targetfd>. Use safe_mount() in mount_entry(), when mounting container proc, and when needed. In particular, safe_mount() need not be used in any case where: 1. the mount is done in the container's namespace 2. the mount is for the container's rootfs 3. the mount is relative to a tmpfs or proc/sysfs which we have just safe_mount()ed ourselves Since we were using proc/net as a temporary placeholder for /proc/sys/net during container startup, and proc/net is a symbolic link, use proc/tty instead. Update the lxc.container.conf manpage with details about the new restrictions. Finally, add a testcase to test some symbolic link possibilities. Reported-by: Roman Fiedler Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com> Acked-by: Stéphane Graber <stgraber@ubuntu.com>
1 parent f2e4ddd commit 592fd47

File tree

8 files changed

+363
-20
lines changed

8 files changed

+363
-20
lines changed

Diff for: doc/lxc.container.conf.sgml.in

+12
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,18 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
760760
container. This is useful to mount /etc, /var or /home for
761761
examples.
762762
</para>
763+
<para>
764+
NOTE - LXC will generally ensure that mount targets and relative
765+
bind-mount sources are properly confined under the container
766+
root, to avoid attacks involving over-mounting host directories
767+
and files. (Symbolic links in absolute mount sources are ignored)
768+
However, if the container configuration first mounts a directory which
769+
is under the control of the container user, such as /home/joe, into
770+
the container at some <filename>path</filename>, and then mounts
771+
under <filename>path</filename>, then a TOCTTOU attack would be
772+
possible where the container user modifies a symbolic link under
773+
his home directory at just the right time.
774+
</para>
763775
<variablelist>
764776
<varlistentry>
765777
<term>

Diff for: src/lxc/cgfs.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -1363,7 +1363,10 @@ static bool cgroupfs_mount_cgroup(void *hdata, const char *root, int type)
13631363
if (!path)
13641364
return false;
13651365
snprintf(path, bufsz, "%s/sys/fs/cgroup", root);
1366-
r = mount("cgroup_root", path, "tmpfs", MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RELATIME, "size=10240k,mode=755");
1366+
r = safe_mount("cgroup_root", path, "tmpfs",
1367+
MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RELATIME,
1368+
"size=10240k,mode=755",
1369+
root);
13671370
if (r < 0) {
13681371
SYSERROR("could not mount tmpfs to /sys/fs/cgroup in the container");
13691372
return false;

Diff for: src/lxc/cgmanager.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1477,7 +1477,7 @@ static bool cgm_bind_dir(const char *root, const char *dirname)
14771477
}
14781478

14791479
/* mount a tmpfs there so we can create subdirs */
1480-
if (mount("cgroup", cgpath, "tmpfs", 0, "size=10000,mode=755")) {
1480+
if (safe_mount("cgroup", cgpath, "tmpfs", 0, "size=10000,mode=755", root)) {
14811481
SYSERROR("Failed to mount tmpfs at %s", cgpath);
14821482
return false;
14831483
}
@@ -1488,7 +1488,7 @@ static bool cgm_bind_dir(const char *root, const char *dirname)
14881488
return false;
14891489
}
14901490

1491-
if (mount(dirname, cgpath, "none", MS_BIND, 0)) {
1491+
if (safe_mount(dirname, cgpath, "none", MS_BIND, 0, root)) {
14921492
SYSERROR("Failed to bind mount %s to %s", dirname, cgpath);
14931493
return false;
14941494
}

Diff for: src/lxc/conf.c

+19-16
Original file line numberDiff line numberDiff line change
@@ -769,10 +769,11 @@ static int lxc_mount_auto_mounts(struct lxc_conf *conf, int flags, struct lxc_ha
769769
* 2.6.32...
770770
*/
771771
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, "proc", "%r/proc", "proc", MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL },
772-
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, "%r/proc/sys/net", "%r/proc/net", NULL, MS_BIND, NULL },
772+
/* proc/tty is used as a temporary placeholder for proc/sys/net which we'll move back in a few steps */
773+
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, "%r/proc/sys/net", "%r/proc/tty", NULL, MS_BIND, NULL },
773774
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, "%r/proc/sys", "%r/proc/sys", NULL, MS_BIND, NULL },
774775
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, NULL, "%r/proc/sys", NULL, MS_REMOUNT|MS_BIND|MS_RDONLY, NULL },
775-
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, "%r/proc/net", "%r/proc/sys/net", NULL, MS_MOVE, NULL },
776+
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, "%r/proc/tty", "%r/proc/sys/net", NULL, MS_MOVE, NULL },
776777
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, "%r/proc/sysrq-trigger", "%r/proc/sysrq-trigger", NULL, MS_BIND, NULL },
777778
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_MIXED, NULL, "%r/proc/sysrq-trigger", NULL, MS_REMOUNT|MS_BIND|MS_RDONLY, NULL },
778779
{ LXC_AUTO_PROC_MASK, LXC_AUTO_PROC_RW, "proc", "%r/proc", "proc", MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL },
@@ -815,7 +816,7 @@ static int lxc_mount_auto_mounts(struct lxc_conf *conf, int flags, struct lxc_ha
815816
}
816817
mflags = add_required_remount_flags(source, destination,
817818
default_mounts[i].flags);
818-
r = mount(source, destination, default_mounts[i].fstype, mflags, default_mounts[i].options);
819+
r = safe_mount(source, destination, default_mounts[i].fstype, mflags, default_mounts[i].options, conf->rootfs.path ? conf->rootfs.mount : NULL);
819820
saved_errno = errno;
820821
if (r < 0 && errno == ENOENT) {
821822
INFO("Mount source or target for %s on %s doesn't exist. Skipping.", source, destination);
@@ -1167,7 +1168,8 @@ static int mount_autodev(const char *name, const struct lxc_rootfs *rootfs, cons
11671168
return 0;
11681169
}
11691170

1170-
if (mount("none", path, "tmpfs", 0, "size=100000,mode=755")) {
1171+
if (safe_mount("none", path, "tmpfs", 0, "size=100000,mode=755",
1172+
rootfs->path ? rootfs->mount : NULL)) {
11711173
SYSERROR("Failed mounting tmpfs onto %s\n", path);
11721174
return false;
11731175
}
@@ -1252,7 +1254,8 @@ static int fill_autodev(const struct lxc_rootfs *rootfs)
12521254
return -1;
12531255
}
12541256
fclose(pathfile);
1255-
if (mount(hostpath, path, 0, MS_BIND, NULL) != 0) {
1257+
if (safe_mount(hostpath, path, 0, MS_BIND, NULL,
1258+
rootfs->path ? rootfs->mount : NULL) != 0) {
12561259
SYSERROR("Failed bind mounting device %s from host into container",
12571260
d->name);
12581261
return -1;
@@ -1505,7 +1508,7 @@ static int setup_dev_console(const struct lxc_rootfs *rootfs,
15051508
return -1;
15061509
}
15071510

1508-
if (mount(console->name, path, "none", MS_BIND, 0)) {
1511+
if (safe_mount(console->name, path, "none", MS_BIND, 0, rootfs->mount)) {
15091512
ERROR("failed to mount '%s' on '%s'", console->name, path);
15101513
return -1;
15111514
}
@@ -1560,7 +1563,7 @@ static int setup_ttydir_console(const struct lxc_rootfs *rootfs,
15601563
return 0;
15611564
}
15621565

1563-
if (mount(console->name, lxcpath, "none", MS_BIND, 0)) {
1566+
if (safe_mount(console->name, lxcpath, "none", MS_BIND, 0, rootfs->mount)) {
15641567
ERROR("failed to mount '%s' on '%s'", console->name, lxcpath);
15651568
return -1;
15661569
}
@@ -1710,13 +1713,13 @@ static char *get_field(char *src, int nfields)
17101713

17111714
static int mount_entry(const char *fsname, const char *target,
17121715
const char *fstype, unsigned long mountflags,
1713-
const char *data, int optional)
1716+
const char *data, int optional, const char *rootfs)
17141717
{
17151718
#ifdef HAVE_STATVFS
17161719
struct statvfs sb;
17171720
#endif
17181721

1719-
if (mount(fsname, target, fstype, mountflags & ~MS_REMOUNT, data)) {
1722+
if (safe_mount(fsname, target, fstype, mountflags & ~MS_REMOUNT, data, rootfs)) {
17201723
if (optional) {
17211724
INFO("failed to mount '%s' on '%s' (optional): %s", fsname,
17221725
target, strerror(errno));
@@ -1763,7 +1766,7 @@ static int mount_entry(const char *fsname, const char *target,
17631766
#endif
17641767

17651768
if (mount(fsname, target, fstype,
1766-
mountflags | MS_REMOUNT, data)) {
1769+
mountflags | MS_REMOUNT, data) < 0) {
17671770
if (optional) {
17681771
INFO("failed to mount '%s' on '%s' (optional): %s",
17691772
fsname, target, strerror(errno));
@@ -1843,7 +1846,7 @@ static int mount_entry_create_dir_file(const struct mntent *mntent,
18431846
}
18441847

18451848
static inline int mount_entry_on_generic(struct mntent *mntent,
1846-
const char* path)
1849+
const char* path, const char *rootfs)
18471850
{
18481851
unsigned long mntflags;
18491852
char *mntdata;
@@ -1863,7 +1866,7 @@ static inline int mount_entry_on_generic(struct mntent *mntent,
18631866
}
18641867

18651868
ret = mount_entry(mntent->mnt_fsname, path, mntent->mnt_type,
1866-
mntflags, mntdata, optional);
1869+
mntflags, mntdata, optional, rootfs);
18671870

18681871
free(mntdata);
18691872

@@ -1872,7 +1875,7 @@ static inline int mount_entry_on_generic(struct mntent *mntent,
18721875

18731876
static inline int mount_entry_on_systemfs(struct mntent *mntent)
18741877
{
1875-
return mount_entry_on_generic(mntent, mntent->mnt_dir);
1878+
return mount_entry_on_generic(mntent, mntent->mnt_dir, NULL);
18761879
}
18771880

18781881
static int mount_entry_on_absolute_rootfs(struct mntent *mntent,
@@ -1919,7 +1922,7 @@ static int mount_entry_on_absolute_rootfs(struct mntent *mntent,
19191922
return -1;
19201923
}
19211924

1922-
return mount_entry_on_generic(mntent, path);
1925+
return mount_entry_on_generic(mntent, path, rootfs->mount);
19231926
}
19241927

19251928
static int mount_entry_on_relative_rootfs(struct mntent *mntent,
@@ -1935,7 +1938,7 @@ static int mount_entry_on_relative_rootfs(struct mntent *mntent,
19351938
return -1;
19361939
}
19371940

1938-
return mount_entry_on_generic(mntent, path);
1941+
return mount_entry_on_generic(mntent, path, rootfs);
19391942
}
19401943

19411944
static int mount_file_entries(const struct lxc_rootfs *rootfs, FILE *file,
@@ -3602,7 +3605,7 @@ void lxc_execute_bind_init(struct lxc_conf *conf)
36023605
fclose(pathfile);
36033606
}
36043607

3605-
ret = mount(path, destpath, "none", MS_BIND, NULL);
3608+
ret = safe_mount(path, destpath, "none", MS_BIND, NULL, conf->rootfs.mount);
36063609
if (ret < 0)
36073610
SYSERROR("Failed to bind lxc.init.static into container");
36083611
INFO("lxc.init.static bound into container at %s", path);

0 commit comments

Comments
 (0)