From ab5a5764b2d33ea42f3e458da77494a9c864b35b Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 23 Jul 2020 18:17:19 -0700 Subject: QStandardPaths/Unix: improve the XDG_RUNTIME_DIR creation/detection First, use QT_MKDIR instead of QFileSystemEngine::createDirectory(), as the latter can't create a directory with the right permissions. That would allow an attacker to briefly obtain access to the runtime dir between the mkdir() and chmod() system calls. Second, make sure that if the target already exists that it is a directory and not a symlink (even to a directory). If it is a symlink that belongs to another user, it can be changed to point to another place, which we won't like. And as a bonus, we're printing more information to the user in case something went wrong. Sample outputs: QStandardPaths: runtime directory '/root' is not owned by UID 1000, but a directory permissions 0700 owned by UID 0 GID 0 QStandardPaths: runtime directory '/dev/null' is not a directory, but a character device, socket or FIFO permissions 0666 owned by UID 0 GID 0 QStandardPaths: runtime directory '/etc/passwd' is not a directory, but a regular file permissions 0644 owned by UID 0 GID 0 QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-tjmaciei' QStandardPaths: runtime directory '/tmp/runtime-tjmaciei' is not a directory, but a symbolic link to a directory permissions 0755 owned by UID 1000 GID 100 Change-Id: Iea47e0f8fc8b40378df7fffd16248b663794c613 Reviewed-by: David Faure (cherry picked from commit ad5a65b6a2bfca1658634e380559d14ea1e904a4 plus a lot of others) Reviewed-by: Thiago Macieira --- src/corelib/io/qstandardpaths_unix.cpp | 162 ++++++++++++++++++++++++--------- 1 file changed, 120 insertions(+), 42 deletions(-) (limited to 'src/corelib') diff --git a/src/corelib/io/qstandardpaths_unix.cpp b/src/corelib/io/qstandardpaths_unix.cpp index 748ce67dac..d71a28432d 100644 --- a/src/corelib/io/qstandardpaths_unix.cpp +++ b/src/corelib/io/qstandardpaths_unix.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2020 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -71,6 +72,111 @@ static void appendOrganizationAndApp(QString &path) #endif } +static bool checkXdgRuntimeDir(const QString &xdgRuntimeDir) +{ + auto describeMetaData = [](const QFileSystemMetaData &metaData) -> QByteArray { + if (!metaData.exists()) + return "a broken symlink"; + + QByteArray description; + if (metaData.isLink()) + description = "a symbolic link to "; + + if (metaData.isFile()) + description += "a regular file"; + else if (metaData.isDirectory()) + description += "a directory"; + else if (metaData.isSequential()) + description += "a character device, socket or FIFO"; + else + description += "a block device"; + + // convert QFileSystemMetaData permissions back to Unix + mode_t perms = 0; + if (metaData.permissions() & QFile::ReadOwner) + perms |= S_IRUSR; + if (metaData.permissions() & QFile::WriteOwner) + perms |= S_IWUSR; + if (metaData.permissions() & QFile::ExeOwner) + perms |= S_IXUSR; + if (metaData.permissions() & QFile::ReadGroup) + perms |= S_IRGRP; + if (metaData.permissions() & QFile::WriteGroup) + perms |= S_IWGRP; + if (metaData.permissions() & QFile::ExeGroup) + perms |= S_IXGRP; + if (metaData.permissions() & QFile::ReadOther) + perms |= S_IROTH; + if (metaData.permissions() & QFile::WriteOther) + perms |= S_IWOTH; + if (metaData.permissions() & QFile::ExeOther) + perms |= S_IXOTH; + description += " permissions 0" + QByteArray::number(perms, 8); + + return description + + " owned by UID " + QByteArray::number(metaData.userId()) + + " GID " + QByteArray::number(metaData.groupId()); + }; + + // http://standards.freedesktop.org/basedir-spec/latest/ + const uint myUid = uint(geteuid()); + const QFile::Permissions wantedPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; + const QFileSystemMetaData::MetaDataFlags statFlags = QFileSystemMetaData::PosixStatFlags + | QFileSystemMetaData::LinkType; + QFileSystemMetaData metaData; + QFileSystemEntry entry(xdgRuntimeDir); + + // Check that the xdgRuntimeDir is a directory by attempting to create it. + // A stat() before mkdir() that concluded it doesn't exist is a meaningless + // result: we'd race against someone else attempting to create it. + // ### QFileSystemEngine::createDirectory cannot take the extra mode argument. + if (QT_MKDIR(entry.nativeFilePath(), 0700) == 0) + return true; + if (errno != EEXIST) { + qErrnoWarning("QStandardPaths: error creating runtime directory '%ls'", + qUtf16Printable(xdgRuntimeDir)); + return false; + } + + // We use LinkType to force an lstat(), but fillMetaData() still returns error + // on broken symlinks. + if (!QFileSystemEngine::fillMetaData(entry, metaData, statFlags) && !metaData.isLink()) { + qErrnoWarning("QStandardPaths: error obtaining permissions of runtime directory '%ls'", + qUtf16Printable(xdgRuntimeDir)); + return false; + } + + // Checks: + // - is a directory + // - is not a symlink (even is pointing to a directory) + if (metaData.isLink() || !metaData.isDirectory()) { + qWarning("QStandardPaths: runtime directory '%ls' is not a directory, but %s", + qUtf16Printable(xdgRuntimeDir), describeMetaData(metaData).constData()); + return false; + } + + // - "The directory MUST be owned by the user" + if (metaData.userId() != myUid) { + qWarning("QStandardPaths: runtime directory '%ls' is not owned by UID %d, but %s", + qUtf16Printable(xdgRuntimeDir), myUid, describeMetaData(metaData).constData()); + return false; + } + + // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700." + if (metaData.permissions() != wantedPerms) { + // attempt to correct: + QSystemError error; + if (!QFileSystemEngine::setPermissions(entry, wantedPerms, error)) { + qErrnoWarning("QStandardPaths: could not set correct permissions on runtime directory " + "'%ls', which is %s", qUtf16Printable(xdgRuntimeDir), + describeMetaData(metaData).constData()); + return false; + } + } + + return true; +} + QString QStandardPaths::writableLocation(StandardLocation type) { switch (type) { @@ -120,52 +226,24 @@ QString QStandardPaths::writableLocation(StandardLocation type) } case RuntimeLocation: { - const uint myUid = uint(geteuid()); - // http://standards.freedesktop.org/basedir-spec/latest/ - QFileInfo fileInfo; QString xdgRuntimeDir = QFile::decodeName(qgetenv("XDG_RUNTIME_DIR")); - if (xdgRuntimeDir.isEmpty()) { + bool fromEnv = !xdgRuntimeDir.isEmpty(); + if (xdgRuntimeDir.isEmpty() || !checkXdgRuntimeDir(xdgRuntimeDir)) { + // environment variable not set or is set to something unsuitable + const uint myUid = uint(geteuid()); const QString userName = QFileSystemEngine::resolveUserName(myUid); xdgRuntimeDir = QDir::tempPath() + QLatin1String("/runtime-") + userName; - fileInfo.setFile(xdgRuntimeDir); - if (!fileInfo.isDir()) { - if (!QDir().mkdir(xdgRuntimeDir)) { - qWarning("QStandardPaths: error creating runtime directory %s: %s", qPrintable(xdgRuntimeDir), qPrintable(qt_error_string(errno))); - return QString(); - } - } - qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%s'", qPrintable(xdgRuntimeDir)); - } else { - fileInfo.setFile(xdgRuntimeDir); - if (!fileInfo.exists()) { - qWarning("QStandardPaths: XDG_RUNTIME_DIR points to non-existing path '%s', " - "please create it with 0700 permissions.", qPrintable(xdgRuntimeDir)); - return QString(); - } - if (!fileInfo.isDir()) { - qWarning("QStandardPaths: XDG_RUNTIME_DIR points to '%s' which is not a directory", - qPrintable(xdgRuntimeDir)); - return QString(); - } - } - // "The directory MUST be owned by the user" - if (fileInfo.ownerId() != myUid) { - qWarning("QStandardPaths: wrong ownership on runtime directory %s, %d instead of %d", qPrintable(xdgRuntimeDir), - fileInfo.ownerId(), myUid); - return QString(); - } - // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700." - // since the current user is the owner, set both xxxUser and xxxOwner - const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser - | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; - if (fileInfo.permissions() != wantedPerms) { - QFile file(xdgRuntimeDir); - if (!file.setPermissions(wantedPerms)) { - qWarning("QStandardPaths: could not set correct permissions on runtime directory %s: %s", - qPrintable(xdgRuntimeDir), qPrintable(file.errorString())); - return QString(); + + if (!fromEnv) { +#ifndef Q_OS_WASM + qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%ls'", qUtf16Printable(xdgRuntimeDir)); +#endif } + + if (!checkXdgRuntimeDir(xdgRuntimeDir)) + xdgRuntimeDir.clear(); } + return xdgRuntimeDir; } default: -- cgit v1.2.3