Skip to content

Commit b5d91e3

Browse files
committed
WIP: fix(win32): try to set ACLs without propagating them
apparently `SetFileSecurity` did not do that, unlike the "new" (since Windows 2000) replacement `SetSecurityInfo`/`SetNamedSecurityInfo`. `SetSecurityInfo` should not perform the propagation if the handle is opened with an access mask value of `MAXIMUM_ALLOWED`, so let's give that a try. also removed the restriction on the FILE_WRITE_ATTRIBUTES permission so that `FileSystem::setFileReadOnly` has an easier time modifying the basic attribute Signed-off-by: Jyrki Gadinger <[email protected]>
1 parent 5113f17 commit b5d91e3

File tree

1 file changed

+17
-22
lines changed

1 file changed

+17
-22
lines changed

src/common/filesystembase.cpp

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ void FileSystem::setFileReadOnly(const QString &filename, bool readonly)
116116
return;
117117
}
118118

119-
// current read-only folder ACL needs to be removed from files also when making a folder read-write
120-
// we currently have a too limited set of authorization for files when applying the restrictive ACL for folders on the child files
121-
// the file's attributes can only be modified if there's no access denied ACE present
122-
setAclPermission(filename, FileSystem::FolderPermissions::ReadWrite, false);
123119

124120
auto newFileAttributes = fileAttributes;
125121
if (readonly) {
122+
// replace any existing access denied ACE on this object with one that allows us to at least modify the file attributes
123+
setAclPermission(filename, FileSystem::FolderPermissions::ReadOnly, false);
126124
newFileAttributes = newFileAttributes | FILE_ATTRIBUTE_READONLY;
127125
} else {
126+
// remove the access denied ACE from this object in case we have a too restrictive setting that does not allow to modify file attributes
127+
setAclPermission(filename, FileSystem::FolderPermissions::ReadWrite, false);
128128
newFileAttributes = newFileAttributes & (~FILE_ATTRIBUTE_READONLY);
129129
}
130130

@@ -133,11 +133,6 @@ void FileSystem::setFileReadOnly(const QString &filename, bool readonly)
133133
qCWarning(lcFileSystem()).nospace() << "SetFileAttributesW failed, action=" << (readonly ? "readonly" : "read write") << " filename=" << windowsFilename << " lastError=" << lastError << " errorMessage=" << Utility::formatWinError(lastError);
134134
}
135135

136-
if (readonly) {
137-
// re-add the previously removed readonly ACE
138-
setAclPermission(filename, FileSystem::FolderPermissions::ReadWrite, true);
139-
}
140-
141136
return;
142137
#endif
143138
QFile file(filename);
@@ -776,10 +771,11 @@ bool FileSystem::setAclPermission(const QString &unsafePath, FolderPermissions p
776771
const auto safePathFileInfo = QFileInfo{path};
777772

778773
// CreateFileW is known to work with long paths in the \\?\ variant
779-
constexpr DWORD desiredAccess = READ_CONTROL | WRITE_DAC;
774+
// MAXIMUM_ALLOWED will not propagate ACEs to children when setting the DACL
775+
constexpr DWORD desiredAccess = READ_CONTROL | WRITE_DAC | MAXIMUM_ALLOWED;
780776
constexpr DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
781777
constexpr DWORD creationDisposition = OPEN_EXISTING;
782-
constexpr DWORD flagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_OPEN_NO_RECALL;
778+
constexpr DWORD flagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT;
783779
fileHandle.reset(CreateFileW(rawPath, desiredAccess, shareMode, nullptr, creationDisposition, flagsAndAttributes, nullptr));
784780

785781
if (fileHandle.get() == INVALID_HANDLE_VALUE) {
@@ -828,8 +824,8 @@ bool FileSystem::setAclPermission(const QString &unsafePath, FolderPermissions p
828824

829825
if (permissions == FileSystem::FolderPermissions::ReadOnly) {
830826
// the access denied ACE needs to appear at the start of the ACL
831-
if (!AddAccessDeniedAceEx(newDacl.get(), ACL_REVISION, OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
832-
FILE_DELETE_CHILD | DELETE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA, sid.get())) {
827+
if (!AddAccessDeniedAceEx(newDacl.get(), ACL_REVISION, NO_PROPAGATE_INHERIT_ACE,
828+
FILE_DELETE_CHILD | DELETE | FILE_WRITE_DATA | FILE_WRITE_EA | FILE_APPEND_DATA, sid.get())) {
833829
qCWarning(lcFileSystem).nospace() << "AddAccessDeniedAceEx failed, path=" << path << " errorMessage=" << Utility::formatWinError(GetLastError());
834830
return false;
835831
}
@@ -858,7 +854,6 @@ bool FileSystem::setAclPermission(const QString &unsafePath, FolderPermissions p
858854
}
859855

860856
if (!AddAce(newDacl.get(), ACL_REVISION, newAceIndex, currentAce, currentAceHeader->AceSize)) {
861-
const auto lastError = GetLastError();
862857
qCWarning(lcFileSystem).nospace() << "AddAce failed,"
863858
<< " path=" << path
864859
<< " errorMessage=" << Utility::formatWinError(GetLastError())
@@ -869,21 +864,20 @@ bool FileSystem::setAclPermission(const QString &unsafePath, FolderPermissions p
869864
newAceIndex++;
870865
}
871866

867+
#if 0
868+
// TODO: figure out if this is necessary; before 4.0.2 this did not work at all
869+
// --> currentFolder used to be the parent directory of `path`, so this ended up to be
870+
// iterating over nonexisting entries inside `path`.
871+
// due to the early `return` in the loop this may have never reached the call to
872+
// what-used-to-be `SetFileSecurityW` (now `SetSecurityInfo`)
872873
if (safePathFileInfo.isDir() && applyAlsoToFiles) {
873874
const auto currentFolder = QDir{path};
874875
const auto childFiles = currentFolder.entryList(QDir::Filter::Files);
875876
for (const auto &oneEntry : childFiles) {
876877
const auto childFile = joinPath(path, oneEntry);
877-
878878
const auto rawChildFile = reinterpret_cast<const wchar_t *>(childFile.utf16());;
879-
const auto attributes = GetFileAttributesW(rawChildFile);
880-
881-
// testing if that could be a pure virtual placeholder file (i.e. CfApi file without data)
882-
// we do not want to trigger implicit hydration ourself
883-
if ((attributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0) {
884-
continue;
885-
}
886879

880+
// apparently as long as we do not attempt to read the file contents we will not trigger an implicit hydration ourselves
887881
Utility::UniqueHandle childFileHandle(CreateFileW(rawChildFile, desiredAccess, shareMode, nullptr, creationDisposition, flagsAndAttributes, nullptr));
888882
if (childFileHandle.get() == INVALID_HANDLE_VALUE) {
889883
qCWarning(lcFileSystem).nospace() << "CreateFileW failed, path=" << childFile << " errorMessage=" << Utility::formatWinError(GetLastError());
@@ -896,6 +890,7 @@ bool FileSystem::setAclPermission(const QString &unsafePath, FolderPermissions p
896890
}
897891
}
898892
}
893+
#endif
899894

900895
if (const auto lastError = SetSecurityInfo(fileHandle.get(), SE_FILE_OBJECT, PROTECTED_DACL_SECURITY_INFORMATION | securityInfo, nullptr, nullptr, newDacl.get(), nullptr); lastError != ERROR_SUCCESS) {
901896
qCWarning(lcFileSystem).nospace() << "SetSecurityInfo failed, path=" << path << " errorMessage=" << Utility::formatWinError(lastError);

0 commit comments

Comments
 (0)