Skip to content

Commit c429e2e

Browse files
committed
Sandbox for manifests and plugins should block /tmp and instead provide a unique path specified by TMPDIR
The `/tmp` temporary directory is particularly unsafe because it is noisy — various processes with various ownerships write and read files there. So it's better to use the per-user directory returned by `NSTemporaryDirectory()`. We can improve several things here by passing through the result of calling `NSTemporaryDirectory()` in the sandbox instead of `/tmp`, and also `TMPDIR` in the environment (regardless of whether or not it was set in the inherited one). This will cause the inferior's use of Foundation to respect this directory (the implementation of `NSTemporaryDirectory()` looks at `TMPDIR` on all platforms), and that should also be true for any command line tools it calls, as long as it passes through the environment. Note: if `TMPDIR` happens to be set in SwiftPM's own environment, it will control SwiftPM's own uses of `NSTemporaryDirectory()`, so the callers `TMPDIR` will be respected all the way through (down to manifests and plugins). rdar://91575256
1 parent 22aa4fd commit c429e2e

File tree

4 files changed

+37
-3
lines changed

4 files changed

+37
-3
lines changed

Sources/Build/BuildOperation.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,8 +560,17 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
560560
.writable(try self.fileSystem.tempDirectory)])
561561
commandLine = try sandboxProfile.apply(to: commandLine)
562562
}
563-
let processResult = try TSCBasic.Process.popen(arguments: commandLine, environment: command.configuration.environment)
563+
564+
// Pass `TMPDIR` in the environment, in addition to anything the plugin specifies, in case we have an
565+
// override in our own environment.
566+
var environment = command.configuration.environment
567+
environment["TMPDIR"] = try self.fileSystem.tempDirectory.pathString
568+
569+
// Run the command and wait for it to finish.
570+
let processResult = try Process.popen(arguments: commandLine, environment: environment)
564571
let output = try processResult.utf8Output() + processResult.utf8stderrOutput()
572+
573+
// Throw an error if it failed.
565574
if processResult.exitStatus != .terminated(code: 0) {
566575
throw StringError("failed: \(command)\n\n\(output)")
567576
}

Sources/Build/LLBuildManifestBuilder.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,13 +629,19 @@ extension LLBuildManifestBuilder {
629629
.writable(try self.fileSystem.tempDirectory)])
630630
commandLine = try sandboxProfile.apply(to: commandLine)
631631
}
632+
633+
// Pass `TMPDIR` in the environment, in addition to anything the plugin specifies, in case we have an
634+
// override in our own environment.
635+
var environment = command.configuration.environment
636+
environment["TMPDIR"] = try self.fileSystem.tempDirectory.pathString
637+
632638
manifest.addShellCmd(
633639
name: displayName + "-" + ByteString(encodingAsUTF8: uniquedName).sha256Checksum,
634640
description: displayName,
635641
inputs: command.inputFiles.map{ .file($0) },
636642
outputs: command.outputFiles.map{ .file($0) },
637643
arguments: commandLine,
638-
environment: command.configuration.environment,
644+
environment: environment,
639645
workingDirectory: command.configuration.workingDirectory?.pathString)
640646
}
641647
}

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,13 +662,22 @@ public final class ManifestLoader: ManifestLoaderProtocol {
662662
}
663663
}
664664

665-
// Run the compiled manifest.
665+
// Set up the environment so that the manifest inherits our own environment, but make sure that
666+
// `TMPDIR` is set to whatever the temporary directory is that is allowed in the sandbox.
666667
var environment = ProcessEnv.vars
668+
do {
669+
environment["TMPDIR"] = try localFileSystem.tempDirectory.pathString
670+
} catch {
671+
return completion(.failure(error))
672+
}
673+
674+
// On Windows, also make sure that the `Path` is set up correctly in the environment.
667675
#if os(Windows)
668676
let windowsPathComponent = runtimePath.pathString.replacingOccurrences(of: "/", with: "\\")
669677
environment["Path"] = "\(windowsPathComponent);\(environment["Path"] ?? "")"
670678
#endif
671679

680+
// Run the compiled manifest.
672681
let cleanupAfterRunning = cleanupIfError.delay()
673682
TSCBasic.Process.popen(arguments: cmd, environment: environment, queue: callbackQueue) { result in
674683
dispatchPrecondition(condition: .onQueue(callbackQueue))

Sources/Workspace/DefaultPluginScriptRunner.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,16 @@ public struct DefaultPluginScriptRunner: PluginScriptRunner, Cancellable {
463463
#endif
464464
process.currentDirectoryURL = workingDirectory.asURL
465465

466+
// Pass `TMPDIR` in the environment, in addition to anything the plugin specifies. This lets us respect our own
467+
// override, and also makes sure that the plugin always knows what temporary directory we opened up for it.
468+
var environment = ProcessInfo.processInfo.environment
469+
do {
470+
environment["TMPDIR"] = try localFileSystem.tempDirectory.pathString
471+
} catch {
472+
return completion(.failure(error))
473+
}
474+
process.environment = environment
475+
466476
// Set up a pipe for sending structured messages to the plugin on its stdin.
467477
let stdinPipe = Pipe()
468478
let outputHandle = stdinPipe.fileHandleForWriting

0 commit comments

Comments
 (0)