Skip to content

Commit 076342a

Browse files
authored
Resolve sandbox issues on external volumes (#6910)
The sandbox profile SwiftPM generates curates the directories plugins and manifests are able to write to. Several Foundation APIs use a so-called "item replacement directory" when doing atomic operations and this happens to be a special path on the same volume when performing operations on an external volume. This lead to those operations being blocked in plugins which does not seem right since those item replacement directories are temporary directories in spirit. We now automatically allow writes for those when generating a sandbox profile. rdar://112217177 (cherry picked from commit 8686504)
1 parent 5fa6997 commit 076342a

File tree

6 files changed

+35
-4
lines changed

6 files changed

+35
-4
lines changed

Sources/Basics/Sandbox.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,26 @@ public enum Sandbox {
4444
///
4545
/// - Parameters:
4646
/// - command: The command line to sandbox (including executable as first argument)
47+
/// - fileSystem: The file system instance to use.
4748
/// - strictness: The basic strictness level of the standbox.
4849
/// - writableDirectories: Paths under which writing should be allowed, even if they would otherwise be read-only based on the strictness or paths in `readOnlyDirectories`.
4950
/// - readOnlyDirectories: Paths under which writing should be denied, even if they would have otherwise been allowed by the rules implied by the strictness level.
5051
public static func apply(
5152
command: [String],
53+
fileSystem: FileSystem,
5254
strictness: Strictness = .default,
5355
writableDirectories: [AbsolutePath] = [],
5456
readOnlyDirectories: [AbsolutePath] = [],
5557
allowNetworkConnections: [SandboxNetworkPermission] = []
5658
) throws -> [String] {
5759
#if os(macOS)
58-
let profile = try macOSSandboxProfile(strictness: strictness, writableDirectories: writableDirectories, readOnlyDirectories: readOnlyDirectories, allowNetworkConnections: allowNetworkConnections)
60+
let profile = try macOSSandboxProfile(
61+
fileSystem: fileSystem,
62+
strictness: strictness,
63+
writableDirectories: writableDirectories,
64+
readOnlyDirectories: readOnlyDirectories,
65+
allowNetworkConnections: allowNetworkConnections
66+
)
5967
return ["/usr/bin/sandbox-exec", "-p", profile] + command
6068
#else
6169
// rdar://40235432, rdar://75636874 tracks implementing sandboxes for other platforms.
@@ -101,6 +109,7 @@ fileprivate let threadSafeDarwinCacheDirectories: [AbsolutePath] = {
101109
}()
102110

103111
fileprivate func macOSSandboxProfile(
112+
fileSystem: FileSystem,
104113
strictness: Sandbox.Strictness,
105114
writableDirectories: [AbsolutePath],
106115
readOnlyDirectories: [AbsolutePath],
@@ -206,7 +215,8 @@ fileprivate func macOSSandboxProfile(
206215
// Emit rules for paths under which writing is allowed, even if they are descendants directories that are otherwise read-only.
207216
if writableDirectories.count > 0 {
208217
contents += "(allow file-write*\n"
209-
for path in writableDirectories {
218+
// For any explicit writable directories, also include the relevant item replacement directories so that Foundation APIs using atomic writes are not blocked by the sandbox.
219+
for path in writableDirectories + Set(writableDirectories.compactMap { try? fileSystem.itemReplacementDirectories(for: $0) }.flatMap { $0}) {
210220
contents += " (subpath \(try resolveSymlinks(path).quotedAsSubpathForSandboxProfile))\n"
211221
}
212222
contents += ")\n"

Sources/Build/BuildOperation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
602602
// TODO: We need to also use any working directory, but that support isn't yet available on all platforms at a lower level.
603603
var commandLine = [command.configuration.executable.pathString] + command.configuration.arguments
604604
if !pluginConfiguration.disableSandbox {
605-
commandLine = try Sandbox.apply(command: commandLine, strictness: .writableTemporaryDirectory, writableDirectories: [pluginResult.pluginOutputDirectory])
605+
commandLine = try Sandbox.apply(command: commandLine, fileSystem: self.fileSystem, strictness: .writableTemporaryDirectory, writableDirectories: [pluginResult.pluginOutputDirectory])
606606
}
607607
let processResult = try TSCBasic.Process.popen(arguments: commandLine, environment: command.configuration.environment)
608608
let output = try processResult.utf8Output() + processResult.utf8stderrOutput()

Sources/Build/LLBuildManifestBuilder.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ extension LLBuildManifestBuilder {
221221
if !self.disableSandboxForPluginCommands {
222222
commandLine = try Sandbox.apply(
223223
command: commandLine,
224+
fileSystem: self.fileSystem,
224225
strictness: .writableTemporaryDirectory,
225226
writableDirectories: [result.pluginOutputDirectory]
226227
)

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
751751
let cacheDirectories = [self.databaseCacheDir, moduleCachePath].compactMap{ $0 }
752752
let strictness: Sandbox.Strictness = toolsVersion < .v5_3 ? .manifest_pre_53 : .default
753753
do {
754-
cmd = try Sandbox.apply(command: cmd, strictness: strictness, writableDirectories: cacheDirectories)
754+
cmd = try Sandbox.apply(command: cmd, fileSystem: localFileSystem, strictness: strictness, writableDirectories: cacheDirectories)
755755
} catch {
756756
return completion(.failure(error))
757757
}

Sources/Workspace/DefaultPluginScriptRunner.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ public struct DefaultPluginScriptRunner: PluginScriptRunner, Cancellable {
441441
do {
442442
command = try Sandbox.apply(
443443
command: command,
444+
fileSystem: self.fileSystem,
444445
strictness: .writableTemporaryDirectory,
445446
writableDirectories: writableDirectories + [self.cacheDir],
446447
readOnlyDirectories: readOnlyDirectories,

Tests/BasicsTests/SandboxTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,22 @@ final class SandboxTest: XCTestCase {
219219
}
220220
}
221221
}
222+
223+
extension Sandbox {
224+
public static func apply(
225+
command: [String],
226+
strictness: Strictness = .default,
227+
writableDirectories: [AbsolutePath] = [],
228+
readOnlyDirectories: [AbsolutePath] = [],
229+
allowNetworkConnections: [SandboxNetworkPermission] = []
230+
) throws -> [String] {
231+
return try self.apply(
232+
command: command,
233+
fileSystem: InMemoryFileSystem(),
234+
strictness: strictness,
235+
writableDirectories: writableDirectories,
236+
readOnlyDirectories: readOnlyDirectories,
237+
allowNetworkConnections: allowNetworkConnections
238+
)
239+
}
240+
}

0 commit comments

Comments
 (0)