From 0a3ac2c40b669c4631bf25c7069916cab1a1be97 Mon Sep 17 00:00:00 2001 Message-Id: <0a3ac2c40b669c4631bf25c7069916cab1a1be97@dist-git> From: "Daniel P. Berrange" Date: Tue, 18 Feb 2014 15:45:38 -0700 Subject: [PATCH] Add helper for running code in separate namespaces https://bugzilla.redhat.com/show_bug.cgi?id=1045643 prereq of CVE-2013-6456 Implement virProcessRunInMountNamespace, which runs callback of type virProcessNamespaceCallback in a container namespace. This uses a child process to run the callback, since you can't change the mount namespace of a thread. This implies that callbacks have to be careful about what code they run due to async safety rules. Idea by Dan Berrange, based on an initial report by Reco at http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=732394 Signed-off-by: Daniel Berrange Signed-off-by: Eric Blake (cherry picked from commit 7c72ef6f555f1f9844d51be2f38f078bc908652c) Signed-off-by: Jiri Denemark --- src/libvirt_private.syms | 1 + src/util/virprocess.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++ src/util/virprocess.h | 11 +++++ 3 files changed, 118 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e5f80e1..12f70cb 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1829,6 +1829,7 @@ virProcessGetNamespaces; virProcessGetStartTime; virProcessKill; virProcessKillPainfully; +virProcessRunInMountNamespace; virProcessSetAffinity; virProcessSetMaxFiles; virProcessSetMaxMemLock; diff --git a/src/util/virprocess.c b/src/util/virprocess.c index 9fc3207..868dd04 100644 --- a/src/util/virprocess.c +++ b/src/util/virprocess.c @@ -46,6 +46,7 @@ #include "virlog.h" #include "virutil.h" #include "virstring.h" +#include "vircommand.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -847,3 +848,108 @@ int virProcessGetStartTime(pid_t pid, return 0; } #endif + + +#ifdef HAVE_SETNS +static int virProcessNamespaceHelper(int errfd, + pid_t pid, + virProcessNamespaceCallback cb, + void *opaque) +{ + char *path; + int fd = -1; + int ret = -1; + + if (virAsprintf(&path, "/proc/%llu/ns/mnt", (unsigned long long)pid) < 0) + goto cleanup; + + if ((fd = open(path, O_RDONLY)) < 0) { + virReportSystemError(errno, "%s", + _("Kernel does not provide mount namespace")); + goto cleanup; + } + + if (setns(fd, 0) < 0) { + virReportSystemError(errno, "%s", + _("Unable to enter mount namespace")); + goto cleanup; + } + + ret = cb(pid, opaque); + + cleanup: + if (ret < 0) { + virErrorPtr err = virGetLastError(); + if (err) { + size_t len = strlen(err->message) + 1; + ignore_value(safewrite(errfd, err->message, len)); + } + } + VIR_FREE(path); + VIR_FORCE_CLOSE(fd); + return ret; +} + +/* Run cb(opaque) in the mount namespace of pid. Return -1 with error + * message raised if we fail to run the child, if the child dies from + * a signal, or if the child has status 1; otherwise return the exit + * status of the child. The callback will be run in a child process + * so must be careful to only use async signal safe functions. + */ +int +virProcessRunInMountNamespace(pid_t pid, + virProcessNamespaceCallback cb, + void *opaque) +{ + int ret = -1; + pid_t child = -1; + int errfd[2] = { -1, -1 }; + + if (pipe(errfd) < 0) { + virReportSystemError(errno, "%s", + _("Cannot create pipe for child")); + return -1; + } + + ret = virFork(&child); + + if (ret < 0 || child < 0) { + if (child == 0) + _exit(1); + + /* parent */ + virProcessAbort(child); + goto cleanup; + } + + if (child == 0) { + VIR_FORCE_CLOSE(errfd[0]); + ret = virProcessNamespaceHelper(errfd[1], pid, + cb, opaque); + VIR_FORCE_CLOSE(errfd[1]); + _exit(ret < 0 ? 1 : 0); + } else { + char *buf = NULL; + VIR_FORCE_CLOSE(errfd[1]); + + ignore_value(virFileReadHeaderFD(errfd[0], 1024, &buf)); + ret = virProcessWait(child, NULL); + VIR_FREE(buf); + } + +cleanup: + VIR_FORCE_CLOSE(errfd[0]); + VIR_FORCE_CLOSE(errfd[1]); + return ret; +} +#else /* !HAVE_SETNS */ +int +virProcessRunInMountNamespace(pid_t pid ATTRIBUTE_UNUSED, + virProcessNamespaceCallback cb ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + virReportSystemError(ENOSYS, "%s", + _("Mount namespaces are not available on this platform")); + return -1; +} +#endif diff --git a/src/util/virprocess.h b/src/util/virprocess.h index 9f77bc5..5c173b0 100644 --- a/src/util/virprocess.h +++ b/src/util/virprocess.h @@ -60,4 +60,15 @@ int virProcessSetNamespaces(size_t nfdlist, int virProcessSetMaxMemLock(pid_t pid, unsigned long long bytes); int virProcessSetMaxProcesses(pid_t pid, unsigned int procs); int virProcessSetMaxFiles(pid_t pid, unsigned int files); + +/* Callback to run code within the mount namespace tied to the given + * pid. This function must use only async-signal-safe functions, as + * it gets run after a fork of a multi-threaded process. The return + * value of this function is passed to _exit(), except that a + * negative value is treated as an error. */ +typedef int (*virProcessNamespaceCallback)(pid_t pid, void *opaque); + +int virProcessRunInMountNamespace(pid_t pid, + virProcessNamespaceCallback cb, + void *opaque); #endif /* __VIR_PROCESS_H__ */ -- 1.9.0