Hi All,
The lxc tools can be run as non-root with all the needed capabilities
set by lxc-setcap via the file capabilities. The command run by lxc
won't have these privileges of course.
In the execution flow of lxc, there are two processes: the first process
of the container and its parent.
The container's init lxc code runs with privilege until we exec the
command (the kernel removes the capabilities).
The container's init parent runs with privilege until the end because it
needs them to do some cleanup before exiting (eg. removing the cgroup).
So at any time, we can't drop definitively the capabilities.
If I refer the document "Secure programmer: Minimizing privileges"
http://www.ibm.com/developerworks/linux/library/l-sppriv.html
The following section suggests to drop and reacquire the privilege
during code execution.
"... If you can't permanently give up the privilege, then you can at
least temporarily drop the privilege as often as possible. This isn't as
good as permanently dropping the privilege, since if an attacker can
take control of your program, the attacker can re-enable the privilege
and exploit it. Still, it's worth doing. Many attacks only work if they
trick the privileged program into doing something unintended while its
privileges are enabled (for example, by creating weird symbolic links
and hard links). If the program doesn't normally have its privileges
enabled, it's harder for an attacker to exploit the program."
But if I look at the lxc code, the number of syscalls with the privilege
needed is very important, so enabling the capabilities, call the syscall
and disable the capabilities again, will be intrusive and will modify
all the lxc code, with the disadvantage of adding more overhead at
container startup by increasing the number of syscalls.
So I am wondering if it's worth to do that considering the following:
* the code is not a web server, sendmail or sshd
* the privilege are dropped definitively for the application
* lxc is opensource, so it is easy for a hacker to check where is a
buffer overflow and re-enable the capabilities
* the compiler sets -f-stack-protector by default protecting lxc for
buffer overflows
* inherited capabilities are not set, so if a buffer overflow succeed,
it won't be able to exec anything until the code injection copy the
permitted capabilities to the inherited ones.
1) Shall we consider a buffer overflow is a bug not a security breach?
2) Shall we wrap the syscalls with privilege ?
3) Shall we bound with privilege a large scope of lxc code like
lxc_setup or lxc_spawn reducing the number of caps flip/flop ?
I put in attachement 3 patches illustrating a part of the modifications
for dropping / getting the capabilities.
Thanks
-- Daniel
---
src/lxc/cgroup.c | 16 ++++++++--------
src/lxc/conf.c | 40 +++++++++++++++++++++++++---------------
src/lxc/lxc_execute.c | 3 +++
src/lxc/lxc_start.c | 3 +++
src/lxc/namespace.c | 8 +++++---
src/lxc/network.c | 27 +++++++++++++++------------
src/lxc/start.c | 3 ++-
7 files changed, 61 insertions(+), 39 deletions(-)
Index: lxc/src/lxc/cgroup.c
===================================================================
--- lxc.orig/src/lxc/cgroup.c
+++ lxc/src/lxc/cgroup.c
@@ -38,10 +38,10 @@
#include "error.h"
#include "config.h"
-
-#include <lxc/log.h>
-#include <lxc/cgroup.h>
-#include <lxc/start.h>
+#include "caps.h"
+#include "log.h"
+#include "cgroup.h"
+#include "start.h"
lxc_log_define(lxc_cgroup, lxc);
@@ -103,7 +103,7 @@ int lxc_rename_nsgroup(const char *name,
/* there is a previous cgroup, assume it is empty, otherwise
* that fails */
if (!access(newname, F_OK)) {
- ret = rmdir(newname);
+ ret = lxc_privilegied(rmdir(newname));
if (ret) {
SYSERROR("failed to remove previous cgroup '%s'",
newname);
@@ -111,7 +111,7 @@ int lxc_rename_nsgroup(const char *name,
}
}
- ret = rename(oldname, newname);
+ ret = lxc_privilegied(rename(oldname, newname));
if (ret)
SYSERROR("failed to rename cgroup %s->%s", oldname, newname);
else
@@ -133,7 +133,7 @@ int lxc_unlink_nsgroup(const char *name)
}
snprintf(nsgroup, MAXPATHLEN, "%s/%s", cgroup, name);
- ret = rmdir(nsgroup);
+ ret = lxc_privilegied(rmdir(nsgroup));
if (ret)
SYSERROR("failed to remove cgroup '%s'", nsgroup);
else
@@ -181,7 +181,7 @@ int lxc_cgroup_set(const char *name, con
return -1;
}
- ret = write(fd, value, strlen(value));
+ ret = lxc_privilegied(write(fd, value, strlen(value)));
if (ret < 0) {
ERROR("write %s : %s", path, strerror(errno));
goto out;
Index: lxc/src/lxc/lxc_execute.c
===================================================================
--- lxc.orig/src/lxc/lxc_execute.c
+++ lxc/src/lxc/lxc_execute.c
@@ -92,6 +92,9 @@ int main(int argc, char *argv[])
char *rcfile;
struct lxc_conf *conf;
+ if (lxc_caps_init())
+ return -1;
+
lxc_list_init(&defines);
if (lxc_arguments_parse(&my_args, argc, argv))
Index: lxc/src/lxc/namespace.c
===================================================================
--- lxc.orig/src/lxc/namespace.c
+++ lxc/src/lxc/namespace.c
@@ -32,6 +32,7 @@
#include <fcntl.h>
#include "namespace.h"
+#include "caps.h"
#include "log.h"
#ifndef __NR_setns
@@ -83,10 +84,11 @@ pid_t lxc_clone(int (*fn)(void *), void
pid_t ret;
#ifdef __ia64__
- ret = __clone2(do_clone, stack,
- stack_size, flags | SIGCHLD, &clone_arg);
+ ret = lxc_privilegied(__clone2(do_clone, stack, stack_size,
+ flags | SIGCHLD, &clone_arg));
#else
- ret = clone(do_clone, stack, flags | SIGCHLD, &clone_arg);
+ ret = lxc_privilegied(clone(do_clone, stack,
+ flags | SIGCHLD, &clone_arg));
#endif
if (ret < 0)
ERROR("failed to clone(0x%x): %s", flags, strerror(errno));
Index: lxc/src/lxc/start.c
===================================================================
--- lxc.orig/src/lxc/start.c
+++ lxc/src/lxc/start.c
@@ -113,6 +113,7 @@ int signalfd(int fd, const sigset_t *mas
#endif
#include "start.h"
+#include "caps.h"
#include "conf.h"
#include "cgroup.h"
#include "log.h"
@@ -436,7 +437,7 @@ static int do_start(void *data)
goto out_warn_father;
}
- if (prctl(PR_CAPBSET_DROP, CAP_SYS_BOOT, 0, 0, 0)) {
+ if (lxc_privilegied(prctl(PR_CAPBSET_DROP, CAP_SYS_BOOT, 0, 0, 0))) {
SYSERROR("failed to remove CAP_SYS_BOOT capability");
return -1;
}
Index: lxc/src/lxc/conf.c
===================================================================
--- lxc.orig/src/lxc/conf.c
+++ lxc/src/lxc/conf.c
@@ -48,6 +48,7 @@
#include <libgen.h>
#include "network.h"
+#include "caps.h"
#include "error.h"
#include "parse.h"
#include "config.h"
@@ -201,11 +202,12 @@ static int configure_find_fstype_cb(char
fstype += lxc_char_left_gc(fstype, strlen(fstype));
fstype[lxc_char_right_gc(fstype, strlen(fstype))] = '\0';
- if (mount(cbarg->rootfs, cbarg->testdir, fstype, cbarg->mntopt, NULL))
+ if (lxc_privilegied(mount(cbarg->rootfs, cbarg->testdir,
+ fstype, cbarg->mntopt, NULL)))
return 0;
/* found ! */
- umount(cbarg->testdir);
+ lxc_privilegied(umount(cbarg->testdir));
strcpy(cbarg->fstype, fstype);
return 1;
@@ -396,7 +398,8 @@ static int setup_tty(const struct lxc_ro
* to check the file is present or not because it fails
* with EACCES errno and I don't know why :( */
- if (mount(pty_info->name, path, "none", MS_BIND, 0)) {
+ if (lxc_privilegied(mount(pty_info->name, path, "none",
+ MS_BIND, 0))) {
WARN("failed to mount '%s'->'%s'",
pty_info->name, path);
continue;
@@ -501,7 +504,7 @@ static int umount_oldrootfs(const char *
lxc_list_for_each(iterator, &mountlist) {
/* umount normally */
- if (!umount(iterator->elem)) {
+ if (!lxc_privilegied(umount(iterator->elem))) {
DEBUG("umounted '%s'", (char *)iterator->elem);
lxc_list_del(iterator);
continue;
@@ -564,7 +567,7 @@ static int setup_rootfs_pivot_root(const
DEBUG("mountpoint for old rootfs is '%s'", path);
/* pivot_root into our new root fs */
- if (pivot_root(".", path)) {
+ if (lxc_privilegied(pivot_root(".", path))) {
SYSERROR("pivot_root syscall failed");
return -1;
}
@@ -606,7 +609,8 @@ static int setup_rootfs(const struct lxc
return -1;
}
- if (mount(rootfs->path, mpath, "none", MS_BIND|MS_REC, NULL)) {
+ if (lxc_privilegied(mount(rootfs->path, mpath, "none",
+ MS_BIND|MS_REC, NULL))) {
SYSERROR("failed to mount '%s'->'%s'", rootfs->path, mpath);
return -1;
}
@@ -626,12 +630,14 @@ static int setup_pts(int pts)
if (!pts)
return 0;
- if (!access("/dev/pts/ptmx", F_OK) && umount("/dev/pts")) {
+ if (!access("/dev/pts/ptmx", F_OK) &&
+ lxc_privilegied(umount("/dev/pts"))) {
SYSERROR("failed to umount 'dev/pts'");
return -1;
}
- if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, "newinstance,ptmxmode=0666")) {
+ if (lxc_privilegied(mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL,
+ "newinstance,ptmxmode=0666"))) {
SYSERROR("failed to mount a new instance of '/dev/pts'");
return -1;
}
@@ -644,7 +650,8 @@ static int setup_pts(int pts)
}
/* fallback here, /dev/pts/ptmx exists just mount bind */
- if (mount("/dev/pts/ptmx", "/dev/ptmx", "none", MS_BIND, 0)) {
+ if (lxc_privilegied(mount("/dev/pts/ptmx",
+ "/dev/ptmx", "none", MS_BIND, 0))) {
SYSERROR("mount failed '/dev/pts/ptmx'->'/dev/ptmx'");
return -1;
}
@@ -688,7 +695,7 @@ static int setup_console(const struct lx
return -1;
}
- if (mount(console->name, path, "none", MS_BIND, 0)) {
+ if (lxc_privilegied(mount(console->name, path, "none", MS_BIND, 0))) {
ERROR("failed to mount '%s' on '%s'", console->name, path);
return -1;
}
@@ -798,8 +805,9 @@ static int mount_file_entries(FILE *file
goto out;
}
- if (mount(mntent->mnt_fsname, mntent->mnt_dir,
- mntent->mnt_type, mntflags & ~MS_REMOUNT, mntdata)) {
+ if (lxc_privilegied(mount(mntent->mnt_fsname, mntent->mnt_dir,
+ mntent->mnt_type,
+ mntflags & ~MS_REMOUNT, mntdata))) {
SYSERROR("failed to mount '%s' on '%s'",
mntent->mnt_fsname, mntent->mnt_dir);
goto out;
@@ -812,9 +820,11 @@ static int mount_file_entries(FILE *file
"or remount options",
mntent->mnt_fsname, mntent->mnt_dir);
- if (mount(mntent->mnt_fsname, mntent->mnt_dir,
- mntent->mnt_type,
- mntflags | MS_REMOUNT, mntdata)) {
+ if (lxc_privilegied(mount(mntent->mnt_fsname,
+ mntent->mnt_dir,
+ mntent->mnt_type,
+ mntflags | MS_REMOUNT,
+ mntdata))) {
SYSERROR("failed to mount '%s' on '%s'",
mntent->mnt_fsname, mntent->mnt_dir);
goto out;
Index: lxc/src/lxc/lxc_start.c
===================================================================
--- lxc.orig/src/lxc/lxc_start.c
+++ lxc/src/lxc/lxc_start.c
@@ -99,6 +99,9 @@ int main(int argc, char *argv[])
'\0',
};
+ if (lxc_caps_init())
+ return -1;
+
lxc_list_init(&defines);
if (lxc_arguments_parse(&my_args, argc, argv))
Index: lxc/src/lxc/network.c
===================================================================
--- lxc.orig/src/lxc/network.c
+++ lxc/src/lxc/network.c
@@ -47,6 +47,8 @@
#include <nl.h>
#include <network.h>
+#include "caps.h"
+
#ifndef IFLA_LINKMODE
# define IFLA_LINKMODE 17
#endif
@@ -115,7 +117,7 @@ int lxc_device_move(int ifindex, pid_t p
if (nla_put_u32(nlmsg, IFLA_NET_NS_PID, pid))
goto out;
- err = netlink_transaction(&nlh, nlmsg, nlmsg);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, nlmsg));
out:
netlink_close(&nlh);
nlmsg_free(nlmsg);
@@ -162,7 +164,7 @@ int lxc_device_delete(const char *name)
if (nla_put_string(nlmsg, IFLA_IFNAME, name))
goto out;
- err = netlink_transaction(&nlh, nlmsg, answer);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, answer));
out:
netlink_close(&nlh);
nlmsg_free(answer);
@@ -197,7 +199,7 @@ int lxc_device_delete_index(int ifindex)
nlmsg->nlmsghdr.nlmsg_flags = NLM_F_ACK|NLM_F_REQUEST;
nlmsg->nlmsghdr.nlmsg_type = RTM_DELLINK;
- err = netlink_transaction(&nlh, nlmsg, answer);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, answer));
out:
netlink_close(&nlh);
nlmsg_free(answer);
@@ -244,7 +246,7 @@ static int device_set_flag(const char *n
nlmsg->nlmsghdr.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK;
nlmsg->nlmsghdr.nlmsg_type = RTM_NEWLINK;
- err = netlink_transaction(&nlh, nlmsg, answer);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, answer));
out:
netlink_close(&nlh);
nlmsg_free(nlmsg);
@@ -292,7 +294,7 @@ int lxc_device_set_mtu(const char *name,
if (nla_put_u32(nlmsg, IFLA_MTU, mtu))
goto out;
- err = netlink_transaction(&nlh, nlmsg, answer);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, answer));
out:
netlink_close(&nlh);
nlmsg_free(nlmsg);
@@ -354,7 +356,7 @@ int lxc_device_rename(const char *oldnam
if (nla_put_string(nlmsg, IFLA_IFNAME, newname))
goto out;
- err = netlink_transaction(&nlh, nlmsg, answer);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, answer));
out:
netlink_close(&nlh);
nlmsg_free(answer);
@@ -429,7 +431,7 @@ int lxc_veth_create(const char *name1, c
if (nla_put_string(nlmsg, IFLA_IFNAME, name1))
goto out;
- err = netlink_transaction(&nlh, nlmsg, answer);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, answer));
out:
netlink_close(&nlh);
nlmsg_free(answer);
@@ -582,7 +584,7 @@ int lxc_macvlan_create(const char *maste
if (nla_put_string(nlmsg, IFLA_IFNAME, name))
goto out;
- err = netlink_transaction(&nlh, nlmsg, answer);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, answer));
out:
netlink_close(&nlh);
nlmsg_free(answer);
@@ -594,11 +596,12 @@ static int proc_sys_net_write(const char
{
int fd, err = 0;
- fd = open(path, O_WRONLY);
+ fd = lxc_privilegied(open(path, O_WRONLY));
if (fd < 0)
return -errno;
- if (write(fd, value, strlen(value)) < 0)
+ err = write(fd, value, strlen(value));
+ if (err < 0)
err = -errno;
close(fd);
@@ -752,7 +755,7 @@ static int ip_addr_add(int family, int i
memcmp(acast, &in6addr_any, sizeof(in6addr_any))))
goto out;
- err = netlink_transaction(&nlh, nlmsg, answer);
+ err = lxc_privilegied(netlink_transaction(&nlh, nlmsg, answer));
out:
netlink_close(&nlh);
nlmsg_free(answer);
@@ -795,7 +798,7 @@ int lxc_bridge_attach(const char *bridge
strncpy(ifr.ifr_name, bridge, IFNAMSIZ);
ifr.ifr_ifindex = index;
- err = ioctl(fd, SIOCBRADDIF, &ifr);
+ err = lxc_privilegied(ioctl(fd, SIOCBRADDIF, &ifr));
close(fd);
if (err)
err = -errno;
Subject: add a macro to wrap a privilegied function
From: Daniel Lezcano <dlezc...@fr.ibm.com>
This macro is a helper to call a function into a privilegied section.
Signed-off-by: Daniel Lezcano <dlezc...@fr.ibm.com>
---
src/lxc/caps.h | 9 +++++++++
1 file changed, 9 insertions(+)
Index: lxc/src/lxc/caps.h
===================================================================
--- lxc.orig/src/lxc/caps.h
+++ lxc/src/lxc/caps.h
@@ -25,4 +25,13 @@
int lxc_caps_down(void);
int lxc_caps_up(void);
int lxc_caps_init(void);
+
+#define lxc_privilegied(__lxc_function) \
+ ({ \
+ int __ret; \
+ lxc_caps_up(); \
+ __ret = __lxc_function; \
+ lxc_caps_down(); \
+ __ret; \
+ })
#endif
Subject: remove/restore effective capabilities
From: Daniel Lezcano <dlezc...@fr.ibm.com
This patch adds the functions to drop the 'effective' capabilities and
restore them from the 'permitted' capabilities.
When the command is run as 'root' we do nothing.
When the command is run as 'lambda' user, we drop the effective capabilities
When the command is run as 'root' but real uid is not root, we keep the capabilies,
switch to real uid, and drop the effective capabilities.
This approach is compatible for root user, lambda + file capabilities
and lambda + setuid.
Signed-off-by: Daniel Lezcano <dlezc...@fr.ibm.com>
---
src/lxc/Makefile.am | 2
src/lxc/caps.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lxc/caps.h | 28 ++++++++++
3 files changed, 165 insertions(+)
Index: lxc/src/lxc/Makefile.am
===================================================================
--- lxc.orig/src/lxc/Makefile.am
+++ lxc/src/lxc/Makefile.am
@@ -5,6 +5,7 @@ pkginclude_HEADERS = \
monitor.h \
utils.h \
namespace.h \
+ caps.h \
lxc.h \
cgroup.h \
conf.h \
@@ -44,6 +45,7 @@ liblxc_so_SOURCES = \
rtnl.c rtnl.h \
genl.c genl.h \
\
+ caps.c caps.h \
mainloop.c mainloop.h \
af_unix.c af_unix.h \
\
Index: lxc/src/lxc/caps.c
===================================================================
--- /dev/null
+++ lxc/src/lxc/caps.c
@@ -0,0 +1,135 @@
+/*
+ * lxc: linux Container library
+ *
+ * (C) Copyright IBM Corp. 2007, 2008
+ *
+ * Authors:
+ * Daniel Lezcano <dlezcano at fr.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <sys/prctl.h>
+#include <sys/capability.h>
+
+#include "log.h"
+
+lxc_log_define(lxc_caps, lxc);
+
+int lxc_caps_down(void)
+{
+ cap_t caps;
+ int ret;
+
+ caps = cap_get_proc();
+ if (!caps) {
+ ERROR("failed to cap_init: %m");
+ return -1;
+ }
+
+ ret = cap_clear_flag(caps, CAP_EFFECTIVE);
+ if (ret) {
+ ERROR("failed to cap_clear_flag: %m");
+ goto out;
+ }
+
+ ret = cap_set_proc(caps);
+ if (ret) {
+ ERROR("failed to cap_set_proc: %m");
+ goto out;
+ }
+
+out:
+ cap_free(caps);
+ return 0;
+}
+
+int lxc_caps_up(void)
+{
+ cap_t caps;
+ cap_value_t cap;
+ int ret;
+
+ caps = cap_get_proc();
+ if (!caps) {
+ ERROR("failed to cap_init: %m");
+ return -1;
+ }
+
+ for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
+
+ cap_flag_value_t flag;
+
+ ret = cap_get_flag(caps, cap, CAP_PERMITTED, &flag);
+ if (ret) {
+ ERROR("failed to cap_get_flag: %m");
+ goto out;
+ }
+
+ ret = cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, flag);
+ if (ret) {
+ ERROR("failed to cap_set_flag: %m");
+ goto out;
+ }
+ }
+
+ ret = cap_set_proc(caps);
+ if (ret) {
+ ERROR("failed to cap_set_proc: %m");
+ goto out;
+ }
+
+out:
+ cap_free(caps);
+ return 0;
+}
+
+int lxc_caps_init(void)
+{
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+ uid_t euid = geteuid();
+
+ if (!uid) {
+ INFO("command is run as 'root'");
+ return 0;
+ }
+
+ if (uid && !euid) {
+ INFO("command is run as setuid root (uid : %d)", uid);
+
+ if (prctl(PR_SET_KEEPCAPS, 1)) {
+ ERROR("failed to 'PR_SET_KEEPCAPS': %m");
+ return -1;
+ }
+
+ if (setresgid(gid, gid, gid)) {
+ ERROR("failed to change gid to '%d': %m", gid);
+ return -1;
+ }
+
+ if (setresuid(uid, uid, uid)) {
+ ERROR("failed to change uid to '%d': %m", uid);
+ return -1;
+ }
+ }
+
+ if (uid == euid)
+ INFO("command is run as user '%d'", uid);
+
+ return lxc_caps_down();
+}
Index: lxc/src/lxc/caps.h
===================================================================
--- /dev/null
+++ lxc/src/lxc/caps.h
@@ -0,0 +1,28 @@
+/*
+ * lxc: linux Container library
+ *
+ * (C) Copyright IBM Corp. 2007, 2008
+ *
+ * Authors:
+ * Daniel Lezcano <dlezcano at fr.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _caps_h
+#define _caps_h
+int lxc_caps_down(void);
+int lxc_caps_up(void);
+int lxc_caps_init(void);
+#endif
------------------------------------------------------------------------------
This SF.net email is sponsored by Sprint
What will you do first with EVO, the first 4G phone?
Visit sprint.com/first -- http://p.sf.net/sfu/sprint-com-first
_______________________________________________
Lxc-devel mailing list
Lxc-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/lxc-devel