Skip to content

Commit 3a3d5dc

Browse files
authored
(140882573) Home directory for non-existent user should not fall back to /var/empty or %ALLUSERSPROFILE% (#1072) (#1073)
1 parent 80093d6 commit 3a3d5dc

File tree

3 files changed

+101
-88
lines changed

3 files changed

+101
-88
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Directories.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ extension _FileManagerImpl {
4848
}
4949

5050
func homeDirectory(forUser userName: String?) -> URL? {
51-
URL(filePath: String.homeDirectoryPath(forUser: userName), directoryHint: .isDirectory)
51+
guard let userName else {
52+
return homeDirectoryForCurrentUser
53+
}
54+
guard let path = String.homeDirectoryPath(forUser: userName) else {
55+
return nil
56+
}
57+
return URL(filePath: path, directoryHint: .isDirectory)
5258
}
5359

5460
var temporaryDirectory: URL {

Sources/FoundationEssentials/String/String+Path.swift

Lines changed: 92 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -422,86 +422,42 @@ extension String {
422422
// MARK: - Filesystem String Extensions
423423

424424
#if !NO_FILESYSTEM
425-
426-
internal static func homeDirectoryPath(forUser user: String? = nil) -> String {
425+
426+
internal static func homeDirectoryPath() -> String {
427427
#if os(Windows)
428-
if let user {
429-
func fallbackUserDirectory() -> String {
430-
guard let fallback = ProcessInfo.processInfo.environment["ALLUSERSPROFILE"] else {
431-
fatalError("Unable to find home directory for user \(user) and ALLUSERSPROFILE environment variable is not set")
432-
}
433-
434-
return fallback
435-
}
436-
437-
guard !user.isEmpty else {
438-
return fallbackUserDirectory()
428+
func fallbackCurrentUserDirectory() -> String {
429+
guard let fallback = ProcessInfo.processInfo.environment["ALLUSERSPROFILE"] else {
430+
fatalError("Unable to find home directory for current user and ALLUSERSPROFILE environment variable is not set")
439431
}
440432

441-
return user.withCString(encodedAs: UTF16.self) { pwszUserName in
442-
var cbSID: DWORD = 0
443-
var cchReferencedDomainName: DWORD = 0
444-
var eUse: SID_NAME_USE = SidTypeUnknown
445-
LookupAccountNameW(nil, pwszUserName, nil, &cbSID, nil, &cchReferencedDomainName, &eUse)
446-
guard cbSID > 0 else {
447-
return fallbackUserDirectory()
448-
}
449-
450-
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(cbSID)) { pSID in
451-
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(cchReferencedDomainName)) { pwszReferencedDomainName in
452-
guard LookupAccountNameW(nil, pwszUserName, pSID.baseAddress, &cbSID, pwszReferencedDomainName.baseAddress, &cchReferencedDomainName, &eUse) else {
453-
return fallbackUserDirectory()
454-
}
455-
456-
var pwszSID: LPWSTR? = nil
457-
guard ConvertSidToStringSidW(pSID.baseAddress, &pwszSID) else {
458-
fatalError("unable to convert SID to string for user \(user)")
459-
}
460-
461-
return #"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\\#(String(decodingCString: pwszSID!, as: UTF16.self))"#.withCString(encodedAs: UTF16.self) { pwszKeyPath in
462-
return "ProfileImagePath".withCString(encodedAs: UTF16.self) { pwszKey in
463-
var cbData: DWORD = 0
464-
RegGetValueW(HKEY_LOCAL_MACHINE, pwszKeyPath, pwszKey, RRF_RT_REG_SZ, nil, nil, &cbData)
465-
guard cbData > 0 else {
466-
fatalError("unable to query ProfileImagePath for user \(user)")
467-
}
468-
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(cbData)) { pwszData in
469-
guard RegGetValueW(HKEY_LOCAL_MACHINE, pwszKeyPath, pwszKey, RRF_RT_REG_SZ, nil, pwszData.baseAddress, &cbData) == ERROR_SUCCESS else {
470-
fatalError("unable to query ProfileImagePath for user \(user)")
471-
}
472-
return String(decodingCString: pwszData.baseAddress!, as: UTF16.self)
473-
}
474-
}
475-
}
476-
477-
}
478-
}
479-
}
433+
return fallback
480434
}
481-
435+
482436
var hToken: HANDLE? = nil
483437
guard OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken) else {
484438
guard let UserProfile = ProcessInfo.processInfo.environment["UserProfile"] else {
485-
fatalError("unable to evaluate `%UserProfile%`")
439+
return fallbackCurrentUserDirectory()
486440
}
487441
return UserProfile
488442
}
489443
defer { CloseHandle(hToken) }
490-
444+
491445
var dwcchSize: DWORD = 0
492446
_ = GetUserProfileDirectoryW(hToken, nil, &dwcchSize)
493-
447+
494448
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwcchSize)) {
495449
var dwcchSize: DWORD = DWORD($0.count)
496450
guard GetUserProfileDirectoryW(hToken, $0.baseAddress, &dwcchSize) else {
497-
fatalError("unable to query user profile directory")
451+
return fallbackCurrentUserDirectory()
498452
}
499453
return String(decodingCString: $0.baseAddress!, as: UTF16.self)
500454
}
501-
#else // os(Windows)
502-
455+
#else
456+
457+
503458
#if targetEnvironment(simulator)
504-
if user == nil, let envValue = getenv("CFFIXED_USER_HOME") ?? getenv("HOME") {
459+
// Simulator checks these environment variables first for the current user
460+
if let envValue = getenv("CFFIXED_USER_HOME") ?? getenv("HOME") {
505461
return String(cString: envValue).standardizingPath
506462
}
507463
#endif
@@ -512,28 +468,81 @@ extension String {
512468
}
513469

514470
#if !os(WASI) // WASI does not have user concept
515-
// Next, attempt to find the home directory via getpwnam/getpwuid
516-
if let user {
517-
if let dir = Platform.homeDirectory(forUserName: user) {
518-
return dir.standardizingPath
519-
}
520-
} else {
521-
// We use the real UID instead of the EUID here when the EUID is the root user (i.e. a process has called seteuid(0))
522-
// In this instance, we historically do this to ensure a stable home directory location for processes that call seteuid(0)
523-
if let dir = Platform.homeDirectory(forUID: Platform.getUGIDs(allowEffectiveRootUID: false).uid) {
524-
return dir.standardizingPath
525-
}
471+
// Next, attempt to find the home directory via getpwuid
472+
// We use the real UID instead of the EUID here when the EUID is the root user (i.e. a process has called seteuid(0))
473+
// In this instance, we historically do this to ensure a stable home directory location for processes that call seteuid(0)
474+
if let dir = Platform.homeDirectory(forUID: Platform.getUGIDs(allowEffectiveRootUID: false).uid) {
475+
return dir.standardizingPath
526476
}
527-
#endif // !os(WASI)
528-
477+
#endif
478+
529479
// Fallback to HOME for the current user if possible
530-
if user == nil, let home = getenv("HOME") {
480+
if let home = getenv("HOME") {
531481
return String(cString: home).standardizingPath
532482
}
533483

534-
// If all else fails, log and fall back to /var/empty
484+
// If all else fails, fall back to /var/empty
535485
return "/var/empty"
536-
#endif // os(Windows)
486+
#endif
487+
}
488+
489+
internal static func homeDirectoryPath(forUser user: String) -> String? {
490+
#if os(Windows)
491+
guard !user.isEmpty else {
492+
return nil
493+
}
494+
495+
return user.withCString(encodedAs: UTF16.self) { pwszUserName in
496+
var cbSID: DWORD = 0
497+
var cchReferencedDomainName: DWORD = 0
498+
var eUse: SID_NAME_USE = SidTypeUnknown
499+
LookupAccountNameW(nil, pwszUserName, nil, &cbSID, nil, &cchReferencedDomainName, &eUse)
500+
guard cbSID > 0 else {
501+
return nil
502+
}
503+
504+
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(cbSID)) { pSID in
505+
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(cchReferencedDomainName)) { pwszReferencedDomainName in
506+
guard LookupAccountNameW(nil, pwszUserName, pSID.baseAddress, &cbSID, pwszReferencedDomainName.baseAddress, &cchReferencedDomainName, &eUse) else {
507+
return nil
508+
}
509+
510+
var pwszSID: LPWSTR? = nil
511+
guard ConvertSidToStringSidW(pSID.baseAddress, &pwszSID) else {
512+
fatalError("unable to convert SID to string for user \(user)")
513+
}
514+
515+
return #"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\\#(String(decodingCString: pwszSID!, as: UTF16.self))"#.withCString(encodedAs: UTF16.self) { pwszKeyPath in
516+
return "ProfileImagePath".withCString(encodedAs: UTF16.self) { pwszKey in
517+
var cbData: DWORD = 0
518+
RegGetValueW(HKEY_LOCAL_MACHINE, pwszKeyPath, pwszKey, RRF_RT_REG_SZ, nil, nil, &cbData)
519+
guard cbData > 0 else {
520+
fatalError("unable to query ProfileImagePath for user \(user)")
521+
}
522+
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(cbData)) { pwszData in
523+
guard RegGetValueW(HKEY_LOCAL_MACHINE, pwszKeyPath, pwszKey, RRF_RT_REG_SZ, nil, pwszData.baseAddress, &cbData) == ERROR_SUCCESS else {
524+
fatalError("unable to query ProfileImagePath for user \(user)")
525+
}
526+
return String(decodingCString: pwszData.baseAddress!, as: UTF16.self)
527+
}
528+
}
529+
}
530+
531+
}
532+
}
533+
}
534+
#else
535+
// First check CFFIXED_USER_HOME if the environment is not considered tainted
536+
if let envVar = Platform.getEnvSecure("CFFIXED_USER_HOME") {
537+
return envVar.standardizingPath
538+
}
539+
#if !os(WASI) // WASI does not have user concept
540+
// Next, attempt to find the home directory via getpwnam
541+
return Platform.homeDirectory(forUserName: user)?.standardizingPath
542+
#else
543+
return nil
544+
#endif
545+
#endif
537546
}
538547

539548
// From swift-corelibs-foundation's NSTemporaryDirectory. Internal for now, pending a better public API.
@@ -674,13 +683,17 @@ extension String {
674683

675684
private var _expandingTildeInPath: String {
676685
guard utf8.first == UInt8(ascii: "~") else { return self }
677-
var user: String? = nil
678686
let firstSlash = utf8.firstIndex(of: ._slash) ?? endIndex
679687
let indexAfterTilde = utf8.index(after: startIndex)
688+
var userDir: String
680689
if firstSlash != indexAfterTilde {
681-
user = String(self[indexAfterTilde ..< firstSlash])
690+
guard let dir = String.homeDirectoryPath(forUser: String(self[indexAfterTilde ..< firstSlash])) else {
691+
return self
692+
}
693+
userDir = dir
694+
} else {
695+
userDir = String.homeDirectoryPath()
682696
}
683-
let userDir = String.homeDirectoryPath(forUser: user)
684697
return userDir + self[firstSlash...]
685698
}
686699

Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,14 +1029,8 @@ final class FileManagerTests : XCTestCase {
10291029
#if canImport(Darwin) && !os(macOS)
10301030
throw XCTSkip("This test is not applicable on this platform")
10311031
#else
1032-
#if os(Windows)
1033-
let fallbackPath = URL(filePath: try XCTUnwrap(ProcessInfo.processInfo.environment["ALLUSERSPROFILE"]), directoryHint: .isDirectory)
1034-
#else
1035-
let fallbackPath = URL(filePath: "/var/empty", directoryHint: .isDirectory)
1036-
#endif
1037-
1038-
XCTAssertEqual(FileManager.default.homeDirectory(forUser: ""), fallbackPath)
1039-
XCTAssertEqual(FileManager.default.homeDirectory(forUser: UUID().uuidString), fallbackPath)
1032+
XCTAssertNil(FileManager.default.homeDirectory(forUser: ""))
1033+
XCTAssertNil(FileManager.default.homeDirectory(forUser: UUID().uuidString))
10401034
#endif
10411035
}
10421036

0 commit comments

Comments
 (0)