Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions apps/helper/Sources/SimEnclaveHelperKit/InjectionEnv.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,29 @@
/// `SIMBLE_*`) and are set and cleared directly.
public enum InjectionEnv {
/// Add `dylib` to a `DYLD_INSERT_LIBRARIES` value exactly once, preserving every other entry.
/// An entry already carrying `dylib`'s file name is replaced, so re-arming with a relocated
/// slice path never doubles our entry.
public static func composed(current: String?, adding dylib: String) -> String {
var entries = split(current)
entries.removeAll { $0 == dylib }
let name = fileName(dylib)
var entries = split(current).filter { fileName($0) != name }
entries.append(dylib)
return entries.joined(separator: ":")
}

/// Remove `dylib` from a `DYLD_INSERT_LIBRARIES` value, leaving every other tool's entry.
/// Matches on the file name, so teardown finds our slice even when its absolute path has since
/// moved or the file is gone and only its canonical name is known.
public static func removed(current: String?, removing dylib: String) -> String {
split(current).filter { $0 != dylib }.joined(separator: ":")
let name = fileName(dylib)
return split(current).filter { fileName($0) != name }.joined(separator: ":")
}

private static func split(_ value: String?) -> [String] {
(value ?? "").split(separator: ":").map(String.init).filter { !$0.isEmpty }
}

/// The last path component of an insert-list entry, the slice's file name.
private static func fileName(_ path: String) -> String {
String(path.split(separator: "/").last ?? Substring(path))
}
}
12 changes: 6 additions & 6 deletions apps/helper/Sources/simenclave-menubar/HelperModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,14 @@ final class HelperModel {
/// cleanup before the helper goes away and a stale port/token cannot mislead a later app.
func clearInjection() {
for sim in Self.bootedSimulators() {
// Remove only our slice from the shared DYLD list; never blanket-unset it, which would
// also drop a peer tool's interposer. Unset the variable only when nothing else is left.
if let dylib = Self.interposerDylib(for: sim.platform) {
let current = Self.simulatorEnv(sim.udid, "DYLD_INSERT_LIBRARIES")
let remaining = InjectionEnv.removed(current: current, removing: dylib)
// Remove our slice by its canonical file name, not a locator-resolved path: teardown
// must succeed even when the slice moved or was deleted since arming. Write the shared
// list only when it carries our entry, leaving a peer tool's interposer alone.
if let current = Self.simulatorEnv(sim.udid, "DYLD_INSERT_LIBRARIES") {
let remaining = InjectionEnv.removed(current: current, removing: sim.platform.dylibName)
if remaining.isEmpty {
Self.runSimctl(["spawn", sim.udid, "launchctl", "unsetenv", "DYLD_INSERT_LIBRARIES"])
} else if remaining != (current ?? "") {
} else if remaining != current {
Self.runSimctl(["spawn", sim.udid, "launchctl", "setenv", "DYLD_INSERT_LIBRARIES", remaining])
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,22 @@ final class InjectionEnvTests: XCTestCase {
XCTAssertEqual(InjectionEnv.removed(current: other, removing: mine), other)
XCTAssertEqual(InjectionEnv.removed(current: nil, removing: mine), "")
}

func testComposedDedupesByFileNameAcrossPaths() {
// Re-arm from a relocated build: the old path drops by name, the new one lands, no double.
let oldPath = "/old/simenclave-interpose.dylib"
let newPath = "/new/simenclave-interpose.dylib"
XCTAssertEqual(InjectionEnv.composed(current: oldPath, adding: newPath), newPath)
XCTAssertEqual(
InjectionEnv.composed(current: "\(other):\(oldPath)", adding: newPath),
"\(other):\(newPath)")
}

func testRemovedMatchesByFileNameAcrossPaths() {
// Locator-independent teardown: remove by canonical name even when the armed path differs.
let armed = "/old/simenclave-interpose.dylib"
let name = "simenclave-interpose.dylib"
XCTAssertEqual(InjectionEnv.removed(current: armed, removing: name), "")
XCTAssertEqual(InjectionEnv.removed(current: "\(other):\(armed)", removing: name), other)
}
}