Skip to content

Commit d84ec9e

Browse files
authored
On Darwin, lock access to the environ block. (#403)
This PR adds calls to Darwin's internal libc functions `environ_lock_np()` and `environ_unlock_np()` when directly accessing the `environ` block (via the CRT function `_NSGetEnviron()`.) These calls are necessary because direct access to `environ` is not thread-safe by default. The standard library guards accesses to `getenv()` and `setenv()`, but `environ` is a global variable and neither it nor `_NSGetEnviron()` can be made thread-safe internally. This change does not prevent data races when using the result of `getenv()`, but short of a new `copyenv_np()` or `getenv_r()` function, there's not much we can do about it. This change has no effect on Linux since it doesn't have these non-portable functions, but if equivalents are added in the future, we can add support too. Windows' `GetEnvironmentStringsW()` API returns a copy of the environment block, so it doesn't need a lock. Compare Foundation's use [here](https://github.com/apple/swift-foundation/blob/main/Sources/_CShims/platform_shims.c). ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 775da90 commit d84ec9e

File tree

1 file changed

+56
-1
lines changed

1 file changed

+56
-1
lines changed

Sources/Testing/Support/Environment.swift

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,42 @@ enum Environment {
6767
}
6868
#endif
6969

70+
#if SWT_TARGET_OS_APPLE && !SWT_NO_ENVIRONMENT_VARIABLES && !SWT_NO_DYNAMIC_LINKING
71+
/// A non-POSIX/non-portable function that locks for access to `environ`.
72+
///
73+
/// If the `environ_lock_np()` function is not available on the current
74+
/// system, the value of this property is `nil`.
75+
private static let _environ_lock_np = {
76+
swt_getFunctionWithName(nil, "environ_lock_np").map {
77+
unsafeBitCast($0, to: (@convention(c) () -> Void).self)
78+
}
79+
}()
80+
81+
/// A non-POSIX/non-portable function that unlocks after access to `environ`.
82+
///
83+
/// If the `environ_unlock_np()` function is not available on the current
84+
/// system, the value of this property is `nil`.
85+
private static let _environ_unlock_np = {
86+
swt_getFunctionWithName(nil, "environ_unlock_np").map {
87+
unsafeBitCast($0, to: (@convention(c) () -> Void).self)
88+
}
89+
}()
90+
#endif
91+
7092
/// Get all environment variables in the current process.
7193
///
7294
/// - Returns: A copy of the current process' environment dictionary.
7395
static func get() -> [String: String] {
7496
#if SWT_NO_ENVIRONMENT_VARIABLES
7597
simulatedEnvironment.rawValue
7698
#elseif SWT_TARGET_OS_APPLE
77-
_get(fromEnviron: _NSGetEnviron()!.pointee!)
99+
#if !SWT_NO_DYNAMIC_LINKING
100+
_environ_lock_np?()
101+
defer {
102+
_environ_unlock_np?()
103+
}
104+
#endif
105+
return _get(fromEnviron: _NSGetEnviron()!.pointee!)
78106
#elseif os(Linux)
79107
_get(fromEnviron: swt_environ())
80108
#elseif os(WASI)
@@ -115,6 +143,33 @@ enum Environment {
115143
static func variable(named name: String) -> String? {
116144
#if SWT_NO_ENVIRONMENT_VARIABLES
117145
simulatedEnvironment.rawValue[name]
146+
#elseif SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
147+
// Acquire the `environ` lock if possible, then look for the right variable
148+
// in the block. This ensures we still hold the lock when we convert the
149+
// found C string to a Swift string, which we can't do with getenv(). If the
150+
// lock is unavailable, then this implementation is equivalent to Darwin's
151+
// getenv() implementation.
152+
_environ_lock_np?()
153+
defer {
154+
_environ_unlock_np?()
155+
}
156+
let environ = _NSGetEnviron()!.pointee!
157+
158+
return name.withCString { name in
159+
for i in 0... {
160+
guard let rowp = environ[i] else {
161+
break
162+
}
163+
164+
if let equals = strchr(rowp, CInt(UInt8(ascii: "="))) {
165+
let keyLength = UnsafeRawPointer(equals) - UnsafeRawPointer(rowp)
166+
if 0 == strncmp(rowp, name, keyLength) {
167+
return String(validatingUTF8: equals + 1)
168+
}
169+
}
170+
}
171+
return nil
172+
}
118173
#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
119174
getenv(name).flatMap { String(validatingUTF8: $0) }
120175
#elseif os(Windows)

0 commit comments

Comments
 (0)