Skip to content

Commit 0c90c25

Browse files
authored
Fetching user/group info causes race conditions (#994) (#998)
* Avoid racy stdlib functions for fetching user/group info * Refactor naming * Fix build failure
1 parent 77f79ed commit 0c90c25

File tree

7 files changed

+83
-71
lines changed

7 files changed

+83
-71
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Files.swift

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,6 @@ extension Date {
4141
}
4242

4343
#if !os(Windows)
44-
#if !os(WASI)
45-
private func _nameFor(uid: uid_t) -> String? {
46-
guard let pwd = getpwuid(uid), let name = pwd.pointee.pw_name else {
47-
return nil
48-
}
49-
return String(cString: name)
50-
}
51-
52-
private func _nameFor(gid: gid_t) -> String? {
53-
guard let pwd = getgrgid(gid), let name = pwd.pointee.gr_name else {
54-
return nil
55-
}
56-
return String(cString: name)
57-
}
58-
#endif
59-
6044
extension mode_t {
6145
private var _fileType: FileAttributeType {
6246
switch self & S_IFMT {
@@ -192,10 +176,10 @@ extension stat {
192176
.groupOwnerAccountID : _writeFileAttributePrimitive(st_gid, as: UInt.self)
193177
]
194178
#if !os(WASI)
195-
if let userName = _nameFor(uid: st_uid) {
179+
if let userName = Platform.name(forUID: st_uid) {
196180
result[.ownerAccountName] = userName
197181
}
198-
if let groupName = _nameFor(gid: st_gid) {
182+
if let groupName = Platform.name(forGID: st_gid) {
199183
result[.groupOwnerAccountName] = groupName
200184
}
201185
#endif
@@ -941,8 +925,8 @@ extension _FileManagerImpl {
941925
#else
942926
// Bias toward userID & groupID - try to prevent round trips to getpwnam if possible.
943927
var leaveUnchanged: UInt32 { UInt32(bitPattern: -1) }
944-
let rawUserID = userID.flatMap(uid_t.init) ?? user.flatMap(Self._userAccountNameToNumber) ?? leaveUnchanged
945-
let rawGroupID = groupID.flatMap(gid_t.init) ?? group.flatMap(Self._groupAccountNameToNumber) ?? leaveUnchanged
928+
let rawUserID = userID.flatMap(uid_t.init) ?? user.flatMap(Platform.uid(forName:)) ?? leaveUnchanged
929+
let rawGroupID = groupID.flatMap(gid_t.init) ?? group.flatMap(Platform.gid(forName:)) ?? leaveUnchanged
946930
if chown(fileSystemRepresentation, rawUserID, rawGroupID) != 0 {
947931
throw CocoaError.errorWithFilePath(path, errno: errno, reading: false)
948932
}

Sources/FoundationEssentials/FileManager/FileManager+Utilities.swift

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -275,20 +275,6 @@ extension _FileManagerImpl {
275275
}
276276
}
277277
#endif
278-
279-
#if !os(Windows) && !os(WASI)
280-
static func _userAccountNameToNumber(_ name: String) -> uid_t? {
281-
name.withCString { ptr in
282-
getpwnam(ptr)?.pointee.pw_uid
283-
}
284-
}
285-
286-
static func _groupAccountNameToNumber(_ name: String) -> gid_t? {
287-
name.withCString { ptr in
288-
getgrnam(ptr)?.pointee.gr_gid
289-
}
290-
}
291-
#endif
292278
}
293279

294280
extension FileManager {

Sources/FoundationEssentials/FileManager/SearchPaths/FileManager+DarwinSearchPaths.swift

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,20 +160,12 @@ extension String {
160160
guard self == "~" || self.hasPrefix("~/") else {
161161
return self
162162
}
163-
var bufSize = sysconf(_SC_GETPW_R_SIZE_MAX)
164-
if bufSize == -1 {
165-
bufSize = 4096 // A generous guess.
166-
}
167-
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: bufSize) { pwBuff in
168-
var pw: UnsafeMutablePointer<passwd>?
169-
var pwd = passwd()
170-
let euid = geteuid()
171-
let trueUid = euid == 0 ? getuid() : euid
172-
guard getpwuid_r(trueUid, &pwd, pwBuff.baseAddress!, bufSize, &pw) == 0, let pw else {
173-
return self
174-
}
175-
return String(cString: pw.pointee.pw_dir).appendingPathComponent(String(self.dropFirst()))
163+
let euid = geteuid()
164+
let trueUid = euid == 0 ? getuid() : euid
165+
guard let name = Platform.name(forUID: trueUid) else {
166+
return self
176167
}
168+
return name.appendingPathComponent(String(self.dropFirst()))
177169
}
178170
}
179171
#endif // os(macOS) && FOUNDATION_FRAMEWORK

Sources/FoundationEssentials/Platform.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,70 @@ extension Platform {
135135
}
136136
return result
137137
}
138+
139+
#if canImport(Darwin)
140+
typealias Operation<Input, Output> = (Input, UnsafeMutablePointer<Output>?, UnsafeMutablePointer<CChar>?, Int, UnsafeMutablePointer<UnsafeMutablePointer<Output>?>?) -> Int32
141+
#else
142+
typealias Operation<Input, Output> = (Input, UnsafeMutablePointer<Output>, UnsafeMutablePointer<CChar>, Int, UnsafeMutablePointer<UnsafeMutablePointer<Output>?>) -> Int32
143+
#endif
144+
145+
private static func withUserGroupBuffer<Input, Output, R>(_ input: Input, _ output: Output, sizeProperty: Int32, operation: Operation<Input, Output>, block: (Output) throws -> R) rethrows -> R? {
146+
var bufferLen = sysconf(sizeProperty)
147+
if bufferLen == -1 {
148+
bufferLen = 4096 // Generous default size estimate
149+
}
150+
return try withUnsafeTemporaryAllocation(of: CChar.self, capacity: bufferLen) {
151+
var result = output
152+
var ptr: UnsafeMutablePointer<Output>?
153+
let error = operation(input, &result, $0.baseAddress!, bufferLen, &ptr)
154+
guard error == 0 && ptr != nil else {
155+
return nil
156+
}
157+
return try block(result)
158+
}
159+
}
160+
161+
static func uid(forName name: String) -> uid_t? {
162+
withUserGroupBuffer(name, passwd(), sizeProperty: Int32(_SC_GETPW_R_SIZE_MAX), operation: getpwnam_r) {
163+
$0.pw_uid
164+
}
165+
}
166+
167+
static func gid(forName name: String) -> uid_t? {
168+
withUserGroupBuffer(name, group(), sizeProperty: Int32(_SC_GETGR_R_SIZE_MAX), operation: getgrnam_r) {
169+
$0.gr_gid
170+
}
171+
}
172+
173+
static func name(forUID uid: uid_t) -> String? {
174+
withUserGroupBuffer(uid, passwd(), sizeProperty: Int32(_SC_GETPW_R_SIZE_MAX), operation: getpwuid_r) {
175+
String(cString: $0.pw_name)
176+
}
177+
}
178+
179+
static func fullName(forUID uid: uid_t) -> String? {
180+
withUserGroupBuffer(uid, passwd(), sizeProperty: Int32(_SC_GETPW_R_SIZE_MAX), operation: getpwuid_r) {
181+
String(cString: $0.pw_gecos)
182+
}
183+
}
184+
185+
static func name(forGID gid: gid_t) -> String? {
186+
withUserGroupBuffer(gid, group(), sizeProperty: Int32(_SC_GETGR_R_SIZE_MAX), operation: getgrgid_r) {
187+
String(cString: $0.gr_name)
188+
}
189+
}
190+
191+
static func homeDirectory(forUserName userName: String) -> String? {
192+
withUserGroupBuffer(userName, passwd(), sizeProperty: Int32(_SC_GETPW_R_SIZE_MAX), operation: getpwnam_r) {
193+
String(cString: $0.pw_dir)
194+
}
195+
}
196+
197+
static func homeDirectory(forUID uid: uid_t) -> String? {
198+
withUserGroupBuffer(uid, passwd(), sizeProperty: Int32(_SC_GETPW_R_SIZE_MAX), operation: getpwuid_r) {
199+
String(cString: $0.pw_dir)
200+
}
201+
}
138202
}
139203
#endif
140204

Sources/FoundationEssentials/ProcessInfo/ProcessInfo.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,8 @@ final class _ProcessInfo: Sendable {
168168
#if canImport(Darwin) || os(Android) || canImport(Glibc) || canImport(Musl)
169169
// Darwin and Linux
170170
let (euid, _) = Platform.getUGIDs()
171-
if let upwd = getpwuid(euid),
172-
let uname = upwd.pointee.pw_name {
173-
return String(cString: uname)
171+
if let username = Platform.name(forUID: euid) {
172+
return username
174173
} else if let username = self.environment["USER"] {
175174
return username
176175
}
@@ -202,9 +201,8 @@ final class _ProcessInfo: Sendable {
202201
var fullUserName: String {
203202
#if canImport(Darwin) || os(Android) || canImport(Glibc) || canImport(Musl)
204203
let (euid, _) = Platform.getUGIDs()
205-
if let upwd = getpwuid(euid),
206-
let fullname = upwd.pointee.pw_gecos {
207-
return String(cString: fullname)
204+
if let fullName = Platform.fullName(forUID: euid) {
205+
return fullName
208206
}
209207
return ""
210208
#elseif os(WASI)

Sources/FoundationEssentials/String/String+Path.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -469,17 +469,16 @@ extension String {
469469

470470
#if !os(WASI) // WASI does not have user concept
471471
// Next, attempt to find the home directory via getpwnam/getpwuid
472-
var pass: UnsafeMutablePointer<passwd>?
473472
if let user {
474-
pass = getpwnam(user)
473+
if let dir = Platform.homeDirectory(forUserName: user) {
474+
return dir.standardizingPath
475+
}
475476
} else {
476477
// 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))
477478
// In this instance, we historically do this to ensure a stable home directory location for processes that call seteuid(0)
478-
pass = getpwuid(Platform.getUGIDs(allowEffectiveRootUID: false).uid)
479-
}
480-
481-
if let dir = pass?.pointee.pw_dir {
482-
return String(cString: dir).standardizingPath
479+
if let dir = Platform.homeDirectory(forUID: Platform.getUGIDs(allowEffectiveRootUID: false).uid) {
480+
return dir.standardizingPath
481+
}
483482
}
484483
#endif
485484

Tests/FoundationEssentialsTests/StringTests.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,17 +2526,6 @@ final class StringTestsStdlib: XCTestCase {
25262526
expectTrue(availableEncodings.contains("abc".smallestEncoding))
25272527
}
25282528

2529-
func getHomeDir() -> String {
2530-
#if os(macOS)
2531-
return String(cString: getpwuid(getuid()).pointee.pw_dir)
2532-
#elseif canImport(Darwin)
2533-
// getpwuid() returns null in sandboxed apps under iOS simulator.
2534-
return NSHomeDirectory()
2535-
#else
2536-
preconditionFailed("implement")
2537-
#endif
2538-
}
2539-
25402529
func test_addingPercentEncoding() {
25412530
expectEqual(
25422531
"abcd1234",

0 commit comments

Comments
 (0)