LCOV - code coverage report
Current view: top level - manager-lib - sudo.cpp (source / functions) Coverage Total Hit
Test: QtApplicationManager Lines: 24.2 % 248 60
Test Date: 2024-04-20 10:06:23 Functions: 39.1 % 23 9
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 7.4 % 296 22

             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
        

Generated by: LCOV version 2.0-1