diff --git a/daemon/remote.c b/daemon/remote.c index 1f7cbe9873..3b6446d50b 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -2339,6 +2339,7 @@ remoteDispatchAuthList(virNetServerPtr server ATTRIBUTE_UNUSED, uid_t callerUid; gid_t callerGid; pid_t callerPid; + unsigned long long timestamp; /* If the client is root then we want to bypass the * policykit auth to avoid root being denied if @@ -2346,7 +2347,7 @@ remoteDispatchAuthList(virNetServerPtr server ATTRIBUTE_UNUSED, */ if (auth == VIR_NET_SERVER_SERVICE_AUTH_POLKIT) { if (virNetServerClientGetUNIXIdentity(client, &callerUid, &callerGid, - &callerPid) < 0) { + &callerPid, ×tamp) < 0) { /* Don't do anything on error - it'll be validated at next * phase of auth anyway */ virResetLastError(); @@ -2772,6 +2773,7 @@ remoteDispatchAuthPolkit(virNetServerPtr server ATTRIBUTE_UNUSED, pid_t callerPid = -1; gid_t callerGid = -1; uid_t callerUid = -1; + unsigned long long timestamp; const char *action; int status = -1; char *ident = NULL; @@ -2797,7 +2799,7 @@ remoteDispatchAuthPolkit(virNetServerPtr server ATTRIBUTE_UNUSED, } if (virNetServerClientGetUNIXIdentity(client, &callerUid, &callerGid, - &callerPid) < 0) { + &callerPid, ×tamp) < 0) { goto authfail; } @@ -2805,7 +2807,11 @@ remoteDispatchAuthPolkit(virNetServerPtr server ATTRIBUTE_UNUSED, (long long) callerPid, callerUid); virCommandAddArg(cmd, "--process"); - virCommandAddArgFormat(cmd, "%lld", (long long) callerPid); + if (timestamp != 0) { + virCommandAddArgFormat(cmd, "%lld,%llu", (long long) callerPid, timestamp); + } else { + virCommandAddArgFormat(cmd, "%lld", (long long) callerPid); + } virCommandAddArg(cmd, "--allow-user-interaction"); if (virAsprintf(&ident, "pid:%lld,uid:%d", diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index babdde7c5b..bb70595e0b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1755,6 +1755,7 @@ virStrdup; virStringArrayHasString; virStringFreeList; virStringJoin; +virStringListLength; virStringSplit; virStrncpy; virStrndup; diff --git a/src/locking/lock_daemon.c b/src/locking/lock_daemon.c index 0d8ef399ff..20e2c04dc3 100644 --- a/src/locking/lock_daemon.c +++ b/src/locking/lock_daemon.c @@ -783,6 +783,7 @@ virLockDaemonClientNew(virNetServerClientPtr client, virLockDaemonClientPtr priv; uid_t clientuid; gid_t clientgid; + unsigned long long timestamp; bool privileged = opaque != NULL; if (VIR_ALLOC(priv) < 0) { @@ -799,7 +800,8 @@ virLockDaemonClientNew(virNetServerClientPtr client, if (virNetServerClientGetUNIXIdentity(client, &clientuid, &clientgid, - &priv->clientPid) < 0) + &priv->clientPid, + ×tamp) < 0) goto error; VIR_DEBUG("New client pid %llu uid %llu", diff --git a/src/rpc/virnetserverclient.c b/src/rpc/virnetserverclient.c index 5abf7b0d9a..6d7c439f35 100644 --- a/src/rpc/virnetserverclient.c +++ b/src/rpc/virnetserverclient.c @@ -636,12 +636,15 @@ bool virNetServerClientIsLocal(virNetServerClientPtr client) int virNetServerClientGetUNIXIdentity(virNetServerClientPtr client, - uid_t *uid, gid_t *gid, pid_t *pid) + uid_t *uid, gid_t *gid, pid_t *pid, + unsigned long long *timestamp) { int ret = -1; virObjectLock(client); if (client->sock) - ret = virNetSocketGetUNIXIdentity(client->sock, uid, gid, pid); + ret = virNetSocketGetUNIXIdentity(client->sock, + uid, gid, pid, + timestamp); virObjectUnlock(client); return ret; } @@ -651,6 +654,7 @@ static virIdentityPtr virNetServerClientCreateIdentity(virNetServerClientPtr client) { char *processid = NULL; + char *processtime = NULL; char *username = NULL; char *groupname = NULL; #if WITH_SASL @@ -664,15 +668,23 @@ virNetServerClientCreateIdentity(virNetServerClientPtr client) gid_t gid; uid_t uid; pid_t pid; - if (virNetSocketGetUNIXIdentity(client->sock, &uid, &gid, &pid) < 0) + unsigned long long timestamp; + if (virNetSocketGetUNIXIdentity(client->sock, + &uid, &gid, &pid, + ×tamp) < 0) goto cleanup; if (!(username = virGetUserName(uid))) goto cleanup; if (!(groupname = virGetGroupName(gid))) goto cleanup; - if (virAsprintf(&processid, "%lld", - (long long)pid) < 0) { + if (virAsprintf(&processid, "%llu", + (unsigned long long)pid) < 0) { + virReportOOMError(); + goto cleanup; + } + if (virAsprintf(&processtime, "%llu", + timestamp) < 0) { virReportOOMError(); goto cleanup; } @@ -722,6 +734,11 @@ virNetServerClientCreateIdentity(virNetServerClientPtr client) VIR_IDENTITY_ATTR_UNIX_PROCESS_ID, processid) < 0) goto error; + if (processtime && + virIdentitySetAttr(ret, + VIR_IDENTITY_ATTR_UNIX_PROCESS_TIME, + processtime) < 0) + goto error; #if HAVE_SASL if (saslname && virIdentitySetAttr(ret, @@ -744,6 +761,7 @@ cleanup: VIR_FREE(username); VIR_FREE(groupname); VIR_FREE(processid); + VIR_FREE(processtime); VIR_FREE(seccontext); #if HAVE_SASL VIR_FREE(saslname); diff --git a/src/rpc/virnetserverclient.h b/src/rpc/virnetserverclient.h index 85369941f9..8d0fd18fe3 100644 --- a/src/rpc/virnetserverclient.h +++ b/src/rpc/virnetserverclient.h @@ -99,7 +99,8 @@ bool virNetServerClientIsSecure(virNetServerClientPtr client); bool virNetServerClientIsLocal(virNetServerClientPtr client); int virNetServerClientGetUNIXIdentity(virNetServerClientPtr client, - uid_t *uid, gid_t *gid, pid_t *pid); + uid_t *uid, gid_t *gid, pid_t *pid, + unsigned long long *timestamp); int virNetServerClientGetSELinuxContext(virNetServerClientPtr client, char **context); diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c index eb88e27465..67eb496a9e 100644 --- a/src/rpc/virnetsocket.c +++ b/src/rpc/virnetsocket.c @@ -1103,31 +1103,41 @@ int virNetSocketGetPort(virNetSocketPtr sock) int virNetSocketGetUNIXIdentity(virNetSocketPtr sock, uid_t *uid, gid_t *gid, - pid_t *pid) + pid_t *pid, + unsigned long long *timestamp) { struct ucred cr; socklen_t cr_len = sizeof(cr); + int ret = -1; + virObjectLock(sock); if (getsockopt(sock->fd, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len) < 0) { virReportSystemError(errno, "%s", _("Failed to get client socket identity")); - virObjectUnlock(sock); - return -1; + goto cleanup; } + if (virProcessGetStartTime(cr.pid, timestamp) < 0) + goto cleanup; + *pid = cr.pid; *uid = cr.uid; *gid = cr.gid; + ret = 0; + +cleanup: virObjectUnlock(sock); - return 0; + return ret; } #elif defined(LOCAL_PEERCRED) + int virNetSocketGetUNIXIdentity(virNetSocketPtr sock, uid_t *uid, gid_t *gid, - pid_t *pid) + pid_t *pid, + unsigned long long *timestamp ATTRIBUTE_UNUSED) { struct xucred cr; socklen_t cr_len = sizeof(cr); @@ -1151,7 +1161,8 @@ int virNetSocketGetUNIXIdentity(virNetSocketPtr sock, int virNetSocketGetUNIXIdentity(virNetSocketPtr sock ATTRIBUTE_UNUSED, uid_t *uid ATTRIBUTE_UNUSED, gid_t *gid ATTRIBUTE_UNUSED, - pid_t *pid ATTRIBUTE_UNUSED) + pid_t *pid ATTRIBUTE_UNUSED, + unsigned long long *timestamp ATTRIBUTE_UNUSED) { /* XXX Many more OS support UNIX socket credentials we could port to. See dbus ....*/ virReportSystemError(ENOSYS, "%s", diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h index 7bced141e7..ea42081d49 100644 --- a/src/rpc/virnetsocket.h +++ b/src/rpc/virnetsocket.h @@ -113,7 +113,8 @@ int virNetSocketGetPort(virNetSocketPtr sock); int virNetSocketGetUNIXIdentity(virNetSocketPtr sock, uid_t *uid, gid_t *gid, - pid_t *pid); + pid_t *pid, + unsigned long long *timestamp); int virNetSocketGetSELinuxContext(virNetSocketPtr sock, char **context); diff --git a/src/util/viridentity.h b/src/util/viridentity.h index d7293be443..4bae8d63d7 100644 --- a/src/util/viridentity.h +++ b/src/util/viridentity.h @@ -31,6 +31,7 @@ typedef enum { VIR_IDENTITY_ATTR_UNIX_USER_NAME, VIR_IDENTITY_ATTR_UNIX_GROUP_NAME, VIR_IDENTITY_ATTR_UNIX_PROCESS_ID, + VIR_IDENTITY_ATTR_UNIX_PROCESS_TIME, VIR_IDENTITY_ATTR_SASL_USER_NAME, VIR_IDENTITY_ATTR_X509_DISTINGUISHED_NAME, VIR_IDENTITY_ATTR_SELINUX_CONTEXT, diff --git a/src/util/virprocess.c b/src/util/virprocess.c index ac93c4a7b8..99db003352 100644 --- a/src/util/virprocess.c +++ b/src/util/virprocess.c @@ -33,6 +33,13 @@ #endif #include +#ifdef __FreeBSD__ +# include +# include +# include +#endif + +#include "viratomic.h" #include "virprocess.h" #include "virerror.h" #include "viralloc.h" @@ -756,3 +763,112 @@ virProcessSetMaxFiles(pid_t pid ATTRIBUTE_UNUSED, unsigned int files) return -1; } #endif /* ! (HAVE_SETRLIMIT && defined(RLIMIT_NOFILE)) */ + +#ifdef __linux__ +/* + * Port of code from polkitunixprocess.c under terms + * of the LGPLv2+ + */ +int virProcessGetStartTime(pid_t pid, + unsigned long long *timestamp) +{ + char *filename = NULL; + char *buf = NULL; + char *tmp; + int ret = -1; + int len; + char **tokens = NULL; + + if (virAsprintf(&filename, "/proc/%llu/stat", + (unsigned long long)pid) < 0) { + virReportOOMError(); + return -1; + } + + if ((len = virFileReadAll(filename, 1024, &buf)) < 0) + goto cleanup; + + /* start time is the token at index 19 after the '(process name)' entry - since only this + * field can contain the ')' character, search backwards for this to avoid malicious + * processes trying to fool us + */ + + if (!(tmp = strrchr(buf, ')'))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot find start time in %s"), + filename); + goto cleanup; + } + tmp += 2; /* skip ') ' */ + if ((tmp - buf) >= len) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot find start time in %s"), + filename); + goto cleanup; + } + + tokens = virStringSplit(tmp, " ", 0); + + if (virStringListLength(tokens) < 20) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot find start time in %s"), + filename); + goto cleanup; + } + + if (virStrToLong_ull(tokens[19], + NULL, + 10, + timestamp) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse start time %s in %s"), + tokens[19], filename); + goto cleanup; + } + + ret = 0; + +cleanup: + virStringFreeList(tokens); + VIR_FREE(filename); + VIR_FREE(buf); + return ret; +} +#elif defined(__FreeBSD__) +int virProcessGetStartTime(pid_t pid, + unsigned long long *timestamp) +{ + struct kinfo_proc p; + int mib[4]; + size_t len = 4; + + sysctlnametomib("kern.proc.pid", mib, &len); + + len = sizeof(struct kinfo_proc); + mib[3] = pid; + + if (sysctl(mib, 4, &p, &len, NULL, 0) < 0) { + virReportSystemError(errno, "%s", + _("Unable to query process ID start time")); + return -1; + } + + *timestamp = (unsigned long long)p.ki_start.tv_sec; + + return 0; + +} +#else +int virProcessGetStartTime(pid_t pid, + unsigned long long *timestamp) +{ + static int warned = 0; + if (virAtomicIntInc(&warned) == 1) { + VIR_WARN("Process start time of pid %llu not available on this platform", + (unsigned long long)pid); + warned = true; + } + *timestamp = 0; + return 0; +} +#endif diff --git a/src/util/virprocess.h b/src/util/virprocess.h index 5dea334902..9f77bc5491 100644 --- a/src/util/virprocess.h +++ b/src/util/virprocess.h @@ -47,6 +47,9 @@ int virProcessGetAffinity(pid_t pid, virBitmapPtr *map, int maxcpu); +int virProcessGetStartTime(pid_t pid, + unsigned long long *timestamp); + int virProcessGetNamespaces(pid_t pid, size_t *nfdlist, int **fdlist); diff --git a/src/util/virstring.c b/src/util/virstring.c index ec124623b0..1640d8c02a 100644 --- a/src/util/virstring.c +++ b/src/util/virstring.c @@ -593,3 +593,14 @@ virStrndup(char **dest, return 1; } + + +size_t virStringListLength(char **strings) +{ + size_t i = 0; + + while (strings && strings[i]) + i++; + + return i; +} diff --git a/src/util/virstring.h b/src/util/virstring.h index 5bd6c185b0..534ce9139e 100644 --- a/src/util/virstring.h +++ b/src/util/virstring.h @@ -88,7 +88,6 @@ char *virStrcpy(char *dest, const char *src, size_t destbytes) ATTRIBUTE_RETURN_CHECK; # define virStrcpyStatic(dest, src) virStrcpy((dest), (src), sizeof(dest)) - /* Don't call these directly - use the macros below */ int virStrdup(char **dest, const char *src, bool report, int domcode, const char *filename, const char *funcname, size_t linenr) @@ -159,4 +158,7 @@ int virStrndup(char **dest, const char *src, size_t n, bool report, int domcode, */ # define VIR_STRNDUP_QUIET(dst, src, n) virStrndup(&(dst), src, n, false, \ 0, NULL, NULL, 0) + +size_t virStringListLength(char **strings); + #endif /* __VIR_STRING_H__ */