summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArttu Tarkiainen <arttu.tarkiainen@qt.io>2022-01-25 16:04:04 +0200
committerArttu Tarkiainen <arttu.tarkiainen@qt.io>2022-01-31 14:07:03 +0000
commit0126b55180c6aae94f17e0fcb05549ba031f93dc (patch)
tree9454a154dbf14f1098db6eaa517f27b05dc9d9e5
parentc23540ea462c5b91f0e41fc8c10e8b7d9a0610bf (diff)
Gain admin rights when user is missing privilege to create symlinks
On Windows, the non-admin users cannot create symbolic links by default. Extracting archives containing symlinks would fail if the installer was no started as administrator. Catch the case and auto-elevate installer to extract such archives. Do not elevate installer if the current user has the privilege for symlink creation, or if the Developer mode is enabled, allowing unprivileged creation of symlinks. Task-number: QTIFW-2428 Change-Id: I0b6b1079daabb9727055dce8a9475c203d7e92b0 Reviewed-by: Katja Marttila <katja.marttila@qt.io>
-rw-r--r--src/libs/installer/abstractarchive.h4
-rw-r--r--src/libs/installer/extractarchiveoperation.cpp6
-rw-r--r--src/libs/installer/extractarchiveoperation_p.h37
-rw-r--r--src/libs/installer/lib7z_facade.cpp3
-rw-r--r--src/libs/installer/libarchivearchive.cpp7
-rw-r--r--src/libs/installer/utils.cpp98
-rw-r--r--src/libs/installer/utils.h7
7 files changed, 155 insertions, 7 deletions
diff --git a/src/libs/installer/abstractarchive.h b/src/libs/installer/abstractarchive.h
index 55489eb14..a41d7e795 100644
--- a/src/libs/installer/abstractarchive.h
+++ b/src/libs/installer/abstractarchive.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -46,6 +46,7 @@ struct INSTALLER_EXPORT ArchiveEntry
{
ArchiveEntry()
: isDirectory(false)
+ , isSymbolicLink(false)
, compressedSize(0)
, uncompressedSize(0)
, permissions_mode(0)
@@ -55,6 +56,7 @@ struct INSTALLER_EXPORT ArchiveEntry
QDateTime utcTime;
QPoint archiveIndex;
bool isDirectory;
+ bool isSymbolicLink;
quint64 compressedSize;
quint64 uncompressedSize;
mode_t permissions_mode;
diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp
index 7660dadb0..19db53472 100644
--- a/src/libs/installer/extractarchiveoperation.cpp
+++ b/src/libs/installer/extractarchiveoperation.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -92,8 +92,10 @@ bool ExtractArchiveOperation::performOperation()
connect(worker, &Worker::finished, &receiver, &Receiver::workerFinished,
Qt::QueuedConnection);
- if (PackageManagerCore *core = packageManager())
+ if (PackageManagerCore *core = packageManager()) {
connect(core, &PackageManagerCore::statusChanged, worker, &Worker::onStatusChanged);
+ worker->setPackageManagerCore(core);
+ }
QFileInfo fileInfo(archivePath);
emit outputTextChanged(tr("Extracting \"%1\"").arg(fileInfo.fileName()));
diff --git a/src/libs/installer/extractarchiveoperation_p.h b/src/libs/installer/extractarchiveoperation_p.h
index c674da78e..87c126a81 100644
--- a/src/libs/installer/extractarchiveoperation_p.h
+++ b/src/libs/installer/extractarchiveoperation_p.h
@@ -33,6 +33,10 @@
#include "fileutils.h"
#include "archivefactory.h"
#include "packagemanagercore.h"
+#include "remoteclient.h"
+#include "adminauthorization.h"
+#include "utils.h"
+#include "errors.h"
#include <QRunnable>
#include <QThread>
@@ -158,8 +162,14 @@ public:
, m_targetDir(targetDir)
, m_canceled(false)
, m_callback(callback)
+ , m_core(nullptr)
{}
+ void setPackageManagerCore(PackageManagerCore *core)
+ {
+ m_core = core;
+ }
+
Q_SIGNALS:
void finished(bool success, const QString &errorString);
@@ -188,23 +198,49 @@ public Q_SLOTS:
m_archive->errorString()));
return;
}
+ const bool hasAdminRights = (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive());
+ const bool canCreateSymLinks = QInstaller::canCreateSymbolicLinks();
+ bool needsAdminRights = false;
+
for (auto &entry : entries) {
QString completeFilePath = m_targetDir + QDir::separator() + entry.path;
if (!entry.isDirectory && !m_callback->prepareForFile(completeFilePath)) {
emit finished(false, tr("Cannot prepare for file \"%1\"").arg(completeFilePath));
return;
}
+ if (!hasAdminRights && !canCreateSymLinks && entry.isSymbolicLink)
+ needsAdminRights = true;
}
if (m_canceled) {
// For large archives the reading takes some time, and the user might have
// canceled before we start the actual extracting.
emit finished(false, tr("Extract for archive \"%1\" canceled.").arg(m_archivePath));
+ return;
+ }
+
+ bool gainedAdminRights = false;
+ if (needsAdminRights && m_core) {
+ // This must be invoked in the main thread.
+ QMetaObject::invokeMethod(m_core, [&] {
+ try {
+ return m_core->gainAdminRights();
+ } catch (const QInstaller::Error &) {
+ return false;
+ }
+ }, Qt::BlockingQueuedConnection, &gainedAdminRights);
+ }
+ if (needsAdminRights && !gainedAdminRights) {
+ emit finished(false, tr("Could not request administrator privileges required to extract "
+ "archive \"%1\".").arg(m_archivePath));
} else if (!m_archive->extract(m_targetDir, entries.size())) {
emit finished(false, tr("Error while extracting archive \"%1\": %2").arg(m_archivePath,
m_archive->errorString()));
} else {
emit finished(true, QString());
}
+
+ if (gainedAdminRights)
+ m_core->dropAdminRights();
}
void onStatusChanged(PackageManagerCore::Status status)
@@ -232,6 +268,7 @@ private:
QScopedPointer<AbstractArchive> m_archive;
bool m_canceled;
Callback *m_callback;
+ PackageManagerCore *m_core;
};
class ExtractArchiveOperation::Receiver : public QObject
diff --git a/src/libs/installer/lib7z_facade.cpp b/src/libs/installer/lib7z_facade.cpp
index 6d1b6a57d..fc8c6c334 100644
--- a/src/libs/installer/lib7z_facade.cpp
+++ b/src/libs/installer/lib7z_facade.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -538,6 +538,7 @@ QVector<File> listArchive(QFileDevice *archive)
f.archiveIndex.setY(item);
f.path = UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/'));
Archive_IsItem_Folder(arch, item, f.isDirectory);
+ Archive_GetItemBoolProp(arch, item, kpidSymLink, f.isSymbolicLink);
f.permissions_enum = getPermissions(arch, item, nullptr);
getDateTimeProperty(arch, item, kpidMTime, &(f.utcTime));
f.uncompressedSize = getUInt64Property(arch, item, kpidSize, 0);
diff --git a/src/libs/installer/libarchivearchive.cpp b/src/libs/installer/libarchivearchive.cpp
index 3ada73dd2..4493e7a8f 100644
--- a/src/libs/installer/libarchivearchive.cpp
+++ b/src/libs/installer/libarchivearchive.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -39,6 +39,10 @@
#include <QDir>
#include <QTimer>
+#if defined(Q_OS_WIN) && !defined(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
+#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
+#endif
+
namespace QInstaller {
/*!
@@ -588,6 +592,7 @@ QVector<ArchiveEntry> LibArchiveArchive::list()
archiveEntry.path = QLatin1String(archive_entry_pathname(entry));
archiveEntry.utcTime = QDateTime::fromTime_t(archive_entry_mtime(entry));
archiveEntry.isDirectory = (archive_entry_filetype(entry) == AE_IFDIR);
+ archiveEntry.isSymbolicLink = (archive_entry_filetype(entry) == AE_IFLNK);
archiveEntry.uncompressedSize = archive_entry_size(entry);
archiveEntry.permissions_mode = archive_entry_perm(entry);
diff --git a/src/libs/installer/utils.cpp b/src/libs/installer/utils.cpp
index 7506a13fe..5294d00f6 100644
--- a/src/libs/installer/utils.cpp
+++ b/src/libs/installer/utils.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -29,6 +29,7 @@
#include "utils.h"
#include "fileutils.h"
+#include "qsettingswrapper.h"
#include <QCoreApplication>
#include <QDateTime>
@@ -323,6 +324,21 @@ QStringList QInstaller::parseCommandLineArgs(int argc, char **argv)
}
#endif
+/*!
+ On Windows checks if the user account has the privilege required to create a symbolic links.
+ Returns \c true if the privilege is held, \c false otherwise.
+
+ On Unix platforms always returns \c true.
+*/
+bool QInstaller::canCreateSymbolicLinks()
+{
+#ifdef Q_OS_WIN
+ return ((setPrivilege(SE_CREATE_SYMBOLIC_LINK_NAME, true)
+ && checkPrivilege(SE_CREATE_SYMBOLIC_LINK_NAME)) || developerModeEnabled());
+#endif
+ return true;
+}
+
#ifdef Q_OS_WIN
// taken from qprocess_win.cpp
static QString qt_create_commandline(const QString &program, const QStringList &arguments)
@@ -401,4 +417,84 @@ QString QInstaller::windowsErrorString(int errorCode)
return ret;
}
+/*!
+ \internal
+
+ Sets the enabled state of \a privilege to \a enable for this process.
+ The privilege must be held by the current login user. Returns \c true
+ on success, \c false on failure.
+*/
+bool QInstaller::setPrivilege(const wchar_t *privilege, bool enable)
+{
+ LUID luid;
+ TOKEN_PRIVILEGES privileges;
+ HANDLE token;
+ HANDLE process = GetCurrentProcess();
+
+ if (!OpenProcessToken(process, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &token))
+ return false;
+
+ if (!LookupPrivilegeValue(nullptr, privilege, &luid))
+ return false;
+
+ privileges.PrivilegeCount = 1;
+ privileges.Privileges[0].Luid = luid;
+ if (enable)
+ privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ else
+ privileges.Privileges[0].Attributes = 0;
+
+ if (!AdjustTokenPrivileges(token, FALSE, &privileges,
+ sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) {
+ return false;
+ }
+ if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
+ return false;
+
+ return true;
+}
+
+/*!
+ \internal
+
+ Returns \c true if the specified \a privilege is enabled for the client
+ process, \c false otherwise.
+*/
+bool QInstaller::checkPrivilege(const wchar_t *privilege)
+{
+ LUID luid;
+ PRIVILEGE_SET privileges;
+ HANDLE token;
+ HANDLE process = GetCurrentProcess();
+
+ if (!OpenProcessToken(process, TOKEN_QUERY, &token))
+ return false;
+
+ if (!LookupPrivilegeValue(nullptr, privilege, &luid))
+ return false;
+
+ privileges.PrivilegeCount = 1;
+ privileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
+ privileges.Privilege[0].Luid = luid;
+ privileges.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ BOOL result;
+ PrivilegeCheck(token, &privileges, &result);
+
+ return result;
+}
+
+/*!
+ \internal
+
+ Returns \c true if the 'Developer mode' is enabled on system.
+*/
+bool QInstaller::developerModeEnabled()
+{
+ QSettingsWrapper appModelUnlock(QLatin1String("HKLM\\SOFTWARE\\Microsoft\\Windows\\"
+ "CurrentVersion\\AppModelUnlock"), QSettingsWrapper::NativeFormat);
+
+ return appModelUnlock.value(QLatin1String("AllowDevelopmentWithoutDevLicense"), false).toBool();
+}
+
#endif
diff --git a/src/libs/installer/utils.h b/src/libs/installer/utils.h
index a22acd879..2bf997835 100644
--- a/src/libs/installer/utils.h
+++ b/src/libs/installer/utils.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -57,9 +57,14 @@ namespace QInstaller {
QString INSTALLER_EXPORT replaceWindowsEnvironmentVariables(const QString &str);
QStringList INSTALLER_EXPORT parseCommandLineArgs(int argc, char **argv);
+ bool INSTALLER_EXPORT canCreateSymbolicLinks();
+
#ifdef Q_OS_WIN
QString windowsErrorString(int errorCode);
QString createCommandline(const QString &program, const QStringList &arguments);
+ bool setPrivilege(const wchar_t *privilege, bool enable);
+ bool checkPrivilege(const wchar_t *privilege);
+ bool developerModeEnabled();
#endif
QStringList INSTALLER_EXPORT localeCandidates(const QString &locale);