From 3369e54fabe5af91eccdf562331712c943981335 Mon Sep 17 00:00:00 2001 From: Dan Blackwell Date: Fri, 14 Nov 2025 15:09:03 +0000 Subject: [PATCH 1/2] [Sanitizers] Add support for -sanitize=memtag-stack This sanitizer adds MTE (memory tagging extension) checks to stack variable accesses. It does not have a runtime library component, so some changes have been made to the driver logic to account for this cleanly. --- Sources/SwiftDriver/Driver/Driver.swift | 27 ++++++++++++++----- .../Jobs/Toolchain+LinkerSupport.swift | 9 +++++-- .../Toolchains/DarwinToolchain.swift | 7 +++-- .../Toolchains/GenericUnixToolchain.swift | 4 +-- .../SwiftDriver/Toolchains/Toolchain.swift | 3 ++- .../Toolchains/WebAssemblyToolchain.swift | 4 +-- .../Toolchains/WindowsToolchain.swift | 7 +++-- Sources/SwiftDriver/Utilities/Sanitizer.swift | 16 +++++++++-- Tests/SwiftDriverTests/SwiftDriverTests.swift | 10 +++++++ 9 files changed, 68 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index d1ca03862..715977f4e 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -2754,12 +2754,17 @@ extension Driver { // Support is determined by existence of the sanitizer library. // FIXME: Should we do this? This prevents cross-compiling with sanitizers // enabled. - var sanitizerSupported = try toolchain.runtimeLibraryExists( - for: stableAbi ? .address_stable_abi : sanitizer, - targetInfo: targetInfo, - parsedOptions: &parsedOptions, - isShared: sanitizer != .fuzzer && !stableAbi - ) + var sanitizerSupported = true + + // memtag-stack sanitizer doesn't have a runtime library + if sanitizer.hasRuntimeLibrary { + sanitizerSupported = try toolchain.runtimeLibraryExists( + for: stableAbi ? .address_stable_abi : sanitizer, + targetInfo: targetInfo, + parsedOptions: &parsedOptions, + isShared: sanitizer != .fuzzer && !stableAbi + ) + } if sanitizer == .thread { // TSAN is unavailable on Windows @@ -2813,6 +2818,16 @@ extension Driver { ) } + // Address and memtag sanitizers can not be enabled concurrently. + if set.contains(.memtag_stack) && set.contains(.address) { + diagnosticEngine.emit( + .error_argument_not_allowed_with( + arg: "-sanitize=memtag-stack", + other: "-sanitize=address" + ) + ) + } + // Scudo can only be run with ubsan. if set.contains(.scudo) { let allowedSanitizers: Set = [.scudo, .undefinedBehavior] diff --git a/Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift b/Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift index f2fad3ad0..3eef72cf3 100644 --- a/Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift +++ b/Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift @@ -65,17 +65,22 @@ extension Toolchain { return result } + /// Returns true if a runtime library exists for this sanitizer. Note: some + /// sanitizers don't have runtime libraries - ideally this function should + /// not be called on them - first check with `sanitizer.hasRuntimeLibrary` func runtimeLibraryExists( for sanitizer: Sanitizer, targetInfo: FrontendTargetInfo, parsedOptions: inout ParsedOptions, isShared: Bool ) throws -> Bool { - let runtimeName = try runtimeLibraryName( + guard let runtimeName = try runtimeLibraryName( for: sanitizer, targetTriple: targetInfo.target.triple, isShared: isShared - ) + ) else { + return false + } let path = try clangLibraryPath( for: targetInfo, parsedOptions: &parsedOptions diff --git a/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift b/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift index 2277bb2d0..eb97593dd 100644 --- a/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift +++ b/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift @@ -168,9 +168,12 @@ public final class DarwinToolchain: Toolchain { for sanitizer: Sanitizer, targetTriple: Triple, isShared: Bool - ) throws -> String { + ) throws -> String? { + guard let libraryName = sanitizer.runtimeLibraryName else { + return nil + } return """ - libclang_rt.\(sanitizer.libraryName)_\ + libclang_rt.\(libraryName)_\ \(targetTriple.darwinPlatform!.libraryNameSuffix)\ \(isShared ? "_dynamic.dylib" : ".a") """ diff --git a/Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift b/Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift index fc241bd96..f5c139ce9 100644 --- a/Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift +++ b/Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift @@ -148,9 +148,9 @@ public final class GenericUnixToolchain: Toolchain { for sanitizer: Sanitizer, targetTriple: Triple, isShared: Bool - ) throws -> String { + ) throws -> String? { let environment = (targetTriple.environment == .android) ? "-android" : "" - return "libclang_rt.\(sanitizer.libraryName)-\(targetTriple.archName)\(environment).a" + return "libclang_rt.\(sanitizer.runtimeLibraryName)-\(targetTriple.archName)\(environment).a" } public func addPlatformSpecificCommonFrontendOptions( diff --git a/Sources/SwiftDriver/Toolchains/Toolchain.swift b/Sources/SwiftDriver/Toolchains/Toolchain.swift index e366bde6e..eb073e2a0 100644 --- a/Sources/SwiftDriver/Toolchains/Toolchain.swift +++ b/Sources/SwiftDriver/Toolchains/Toolchain.swift @@ -143,11 +143,12 @@ public protocol Toolchain { targetInfo: FrontendTargetInfo ) throws -> ResolvedTool + /// Returns the runtime library name for a given sanitizer (or nil if the sanitizer does not have a runtime library) func runtimeLibraryName( for sanitizer: Sanitizer, targetTriple: Triple, isShared: Bool - ) throws -> String + ) throws -> String? func platformSpecificInterpreterEnvironmentVariables( env: ProcessEnvironmentBlock, diff --git a/Sources/SwiftDriver/Toolchains/WebAssemblyToolchain.swift b/Sources/SwiftDriver/Toolchains/WebAssemblyToolchain.swift index db95a4f5e..99abffa80 100644 --- a/Sources/SwiftDriver/Toolchains/WebAssemblyToolchain.swift +++ b/Sources/SwiftDriver/Toolchains/WebAssemblyToolchain.swift @@ -146,10 +146,10 @@ public final class WebAssemblyToolchain: Toolchain { for sanitizer: Sanitizer, targetTriple: Triple, isShared: Bool - ) throws -> String { + ) throws -> String? { switch sanitizer { case .address: - return "libclang_rt.\(sanitizer.libraryName)-\(targetTriple.archName).a" + return "libclang_rt.\(sanitizer.runtimeLibraryName!)-\(targetTriple.archName).a" default: throw Error.sanitizersUnsupportedForTarget(targetTriple.triple) } diff --git a/Sources/SwiftDriver/Toolchains/WindowsToolchain.swift b/Sources/SwiftDriver/Toolchains/WindowsToolchain.swift index d9e341c8c..218b2a7b3 100644 --- a/Sources/SwiftDriver/Toolchains/WindowsToolchain.swift +++ b/Sources/SwiftDriver/Toolchains/WindowsToolchain.swift @@ -135,14 +135,17 @@ extension WindowsToolchain.ToolchainValidationError { public var globalDebugPathRemapping: String? { nil } public func runtimeLibraryName(for sanitizer: Sanitizer, targetTriple: Triple, - isShared: Bool) throws -> String { + isShared: Bool) throws -> String? { // TODO(compnerd) handle shared linking // FIXME(compnerd) when should `clang_rt.ubsan_standalone_cxx` be used? if sanitizer == .undefinedBehavior { return "clang_rt.ubsan_standalone.lib" } - return "clang_rt.\(sanitizer.libraryName).lib" + if sanitizer == .memtag_stack { + throw ToolchainValidationError.unsupportedSanitizer(sanitizer) + } + return "clang_rt.\(sanitizer.runtimeLibraryName!).lib" } public func validateArgs(_ parsedOptions: inout ParsedOptions, diff --git a/Sources/SwiftDriver/Utilities/Sanitizer.swift b/Sources/SwiftDriver/Utilities/Sanitizer.swift index 33f84c832..cfed85db5 100644 --- a/Sources/SwiftDriver/Utilities/Sanitizer.swift +++ b/Sources/SwiftDriver/Utilities/Sanitizer.swift @@ -33,8 +33,19 @@ public enum Sanitizer: String, Hashable { /// Scudo hardened allocator case scudo - /// The name inside the `compiler_rt` library path (e.g. libclang_rt.{name}.a) - var libraryName: String { + /// Memory tagging sanitizer (MemTag) + case memtag_stack = "memtag-stack" + + /// Does this sanitizer have a runtime library + var hasRuntimeLibrary: Bool { + if self == .memtag_stack { + return false + } + return true + } + + /// The name inside the `compiler_rt` runtime library path (e.g. libclang_rt.{name}.a) + var runtimeLibraryName: String? { switch self { case .address: return "asan" case .address_stable_abi: return "asan_abi" @@ -42,6 +53,7 @@ public enum Sanitizer: String, Hashable { case .undefinedBehavior: return "ubsan" case .fuzzer: return "fuzzer" case .scudo: return "scudo" + case .memtag_stack: return nil } } } diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index 57559f00d..35683994d 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -3101,6 +3101,16 @@ final class SwiftDriverTests: XCTestCase { XCTAssertJobInvocationMatches(jobs[2], .flag("-fsanitize=undefined")) } + do { + // memory tagging stack sanitizer + var driver = try Driver(args: commonArgs + ["-sanitize=memtag-stack"]) + let jobs = try driver.planBuild().removingAutolinkExtractJobs() + + XCTAssertEqual(jobs.count, 3) + XCTAssertJobInvocationMatches(jobs[0], .flag("-sanitize=memtag-stack")) + // No runtime for memtag-stack - thus no linker arg required + } + // FIXME: This test will fail when run on macOS, because the driver uses // the existence of the runtime support libraries to determine if // a sanitizer is supported. Until we allow cross-compiling with From d8c9c3172db2901a29201f4b1407a7d7105b85b3 Mon Sep 17 00:00:00 2001 From: Dan Blackwell Date: Sat, 15 Nov 2025 08:49:42 +0000 Subject: [PATCH 2/2] Tidying --- Sources/SwiftDriver/Driver/Driver.swift | 2 +- Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift | 5 ++++- Sources/SwiftDriver/Utilities/Sanitizer.swift | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index 715977f4e..f385fcbbf 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -2818,7 +2818,7 @@ extension Driver { ) } - // Address and memtag sanitizers can not be enabled concurrently. + // Address and memtag-stack sanitizers can not be enabled concurrently. if set.contains(.memtag_stack) && set.contains(.address) { diagnosticEngine.emit( .error_argument_not_allowed_with( diff --git a/Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift b/Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift index f5c139ce9..b1558fe12 100644 --- a/Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift +++ b/Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift @@ -149,8 +149,11 @@ public final class GenericUnixToolchain: Toolchain { targetTriple: Triple, isShared: Bool ) throws -> String? { + guard let runtimeLibraryName = sanitizer.runtimeLibraryName else { + return nil + } let environment = (targetTriple.environment == .android) ? "-android" : "" - return "libclang_rt.\(sanitizer.runtimeLibraryName)-\(targetTriple.archName)\(environment).a" + return "libclang_rt.\(runtimeLibraryName)-\(targetTriple.archName)\(environment).a" } public func addPlatformSpecificCommonFrontendOptions( diff --git a/Sources/SwiftDriver/Utilities/Sanitizer.swift b/Sources/SwiftDriver/Utilities/Sanitizer.swift index cfed85db5..1ae90648f 100644 --- a/Sources/SwiftDriver/Utilities/Sanitizer.swift +++ b/Sources/SwiftDriver/Utilities/Sanitizer.swift @@ -33,7 +33,7 @@ public enum Sanitizer: String, Hashable { /// Scudo hardened allocator case scudo - /// Memory tagging sanitizer (MemTag) + /// Memory-Tagging-based stack sanitizer case memtag_stack = "memtag-stack" /// Does this sanitizer have a runtime library