/* * Copyright (C) 2018 Red Hat, Inc. * * 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, see * . */ #include #include "viralloc.h" #include "virfile.h" #include "virstring.h" #include "virerror.h" #include "virlog.h" #include "viruuid.h" #include "virhostuptime.h" #include "security_util.h" #define VIR_FROM_THIS VIR_FROM_SECURITY VIR_LOG_INIT("security.security_util"); /* There are four namespaces available on Linux (xattr(7)): * * user - can be modified by anybody, * system - used by ACLs * security - used by SELinux * trusted - accessibly by CAP_SYS_ADMIN processes only * * Looks like the last one is way to go. * Unfortunately, FreeBSD only supports: * * user - can be modified by anybody, * system - accessible by CAP_SYS_ADMIN processes only * * Note that 'system' on FreeBSD corresponds to 'trusted' on * Linux. So far the only point where FreeBSD and Linux can meet * is NFS which still doesn't support XATTRs. Therefore we can * use different namespace on each system. If NFS gains support * for XATTRs then we have to find a way to deal with the * different namespaces. But that is a problem for future me. */ #if defined(__linux__) # define XATTR_NAMESPACE "trusted" #elif defined(__FreeBSD__) # define XATTR_NAMESPACE "system" #endif static char * virSecurityGetAttrName(const char *name G_GNUC_UNUSED) { char *ret = NULL; #ifdef XATTR_NAMESPACE ret = g_strdup_printf(XATTR_NAMESPACE".libvirt.security.%s", name); #else errno = ENOSYS; virReportSystemError(errno, "%s", _("Extended attributes are not supported on this system")); #endif return ret; } static char * virSecurityGetRefCountAttrName(const char *name G_GNUC_UNUSED) { char *ret = NULL; #ifdef XATTR_NAMESPACE ret = g_strdup_printf(XATTR_NAMESPACE".libvirt.security.ref_%s", name); #else errno = ENOSYS; virReportSystemError(errno, "%s", _("Extended attributes are not supported on this system")); #endif return ret; } #ifdef XATTR_NAMESPACE static char * virSecurityGetTimestampAttrName(const char *name) { return g_strdup_printf(XATTR_NAMESPACE ".libvirt.security.timestamp_%s", name); } #else /* !XATTR_NAMESPACE */ static char * virSecurityGetTimestampAttrName(const char *name G_GNUC_UNUSED) { errno = ENOSYS; virReportSystemError(errno, "%s", _("Extended attributes are not supported on this system")); return NULL; } #endif /* !XATTR_NAMESPACE */ static char * virSecurityGetTimestamp(void) { unsigned long long boottime = 0; if (virHostGetBootTime(&boottime) < 0) { virReportSystemError(errno, "%s", _("Unable to get host boot time")); return NULL; } return g_strdup_printf("%llu", boottime); } /** * virSecurityValidateTimestamp: * @name: security driver name * @path: file name * * Check if remembered label on @path for security driver @name * is valid, i.e. the label has been set since the last boot. If * the label was set in previous runs, all XATTRs related to * @name are removed so that clean slate is restored. * * This is done having extra attribute timestamp_$SECDRIVER which * contains the host boot time. Its value is then compared to * actual host boot time. If these two values don't match then * XATTRs are considered as stale and thus invalid. * * In ideal world, where there network file systems have XATTRs * using plain host boot time is not enough as it may lead to a * situation where a freshly started host sees XATTRs, sees the * timestamp put there by some longer running host and considers * the XATTRs invalid. Well, there is not an easy way out. We * would need to somehow check if the longer running host is * still there and uses the @path (how?). * Fortunately, there is only one network file system which * supports XATTRs currently (GlusterFS via FUSE) and it is used * so rarely that it's almost a corner case. * The worst thing that happens there is that we remove XATTRs * and thus return @path to the default label for $SECDRIVER. * * Returns: 0 if remembered label is valid, * 1 if remembered label was not valid, * -2 if underlying file system doesn't support XATTRs, * -1 otherwise. */ static int virSecurityValidateTimestamp(const char *name, const char *path) { g_autofree char *expected_timestamp = NULL; g_autofree char *timestamp_name = NULL; g_autofree char *value = NULL; if (!(expected_timestamp = virSecurityGetTimestamp()) || !(timestamp_name = virSecurityGetTimestampAttrName(name))) return -1; errno = 0; if (virFileGetXAttrQuiet(path, timestamp_name, &value) < 0) { if (errno == ENOSYS || errno == ENOTSUP) { return -2; } else if (errno != ENODATA) { virReportSystemError(errno, _("Unable to get XATTR %s on %s"), timestamp_name, path); return -1; } /* Timestamp is missing. We could continue and claim a valid timestamp. * But then we would never remove stale XATTRs. Therefore, claim it * invalid and have the code below remove all XATTRs. This of course * means that we will not restore the original owner, but the plus side * is that we reset refcounter which will represent the true state. */ } if (STREQ_NULLABLE(value, expected_timestamp)) { VIR_DEBUG("XATTRs on %s secdriver=%s are valid", path, name); return 0; } VIR_WARN("Invalid XATTR timestamp detected on %s secdriver=%s", path, name); if (virSecurityMoveRememberedLabel(name, path, NULL) < 0) return -1; return 1; } static int virSecurityAddTimestamp(const char *name, const char *path) { g_autofree char *timestamp_name = NULL; g_autofree char *timestamp_value = NULL; if (!(timestamp_value = virSecurityGetTimestamp()) || !(timestamp_name = virSecurityGetTimestampAttrName(name))) return -1; return virFileSetXAttr(path, timestamp_name, timestamp_value); } static int virSecurityRemoveTimestamp(const char *name, const char *path) { g_autofree char *timestamp_name = NULL; if (!(timestamp_name = virSecurityGetTimestampAttrName(name))) return -1; if (virFileRemoveXAttr(path, timestamp_name) < 0 && errno != ENOENT) return -1; return 0; } /** * virSecurityGetRememberedLabel: * @name: security driver name * @path: file name * @label: label * * For given @path and security driver (@name) fetch remembered * @label. The caller must not restore label if an error is * indicated or if @label is NULL upon return. * * The idea is that the first time * virSecuritySetRememberedLabel() is called over @path the * @label is recorded and refcounter is set to 1. Each subsequent * call to virSecuritySetRememberedLabel() increases the counter. * Counterpart to this is virSecurityGetRememberedLabel() which * decreases the counter and reads the @label only if the counter * reached value of zero. For any other call (i.e. when the * counter is not zero), virSecurityGetRememberedLabel() sets * @label to NULL (to notify the caller that the refcount is not * zero) and returns zero. * * Returns: 0 on success, * -2 if underlying file system doesn't support XATTRs, * -1 otherwise (with error reported) */ int virSecurityGetRememberedLabel(const char *name, const char *path, char **label) { g_autofree char *ref_name = NULL; g_autofree char *attr_name = NULL; g_autofree char *value = NULL; unsigned int refcount = 0; *label = NULL; if (!(ref_name = virSecurityGetRefCountAttrName(name))) return -1; if (virFileGetXAttrQuiet(path, ref_name, &value) < 0) { if (errno == ENOSYS || errno == ENODATA || errno == ENOTSUP) return -2; virReportSystemError(errno, _("Unable to get XATTR %s on %s"), ref_name, path); return -1; } if (value) { int rc; /* Do this after we've tried to get refcounter to ensure underlying FS * supports XATTRs and @path has refcounter attribute set, because * validator might throws a warning. */ if ((rc = virSecurityValidateTimestamp(name, path)) < 0) return rc; /* Invalid label is like a non-existent one */ if (rc == 1) return -2; } if (virStrToLong_ui(value, NULL, 10, &refcount) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("malformed refcount %s on %s"), value, path); return -1; } VIR_FREE(value); refcount--; if (refcount > 0) { value = g_strdup_printf("%u", refcount); if (virFileSetXAttr(path, ref_name, value) < 0) return -1; } else { if (virFileRemoveXAttr(path, ref_name) < 0) return -1; if (!(attr_name = virSecurityGetAttrName(name))) return -1; if (virFileGetXAttr(path, attr_name, label) < 0) return -1; if (virFileRemoveXAttr(path, attr_name) < 0) return -1; if (virSecurityRemoveTimestamp(name, path) < 0) return -1; } return 0; } /** * virSecuritySetRememberedLabel: * @name: security driver name * @path: file name * @label: label * * For given @path and security driver (@name), if called the * first time over @path, set the @label to remember (i.e. the * original owner of the @path). Any subsequent call over @path * will increment refcounter. It is strongly recommended that the * caller checks for the return value and if it is greater than 1 * (meaning that some domain is already using @path) the current * label is required instead of setting a new one. * * See also virSecurityGetRememberedLabel. * * Returns: the new refcount value on success, * -2 if underlying file system doesn't support XATTRs, * -1 otherwise (with error reported) */ int virSecuritySetRememberedLabel(const char *name, const char *path, const char *label) { g_autofree char *ref_name = NULL; g_autofree char *attr_name = NULL; g_autofree char *value = NULL; unsigned int refcount = 0; if (!(ref_name = virSecurityGetRefCountAttrName(name))) return -1; if (virFileGetXAttrQuiet(path, ref_name, &value) < 0) { if (errno == ENOSYS || errno == ENOTSUP) { return -2; } else if (errno != ENODATA) { virReportSystemError(errno, _("Unable to get XATTR %s on %s"), ref_name, path); return -1; } } if (value) { int rc; /* Do this after we've tried to get refcounter to ensure underlying FS * supports XATTRs and @path has refcounter attribute set, because * validator might throws a warning. */ if ((rc = virSecurityValidateTimestamp(name, path)) < 0) return rc; /* Invalid label is like a non-existent one */ if (rc == 1) VIR_FREE(value); } if (value && virStrToLong_ui(value, NULL, 10, &refcount) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("malformed refcount %s on %s"), value, path); return -1; } VIR_FREE(value); refcount++; if (refcount == 1) { if (!(attr_name = virSecurityGetAttrName(name))) return -1; if (virFileSetXAttr(path, attr_name, label) < 0) return -1; if (virSecurityAddTimestamp(name, path) < 0) return -1; } value = g_strdup_printf("%u", refcount); if (virFileSetXAttr(path, ref_name, value) < 0) return -1; return refcount; } /** * virSecurityMoveRememberedLabel: * @name: security driver name * @src: source file * @dst: destination file * * For given security driver @name, move all XATTRs related to seclabel * remembering from @src to @dst. However, if @dst is NULL, then XATTRs * are just removed from @src. * * Returns: 0 on success, * -1 otherwise. */ int virSecurityMoveRememberedLabel(const char *name, const char *src, const char *dst) { g_autofree char *ref_name = NULL; g_autofree char *ref_value = NULL; g_autofree char *attr_name = NULL; g_autofree char *attr_value = NULL; g_autofree char *timestamp_name = NULL; g_autofree char *timestamp_value = NULL; if (!(ref_name = virSecurityGetRefCountAttrName(name)) || !(attr_name = virSecurityGetAttrName(name)) || !(timestamp_name = virSecurityGetTimestampAttrName(name))) return -1; if (virFileGetXAttrQuiet(src, ref_name, &ref_value) < 0) { if (errno == ENOSYS || errno == ENOTSUP) { return -2; } else if (errno != ENODATA) { virReportSystemError(errno, _("Unable to get XATTR %s on %s"), ref_name, src); return -1; } } if (virFileGetXAttrQuiet(src, attr_name, &attr_value) < 0) { if (errno == ENOSYS || errno == ENOTSUP) { return -2; } else if (errno != ENODATA) { virReportSystemError(errno, _("Unable to get XATTR %s on %s"), attr_name, src); return -1; } } if (virFileGetXAttrQuiet(src, timestamp_name, ×tamp_value) < 0) { if (errno == ENOSYS || errno == ENOTSUP) { return -2; } else if (errno != ENODATA) { virReportSystemError(errno, _("Unable to get XATTR %s on %s"), attr_name, src); return -1; } } if (ref_value && virFileRemoveXAttr(src, ref_name) < 0) { return -1; } if (attr_value && virFileRemoveXAttr(src, attr_name) < 0) { return -1; } if (timestamp_value && virFileRemoveXAttr(src, timestamp_name) < 0) { return -1; } if (dst) { if (ref_value && virFileSetXAttr(dst, ref_name, ref_value) < 0) { return -1; } if (attr_value && virFileSetXAttr(dst, attr_name, attr_value) < 0) { ignore_value(virFileRemoveXAttr(dst, ref_name)); return -1; } if (timestamp_value && virFileSetXAttr(dst, timestamp_name, timestamp_value) < 0) { ignore_value(virFileRemoveXAttr(dst, ref_name)); ignore_value(virFileRemoveXAttr(dst, attr_name)); return -1; } } return 0; }