From 0c9f099d881d97e54ebd700cac337f161c722c1c Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 17 Nov 2023 13:42:16 -0800 Subject: [PATCH] [Macros] Add option to disable sandbox for exectuable plugins `-disable-sandbox` to disable sandboxing when invoking subprocess from from the frontend. Since `sandbox(7)` in macOS doesn't support nested sandbox, complation used to fail when the parent build process is sandboxed. --- include/swift/AST/PluginLoader.h | 6 +- include/swift/AST/PluginRegistry.h | 11 ++- include/swift/Frontend/FrontendOptions.h | 3 + include/swift/Option/Options.td | 5 ++ lib/AST/PluginLoader.cpp | 3 +- lib/AST/PluginRegistry.cpp | 8 +- .../ArgsToFrontendOptionsConverter.cpp | 2 + lib/Frontend/Frontend.cpp | 5 +- .../Macros/macro_plugin_disable_sandbox.swift | 84 +++++++++++++++++++ 9 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 test/Macros/macro_plugin_disable_sandbox.swift diff --git a/include/swift/AST/PluginLoader.h b/include/swift/AST/PluginLoader.h index 43b2ff8413a9f..e7e93c19da59a 100644 --- a/include/swift/AST/PluginLoader.h +++ b/include/swift/AST/PluginLoader.h @@ -44,6 +44,7 @@ class PluginLoader { ASTContext &Ctx; DependencyTracker *DepTracker; + const bool disableSandbox; /// Map a module name to an plugin entry that provides the module. llvm::Optional> PluginMap; @@ -52,8 +53,9 @@ class PluginLoader { llvm::DenseMap &getPluginMap(); public: - PluginLoader(ASTContext &Ctx, DependencyTracker *DepTracker) - : Ctx(Ctx), DepTracker(DepTracker) {} + PluginLoader(ASTContext &Ctx, DependencyTracker *DepTracker, + bool disableSandbox = false) + : Ctx(Ctx), DepTracker(DepTracker), disableSandbox(disableSandbox) {} void setRegistry(PluginRegistry *newValue); PluginRegistry *getRegistry(); diff --git a/include/swift/AST/PluginRegistry.h b/include/swift/AST/PluginRegistry.h index 3ffcc4abb982e..74a323e520b80 100644 --- a/include/swift/AST/PluginRegistry.h +++ b/include/swift/AST/PluginRegistry.h @@ -89,6 +89,9 @@ class LoadedExecutablePlugin { /// Callbacks to be called when the connection is restored. llvm::SmallVector *, 0> onReconnect; + /// Disable sandbox. + bool disableSandbox = false; + /// Flag to dump plugin messagings. bool dumpMessaging = false; @@ -99,9 +102,11 @@ class LoadedExecutablePlugin { public: LoadedExecutablePlugin(llvm::StringRef ExecutablePath, - llvm::sys::TimePoint<> LastModificationTime) + llvm::sys::TimePoint<> LastModificationTime, + bool disableSandbox) : ExecutablePath(ExecutablePath), - LastModificationTime(LastModificationTime){}; + LastModificationTime(LastModificationTime), + disableSandbox(disableSandbox){}; ~LoadedExecutablePlugin(); /// The last modification time of 'ExecutablePath' when this object is @@ -181,7 +186,7 @@ class PluginRegistry { /// Load an executable plugin specified by \p path . /// If \p path plugin is already loaded, this returns the cached object. llvm::Expected - loadExecutablePlugin(llvm::StringRef path); + loadExecutablePlugin(llvm::StringRef path, bool disableSandbox); }; } // namespace swift diff --git a/include/swift/Frontend/FrontendOptions.h b/include/swift/Frontend/FrontendOptions.h index f79b7a5cda5ad..25eb37d970676 100644 --- a/include/swift/Frontend/FrontendOptions.h +++ b/include/swift/Frontend/FrontendOptions.h @@ -415,6 +415,9 @@ class FrontendOptions { /// are present at LTO time. bool HermeticSealAtLink = false; + /// Disable using the sandbox when executing subprocesses. + bool DisableSandbox = false; + /// The different modes for validating TBD against the LLVM IR. enum class TBDValidationMode { Default, ///< Do the default validation for the current platform. diff --git a/include/swift/Option/Options.td b/include/swift/Option/Options.td index 8c85bd5d3981f..0f161dd8e06d8 100644 --- a/include/swift/Option/Options.td +++ b/include/swift/Option/Options.td @@ -1948,4 +1948,9 @@ def load_plugin_executable: "of module names where the macro types are declared">, MetaVarName<"#">; +def disable_sandbox: + Flag<["-"], "disable-sandbox">, + Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>, + HelpText<"Disable using the sandbox when executing subprocesses">; + include "FrontendOptions.td" diff --git a/lib/AST/PluginLoader.cpp b/lib/AST/PluginLoader.cpp index 94b315bdf9376..12fc048b60e67 100644 --- a/lib/AST/PluginLoader.cpp +++ b/lib/AST/PluginLoader.cpp @@ -199,7 +199,8 @@ PluginLoader::loadExecutablePlugin(StringRef path) { DepTracker->addDependency(resolvedPath, /*IsSystem=*/false); // Load the plugin. - auto plugin = getRegistry()->loadExecutablePlugin(resolvedPath); + auto plugin = + getRegistry()->loadExecutablePlugin(resolvedPath, disableSandbox); if (!plugin) { resolvedPath.push_back(0); return llvm::handleErrors( diff --git a/lib/AST/PluginRegistry.cpp b/lib/AST/PluginRegistry.cpp index 7e337c8477e8e..3bd5d0e870291 100644 --- a/lib/AST/PluginRegistry.cpp +++ b/lib/AST/PluginRegistry.cpp @@ -84,7 +84,7 @@ void *LoadedLibraryPlugin::getAddressOfSymbol(const char *symbolName) { } llvm::Expected -PluginRegistry::loadExecutablePlugin(StringRef path) { +PluginRegistry::loadExecutablePlugin(StringRef path, bool disableSandbox) { llvm::sys::fs::file_status stat; if (auto err = llvm::sys::fs::status(path, stat)) { return llvm::errorCodeToError(err); @@ -114,7 +114,7 @@ PluginRegistry::loadExecutablePlugin(StringRef path) { } auto plugin = std::make_unique( - path, stat.getLastModificationTime()); + path, stat.getLastModificationTime(), disableSandbox); plugin->setDumpMessaging(dumpMessaging); @@ -147,7 +147,9 @@ llvm::Error LoadedExecutablePlugin::spawnIfNeeded() { // Apply sandboxing. llvm::BumpPtrAllocator Allocator; - Sandbox::apply(command, Allocator); + if (!disableSandbox) { + Sandbox::apply(command, Allocator); + } // Launch. auto childInfo = ExecuteWithPipe(command[0], command); diff --git a/lib/Frontend/ArgsToFrontendOptionsConverter.cpp b/lib/Frontend/ArgsToFrontendOptionsConverter.cpp index d377824d737ba..05345ce426db4 100644 --- a/lib/Frontend/ArgsToFrontendOptionsConverter.cpp +++ b/lib/Frontend/ArgsToFrontendOptionsConverter.cpp @@ -395,6 +395,8 @@ bool ArgsToFrontendOptionsConverter::convert( Opts.UseCASBackend = Args.hasArg(OPT_cas_backend); Opts.EmitCASIDFile = Args.hasArg(OPT_cas_emit_casid_file); + Opts.DisableSandbox = Args.hasArg(OPT_disable_sandbox); + return false; } diff --git a/lib/Frontend/Frontend.cpp b/lib/Frontend/Frontend.cpp index 2289649b3a692..ae72d1632bf50 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -812,8 +812,9 @@ bool CompilerInstance::setUpModuleLoaders() { bool CompilerInstance::setUpPluginLoader() { /// FIXME: If Invocation has 'PluginRegistry', we can set it. But should we? - auto loader = - std::make_unique(*Context, getDependencyTracker()); + auto loader = std::make_unique( + *Context, getDependencyTracker(), + Invocation.getFrontendOptions().DisableSandbox); Context->setPluginLoader(std::move(loader)); return false; } diff --git a/test/Macros/macro_plugin_disable_sandbox.swift b/test/Macros/macro_plugin_disable_sandbox.swift new file mode 100644 index 0000000000000..633077786c824 --- /dev/null +++ b/test/Macros/macro_plugin_disable_sandbox.swift @@ -0,0 +1,84 @@ +// REQUIRES: swift_swift_parser + +// sandbox-exec is only avaiable in Darwin +// REQUIRES: OS=macosx + +// RUN: %empty-directory(%t) +// RUN: %empty-directory(%t/plugins) + +// RUN: split-file %s %t + +//== Build the plugins +// RUN: %host-build-swift \ +// RUN: -swift-version 5 \ +// RUN: -emit-library \ +// RUN: -o %t/plugins/%target-library-name(MacroDefinition) \ +// RUN: -module-name=MacroDefinition \ +// RUN: %t/MacroDefinition.swift \ +// RUN: -g -no-toolchain-stdlib-rpath + +// RUN: %swift-build-cxx-plugin -o %t/mock-plugin %t/TestPlugin.c + +//== Nested sandbox. Expected to fail because sandbox-exec doesn't support nested sandboxing. +// RUN: not sandbox-exec -p '(version 1)(allow default)' \ +// RUN: %swift-target-frontend \ +// RUN: -typecheck -verify \ +// RUN: -swift-version 5 \ +// RUN: -external-plugin-path %t/plugins#%swift-plugin-server \ +// RUN: -load-plugin-executable %t/mock-plugin#TestPlugin \ +// RUN: -module-name MyApp \ +// RUN: %t/test.swift + +//== Avoid nested sandbox by -disable-sandbox +// RUN: sandbox-exec -p '(version 1)(allow default)' \ +// RUN: %swift-target-frontend \ +// RUN: -disable-sandbox \ +// RUN: -typecheck -verify \ +// RUN: -swift-version 5 \ +// RUN: -external-plugin-path %t/plugins#%swift-plugin-server \ +// RUN: -load-plugin-executable %t/mock-plugin#TestPlugin \ +// RUN: -module-name MyApp \ +// RUN: %t/test.swift + + +//--- MacroDefinition.swift +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public struct StringifyMacro: ExpressionMacro { + public static func expansion( + of macro: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + guard let argument = macro.arguments.first?.expression else { + fatalError("boom") + } + + return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))" + } +} + +//--- TestPlugin.c +#include "swift-c/MockPlugin/MockPlugin.h" + +MOCK_PLUGIN([ + { + "expect": {"getCapability": {}}, + "response": {"getCapabilityResult": {"capability": {"protocolVersion": 1}}} + }, + { + "expect": {"expandFreestandingMacro": { + "macro": {"moduleName": "TestPlugin", "typeName": "TestStringMacro"}}}, + "response": {"expandMacroResult": {"expandedSource": "\"test string\"", "diagnostics": []}} + } +]) + +//--- test.swift +@freestanding(expression) macro stringify(_ value: T) -> (T, String) = #externalMacro(module: "MacroDefinition", type: "StringifyMacro") +@freestanding(expression) macro testString() -> String = #externalMacro(module: "TestPlugin", type: "TestStringMacro") + +func test() { + let _: String = #stringify(42).1 + let _: String = #testString +}