|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v2 08/10] libxl: Kill QEMU by uid when possible
George Dunlap writes ("[PATCH v2 08/10] libxl: Kill QEMU by uid when possible"):
> The privcmd fd that a dm_restrict'ed QEMU has gives it permission to
> one specific domain ID. This domain ID will probably eventually be
> used again. It is therefore necessary to make absolutely sure that a
> rogue QEMU process cannot hang around after its domain has exited.
Thanks. Detailed comments mostly on error handling follow...
> diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h
> index f9e0bf6578..cd3208f4b8 100644
> --- a/tools/libxl/libxl_internal.h
> +++ b/tools/libxl/libxl_internal.h
> @@ -1135,7 +1135,7 @@ typedef struct {
> const char *shim_cmdline;
> const char *pv_cmdline;
>
> - char *dm_runas;
> + char *dm_runas, *dm_uid;
I think `dm_uid' is misnamed. It is only set if the dm has a
*dedicated* uid. If the dm is run as uid 0 or as a shared qemu uid,
it is set to NULL.
> @@ -3706,6 +3706,8 @@ struct libxl__destroy_devicemodel_state {
> uint32_t domid;
> libxl__devicemodel_destroy_cb *callback; /* May be called re-entrantly */
> /* private to implementation */
> + libxl__ev_child destroyer;
> + int rc; /* Accumulated return value for the destroy operation */
Excellent.
> diff --git a/tools/libxl/libxl_dm.c b/tools/libxl/libxl_dm.c
> index 7f9c6a62fe..53fdf8daf7 100644
> --- a/tools/libxl/libxl_dm.c
> +++ b/tools/libxl/libxl_dm.c
> @@ -129,6 +129,8 @@ static int libxl__domain_get_device_model_uid(libxl__gc
> *gc,
> int rc;
> char *user;
> uid_t intended_uid;
> + bool kill_by_uid;
> +
I guess this new blank line is deliberate. I think it is probably a
good idea.
> @@ -148,8 +150,10 @@ static int libxl__domain_get_device_model_uid(libxl__gc
> *gc,
> LOGD(ERROR, guest_domid, "Couldn't find device_model_user %s",
> user);
> rc = ERROR_INVAL;
> - } else
> + } else {
> intended_uid = user_base->pw_uid;
> + kill_by_uid = true;
> + }
>
> goto out;
> }
I think your changes to the out block newly imply that all `goto out'
with `rc=0' must also set kill_by_uid.
I have not (in this revision) applied this patch and searched for that
because I think there will have to be a respin to consistently apply
the rules I previously identified.
(I mention this here partly so that when I review v3 I know that my
former self wanted to double check that.)
> @@ -226,6 +234,8 @@ out:
> }
>
> state->dm_runas = user;
> + if (kill_by_uid)
> + state->dm_uid = GCSPRINTF("%ld", (long)intended_uid);
> }
More stuff is accumulating in this post-0-check success path.
Perhaps this means my suggestion of
if (!rc) { ... } if (!rc) { ... }
will be most convenient.
> + /*
> + * If we're starting the dm with a non-root UID, save the UID so
> + * that we can reliably kill it and any subprocesses
> + */
> + if (state->dm_uid)
> + libxl__xs_printf(gc, XBT_NULL,
> + GCSPRINTF("%s/image/device-model-uid", dom_path),
My comment about the misnamed libxl variable applies to
device-model-uid too I think.
> +#define PROPAGATE_RC if(!ddms->rc) ddms->rc = rc
^
I like this macro. But (i) it needs proper macro hygiene - either a
do{ }while(0) block, or refactoring into an expression (and then
putting in parens). And (ii) you missed out a space.
(The references to ddms and rc are anaphoric, not macro parameters, so
do not need parens.)
> + path = DEVICE_MODEL_XS_PATH(gc, LIBXL_TOOLSTACK_DOMID, domid, "");
> + rc = libxl__xs_rm_checked(gc, XBT_NULL, path);
> + if (rc) {
> + PROPAGATE_RC;
> LOGD(ERROR, domid, "xs_rm failed for %s", path);
> + }
Your new macro makes this a nicely transparent pasttern.
> + /*
> + * See if we should try to kill by uid
> + */
> + path = GCSPRINTF("/local/domain/%d/image/device-model-uid", domid);
> + rc = libxl__xs_read_checked(gc, XBT_NULL, path, &dm_uid_str);
> +
> + /*
> + * If there was an error here, accumulate the error and fall back
> + * to killing by pid.
> + */
> + if (rc) {
> + PROPAGATE_RC;
> + LOGD(ERROR, domid, "Reading dm UID path failed for %s", path);
> + }
From the comment for libxl__xs_read_checked:
| * On error, *result_out is undefined.
Arguably this is a bear trap. Maybe you would like to fix it there
rather than by setting dm_uid_str to 0 here.
> + reaper_pid = libxl__ev_child_fork(gc, &ddms->destroyer,
> + kill_device_model_uid_cb);
> + if (reaper_pid < 0) {
> + rc = ERROR_FAIL;
> + PROPAGATE_RC;
> + /*
> + * Note that if this fails, we still don't kill by pid, to
> + * make sure that an untrusted DM has not "maliciously"
> + * exited (potentially causing us to kill an unrelated
> + * process which happened to get the same pid).
> + */
> + goto out;
> + }
> +
> + if (!reaper_pid) { /* child */
> + rc = kill_device_model_uid_child(ddms, dm_uid_str);
> + _exit(rc);
> + }
You cannot _exit(rc). See my comments below...
Personally I like to put the child process block right after the fork,
especially when (like here) it is very short because the meat has been
lifted elsewhere. Just a suggestion; you can leave it here if you
prefer.
> - /* We should try to destroy the device model anyway. */
> - rc = kill_device_model(gc,
> - GCSPRINTF("/local/domain/%d/image/device-model-pid", domid));
> + /*
> + * No uid to kill; attept to kill by pid.
> + */
> + LOGD(DEBUG, domid, "Didn't find dm UID; destroying by pid");
> +
> + path = GCSPRINTF("/local/domain/%d/image/device-model-pid", domid);
> + rc = kill_device_model(gc, path);
> +
> + if (rc) {
> + PROPAGATE_RC;
> + LOGD(ERROR, domid, "Killing device model pid from path %s", path);
> + }
> +
> +out:
I would prefer the apparently-redundant:
+ rc = 0;
> +out:
This avoids introducing a bug if, just before the success exit path,
new code gets added which doesn't happen to leave rc==0.
> + /*
> + * NB that we always return '0' here for the "status of exited
> + * process"; since there is no process, it always "succeeds".
I had to read this three times to figure out what this meant. Perhaps
I'm being dense but would you mind writing
+ * NB that we always pass '0' here for the "status of exited
^^^^
> +/*
> + * Destroy all processes of the given uid by setresuid to the
> + * specified uid and kill(-1). NB this MUST BE CALLED FROM A SEPARATE
> + * PROCESS from the normal libxl process. Returns a libxl-style error
> + * code.
> + */
> +static int kill_device_model_uid_child(libxl__destroy_devicemodel_state
> *ddms,
> + const char *dm_uid_str) {
> + STATE_AO_GC(ddms->ao);
> + int domid = ddms->domid;
> + int r, rc = 0;
> + uid_t dm_uid = atoi(dm_uid_str);
> +
> + /*
> + * FIXME: the second uid needs to be distinct to avoid being
> + * killed by a potential rogue process
> + */
> +
> + /*
> + * Should never happen; but if it does, better to have the
> + * toolstack crash with an error than nuking dom0.
> + */
> + assert(dm_uid);
> +
> + LOGD(DEBUG, domid, "DM reaper: calling setresuid(%d, %d, 0)",
> + dm_uid, dm_uid);
> + r = setresuid(dm_uid, dm_uid, 0);
> + if (r) {
> + LOGED(ERROR, domid, "setresuid to (%d, %d, 0)", dm_uid, dm_uid);
> + rc = ERROR_FAIL;
> + goto out;
> + }
> +
> + /*
> + * And kill everyone but me.
> + *
> + * NB that it's not clear from either POSIX or the Linux man page
> + * that ESRCH would be returned with a pid value of -1, but it
> + * doesn't hurt to check.
> + */
> + r = kill(-1, 9);
> + if (r && errno != ESRCH) {
> + LOGED(ERROR, domid, "kill(-1,9)");
> + rc = ERROR_FAIL;
> + }
> +
> +out:
> + return rc;
> +}
> +
> +static void kill_device_model_uid_cb(libxl__egc *egc,
> + libxl__ev_child *destroyer,
> + pid_t pid, int status)
> +{
> + libxl__destroy_devicemodel_state *ddms = CONTAINER_OF(destroyer, *ddms,
> destroyer);
> + STATE_AO_GC(ddms->ao);
> +
> + if (status) {
> + int rc = ERROR_FAIL;;
> +
> + if (WIFEXITED(status))
> + rc = WEXITSTATUS(status) - 128;
This can't be right. Where does this 128 come from ?
I don't see a comment anywhere about your encoding of the libxl error
value in the exit status.
A libxl error code does not necessarily fit in an exit status since an
exit status is just one byte. Your protocol reserves the exit status
0 for success. The C implementation typically reserves -1 (255) and
sometimes also 127. By convention the exit status is normally
regarded as positive and libxl error codes are negative.
I suggest one of the following strategies:
- Give up on the idea of distinguishing these error codes at all and
simply _exit(!!rc). (After all the real error is logged and the
function only ever returns FAIL.)
- Say that the child function may only return one of a limited
subset of libxl error codes (since only FAIL is currently needed),
and assert that -125 <= rc <= -1, and _exit(-rc). Then the exit
status can be recovered with -WEXITSTATUS(status).
- Do something more subtle involving turning out-of-range
rc values into -126 or some such. This seems like overkill.
> - ddms->callback(egc, ddms, rc);
> + ddms->callback(egc, ddms, ddms->rc);
> }
> +#undef PROPAGATE_RC
I am tempted to suggest replacing each call
PROPAGATE_RC;
with
ACCUMULATE_RC(ddms);
and put the definition in libxl_internal.h for use elsewhere.
I think we would probably already have some other call sites and if we
go and fix the destroy stuff probably we will want a lot more of it.
Up to you. Like this is certainly fine for now.
Thanks,
Ian.
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |