From 6ed3a6fd46ac356bb9f8cab36ab0a54927a351b1 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 22 Feb 2024 23:12:58 +0100 Subject: [PATCH] enfore read-only folder via ACL on windows Signed-off-by: Matthieu Gallien --- src/common/filesystembase.h | 9 ++- src/libsync/filesystem.cpp | 119 ++++++++++++++++++++++++++++- src/libsync/filesystem.h | 11 +-- src/libsync/owncloudpropagator.cpp | 19 ++++- src/libsync/propagatedownload.cpp | 4 +- src/libsync/propagatorjobs.cpp | 34 +++++---- 6 files changed, 169 insertions(+), 27 deletions(-) diff --git a/src/common/filesystembase.h b/src/common/filesystembase.h index fd0572804dcb2..6a3baa7afb6e3 100644 --- a/src/common/filesystembase.h +++ b/src/common/filesystembase.h @@ -20,12 +20,13 @@ #include "config.h" +#include "csync/ocsynclib.h" + #include -#include #include #include -#include +#include class QFile; @@ -42,6 +43,10 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem) * @brief This file contains file system helper */ namespace FileSystem { + enum class FolderPermissions { + ReadOnly, + ReadWrite, + }; /** * @brief Mark the file as hidden (only has effects on windows) diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index 346d922b1e089..6f9f47efceac8 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -25,8 +25,11 @@ #include #include +#include + #ifdef Q_OS_WIN #include +#include #endif namespace OCC { @@ -193,9 +196,123 @@ bool FileSystem::getInode(const QString &filename, quint64 *inode) return false; } -bool FileSystem::setFolderPermissions(const QString &path) +bool FileSystem::setFolderPermissions(const QString &path, + FileSystem::FolderPermissions permissions) { + auto modified = false; + + switch (permissions) { + case OCC::FileSystem::FolderPermissions::ReadOnly: + std::filesystem::permissions(path.toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); + qCInfo(lcFileSystem) << "new permissions" << static_cast(std::filesystem::status(path.toStdWString()).permissions()); + break; + case OCC::FileSystem::FolderPermissions::ReadWrite: + break; + } + +#ifdef Q_OS_WIN + SECURITY_INFORMATION info = DACL_SECURITY_INFORMATION; + constexpr auto length = 512; + char securityDescriptor[length]; + auto neededLength = 0ul; + + if (!GetFileSecurityW(path.toStdWString().c_str(), info, &securityDescriptor, length, &neededLength)) { + if (neededLength > length) { + qCWarning(lcFileSystem) << "error when calling GetFileSecurityW" << path << "size is too small to get the result" << "need" << neededLength << "instead of" << length; + return false; + } + qCWarning(lcFileSystem) << "error when calling GetFileSecurityW" << path << GetLastError(); + return false; + } + + int daclPresent = false, daclDefault = false; + PACL resultDacl = nullptr; + if (!GetSecurityDescriptorDacl(&securityDescriptor, &daclPresent, &resultDacl, &daclDefault)) { + qCWarning(lcFileSystem) << "error when calling GetSecurityDescriptorDacl" << path << GetLastError(); + return false; + } + if (!daclPresent) { + qCWarning(lcFileSystem) << "error when calling DACL needed to set a folder read-only or read-write is missing" << path; + return false; + } + + PACL newDacl = reinterpret_cast(new char[length]); + if (!InitializeAcl(newDacl, length, ACL_REVISION)) { + qCWarning(lcFileSystem) << "error when calling InitializeAcl" << path << GetLastError(); + return false; + } + + PSID sid = nullptr; + if (!ConvertStringSidToSidA("S-1-5-32-545", &sid)) + { + qCWarning(lcFileSystem) << "error when calling ConvertStringSidToSidA" << path << GetLastError(); + return false; + } + + if (permissions == FileSystem::FolderPermissions::ReadOnly) { + qCInfo(lcFileSystem) << path << "will be read only"; + if (!AddAccessDeniedAce(newDacl, ACL_REVISION, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD, sid)) { + qCWarning(lcFileSystem) << "error when calling AddAccessDeniedAce << path" << GetLastError(); + return false; + } + } + + ACL_SIZE_INFORMATION aclSize; + if (!GetAclInformation(resultDacl, &aclSize, sizeof(aclSize), AclSizeInformation)) { + qCWarning(lcFileSystem) << "error when calling GetAclInformation" << path << GetLastError(); + return false; + } + + for (int i = 0; i < aclSize.AceCount; ++i) { + void *currentAce = nullptr; + if (!GetAce(resultDacl, i, ¤tAce)) { + qCWarning(lcFileSystem) << "error when calling GetAce" << path << GetLastError(); + return false; + } + + const auto currentAceHeader = reinterpret_cast(currentAce); + + if (permissions == FileSystem::FolderPermissions::ReadWrite) { + qCInfo(lcFileSystem) << path << "will be read write"; + } + if (permissions == FileSystem::FolderPermissions::ReadWrite && (ACCESS_DENIED_ACE_TYPE == (currentAceHeader->AceType & ACCESS_DENIED_ACE_TYPE))) { + qCWarning(lcFileSystem) << "AceHeader" << path << currentAceHeader->AceFlags << currentAceHeader->AceSize << currentAceHeader->AceType; + continue; + } + + if (!AddAce(newDacl, ACL_REVISION, i + 1, currentAce, currentAceHeader->AceSize)) { + qCWarning(lcFileSystem) << "error when calling AddAce" << path << GetLastError(); + return false; + } + } + SECURITY_DESCRIPTOR newSecurityDescriptor; + if (!InitializeSecurityDescriptor(&newSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION)) { + qCWarning(lcFileSystem) << "error when calling InitializeSecurityDescriptor" << path << GetLastError(); + return false; + } + + if (!SetSecurityDescriptorDacl(&newSecurityDescriptor, true, newDacl, false)) { + qCWarning(lcFileSystem) << "error when calling SetSecurityDescriptorDacl" << path << GetLastError(); + return false; + } + + if (!SetFileSecurityW(path.toStdWString().c_str(), info, &newSecurityDescriptor)) { + qCWarning(lcFileSystem) << "error when calling SetFileSecurityW" << path << GetLastError(); + return false; + } +#endif + + switch (permissions) { + case OCC::FileSystem::FolderPermissions::ReadOnly: + break; + case OCC::FileSystem::FolderPermissions::ReadWrite: + std::filesystem::permissions(path.toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add); + qCInfo(lcFileSystem) << "new permissions" << static_cast(std::filesystem::status(path.toStdWString()).permissions()); + break; + } + + return true; } diff --git a/src/libsync/filesystem.h b/src/libsync/filesystem.h index 24f4c6234681a..224e05b8a6052 100644 --- a/src/libsync/filesystem.h +++ b/src/libsync/filesystem.h @@ -16,14 +16,14 @@ #include "config.h" +#include "owncloudlib.h" +#include "common/filesystembase.h" + #include + #include #include -#include -// Chain in the base include and extend the namespace -#include "common/filesystembase.h" - class QFile; namespace OCC { @@ -97,7 +97,8 @@ namespace FileSystem { const std::function &onDeleted = nullptr, QStringList *errors = nullptr); - bool OWNCLOUDSYNC_EXPORT setFolderPermissions(const QString &path); + bool OWNCLOUDSYNC_EXPORT setFolderPermissions(const QString &path, + FileSystem::FolderPermissions permissions); } /** @} */ diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 9bcd3053ebceb..67355f3222865 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -1460,7 +1460,18 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status) !_item->_remotePerm.hasPermission(RemotePermissions::CanMove) && !_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)) { try { - std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); + if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) { + FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadOnly); + qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); + std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); + qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); + } + if (QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) { + FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadOnly); + qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); + std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); + qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); + } } catch (const std::filesystem::filesystem_error &e) { @@ -1475,10 +1486,16 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status) !_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) { try { if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) { + FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadWrite); + qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add); + qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); } if (QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) { + FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadWrite); + qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add); + qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); } } catch (const std::filesystem::filesystem_error &e) diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index a5fadf2792287..f8cabf6bffde3 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -680,7 +680,7 @@ void PropagateDownloadFile::startDownload() auto parentFolderStatus = std::filesystem::status(_parentFolderPath); _parentPermissions = parentFolderStatus.permissions(); if ((_parentPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write) { - std::filesystem::permissions(_parentFolderPath, _parentPermissions | std::filesystem::perms::owner_write, std::filesystem::perm_options::replace); + FileSystem::setFolderPermissions(QString::fromStdWString(_parentFolderPath), FileSystem::FolderPermissions::ReadWrite); emit propagator()->touchedFile(QString::fromStdWString(_parentFolderPath.wstring())); _needParentFolderRestorePermissions = true; } @@ -1292,7 +1292,7 @@ void PropagateDownloadFile::downloadFinished() if (_needParentFolderRestorePermissions) { try { - std::filesystem::permissions(_parentFolderPath, _parentPermissions, std::filesystem::perm_options::replace); + FileSystem::setFolderPermissions(QString::fromStdWString(_parentFolderPath), FileSystem::FolderPermissions::ReadWrite); emit propagator()->touchedFile(QString::fromStdWString(_parentFolderPath.wstring())); _needParentFolderRestorePermissions = false; } diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index 6e86cc906fca8..0223d5988dda3 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -185,6 +185,7 @@ void PropagateLocalMkdir::startLocalMkdir() auto parentFolderPath = std::filesystem::path{}; auto parentPermissions = std::filesystem::perms{}; + auto parentNeedRollbackPermissions = false; try { const auto newDirPath = std::filesystem::path{newDirStr.toStdWString()}; Q_ASSERT(newDirPath.has_parent_path()); @@ -192,7 +193,8 @@ void PropagateLocalMkdir::startLocalMkdir() auto parentFolderStatus = std::filesystem::status(parentFolderPath); parentPermissions = parentFolderStatus.permissions(); if ((parentPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write) { - std::filesystem::permissions(parentFolderPath, parentPermissions | std::filesystem::perms::owner_write, std::filesystem::perm_options::replace); + FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath), FileSystem::FolderPermissions::ReadWrite); + parentNeedRollbackPermissions = true; emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring())); } } @@ -208,24 +210,13 @@ void PropagateLocalMkdir::startLocalMkdir() return; } - try { - if ((parentPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write) { - std::filesystem::permissions(parentFolderPath, parentPermissions, std::filesystem::perm_options::replace); - emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring())); - } - } - catch (const std::filesystem::filesystem_error &e) - { - qCWarning(lcPropagateLocalRename) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); - } - if (!_item->_remotePerm.isNull() && !_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) && !_item->_remotePerm.hasPermission(RemotePermissions::CanRename) && !_item->_remotePerm.hasPermission(RemotePermissions::CanMove) && !_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)) { try { - std::filesystem::permissions(newDirStr.toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::owner_write, std::filesystem::perm_options::remove); + FileSystem::setFolderPermissions(newDirStr, FileSystem::FolderPermissions::ReadOnly); } catch (const std::filesystem::filesystem_error &e) { @@ -235,6 +226,17 @@ void PropagateLocalMkdir::startLocalMkdir() } } + try { + if (parentNeedRollbackPermissions) { + FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath), FileSystem::FolderPermissions::ReadOnly); + emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring())); + } + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateLocalMkdir) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + } + // Insert the directory into the database. The correct etag will be set later, // once all contents have been propagated, because should_update_metadata is true. // Adding an entry with a dummy etag to the database still makes sense here @@ -312,7 +314,7 @@ void PropagateLocalRename::start() auto parentFolderStatus = std::filesystem::status(targetParentFolderPath); targetParentPermissions = parentFolderStatus.permissions(); if ((targetParentPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write) { - std::filesystem::permissions(targetParentFolderPath, targetParentPermissions | std::filesystem::perms::owner_write, std::filesystem::perm_options::replace); + FileSystem::setFolderPermissions(QString::fromStdWString(targetParentFolderPath), FileSystem::FolderPermissions::ReadWrite); emit propagator()->touchedFile(QString::fromStdWString(targetParentFolderPath.wstring())); } } @@ -330,7 +332,7 @@ void PropagateLocalRename::start() auto parentFolderStatus = std::filesystem::status(originParentFolderPath); originParentPermissions = parentFolderStatus.permissions(); if ((originParentPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write) { - std::filesystem::permissions(originParentFolderPath, originParentPermissions | std::filesystem::perms::owner_write, std::filesystem::perm_options::replace); + FileSystem::setFolderPermissions(QString::fromStdWString(originParentFolderPath), FileSystem::FolderPermissions::ReadWrite); emit propagator()->touchedFile(QString::fromStdWString(originParentFolderPath.wstring())); } } @@ -342,7 +344,7 @@ void PropagateLocalRename::start() const auto restoreTargetPermissions = [this] (const auto &parentFolderPath, const auto &parentPermissions) { try { if ((parentPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write) { - std::filesystem::permissions(parentFolderPath, parentPermissions, std::filesystem::perm_options::replace); + FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath), FileSystem::FolderPermissions::ReadOnly); emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring())); } }