QProcess/Unix: add a simple way to reset the UID and GID for the child

This is done as one of the last steps inside QProcess itself, so the
child modifier and all other tasks still run with the parent process'
permissions. On Linux, setting the UID to non-zero will also
automatically clear the effective capabilities(7) set.

This feature is only useful for setuid or setgid applications, so this
commit updates the QCoreApplication::setSetuidAllowed() documentation to
mention the QProcess flag.

Change-Id: I3e3bfef633af4130a03afffd175e940c0668d244
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Thiago Macieira 2023-05-12 19:43:18 -07:00
parent 13a1995e9d
commit 271901c5cf
6 changed files with 34 additions and 1 deletions

View File

@ -873,6 +873,12 @@ void QProcessPrivate::Channel::clear()
terminate immediately; with this flag, the write operation fails
without a signal and the child may continue executing.
\value [since 6.7] ResetIds Drops any retained, effective user or group
ID the current process may still have (see \c{setuid(2)} and
\c{setgid(2)}, plus QCoreApplication::setSetuidAllowed()). This is
useful if the current process was setuid or setgid and does not wish
the child process to retain the elevated privileges.
\value ResetSignalHandlers Resets all Unix signal handlers back to their
default state (that is, pass \c SIG_DFL to \c{signal(2)}). This flag
is useful to ensure any ignored (\c SIG_IGN) signal does not affect

View File

@ -184,6 +184,7 @@ public:
UseVFork = 0x0020, // like POSIX_SPAWN_USEVFORK
CreateNewSession = 0x0040, // like POSIX_SPAWN_SETSID
DisconnectControllingTerminal = 0x0080,
ResetIds = 0x0100, // like POSIX_SPAWN_RESETIDS
};
Q_DECLARE_FLAGS(UnixProcessFlags, UnixProcessFlag)
struct UnixProcessParameters

View File

@ -848,6 +848,15 @@ static const char *applyProcessParameters(const QProcess::UnixProcessParameters
}
}
// Apply UID and GID parameters last. This isn't expected to fail either:
// either we're trying to impersonate what we already are, or we're EUID or
// EGID root, in which case we are allowed to do this.
if (params.flags.testFlag(QProcess::UnixProcessFlag::ResetIds)) {
int r = setgid(getgid());
r = setuid(getuid());
(void) r;
}
return nullptr;
}

View File

@ -987,7 +987,10 @@ QCoreApplication::~QCoreApplication()
and must be set before a QCoreApplication instance is created.
\note It is strongly recommended not to enable this option since
it introduces security risks.
it introduces security risks. If this application does enable the flag and
starts child processes, it should drop the privileges as early as possible
by calling \c{setuid(2)} for itself, or at the latest by using the
QProcess::UnixProcessParameters::ResetIds flag.
*/
void QCoreApplication::setSetuidAllowed(bool allow)
{

View File

@ -29,6 +29,14 @@ int main(int argc, char **argv)
return EXIT_SUCCESS;
}
if (cmd == "reset-ids") {
if (getuid() == geteuid() && getgid() == getegid())
return EXIT_SUCCESS;
fprintf(stderr, "Real: %d %d; Effective: %d %d\n",
getuid(), getgid(), geteuid(), getegid());
return EXIT_FAILURE;
}
if (cmd == "reset-sighand") {
bool ok = true;

View File

@ -1733,6 +1733,7 @@ void tst_QProcess::unixProcessParameters_data()
addRow("ignore-sigpipe", P::IgnoreSigPipe);
addRow("file-descriptors", P::CloseFileDescriptors);
addRow("setsid", P::CreateNewSession);
addRow("reset-ids", P::ResetIds);
// On FreeBSD, we need to be session leader to disconnect from the CTTY
addRow("noctty", P::DisconnectControllingTerminal | P::CreateNewSession);
@ -1784,6 +1785,11 @@ void tst_QProcess::unixProcessParameters()
}
} scope;
if (params.flags & QProcess::UnixProcessFlag::ResetIds) {
if (getuid() == geteuid() && getgid() == getegid())
qInfo("Process has identical real and effective IDs; this test will do nothing");
}
if (params.flags & QProcess::UnixProcessFlag::DisconnectControllingTerminal) {
if (int fd = open("/dev/tty", O_RDONLY); fd < 0) {
qInfo("Process has no controlling terminal; this test will do nothing");