|
5 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | 6 |
|
7 | 7 | #include "mozilla/ArrayUtils.h" |
| 8 | +#include "mozilla/Assertions.h" |
8 | 9 | #include "mozilla/DebugOnly.h" |
9 | 10 | #include "mozilla/ProfilerLabels.h" |
10 | 11 | #include "mozilla/TextUtils.h" |
@@ -96,7 +97,6 @@ nsresult NewLocalFile(const nsAString& aPath, bool aUseDOSDevicePathSyntax, |
96 | 97 | file.forget(aResult); |
97 | 98 | return NS_OK; |
98 | 99 | } |
99 | | - |
100 | 100 | } // anonymous namespace |
101 | 101 |
|
102 | 102 | static HWND GetMostRecentNavigatorHWND() { |
@@ -203,6 +203,73 @@ bool nsLocalFile::CheckForReservedFileName(const nsString& aFileName) { |
203 | 203 | return false; |
204 | 204 | } |
205 | 205 |
|
| 206 | +/* static */ |
| 207 | +bool nsLocalFile::ChildAclMatchesAclInheritedFromParent( |
| 208 | + const NotNull<ACL*> aChildDacl, bool aIsChildDir, |
| 209 | + const AutoFreeSecurityDescriptor& aChildSecDesc, nsIFile* aParentDir) { |
| 210 | + // If we fail at any point return false. |
| 211 | + ACL* parentDacl = nullptr; |
| 212 | + AutoFreeSecurityDescriptor parentSecDesc; |
| 213 | + nsAutoString parentPath; |
| 214 | + MOZ_ALWAYS_SUCCEEDS(aParentDir->GetTarget(parentPath)); |
| 215 | + DWORD errCode = ::GetNamedSecurityInfoW( |
| 216 | + parentPath.getW(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, |
| 217 | + nullptr, &parentDacl, nullptr, getter_Transfers(parentSecDesc)); |
| 218 | + if (errCode != ERROR_SUCCESS || !parentDacl) { |
| 219 | + NS_ERROR(nsPrintfCString( |
| 220 | + "Failed to get parent dir DACL for comparison: %lx", errCode) |
| 221 | + .get()); |
| 222 | + return false; |
| 223 | + } |
| 224 | + |
| 225 | + // Create a new security descriptor with a DACL that just inherits from the |
| 226 | + // parent. We can then compare the current childs DACL to make sure the counts |
| 227 | + // of inherited ACEs match. We pass the child security descriptor as the |
| 228 | + // creator to get the owner and group information. |
| 229 | + AutoDestroySecurityDescriptor newSecDesc; |
| 230 | + GENERIC_MAPPING mapping; |
| 231 | + mapping.GenericRead = FILE_GENERIC_READ; |
| 232 | + mapping.GenericWrite = FILE_GENERIC_WRITE; |
| 233 | + mapping.GenericExecute = FILE_GENERIC_EXECUTE; |
| 234 | + mapping.GenericAll = FILE_ALL_ACCESS; |
| 235 | + if (!::CreatePrivateObjectSecurityEx( |
| 236 | + parentSecDesc.get(), aChildSecDesc.get(), |
| 237 | + getter_Transfers(newSecDesc), nullptr, aIsChildDir, |
| 238 | + SEF_DACL_AUTO_INHERIT | SEF_AVOID_OWNER_CHECK | |
| 239 | + SEF_AVOID_PRIVILEGE_CHECK, |
| 240 | + nullptr, &mapping)) { |
| 241 | + // There may be legitimate reasons for this to fail, so we might have to |
| 242 | + // remove this if it causes problems. |
| 243 | + NS_ERROR(nsPrintfCString( |
| 244 | + "Failed to create new inherited DACL for comparison: %lx", |
| 245 | + ::GetLastError()) |
| 246 | + .get()); |
| 247 | + return false; |
| 248 | + } |
| 249 | + |
| 250 | + BOOL daclPresent; |
| 251 | + ACL* newDacl = nullptr; |
| 252 | + BOOL daclDefaulted; |
| 253 | + if (!::GetSecurityDescriptorDacl(newSecDesc.get(), &daclPresent, &newDacl, |
| 254 | + &daclDefaulted) || |
| 255 | + !daclPresent || !newDacl) { |
| 256 | + NS_ERROR( |
| 257 | + nsPrintfCString("Failed to get new DACL from security descriptor: %lx", |
| 258 | + ::GetLastError()) |
| 259 | + .get()); |
| 260 | + return false; |
| 261 | + } |
| 262 | + |
| 263 | + auto getInheritedAceCount = [](const ACL* aAcl) { |
| 264 | + AclAceRange aclAceRange(WrapNotNull(aAcl)); |
| 265 | + return std::count_if( |
| 266 | + aclAceRange.begin(), aclAceRange.end(), |
| 267 | + [](const auto& hdr) { return hdr.AceFlags & INHERITED_ACE; }); |
| 268 | + }; |
| 269 | + |
| 270 | + return getInheritedAceCount(aChildDacl) == getInheritedAceCount(newDacl); |
| 271 | +} |
| 272 | + |
206 | 273 | class nsDriveEnumerator : public nsSimpleEnumerator, |
207 | 274 | public nsIDirectoryEnumerator { |
208 | 275 | public: |
@@ -1800,6 +1867,10 @@ nsresult nsLocalFile::MoveOrCopyAsSingleFileOrDir(nsIFile* aDestParent, |
1800 | 1867 | return NS_ERROR_FILE_ACCESS_DENIED; |
1801 | 1868 | } |
1802 | 1869 |
|
| 1870 | + // Determine if we are a directory before any move/copy. |
| 1871 | + bool isDir = false; |
| 1872 | + MOZ_ALWAYS_SUCCEEDS(IsDirectory(&isDir)); |
| 1873 | + |
1803 | 1874 | int copyOK = 0; |
1804 | 1875 | if (move) { |
1805 | 1876 | copyOK = ::MoveFileExW(filePath.get(), destPath.get(), |
@@ -1850,31 +1921,30 @@ nsresult nsLocalFile::MoveOrCopyAsSingleFileOrDir(nsIFile* aDestParent, |
1850 | 1921 | } else if (move && !(aOptions & SkipNtfsAclReset)) { |
1851 | 1922 | // Set security permissions to inherit from parent. |
1852 | 1923 | // Note: propagates to all children: slow for big file trees |
1853 | | - PACL pOldDACL = nullptr; |
1854 | | - PSECURITY_DESCRIPTOR pSD = nullptr; |
1855 | | - ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, |
1856 | | - DACL_SECURITY_INFORMATION, nullptr, nullptr, |
1857 | | - &pOldDACL, nullptr, &pSD); |
1858 | | - UniquePtr<VOID, LocalFreeDeleter> autoFreeSecDesc(pSD); |
1859 | | - if (pOldDACL) { |
1860 | | - // Test the current DACL, if we find one that is inherited then we can |
1861 | | - // skip the reset. This avoids a request for SeTcbPrivilege, which can |
1862 | | - // cause a lot of audit events if enabled (Bug 1816694). |
1863 | | - bool inherited = false; |
1864 | | - for (DWORD i = 0; i < pOldDACL->AceCount; ++i) { |
1865 | | - VOID* pAce = nullptr; |
1866 | | - if (::GetAce(pOldDACL, i, &pAce) && |
1867 | | - static_cast<PACE_HEADER>(pAce)->AceFlags & INHERITED_ACE) { |
1868 | | - inherited = true; |
1869 | | - break; |
1870 | | - } |
1871 | | - } |
1872 | | - |
1873 | | - if (!inherited) { |
1874 | | - ::SetNamedSecurityInfoW( |
1875 | | - (LPWSTR)destPath.get(), SE_FILE_OBJECT, |
1876 | | - DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, |
1877 | | - nullptr, nullptr, pOldDACL, nullptr); |
| 1924 | + ACL* childDacl = nullptr; |
| 1925 | + AutoFreeSecurityDescriptor childSecDesc; |
| 1926 | + // We need owner and group information for the parent ACL check. |
| 1927 | + DWORD errCode = ::GetNamedSecurityInfoW( |
| 1928 | + destPath.getW(), SE_FILE_OBJECT, |
| 1929 | + DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | |
| 1930 | + GROUP_SECURITY_INFORMATION, |
| 1931 | + nullptr, nullptr, &childDacl, nullptr, getter_Transfers(childSecDesc)); |
| 1932 | + if (errCode == ERROR_SUCCESS && childDacl) { |
| 1933 | + // Compare the number of inherited ACEs on the child to the number we |
| 1934 | + // expect from the parent. If they don't match then we reset. This is |
| 1935 | + // because we can get old inherited ACEs from the previous dir if moved |
| 1936 | + // within the same volume. We check this to prevent unnecessary calls to |
| 1937 | + // SetNamedSecurityInfoW, this avoids a request for SeTcbPrivilege, which |
| 1938 | + // can cause a lot of audit events if enabled (Bug 1816694). |
| 1939 | + if (!ChildAclMatchesAclInheritedFromParent(WrapNotNull(childDacl), isDir, |
| 1940 | + childSecDesc, aDestParent)) { |
| 1941 | + // We don't expect this to fail, but it shouldn't crash in release. |
| 1942 | + MOZ_ALWAYS_TRUE( |
| 1943 | + ERROR_SUCCESS == |
| 1944 | + ::SetNamedSecurityInfoW(destPath.get(), SE_FILE_OBJECT, |
| 1945 | + DACL_SECURITY_INFORMATION | |
| 1946 | + UNPROTECTED_DACL_SECURITY_INFORMATION, |
| 1947 | + nullptr, nullptr, childDacl, nullptr)); |
1878 | 1948 | } |
1879 | 1949 | } |
1880 | 1950 | } |
|
0 commit comments