-
Notifications
You must be signed in to change notification settings - Fork 256
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement new perf elevated privilege system
Instead of using a script that changes system configuration temporarily, just run perf as root. To properly synchronize with a launched app (that should not run as root), launch the app in a separate, initially stopped process, and use the control fifo feature of perf to properly synchronize with it. The control fifos are also needed to be able to stop sudo-perf, as one does not have permission to SIGINT it anymore.
- Loading branch information
1 parent
ec056a2
commit 7bc7972
Showing
14 changed files
with
451 additions
and
203 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
SPDX-FileCopyrightText: Zeno Endemann <[email protected]> | ||
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected] | ||
SPDX-License-Identifier: GPL-2.0-or-later | ||
*/ | ||
|
||
#include "initiallystoppedprocess.h" | ||
|
||
#include <QDebug> | ||
#include <QFile> | ||
#include <QLoggingCategory> | ||
#include <QSocketNotifier> | ||
#include <QStandardPaths> | ||
|
||
#include <fcntl.h> | ||
#include <signal.h> | ||
#include <sys/stat.h> | ||
#include <sys/types.h> | ||
#include <sys/wait.h> | ||
#include <vector> | ||
|
||
namespace { | ||
Q_LOGGING_CATEGORY(initiallystoppedprocess, "hotspot.initiallystoppedprocess") | ||
|
||
void sendSignal(pid_t pid, int signal) | ||
{ | ||
if (!kill(pid, signal)) { | ||
qCCritical(initiallystoppedprocess, "Failed to send %u to %u", signal, pid); | ||
} | ||
} | ||
} | ||
|
||
InitiallyStoppedProcess::~InitiallyStoppedProcess() | ||
{ | ||
kill(); | ||
} | ||
|
||
bool InitiallyStoppedProcess::createProcessAndStop(const QString& exePath, const QStringList& exeOptions, | ||
const QString& workingDirectory) | ||
{ | ||
kill(); | ||
|
||
// convert arguments and working dir into what the C API needs | ||
|
||
std::vector<QByteArray> args; | ||
args.reserve(exeOptions.size() + 1); | ||
args.emplace_back(exePath.toLocal8Bit()); | ||
for (const auto& opt : exeOptions) | ||
args.emplace_back(opt.toLocal8Bit()); | ||
const auto wd = workingDirectory.toLocal8Bit(); | ||
|
||
std::vector<char*> argsArray(args.size() + 1); | ||
for (size_t i = 0; i < args.size(); ++i) | ||
argsArray[i] = args[i].data(); | ||
argsArray.back() = nullptr; | ||
|
||
// fork | ||
m_pid = fork(); | ||
|
||
if (m_pid == 0) { // inside child process | ||
// change working dir | ||
if (!wd.isEmpty() && chdir(wd.data()) != 0) | ||
qFatal("Failed to change working directory to %s", wd.data()); | ||
|
||
// stop self | ||
raise(SIGSTOP); | ||
|
||
// exec | ||
execvp(argsArray[0], argsArray.data()); | ||
qFatal("Failed to exec %s", argsArray[0]); | ||
} else if (m_pid < 0) { | ||
qCCritical(initiallystoppedprocess, "Failed to fork (?)"); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
bool InitiallyStoppedProcess::continueStoppedProcess() | ||
{ | ||
if (m_pid <= 0) | ||
return false; | ||
|
||
// wait for child to be stopped | ||
|
||
int wstatus; | ||
if (waitpid(m_pid, &wstatus, WUNTRACED) == -1) { | ||
qCWarning(initiallystoppedprocess(), "Failed to wait on process"); | ||
} | ||
if (!WIFSTOPPED(wstatus)) { | ||
m_pid = -1; | ||
return false; | ||
} | ||
|
||
// continue | ||
|
||
sendSignal(m_pid, SIGCONT); | ||
return true; | ||
} | ||
|
||
void InitiallyStoppedProcess::terminate() | ||
{ | ||
if (m_pid > 0) | ||
sendSignal(m_pid, SIGTERM); | ||
} | ||
|
||
void InitiallyStoppedProcess::kill() | ||
{ | ||
if (m_pid > 0) { | ||
sendSignal(m_pid, SIGILL); | ||
waitpid(m_pid, nullptr, 0); | ||
m_pid = -1; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
SPDX-FileCopyrightText: Zeno Endemann <[email protected]> | ||
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected] | ||
SPDX-License-Identifier: GPL-2.0-or-later | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <QObject> | ||
#include <QString> | ||
#include <QStringList> | ||
|
||
#include <memory> | ||
#include <unistd.h> | ||
|
||
class QSocketNotifier; | ||
|
||
class InitiallyStoppedProcess | ||
{ | ||
Q_DISABLE_COPY(InitiallyStoppedProcess) | ||
public: | ||
InitiallyStoppedProcess() = default; | ||
~InitiallyStoppedProcess(); | ||
|
||
pid_t processPID() const | ||
{ | ||
return m_pid; | ||
} | ||
|
||
// this function stop any existing child an then will create a process and ch into workDir. The process will then be | ||
// stopped immediately. After receiving SIGCONT it will run exePath with exeOptions | ||
bool createProcessAndStop(const QString& exePath, const QStringList& exeOptions, const QString& workingDirectory); | ||
|
||
// continue stopped process creted by reset, will wait until child is stopped | ||
bool continueStoppedProcess(); | ||
void terminate(); | ||
void kill(); | ||
|
||
private: | ||
pid_t m_pid = -1; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
SPDX-FileCopyrightText: Zeno Endemann <[email protected]> | ||
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected] | ||
SPDX-License-Identifier: GPL-2.0-or-later | ||
*/ | ||
|
||
#include "perfcontrolfifowrapper.h" | ||
|
||
#include <QDebug> | ||
#include <QFile> | ||
#include <QLoggingCategory> | ||
#include <QRandomGenerator> | ||
#include <QStandardPaths> | ||
|
||
#include <fcntl.h> | ||
#include <sys/stat.h> | ||
#include <unistd.h> | ||
|
||
namespace { | ||
Q_LOGGING_CATEGORY(perfcontrolfifowrapper, "hotspot.perfcontrolfifowrapper") | ||
|
||
QString getRandomString() | ||
{ | ||
auto id = 0; | ||
while (id < 1000000) { | ||
id = QRandomGenerator::global()->generate(); | ||
} | ||
return QString::number(id % 1000000, 16); | ||
} | ||
|
||
int createAndOpenFifo(const char* name) | ||
{ | ||
if (mkfifo(name, 0600) != 0) { | ||
qCCritical(perfcontrolfifowrapper) << "Cannot create fifo" << name; | ||
return -1; | ||
} | ||
|
||
auto fd = open(name, O_RDWR); | ||
if (fd < 0) { | ||
qCCritical(perfcontrolfifowrapper) << "Cannot open fifo" << name; | ||
return -1; | ||
} | ||
return fd; | ||
} | ||
} | ||
|
||
PerfControlFifoWrapper::~PerfControlFifoWrapper() | ||
{ | ||
close(); | ||
} | ||
|
||
bool PerfControlFifoWrapper::open() | ||
{ | ||
close(); | ||
|
||
// QStandardPaths::RuntimeLocation may be empty -> fallback to TempLocation | ||
auto fifoParentPath = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); | ||
if (fifoParentPath.isEmpty()) | ||
fifoParentPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation); | ||
|
||
const auto fifoBasePath = | ||
QLatin1String("%1/hotspot-%2-%3-perf").arg(fifoParentPath, QString::number(getpid()), getRandomString()); | ||
m_ctlFifoPath = fifoBasePath + QLatin1String("-control.fifo"); | ||
m_ackFifoPath = fifoBasePath + QLatin1String("-ack.fifo"); | ||
|
||
// error is already handles by createAndOpenFifo | ||
m_ctlFifoFd = createAndOpenFifo(m_ctlFifoPath.toLocal8Bit().data()); | ||
if (m_ctlFifoFd < 0) | ||
return false; | ||
|
||
m_ackFifoFd = createAndOpenFifo(m_ackFifoPath.toLocal8Bit().data()); | ||
if (m_ackFifoFd < 0) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
void PerfControlFifoWrapper::requestStart() | ||
{ | ||
if (m_ctlFifoFd < 0) { | ||
emit noFIFO(); | ||
return; | ||
} | ||
|
||
m_ackReady = std::make_unique<QSocketNotifier>(m_ackFifoFd, QSocketNotifier::Read); | ||
connect(m_ackReady.get(), &QSocketNotifier::activated, this, [this]() { | ||
char buf[10]; | ||
read(m_ackFifoFd, buf, sizeof(buf)); | ||
emit started(); | ||
m_ackReady->disconnect(this); | ||
}); | ||
|
||
const char start_cmd[] = "enable\n"; | ||
write(m_ctlFifoFd, start_cmd, sizeof(start_cmd) - 1); | ||
} | ||
|
||
void PerfControlFifoWrapper::requestStop() | ||
{ | ||
if (m_ctlFifoFd < 0) { | ||
emit noFIFO(); | ||
return; | ||
} | ||
const char stop_cmd[] = "stop\n"; | ||
write(m_ctlFifoFd, stop_cmd, sizeof(stop_cmd) - 1); | ||
} | ||
|
||
void PerfControlFifoWrapper::close() | ||
{ | ||
if (m_ackReady) { | ||
m_ackReady = nullptr; | ||
} | ||
if (m_ctlFifoFd >= 0) { | ||
::close(m_ctlFifoFd); | ||
m_ctlFifoFd = -1; | ||
} | ||
if (m_ackFifoFd >= 0) { | ||
::close(m_ackFifoFd); | ||
m_ackFifoFd = -1; | ||
} | ||
if (!m_ctlFifoPath.isEmpty()) { | ||
QFile::remove(m_ctlFifoPath); | ||
m_ctlFifoPath.clear(); | ||
} | ||
if (!m_ackFifoPath.isEmpty()) { | ||
QFile::remove(m_ackFifoPath); | ||
m_ackFifoPath.clear(); | ||
} | ||
} |
Oops, something went wrong.