Skip to content

Commit 4d720d6

Browse files
authored
Reinstate manifest sandboxing (#2848)
* Reinstate manifest sandboxing Manifest loading has been sandboxed on macOS for a while, but the change in #2518 broke it for 5.3. https://bugs.swift.org/browse/SR-13346 rdar://problem/66586184 * Test manifest sandboxing This adds tests for manifest sandboxing. Since we made the sandbox more restrictive in 5.3, there are two tests: - in 5.2 we check if we can make a network request - in 5.3 we check if we can launch a process * Fix 5.3 sandbox profile * Use 127.0.0.1 for the attempted network request It doesn't actually matter whether or not the request would succeed since the test assumes it will always be blocked.
1 parent d9b22c3 commit 4d720d6

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
711711
moduleCachePath.map({ AbsolutePath($0) })
712712
].compactMap({ $0 })
713713
let profile = sandboxProfile(toolsVersion: toolsVersion, cacheDirectories: cacheDirectories)
714-
cmd += ["sandbox-exec", "-p", profile]
714+
cmd = ["sandbox-exec", "-p", profile] + cmd
715715
}
716716
#endif
717717

@@ -818,13 +818,14 @@ private func sandboxProfile(toolsVersion: ToolsVersion, cacheDirectories: [Absol
818818
stream <<< "(deny default)" <<< "\n"
819819
// Import the system sandbox profile.
820820
stream <<< "(import \"system.sb\")" <<< "\n"
821+
// Allow reading all files. Even in 5.3 we need to be able to read the PD dylibs.
822+
stream <<< "(allow file-read*)" <<< "\n"
823+
// This is needed to launch any processes.
824+
stream <<< "(allow process*)" <<< "\n"
821825

822826
// The following accesses are only needed when interpreting the manifest (versus running a compiled version).
823827
if toolsVersion < .v5_3 {
824-
// Allow reading all files.
825-
stream <<< "(allow file-read*)" <<< "\n"
826-
// These are required by the Swift compiler.
827-
stream <<< "(allow process*)" <<< "\n"
828+
// This is required by the Swift compiler.
828829
stream <<< "(allow sysctl*)" <<< "\n"
829830
// Allow writing in temporary locations.
830831
stream <<< "(allow file-write*" <<< "\n"
@@ -834,9 +835,9 @@ private func sandboxProfile(toolsVersion: ToolsVersion, cacheDirectories: [Absol
834835
for directory in cacheDirectories {
835836
stream <<< " (subpath \"\(directory.pathString)\")" <<< "\n"
836837
}
838+
stream <<< ")" <<< "\n"
837839
}
838840

839-
stream <<< ")" <<< "\n"
840841
return stream.bytes.description
841842
}
842843

Tests/PackageLoadingTests/PD5_2LoadingTests.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,28 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests {
345345
}
346346
}
347347
}
348+
349+
func testManifestLoadingIsSandboxed() throws {
350+
#if os(macOS) // Sandboxing is only done on macOS today.
351+
let stream = BufferedOutputByteStream()
352+
stream <<< """
353+
import Foundation
354+
355+
try! String(contentsOf:URL(string: "http://127.0.0.1")!)
356+
357+
import PackageDescription
358+
let package = Package(
359+
name: "Foo",
360+
targets: [
361+
.target(name: "Foo"),
362+
]
363+
)
364+
"""
365+
366+
XCTAssertManifestLoadThrows(stream.bytes) { error, _ in
367+
guard case ManifestParseError.invalidManifestFormat(let msg, _) = error else { return XCTFail("unexpected error: \(error)") }
368+
XCTAssertTrue(msg.contains("Operation not permitted"), "unexpected error message: \(msg)")
369+
}
370+
#endif
371+
}
348372
}

Tests/PackageLoadingTests/PD5_3LoadingTests.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,4 +406,28 @@ class PackageDescriptionNextLoadingTests: PackageDescriptionLoadingTests {
406406
XCTAssertTrue(error is ManifestParseError, "unexpected error: \(error)")
407407
}
408408
}
409+
410+
func testManifestLoadingIsSandboxed() throws {
411+
#if os(macOS) // Sandboxing is only done on macOS today.
412+
let stream = BufferedOutputByteStream()
413+
stream <<< """
414+
import Foundation
415+
416+
try! "should not be allowed".write(to: URL(fileURLWithPath: "/tmp/file.txt"), atomically: true, encoding: String.Encoding.utf8)
417+
418+
import PackageDescription
419+
let package = Package(
420+
name: "Foo",
421+
targets: [
422+
.target(name: "Foo"),
423+
]
424+
)
425+
"""
426+
427+
XCTAssertManifestLoadThrows(stream.bytes) { error, _ in
428+
guard case ManifestParseError.invalidManifestFormat(let msg, _) = error else { return XCTFail("unexpected error: \(error)") }
429+
XCTAssertTrue(msg.contains("Operation not permitted"), "unexpected error message: \(msg)")
430+
}
431+
#endif
432+
}
409433
}

0 commit comments

Comments
 (0)