MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - data - DatabaseSecurity.h (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 93.4 % 61 57
Test Date: 2026-07-27 17:53:44 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 63.6 % 140 89

             Branch data     Line data    Source code
       1                 :             : #pragma once
       2                 :             : 
       3                 :             : #include <QDir>
       4                 :             : #include <QFile>
       5                 :             : #include <QFileInfo>
       6                 :             : #include <QString>
       7                 :             : 
       8                 :             : #ifdef Q_OS_UNIX
       9                 :             : #include <sys/stat.h>
      10                 :             : #include <unistd.h>
      11                 :             : #endif
      12                 :             : 
      13                 :             : namespace DatabaseSecurity {
      14                 :             : 
      15                 :        2158 : inline bool isOwnedByCurrentUser(const QFileInfo &info) {
      16                 :             : #ifdef Q_OS_UNIX
      17                 :        2158 :   return info.ownerId() == static_cast<uint>(::geteuid());
      18                 :             : #else
      19                 :             :   Q_UNUSED(info)
      20                 :             :   return true;
      21                 :             : #endif
      22                 :             : }
      23                 :             : 
      24                 :         748 : inline bool isSharedSystemDirectory(const QString &path) {
      25                 :             : #ifdef Q_OS_UNIX
      26   [ +  -  +  -  :         748 :   if (QDir::cleanPath(path) == QDir::rootPath())
                   -  + ]
      27                 :           0 :     return true;
      28                 :             : 
      29                 :         748 :   struct stat metadata {};
      30         [ +  - ]:         748 :   const QByteArray nativePath = QFile::encodeName(path);
      31   [ +  -  -  + ]:        1496 :   if (::lstat(nativePath.constData(), &metadata) != 0 ||
      32         [ -  + ]:         748 :       !S_ISDIR(metadata.st_mode))
      33                 :           0 :     return true;
      34                 :             : 
      35                 :             :   // Sticky directories such as /tmp are deliberately shared trust domains.
      36                 :             :   // Even when MailJD happens to run as their owner (notably uid 0), changing
      37                 :             :   // their mode would damage the system and using them directly would expose
      38                 :             :   // the SQLite filename and sidecars to local races.
      39                 :         748 :   return (metadata.st_mode & S_ISVTX) != 0;
      40                 :             : #else
      41                 :             :   Q_UNUSED(path)
      42                 :             :   return false;
      43                 :             : #endif
      44                 :         748 : }
      45                 :             : 
      46                 :        1418 : inline bool restrictOwnedRegularFile(const QString &path) {
      47         [ +  - ]:        1418 :   const QFileInfo info(path);
      48   [ +  -  +  +  :        2828 :   if (info.isSymLink() || !info.exists() || !info.isFile() ||
          +  -  +  -  +  
             -  +  +  +  
                      + ]
      49   [ +  -  -  + ]:        1410 :       !isOwnedByCurrentUser(info)) {
      50                 :           8 :     return false;
      51                 :             :   }
      52         [ +  - ]:        1410 :   return QFile::setPermissions(
      53                 :        1410 :       path, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
      54                 :        1418 : }
      55                 :             : 
      56                 :        1484 : inline bool secureExistingSidecars(const QString &dbPath) {
      57                 :        5933 :   for (const QString &suffix : {QStringLiteral("-wal"),
      58                 :        1484 :                                 QStringLiteral("-shm"),
      59         [ +  + ]:       19289 :                                 QStringLiteral("-journal")}) {
      60         [ +  - ]:        4450 :     const QString sidecarPath = dbPath + suffix;
      61         [ +  - ]:        4450 :     const QFileInfo sidecar(sidecarPath);
      62   [ +  -  +  +  :        4450 :     if (!sidecar.exists() && !sidecar.isSymLink())
          +  -  +  -  +  
                      + ]
      63                 :        4069 :       continue;
      64   [ +  -  +  + ]:         381 :     if (!restrictOwnedRegularFile(sidecarPath))
      65                 :           1 :       return false;
      66   [ +  +  +  +  :       14456 :   }
          +  +  +  +  -  
                      - ]
      67                 :        1483 :   return true;
      68                 :             : }
      69                 :             : 
      70                 :         774 : inline bool preparePath(const QString &dbPath) {
      71         [ +  + ]:         774 :   if (dbPath == QLatin1String(":memory:"))
      72                 :          21 :     return true;
      73                 :             : 
      74         [ +  - ]:         753 :   const QFileInfo info(dbPath);
      75         [ +  - ]:         753 :   const QString parentPath = info.absolutePath();
      76         [ +  - ]:         753 :   QFileInfo parentInfo(parentPath);
      77   [ +  -  +  +  :         753 :   if (!parentInfo.exists() && !parentInfo.isSymLink()) {
          +  -  +  -  +  
                      + ]
      78   [ +  -  +  -  :           4 :     if (!QDir().mkpath(parentPath))
                   +  - ]
      79                 :           4 :       return false;
      80         [ #  # ]:           0 :     parentInfo.refresh();
      81                 :             :   }
      82   [ +  -  +  -  :        1497 :   if (parentInfo.isSymLink() || !parentInfo.isDir() ||
                   +  - ]
      83   [ +  +  +  -  :        2245 :       !isOwnedByCurrentUser(parentInfo) ||
             +  -  +  + ]
      84   [ +  -  +  + ]:         748 :       isSharedSystemDirectory(parentPath))
      85                 :           3 :     return false;
      86         [ +  - ]:         746 :   if (!QFile::setPermissions(
      87         [ -  + ]:        1492 :           parentPath, QFileDevice::ReadOwner | QFileDevice::WriteOwner |
      88                 :             :                           QFileDevice::ExeOwner))
      89                 :           0 :     return false;
      90                 :             : 
      91                 :             :   // Reject sidecars planted while the directory was still too permissive.
      92   [ +  -  +  + ]:         746 :   if (!secureExistingSidecars(dbPath))
      93                 :           1 :     return false;
      94                 :             : 
      95   [ +  -  +  +  :         745 :   if (info.exists() || info.isSymLink())
          +  -  -  +  +  
                      + ]
      96         [ +  - ]:         299 :     return restrictOwnedRegularFile(dbPath);
      97                 :             : 
      98         [ +  - ]:         446 :   QFile file(dbPath);
      99         [ +  - ]:         446 :   return file.open(QIODevice::WriteOnly | QIODevice::NewOnly,
     100                 :         446 :                    QFileDevice::ReadOwner | QFileDevice::WriteOwner);
     101                 :         753 : }
     102                 :             : 
     103                 :         759 : inline bool restrictExistingFile(const QString &dbPath) {
     104         [ +  + ]:         759 :   if (dbPath == QLatin1String(":memory:"))
     105                 :          21 :     return true;
     106                 :             : 
     107   [ +  -  +  - ]:         738 :   return restrictOwnedRegularFile(dbPath) && secureExistingSidecars(dbPath);
     108                 :             : }
     109                 :             : 
     110                 :             : } // namespace DatabaseSecurity
        

Generated by: LCOV version 2.0-1