Branch data Line data Source code
1 : : // Copyright (C) 2021 The Qt Company Ltd.
2 : : // Copyright (C) 2019 Luxoft Sweden AB
3 : : // Copyright (C) 2018 Pelagicore AG
4 : : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
5 : :
6 : :
7 : : #include <QProcess>
8 : : #include <QDir>
9 : : #include <QFile>
10 : : #include <QtEndian>
11 : : #include <QDataStream>
12 : : #include <qplatformdefs.h>
13 : : #include <QDataStream>
14 : :
15 : : #include "logging.h"
16 : : #include "sudo.h"
17 : : #include "utilities.h"
18 : : #include "exception.h"
19 : : #include "global.h"
20 : :
21 : : #include <errno.h>
22 : :
23 : : using namespace Qt::StringLiterals;
24 : :
25 : : #if defined(Q_OS_LINUX)
26 : : # include "processtitle.h"
27 : :
28 : : # include <fcntl.h>
29 : : # include <unistd.h>
30 : : # include <sys/socket.h>
31 : : # include <sys/errno.h>
32 : : # include <sys/ioctl.h>
33 : : # include <sys/stat.h>
34 : : # include <sys/prctl.h>
35 : : # include <sys/mount.h>
36 : : # include <sys/syscall.h>
37 : : # include <sched.h>
38 : : # include <linux/capability.h>
39 : :
40 : : // These two functions are implemented in glibc, but the header file is
41 : : // in the separate libcap-dev package. Since we want to avoid unnecessary
42 : : // dependencies, we just declare them here
43 : : extern "C" int capset(cap_user_header_t header, cap_user_data_t data);
44 : : extern "C" int capget(cap_user_header_t header, const cap_user_data_t data);
45 : :
46 : : // Support for old/broken C libraries
47 : : # if defined(_LINUX_CAPABILITY_VERSION) && !defined(_LINUX_CAPABILITY_VERSION_1)
48 : : # define _LINUX_CAPABILITY_VERSION_1 _LINUX_CAPABILITY_VERSION
49 : : # define _LINUX_CAPABILITY_U32S_1 1
50 : : # if !defined(CAP_TO_INDEX)
51 : : # define CAP_TO_INDEX(x) ((x) >> 5)
52 : : # endif
53 : : # if !defined(CAP_TO_MASK)
54 : : # define CAP_TO_MASK(x) (1 << ((x) & 31))
55 : : # endif
56 : : # endif
57 : : # if defined(_LINUX_CAPABILITY_VERSION_3) // use 64-bit support, if available
58 : : # define AM_CAP_VERSION _LINUX_CAPABILITY_VERSION_3
59 : : # define AM_CAP_SIZE _LINUX_CAPABILITY_U32S_3
60 : : # else // fallback to 32-bit support
61 : : # define AM_CAP_VERSION _LINUX_CAPABILITY_VERSION_1
62 : : # define AM_CAP_SIZE _LINUX_CAPABILITY_U32S_1
63 : : # endif
64 : :
65 : : // Convenient way to ignore EINTR on any system call
66 : : # define EINTR_LOOP(cmd) __extension__ ({__typeof__(cmd) res = 0; do { res = cmd; } while (res == -1 && errno == EINTR); res; })
67 : :
68 : :
69 : : // Declared as weak symbol here, so we can check at runtime if we were compiled against libgcov
70 : : extern "C" void __gcov_init() __attribute__((weak)); // NOLINT(reserved-identifier)
71 : :
72 : :
73 : : #ifndef OPEN_TREE_CLONE
74 : : # define OPEN_TREE_CLONE 1
75 : : #endif
76 : :
77 : : #ifndef OPEN_TREE_CLOEXEC
78 : : # define OPEN_TREE_CLOEXEC O_CLOEXEC
79 : : #endif
80 : :
81 : : #ifndef SYS_open_tree
82 : : # define SYS_open_tree 428
83 : : #endif
84 : :
85 : : #ifndef MOVE_MOUNT_F_EMPTY_PATH
86 : : # define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
87 : : #endif
88 : :
89 : : #ifndef SYS_move_mount
90 : : # define SYS_move_mount 429
91 : : #endif
92 : :
93 : : #ifndef MOUNT_ATTR_RDONLY
94 : : # define MOUNT_ATTR_RDONLY 0x00000001
95 : : #endif
96 : :
97 : : #ifndef SYS_mount_setattr
98 : : # define SYS_mount_setattr 442
99 : : #endif
100 : :
101 : : #ifndef MOUNT_ATTR_SIZE_VER0
102 : : # define MOUNT_ATTR_SIZE_VER0 32
103 : :
104 : : struct mount_attr {
105 : : __u64 attr_set;
106 : : __u64 attr_clr;
107 : : __u64 propagation;
108 : : __u64 userns_fd;
109 : : };
110 : : #endif
111 : :
112 : : #ifndef AT_RECURSIVE
113 : : # define AT_RECURSIVE 0x8000
114 : : #endif
115 : :
116 : : #ifndef AT_EMPTY_PATH
117 : : # define AT_EMPTY_PATH 0x1000
118 : : #endif
119 : :
120 : : #ifndef SYS_mount_setattr
121 : : # define SYS_mount_setattr 442
122 : : #endif
123 : :
124 : : #ifndef SYS_pidfd_open
125 : : # define SYS_pidfd_open 434
126 : : #endif
127 : :
128 : : QT_BEGIN_NAMESPACE_AM
129 : :
130 : 0 : static void sigHupHandler(int sig)
131 : : {
132 [ # # ]: 0 : if (sig == SIGHUP)
133 : 0 : _exit(0);
134 : 0 : }
135 : :
136 : : QT_END_NAMESPACE_AM
137 : :
138 : : #endif // Q_OS_LINUX
139 : :
140 : :
141 : : QT_BEGIN_NAMESPACE_AM
142 : :
143 : 43 : void Sudo::forkServer(DropPrivileges dropPrivileges)
144 : : {
145 : 43 : bool canSudo = false;
146 : :
147 : : #if defined(Q_OS_LINUX)
148 : 43 : uid_t realUid = getuid();
149 : 43 : uid_t effectiveUid = geteuid();
150 : 43 : canSudo = (realUid == 0) || (effectiveUid == 0);
151 : : #else
152 : : Q_UNUSED(dropPrivileges)
153 : : #endif
154 : :
155 [ + - ]: 43 : if (!canSudo) {
156 : 43 : SudoServer::createInstance(-1);
157 : 43 : SudoClient::createInstance(-1, SudoServer::instance());
158 : 43 : return;
159 : : }
160 : :
161 : : #if defined(Q_OS_LINUX)
162 : 0 : gid_t realGid = getgid();
163 : 0 : uid_t sudoUid = static_cast<uid_t>(qEnvironmentVariableIntValue("SUDO_UID"));
164 : :
165 : : // run as normal user (e.g. 1000): uid == 1000 euid == 1000
166 : : // run with binary suid-root: uid == 1000 euid == 0
167 : : // run with sudo (no suid-root): uid == 0 euid == 0 $SUDO_UID == 1000
168 : :
169 : : // treat sudo as special variant of a SUID executable
170 [ # # # # ]: 0 : if (realUid == 0 && effectiveUid == 0 && sudoUid != 0) {
171 : 0 : realUid = sudoUid;
172 : 0 : realGid = static_cast<gid_t>(qEnvironmentVariableIntValue("SUDO_GID"));
173 : :
174 [ # # # # ]: 0 : if (setresgid(realGid, 0, 0) || setresuid(realUid, 0, 0))
175 : 0 : throw Exception(errno, "Could not set real user or group ID");
176 : : }
177 : :
178 : 0 : int socketFds[2];
179 [ # # # # : 0 : if (EINTR_LOOP(socketpair(AF_UNIX, SOCK_DGRAM, 0, socketFds)) != 0)
# # ]
180 : 0 : throw Exception(errno, "Could not create a pair of sockets");
181 : :
182 : : // We need to make the gcda files generated by the root process writable by the normal user.
183 : : // There is no way to detect a compilation with -ftest-coverage, but we can check for gcov
184 : : // symbols at runtime. GCov will open all gcda files at fork() time, so we can get away with
185 : : // switching umasks around the fork() call.
186 : :
187 : 0 : mode_t realUmask = 0;
188 [ # # ]: 0 : if (__gcov_init)
189 : 0 : realUmask = umask(0);
190 : :
191 : 0 : pid_t pid = fork();
192 [ # # ]: 0 : if (pid < 0) {
193 : 0 : throw Exception(errno, "Could not fork process");
194 [ # # ]: 0 : } else if (pid == 0) {
195 : : // child
196 : 0 : close(0);
197 : 0 : setsid();
198 : :
199 : : // reset umask
200 [ # # ]: 0 : if (realUmask)
201 : 0 : umask(realUmask);
202 : :
203 : : // This call is Linux only, but it makes it so easy to detect a dying parent process.
204 : : // We would have a big problem otherwise, since the main process drops its privileges,
205 : : // which prevents it from sending SIGHUP to the child process, which still runs with
206 : : // root privileges.
207 : 0 : prctl(PR_SET_PDEATHSIG, SIGHUP);
208 : 0 : signal(SIGHUP, sigHupHandler);
209 : :
210 : : // Drop as many capabilities as possible, just to be on the safe side
211 : 0 : static const quint32 neededCapabilities[] = {
212 : : CAP_SYS_ADMIN,
213 : : CAP_SYS_CHROOT,
214 : : CAP_SYS_PTRACE,
215 : : CAP_CHOWN,
216 : : CAP_FOWNER,
217 : : CAP_DAC_OVERRIDE
218 : : };
219 : :
220 : 0 : bool capSetOk = false;
221 : 0 : __user_cap_header_struct capHeader { AM_CAP_VERSION, getpid() };
222 : 0 : __user_cap_data_struct capData[AM_CAP_SIZE];
223 [ # # ]: 0 : if (capget(&capHeader, capData) == 0) {
224 : 0 : quint32 capNeeded[AM_CAP_SIZE];
225 : 0 : memset(&capNeeded, 0, sizeof(capNeeded));
226 [ # # ]: 0 : for (quint32 cap : neededCapabilities) {
227 : 0 : int idx = CAP_TO_INDEX(cap);
228 : 0 : Q_ASSERT(idx < AM_CAP_SIZE);
229 : 0 : capNeeded[idx] |= CAP_TO_MASK(cap);
230 : : }
231 [ # # ]: 0 : for (int i = 0; i < AM_CAP_SIZE; ++i)
232 : 0 : capData[i].effective = capData[i].permitted = capData[i].inheritable = capNeeded[i];
233 [ # # ]: 0 : if (capset(&capHeader, capData) == 0)
234 : 0 : capSetOk = true;
235 : : }
236 : 0 : if (!capSetOk)
237 [ # # # # ]: 0 : qCCritical(LogSystem) << "could not drop privileges in the SudoServer process -- continuing with full root privileges";
238 : :
239 : 0 : SudoServer::createInstance(socketFds[0]);
240 : 0 : ProcessTitle::setTitle("%s", "sudo helper");
241 : 0 : SudoServer::instance()->run();
242 : : }
243 : : // parent
244 : :
245 : : // reset umask
246 [ # # ]: 0 : if (realUmask)
247 : 0 : umask(realUmask);
248 : :
249 : 0 : SudoClient::createInstance(socketFds[1]);
250 : :
251 [ # # ]: 0 : if (realUid != effectiveUid) {
252 : : // drop all root privileges
253 [ # # ]: 0 : if (dropPrivileges == DropPrivilegesPermanently) {
254 [ # # # # ]: 0 : if (setresgid(realGid, realGid, realGid) || setresuid(realUid, realUid, realUid)) {
255 : 0 : kill(pid, SIGKILL);
256 : 0 : throw Exception(errno, "Could not set real user or group ID");
257 : : }
258 : : } else {
259 [ # # # # ]: 0 : qCCritical(LogSystem) << "\nSudo was instructed to NOT drop root privileges permanently.\nThis is dangerous and should only be used in auto-tests!\n";
260 [ # # # # ]: 0 : if (setresgid(realGid, realGid, 0) || setresuid(realUid, realUid, 0)) {
261 : 0 : kill(pid, 9);
262 : 0 : throw Exception(errno, "Could not set real user or group ID");
263 : : }
264 : : }
265 : : }
266 : 0 : ::atexit([]() { SudoClient::instance()->stopServer(); });
267 : : #endif
268 : : }
269 : :
270 : 86 : SudoInterface::SudoInterface()
271 : 0 : { }
272 : :
273 : : #ifdef Q_OS_LINUX
274 : 0 : bool SudoInterface::sendMessage(int socket, const QByteArray &msg, MessageType type, const QString &errorString)
275 : : {
276 : 0 : QByteArray packet;
277 [ # # ]: 0 : QDataStream ds(&packet, QDataStream::WriteOnly);
278 [ # # # # ]: 0 : ds << errorString << msg;
279 [ # # # # ]: 0 : packet.prepend((type == Request) ? "RQST" : "RPLY");
280 : :
281 [ # # # # : 0 : auto bytesWritten = EINTR_LOOP(write(socket, packet.constData(), static_cast<size_t>(packet.size())));
# # # # ]
282 : 0 : return bytesWritten == packet.size();
283 : 0 : }
284 : :
285 : :
286 : 0 : QByteArray SudoInterface::receiveMessage(int socket, MessageType type, QString *errorString)
287 : : {
288 : 0 : const int headerSize = 4;
289 : 0 : char recvBuffer[8*1024];
290 [ # # # # ]: 0 : auto bytesReceived = EINTR_LOOP(recv(socket, recvBuffer, sizeof(recvBuffer), 0));
291 : :
292 [ # # # # : 0 : if ((bytesReceived < headerSize) || qstrncmp(recvBuffer, (type == Request ? "RQST" : "RPLY"), 4)) {
# # ]
293 : 0 : *errorString = u"failed to receive command from the SudoClient process"_s;
294 : : //qCCritical(LogSystem) << *errorString;
295 : 0 : return QByteArray();
296 : : }
297 : :
298 : 0 : QByteArray packet(recvBuffer + headerSize, int(bytesReceived) - headerSize);
299 : :
300 [ # # ]: 0 : QDataStream ds(&packet, QDataStream::ReadOnly);
301 : 0 : QByteArray msg;
302 [ # # # # ]: 0 : ds >> *errorString >> msg;
303 : 0 : return msg;
304 : 0 : }
305 : : #endif // Q_OS_LINUX
306 : :
307 : :
308 : : SudoClient *SudoClient::s_instance = nullptr;
309 : :
310 : 91 : SudoClient *SudoClient::instance()
311 : : {
312 : 91 : return s_instance;
313 : : }
314 : :
315 : 2 : bool SudoClient::isFallbackImplementation() const
316 : : {
317 : 2 : return m_socket < 0;
318 : : }
319 : :
320 : 43 : SudoClient::SudoClient(int socketFd)
321 : 43 : : m_socket(socketFd)
322 : 0 : { }
323 : :
324 : 43 : SudoClient *SudoClient::createInstance(int socketFd, SudoServer *shortCircuit)
325 : : {
326 [ + - ]: 43 : if (!s_instance) {
327 : 43 : s_instance = new SudoClient(socketFd);
328 : 43 : s_instance->m_shortCircuit = shortCircuit;
329 : : }
330 : 43 : return s_instance;
331 : : }
332 : :
333 : :
334 : : // this is not nice, but it prevents a lot of copy/paste errors. (the C++ variadic template version
335 : : // would be equally ugly, since it needs a friend declaration in the public header)
336 : : template <typename R, typename C, typename ...Ps> R returnType(R (C::*)(Ps...));
337 : :
338 : : #define CALL(FUNC_NAME, PARAM) \
339 : : QByteArray msg; \
340 : : QDataStream(&msg, QDataStream::WriteOnly) << #FUNC_NAME << PARAM; \
341 : : QByteArray reply = call(msg); \
342 : : QDataStream result(&reply, QDataStream::ReadOnly); \
343 : : decltype(returnType(&SudoClient::FUNC_NAME)) r; \
344 : : result >> r; \
345 : : return r
346 : :
347 : 43 : bool SudoClient::removeRecursive(const QString &fileOrDir)
348 : : {
349 [ + - + - : 43 : CALL(removeRecursive, fileOrDir);
+ - + - +
- + - ]
350 : 43 : }
351 : :
352 : 0 : bool SudoClient::setOwnerAndPermissionsRecursive(const QString &fileOrDir, uid_t user, gid_t group, mode_t permissions)
353 : : {
354 [ # # # # : 0 : CALL(setOwnerAndPermissionsRecursive, fileOrDir << user << group << permissions);
# # # # #
# # # # #
# # # # ]
355 : 0 : }
356 : :
357 : 0 : bool SudoClient::bindMountFileSystem(const QString &from, const QString &to, bool readOnly,
358 : : quint64 namespacePid)
359 : : {
360 [ # # # # : 0 : CALL(bindMountFileSystem, from << to << readOnly << namespacePid);
# # # # #
# # # # #
# # # # ]
361 : 0 : }
362 : :
363 : 0 : void SudoClient::stopServer()
364 : : {
365 : : #ifdef Q_OS_LINUX
366 [ # # # # ]: 0 : if (!m_shortCircuit && m_socket >= 0) {
367 : 0 : QByteArray msg;
368 [ # # # # ]: 0 : QDataStream(&msg, QDataStream::WriteOnly) << "stopServer";
369 [ # # ]: 0 : sendMessage(m_socket, msg, Request);
370 : 0 : }
371 : : #endif
372 : 0 : }
373 : :
374 : 43 : QByteArray SudoClient::call(const QByteArray &msg)
375 : : {
376 : 43 : QMutexLocker locker(&m_mutex);
377 : :
378 [ + - ]: 43 : if (m_shortCircuit) {
379 [ + - ]: 43 : const QByteArray res = m_shortCircuit->receive(msg);
380 : 43 : m_errorString = m_shortCircuit->lastError();
381 : 43 : return res;
382 : 43 : }
383 : :
384 : : #ifdef Q_OS_LINUX
385 [ # # ]: 0 : if (m_socket >= 0) {
386 [ # # # # : 0 : if (sendMessage(m_socket, msg, Request))
# # ]
387 [ # # ]: 0 : return receiveMessage(m_socket, Reply, &m_errorString);
388 : : }
389 : : #else
390 : : Q_UNUSED(m_socket)
391 : : #endif
392 : :
393 : : //qCCritical(LogSystem) << "failed to send command to the SudoServer process";
394 : 0 : m_errorString = u"failed to send command to the SudoServer process"_s;
395 [ + - ]: 43 : return QByteArray();
396 : 43 : }
397 : :
398 : :
399 : :
400 : : SudoServer *SudoServer::s_instance = nullptr;
401 : :
402 : 43 : SudoServer *SudoServer::instance()
403 : : {
404 : 43 : return s_instance;
405 : : }
406 : :
407 : 43 : SudoServer::SudoServer(int socketFd)
408 : 43 : : m_socket(socketFd)
409 : 0 : { }
410 : :
411 : 43 : SudoServer *SudoServer::createInstance(int socketFd)
412 : : {
413 [ + - ]: 43 : if (!s_instance)
414 : 43 : s_instance = new SudoServer(socketFd);
415 : 43 : return s_instance;
416 : : }
417 : :
418 : 0 : void SudoServer::run()
419 : : {
420 : : #ifdef Q_OS_LINUX
421 : 0 : QString dummy;
422 : :
423 : 0 : forever {
424 [ # # ]: 0 : QByteArray msg = receiveMessage(m_socket, Request, &dummy);
425 [ # # ]: 0 : QByteArray reply = receive(msg);
426 : :
427 [ # # ]: 0 : if (m_stop)
428 : 0 : exit(0);
429 : :
430 [ # # ]: 0 : sendMessage(m_socket, reply, Reply, m_errorString);
431 : 0 : }
432 : : #else
433 : : Q_UNUSED(m_socket)
434 : : Q_ASSERT(false);
435 : : exit(0);
436 : : #endif
437 : 0 : }
438 : :
439 : 43 : QByteArray SudoServer::receive(const QByteArray &msg)
440 : : {
441 : 43 : QDataStream params(msg);
442 : 43 : char *functionArray;
443 [ + - ]: 43 : params >> functionArray;
444 [ + - ]: 43 : QByteArray function(functionArray);
445 [ + - ]: 43 : delete [] functionArray;
446 : 43 : QByteArray reply;
447 [ + - ]: 43 : QDataStream result(&reply, QDataStream::WriteOnly);
448 : 43 : m_errorString.clear();
449 : :
450 [ + - ]: 43 : if (function == "removeRecursive") {
451 : 43 : QString fileOrDir;
452 [ + - ]: 43 : params >> fileOrDir;
453 [ + - + - ]: 43 : result << removeRecursive(fileOrDir);
454 [ - - ]: 43 : } else if (function == "setOwnerAndPermissionsRecursive") {
455 : 0 : QString fileOrDir;
456 : 0 : uid_t user;
457 : 0 : gid_t group;
458 : 0 : mode_t permissions;
459 [ # # # # : 0 : params >> fileOrDir >> user >> group >> permissions;
# # # # ]
460 [ # # # # ]: 0 : result << setOwnerAndPermissionsRecursive(fileOrDir, user, group, permissions);
461 [ # # ]: 0 : } else if (function == "bindMountFileSystem") {
462 : 0 : QString from;
463 : 0 : QString to;
464 : 0 : bool readOnly;
465 : 0 : quint64 namespacePid;
466 [ # # # # : 0 : params >> from >> to >> readOnly >> namespacePid;
# # # # ]
467 [ # # # # ]: 0 : result << bindMountFileSystem(from, to, readOnly, namespacePid);
468 [ # # ]: 0 : } else if (function == "stopServer") {
469 : 0 : m_stop = true;
470 : : } else {
471 [ # # ]: 0 : reply.truncate(0);
472 [ # # # # ]: 0 : m_errorString = u"unknown function '%1' called in SudoServer"_s.arg(QString::fromLatin1(function));
473 : : }
474 : 43 : return reply;
475 : 43 : }
476 : :
477 : 43 : bool SudoServer::removeRecursive(const QString &fileOrDir)
478 : : {
479 : 43 : try {
480 [ + - - + ]: 43 : if (!recursiveOperation(fileOrDir, safeRemove))
481 : 0 : throw Exception(errno, "could not recursively remove %1").arg(fileOrDir);
482 : : return true;
483 [ - - ]: 0 : } catch (const Exception &e) {
484 : 0 : m_errorString = e.errorString();
485 : 0 : return false;
486 : 0 : }
487 : : }
488 : :
489 : 0 : bool SudoServer::setOwnerAndPermissionsRecursive(const QString &fileOrDir, uid_t user, gid_t group, mode_t permissions)
490 : : {
491 : : #if defined(Q_OS_LINUX)
492 : 0 : static auto setOwnerAndPermissions =
493 [ # # ]: 0 : [user, group, permissions](const QString &path, RecursiveOperationType type) -> bool {
494 [ # # ]: 0 : if (type == RecursiveOperationType::EnterDirectory)
495 : : return true;
496 : :
497 : 0 : const QByteArray localPath = path.toLocal8Bit();
498 : 0 : bool noModeChange = (permissions == static_cast<mode_t>(-1));
499 : 0 : mode_t mode = permissions;
500 : :
501 [ # # ]: 0 : if (type == RecursiveOperationType::LeaveDirectory) {
502 : : // set the x bit for directories, but only where it makes sense
503 [ # # ]: 0 : if (mode & 06)
504 : 0 : mode |= 01;
505 [ # # ]: 0 : if (mode & 060)
506 : 0 : mode |= 010;
507 [ # # ]: 0 : if (mode & 0600)
508 : 0 : mode |= 0100;
509 : : }
510 : :
511 [ # # ]: 0 : return ((noModeChange ? true : (chmod(localPath, mode) == 0))
512 [ # # # # ]: 0 : && (chown(localPath, user, group) == 0));
513 [ # # # # ]: 0 : };
514 : :
515 : 0 : try {
516 [ # # # # ]: 0 : if (!recursiveOperation(fileOrDir, setOwnerAndPermissions)) {
517 : 0 : throw Exception(errno, "could not recursively set owner and permission on %1 to %2:%3 / %4")
518 : 0 : .arg(fileOrDir).arg(user).arg(group).arg(int(permissions), 4, 8, QChar(u'0'));
519 : : }
520 : : return true;
521 [ - - ]: 0 : } catch (const Exception &e) {
522 : 0 : m_errorString = e.errorString();
523 : 0 : return false;
524 : 0 : }
525 : : #else
526 : : Q_UNUSED(fileOrDir)
527 : : Q_UNUSED(user)
528 : : Q_UNUSED(group)
529 : : Q_UNUSED(permissions)
530 : : return false;
531 : : #endif // Q_OS_LINUX
532 : : }
533 : :
534 : 0 : bool SudoServer::bindMountFileSystem(const QString &from, const QString &to, bool readOnly, quint64 namespacePid)
535 : : {
536 : : #if defined(Q_OS_LINUX)
537 : 0 : bool result = true;
538 : 0 : int oldNsFd = -1;
539 : :
540 : 0 : try {
541 : : // Create a detached mount point for our source location
542 [ # # ]: 0 : int fromFd = int(::syscall(SYS_open_tree, -EBADF, from.toLocal8Bit().constData(), OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE));
543 [ # # ]: 0 : if (fromFd < 0)
544 : 0 : throw Exception(errno, "could not create a detached mount point for %1").arg(from);
545 : :
546 [ # # ]: 0 : if (readOnly) {
547 : 0 : ::mount_attr mountAttr { MOUNT_ATTR_RDONLY, 0, 0, 0 };
548 [ # # ]: 0 : if (::syscall(SYS_mount_setattr, fromFd, "", AT_EMPTY_PATH | AT_RECURSIVE, &mountAttr, sizeof(mountAttr)) < 0)
549 : 0 : throw Exception(errno, "could not set the detached mount point for %1 to read-only").arg(from);
550 : : }
551 : :
552 [ # # ]: 0 : if (namespacePid) {
553 : : // Save our current mount namespace to be able to restore it later
554 [ # # ]: 0 : oldNsFd = open("/proc/self/ns/mnt", O_RDONLY);
555 [ # # ]: 0 : if (oldNsFd < 0)
556 : 0 : throw Exception(errno, "could not open our own mount namespace");
557 : :
558 : 0 : int pidFd = int(::syscall(SYS_pidfd_open, pid_t(namespacePid), 0));
559 [ # # ]: 0 : if (pidFd < 0)
560 : 0 : throw Exception(errno, "process %1 is not available").arg(namespacePid);
561 [ # # ]: 0 : if (::setns(pidFd, CLONE_NEWNS) < 0)
562 : 0 : throw Exception(errno, "could not enter the mount namespace of process %1").arg(namespacePid);
563 : : }
564 : :
565 : : // Mount the detached mount point to the final location within the mount namespace
566 [ # # # # ]: 0 : if (::syscall(SYS_move_mount, fromFd, "", -EBADF, to.toLocal8Bit().constData(), MOVE_MOUNT_F_EMPTY_PATH) < 0)
567 : 0 : throw Exception(errno, "could not move the detached mount point to %1").arg(to);
568 : :
569 [ - - ]: 0 : } catch (const Exception &e) {
570 : 0 : result = false;
571 : 0 : m_errorString = e.errorString();
572 : 0 : }
573 : :
574 [ # # # # ]: 0 : if ((oldNsFd >= 0) && namespacePid) {
575 : : // Restore our old mount namespace
576 [ # # ]: 0 : if (::setns(oldNsFd, CLONE_NEWNS) < 0)
577 [ # # # # ]: 0 : qFatal() << "SudoHelper process is halted: could not reset the mount namespace:" << strerror(errno);
578 : : }
579 : :
580 : 0 : return result;
581 : : #else
582 : : Q_UNUSED(from)
583 : : Q_UNUSED(to)
584 : : Q_UNUSED(readOnly)
585 : : Q_UNUSED(namespacePid)
586 : : m_errorString = u"bindMountFileSystem is only available on Linux"_s;
587 : : return false;
588 : : #endif // Q_OS_LINUX
589 : : }
590 : :
591 : : QT_END_NAMESPACE_AM
|