[libvirt] [PATCHv2 13/15] util: virSetUIDGIDWithCaps - change uid while keeping caps
Doug Goldstein
cardoe at gentoo.org
Sat Feb 16 05:20:17 UTC 2013
On Tue, Feb 12, 2013 at 2:15 PM, Laine Stump <laine at laine.org> wrote:
> Normally when a process' uid is changed to non-0, all the capabilities
> bits are cleared, even those explicitly set with calls to
> capng_update()/capng_apply() made immediately before setuid. And
> *after* the process' uid has been changed, it no longer has the
> necessary privileges to add capabilities back to the process.
>
> In order to set a non-0 uid while still maintaining any capabilities
> bits, it is necessary to either call capng_change_id() (which
> unfortunately doesn't currently call initgroups to setup auxiliary
> group membership), or to perform the small amount of calisthenics
> contained in the new utility function virSetUIDGIDWithCaps().
>
> Another very important difference between the capabilities
> setting/clearing in virSetUIDGIDWithCaps() and virCommand's
> virSetCapabilities() (which it will replace in the next patch) is that
> the new function properly clears the capabilities bounding set, so it
> will not be possible for a child process to set any new
> capabilities.
>
> A short description of what is done by virSetUIDGIDWithCaps():
>
> 1) clear all capabilities then set all those desired by the caller (in
> capBits) plus CAP_SETGID, CAP_SETUID, and CAP_SETPCAP (which is needed
> to change the capabilities bounding set).
>
> 2) call prctl(), telling it that we want to maintain current
> capabilities across an upcoming setuid().
>
> 3) switch to the new uid/gid
>
> 4) again call prctl(), telling it we will no longer want capabilities
> maintained if this process does another setuid().
>
> 5) clear the capabilities that we added to allow us to
> setuid/setgid/change the bounding set (unless they were also requested
> by the caller via the virCommand API).
>
> Because the modification/maintaining of capabilities is intermingled
> with setting the uid, this is necessarily done in a single function,
> rather than having two independent functions.
>
> Note that, due to the way that effective capabilities are computed (at
> time of execve) for a process that has uid != 0, the *file*
> capabilities of the binary being executed must also have the desired
> capabilities bit(s) set (see "man 7 capabilities"). This can be done
> with the "filecap" command. (e.g. "filecap /usr/bin/qemu-kvm sys_rawio").
> ---
> Change from V1:
> * properly cast when comparing gid/uid, and only short circuit for -1 (not 0)
> * fix // style comments
> * add ATTRIBUTE_UNUSED where appropriate for capBits argument.
>
> src/libvirt_private.syms | 1 +
> src/util/virutil.c | 111 +++++++++++++++++++++++++++++++++++++++++++++++
> src/util/virutil.h | 1 +
> 3 files changed, 113 insertions(+)
>
> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
> index dcdcb67..d8d5877 100644
> --- a/src/libvirt_private.syms
> +++ b/src/libvirt_private.syms
> @@ -1312,6 +1312,7 @@ virSetDeviceUnprivSGIO;
> virSetInherit;
> virSetNonBlock;
> virSetUIDGID;
> +virSetUIDGIDWithCaps;
> virSkipSpaces;
> virSkipSpacesAndBackslash;
> virSkipSpacesBackwards;
> diff --git a/src/util/virutil.c b/src/util/virutil.c
> index 0d7db00..28fcc2f 100644
> --- a/src/util/virutil.c
> +++ b/src/util/virutil.c
> @@ -60,6 +60,7 @@
> #endif
> #if WITH_CAPNG
> # include <cap-ng.h>
> +# include <sys/prctl.h>
> #endif
> #if defined HAVE_MNTENT_H && defined HAVE_GETMNTENT_R
> # include <mntent.h>
> @@ -2990,6 +2991,116 @@ virGetGroupName(gid_t gid ATTRIBUTE_UNUSED)
> }
> #endif /* HAVE_GETPWUID_R */
>
> +#if WITH_CAPNG
> +/* Set the real and effective uid and gid to the given values, while
> + * maintaining the capabilities indicated by bits in @capBits. return
> + * 0 on success, -1 on failure (the original system error remains in
> + * errno).
> + */
> +int
> +virSetUIDGIDWithCaps(uid_t uid, gid_t gid, unsigned long long capBits)
> +{
> + int ii, capng_ret, ret = -1;
> + bool need_setgid = false, need_setuid = false;
> + bool need_prctl = false, need_setpcap = false;
> +
> + /* First drop all caps except those in capBits + the extra ones we
> + * need to change uid/gid and change the capabilities bounding
> + * set.
> + */
> +
> + capng_clear(CAPNG_SELECT_BOTH);
> +
> + for (ii = 0; ii <= CAP_LAST_CAP; ii++) {
> + if (capBits & (1ULL << ii)) {
> + capng_update(CAPNG_ADD,
> + CAPNG_EFFECTIVE|CAPNG_INHERITABLE|
> + CAPNG_PERMITTED|CAPNG_BOUNDING_SET,
> + ii);
> + }
> + }
> +
> + if (gid != (gid_t)-1 &&
> + !capng_have_capability(CAPNG_EFFECTIVE, CAP_SETGID)) {
> + need_setgid = true;
> + capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETGID);
> + }
> + if (uid != (uid_t)-1 &&
> + !capng_have_capability(CAPNG_EFFECTIVE, CAP_SETUID)) {
> + need_setuid = true;
> + capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETUID);
> + }
> +# ifdef PR_CAPBSET_DROP
> + /* If newer kernel, we need also need setpcap to change the bounding set */
> + if ((capBits || need_setgid || need_setuid) &&
> + !capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP)) {
> + need_setpcap = true;
> + }
> + if (need_setpcap)
> + capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETPCAP);
> +# endif
> +
> + need_prctl = capBits || need_setgid || need_setuid || need_setpcap;
> +
> + /* Tell system we want to keep caps across uid change */
> + if (need_prctl && prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) {
> + virReportSystemError(errno, "%s",
> + _("prctl failed to set KEEPCAPS"));
> + goto cleanup;
> + }
> +
> + /* Change to the temp capabilities */
> + if ((capng_ret = capng_apply(CAPNG_SELECT_BOTH)) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("cannot apply process capabilities %d"), capng_ret);
> + goto cleanup;
> + }
> +
> + if (virSetUIDGID(uid, gid) < 0)
> + goto cleanup;
> +
> + /* Tell it we are done keeping capabilities */
> + if (need_prctl && prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0)) {
> + virReportSystemError(errno, "%s",
> + _("prctl failed to reset KEEPCAPS"));
> + goto cleanup;
> + }
> +
> + /* Drop the caps that allow setuid/gid (unless they were requested) */
> + if (need_setgid)
> + capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETGID);
> + if (need_setuid)
> + capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETUID);
> + /* Throw away CAP_SETPCAP so no more changes */
> + if (need_setpcap)
> + capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETPCAP);
> +
> + if (need_prctl && ((capng_ret = capng_apply(CAPNG_SELECT_BOTH)) < 0)) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("cannot apply process capabilities %d"), capng_ret);
> + ret = -1;
> + goto cleanup;
> + }
> +
> + ret = 0;
> +cleanup:
> + return ret;
> +}
> +
> +#else
> +/*
> + * On platforms without libcapng, the capabilities setting is treated
> + * as a NOP.
> + */
> +
> +int
> +virSetUIDGIDWithCaps(uid_t uid, gid_t gid,
> + unsigned long long capBits ATTRIBUTE_UNUSED)
> +{
> + return virSetUIDGID(uid, gid);
> +}
> +#endif
> +
>
> #if defined HAVE_MNTENT_H && defined HAVE_GETMNTENT_R
> /* search /proc/mounts for mount point of *type; return pointer to
> diff --git a/src/util/virutil.h b/src/util/virutil.h
> index 4201aa1..2dc3403 100644
> --- a/src/util/virutil.h
> +++ b/src/util/virutil.h
> @@ -54,6 +54,7 @@ int virPipeReadUntilEOF(int outfd, int errfd,
> char **outbuf, char **errbuf);
>
> int virSetUIDGID(uid_t uid, gid_t gid);
> +int virSetUIDGIDWithCaps(uid_t uid, gid_t gid, unsigned long long capBits);
>
> int virFileReadLimFD(int fd, int maxlen, char **buf) ATTRIBUTE_RETURN_CHECK;
>
> --
> 1.8.1
The following error bisect's down to this commit when running out of
my local checkout for testing.
2013-02-16 05:16:55.102+0000: 29992: error : virCommandWait:2270 :
internal error Child process (LC_ALL=C
LD_LIBRARY_PATH=/home/cardoe/work/libvirt/src/.libs
PATH=/usr/local/bin:/usr/bin:/bin:/opt/bin:/usr/x86_64-pc-linux-gnu/gcc-bin/4.6.3:/usr/games/bin
HOME=/home/cardoe USER=cardoe LOGNAME=cardoe /usr/bin/qemu-kvm -help)
unexpected exit status 1: libvir: error : internal error cannot apply
process capabilities -1
--
Doug Goldstein
More information about the libvir-list
mailing list