Skip to content

Update Hardened Runtime sub options to work with build settings. #342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Sources/SWBCore/Settings/BuiltinMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,10 @@ public final class BuiltinMacros {

public static let AD_HOC_CODE_SIGNING_ALLOWED = BuiltinMacros.declareBooleanMacro("AD_HOC_CODE_SIGNING_ALLOWED")
public static let __AD_HOC_CODE_SIGNING_NOT_ALLOWED_SUPPLEMENTAL_MESSAGE = BuiltinMacros.declareStringMacro("__AD_HOC_CODE_SIGNING_NOT_ALLOWED_SUPPLEMENTAL_MESSAGE")
public static let RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = BuiltinMacros.declareBooleanMacro("RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES")
public static let RUNTIME_EXCEPTION_ALLOW_JIT = BuiltinMacros.declareBooleanMacro("RUNTIME_EXCEPTION_ALLOW_JIT")
public static let RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = BuiltinMacros.declareBooleanMacro("RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY")
public static let AUTOMATION_APPLE_EVENTS = BuiltinMacros.declareBooleanMacro("AUTOMATION_APPLE_EVENTS")
public static let CODE_SIGNING_ALLOWED = BuiltinMacros.declareBooleanMacro("CODE_SIGNING_ALLOWED")
public static let CODE_SIGNING_REQUIRED = BuiltinMacros.declareBooleanMacro("CODE_SIGNING_REQUIRED")
public static let CODE_SIGNING_REQUIRES_TEAM = BuiltinMacros.declareBooleanMacro("CODE_SIGNING_REQUIRES_TEAM")
Expand All @@ -345,8 +349,11 @@ public final class BuiltinMacros {
public static let CODE_SIGN_RESTRICT = BuiltinMacros.declareBooleanMacro("CODE_SIGN_RESTRICT")
public static let CODE_SIGN_RESOURCE_RULES_PATH = BuiltinMacros.declareStringMacro("CODE_SIGN_RESOURCE_RULES_PATH")
public static let CODE_SIGN_STYLE = BuiltinMacros.declareStringMacro("CODE_SIGN_STYLE")
public static let RUNTIME_EXCEPTION_DEBUGGING_TOOL = BuiltinMacros.declareBooleanMacro("RUNTIME_EXCEPTION_DEBUGGING_TOOL")
public static let DIAGNOSE_MISSING_TARGET_DEPENDENCIES = BuiltinMacros.declareEnumMacro("DIAGNOSE_MISSING_TARGET_DEPENDENCIES") as EnumMacroDeclaration<BooleanWarningLevel>
public static let RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = BuiltinMacros.declareBooleanMacro("RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION")
public static let DISABLE_FREEFORM_CODE_SIGN_OPTION_FLAGS = BuiltinMacros.declareBooleanMacro("DISABLE_FREEFORM_CODE_SIGN_OPTION_FLAGS")
public static let RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = BuiltinMacros.declareBooleanMacro("RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION")
public static let ENABLE_CLOUD_SIGNING = BuiltinMacros.declareBooleanMacro("ENABLE_CLOUD_SIGNING")

public static let ENABLE_GENERIC_TASK_CACHING = BuiltinMacros.declareBooleanMacro("ENABLE_GENERIC_TASK_CACHING")
Expand Down Expand Up @@ -2382,6 +2389,13 @@ public final class BuiltinMacros {

/// Force initialization of entitlements macros.
private static let allEntitlementsMacros = [
RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES,
RUNTIME_EXCEPTION_ALLOW_JIT,
RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY,
AUTOMATION_APPLE_EVENTS,
RUNTIME_EXCEPTION_DEBUGGING_TOOL,
RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION,
RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION,
ENABLE_APP_SANDBOX,
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT,
ENABLE_RESOURCE_ACCESS_BLUETOOTH,
Expand Down
145 changes: 84 additions & 61 deletions Sources/SWBCore/SpecImplementations/Tools/ProductPackaging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,36 @@ public final class ProductPackagingToolSpec : GenericCommandLineToolSpec, SpecId
fatalError("unexpected direct invocation")
}

fileprivate static let sandboxAndHardenedRuntimeBooleanEntitlements = [
BuiltinMacros.RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES: "com.apple.security.cs.allow-dyld-environment-variables",
BuiltinMacros.RUNTIME_EXCEPTION_ALLOW_JIT: "com.apple.security.cs.allow-jit",
BuiltinMacros.RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY: "com.apple.security.cs.allow-unsigned-executable-memory",
BuiltinMacros.RUNTIME_EXCEPTION_DEBUGGING_TOOL: "com.apple.security.cs.debugger",
BuiltinMacros.RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION: "com.apple.security.cs.disable-executable-page-protection",
BuiltinMacros.RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION: "com.apple.security.cs.disable-library-validation",
BuiltinMacros.ENABLE_APP_SANDBOX: "com.apple.security.app-sandbox",
BuiltinMacros.ENABLE_INCOMING_NETWORK_CONNECTIONS: "com.apple.security.network.server",
BuiltinMacros.ENABLE_OUTGOING_NETWORK_CONNECTIONS: "com.apple.security.network.client",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_AUDIO_INPUT: "com.apple.security.device.audio-input",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_BLUETOOTH: "com.apple.security.device.bluetooth",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_CALENDARS: "com.apple.security.personal-information.calendars",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_CAMERA: "com.apple.security.device.camera",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_CONTACTS: "com.apple.security.personal-information.addressbook",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_LOCATION: "com.apple.security.personal-information.location",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY: "com.apple.security.personal-information.photos-library",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_PRINTING: "com.apple.security.print",
BuiltinMacros.ENABLE_RESOURCE_ACCESS_USB: "com.apple.security.device.usb",
BuiltinMacros.AUTOMATION_APPLE_EVENTS: "com.apple.security.automation.apple-events",
]

fileprivate static let sandboxFileAccessSettingsAndEntitlements: [(EnumMacroDeclaration<FileAccessMode>, String)] = [
(BuiltinMacros.ENABLE_FILE_ACCESS_DOWNLOADS_FOLDER, "com.apple.security.files.downloads"),
(BuiltinMacros.ENABLE_FILE_ACCESS_PICTURE_FOLDER, "com.apple.security.assets.pictures"),
(BuiltinMacros.ENABLE_FILE_ACCESS_MUSIC_FOLDER, "com.apple.security.assets.music"),
(BuiltinMacros.ENABLE_FILE_ACCESS_MOVIES_FOLDER, "com.apple.security.assets.movies"),
(BuiltinMacros.ENABLE_USER_SELECTED_FILES, "com.apple.security.files.user-selected"),
]

/// Construct a task to create the entitlements (`.xcent`) file.
/// - parameter cbc: The command build context. This includes the input file to process (until <rdar://problem/29117572> is fixed), and the output file in the product to which write the contents.
/// - parameter delegate: The task generation delegate.
Expand Down Expand Up @@ -88,22 +118,14 @@ public final class ProductPackagingToolSpec : GenericCommandLineToolSpec, SpecId

// rdar://142845111 (Turn on `AppSandboxConflictingValuesEmitsWarning` by default)
if SWBFeatureFlag.enableAppSandboxConflictingValuesEmitsWarning.value {
EntitlementConflictDiagnosticEmitter.checkForConflicts(cbc, delegate, entitlementsDictionary: entitlementsDictionary)
EntitlementConflictDiagnosticEmitter.checkForConflicts(cbc, delegate, entitlementsDictionary: entitlementsDictionary, entitlementsPath: codeSignEntitlementsInput?.absolutePath)
}

if isAppSandboxEnabled || isHardenedRuntimeEnabled {
// Inject entitlements that are settable via build settings.
// This is only supported when App Sandbox or Hardened Runtime is enabled.
let fileAccessSettingsAndEntitlements: [(EnumMacroDeclaration<FileAccessMode>, String)] = [
(BuiltinMacros.ENABLE_FILE_ACCESS_DOWNLOADS_FOLDER, "com.apple.security.files.downloads"),
(BuiltinMacros.ENABLE_FILE_ACCESS_PICTURE_FOLDER, "com.apple.security.assets.pictures"),
(BuiltinMacros.ENABLE_FILE_ACCESS_MUSIC_FOLDER, "com.apple.security.assets.music"),
(BuiltinMacros.ENABLE_FILE_ACCESS_MOVIES_FOLDER, "com.apple.security.assets.movies"),
(BuiltinMacros.ENABLE_USER_SELECTED_FILES, "com.apple.security.files.user-selected"),
]

for (buildSettingSetting, entitlementPrefix) in fileAccessSettingsAndEntitlements {
let fileAccessValue = cbc.scope.evaluate(buildSettingSetting)
for (buildSetting, entitlementPrefix) in Self.sandboxFileAccessSettingsAndEntitlements {
let fileAccessValue = cbc.scope.evaluate(buildSetting)
switch fileAccessValue {
case .readOnly:
entitlementsDictionary["\(entitlementPrefix).read-only"] = .plBool(true)
Expand All @@ -114,41 +136,10 @@ public final class ProductPackagingToolSpec : GenericCommandLineToolSpec, SpecId
}
}

if cbc.scope.evaluate(BuiltinMacros.ENABLE_APP_SANDBOX) {
entitlementsDictionary["com.apple.security.app-sandbox"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_INCOMING_NETWORK_CONNECTIONS) {
entitlementsDictionary["com.apple.security.network.server"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_OUTGOING_NETWORK_CONNECTIONS) {
entitlementsDictionary["com.apple.security.network.client"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_AUDIO_INPUT) {
entitlementsDictionary["com.apple.security.device.audio-input"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_BLUETOOTH) {
entitlementsDictionary["com.apple.security.device.bluetooth"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_CALENDARS) {
entitlementsDictionary["com.apple.security.personal-information.calendars"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_CAMERA) {
entitlementsDictionary["com.apple.security.device.camera"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_CONTACTS) {
entitlementsDictionary["com.apple.security.personal-information.addressbook"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_LOCATION) {
entitlementsDictionary["com.apple.security.personal-information.location"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY) {
entitlementsDictionary["com.apple.security.personal-information.photos-library"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_PRINTING) {
entitlementsDictionary["com.apple.security.print"] = .plBool(true)
}
if cbc.scope.evaluate(BuiltinMacros.ENABLE_RESOURCE_ACCESS_USB) {
entitlementsDictionary["com.apple.security.device.usb"] = .plBool(true)
for (buildSetting, entitlement) in Self.sandboxAndHardenedRuntimeBooleanEntitlements {
if cbc.scope.evaluate(buildSetting) {
entitlementsDictionary[entitlement] = .plBool(true)
}
}
}

Expand Down Expand Up @@ -218,29 +209,32 @@ public final class ProductPackagingToolSpec : GenericCommandLineToolSpec, SpecId

private extension ProductPackagingToolSpec {
enum EntitlementConflictDiagnosticEmitter {
static func checkForConflicts(_ cbc: CommandBuildContext, _ delegate: some TaskGenerationDelegate, entitlementsDictionary: [String: PropertyListItem]) {

let isAppSandboxEnabled = cbc.scope.evaluate(BuiltinMacros.ENABLE_APP_SANDBOX)

switch entitlementsDictionary["com.apple.security.app-sandbox"] {
case let .plBool(value):
if isAppSandboxEnabled != value {
let appSandboxBuildSettingName = BuiltinMacros.ENABLE_APP_SANDBOX.name
private static func validate(entitlement: String, withExpectedValue expectedValue: Bool, entitlementsDictionary: [String: PropertyListItem], entitlementsPath: Path?, buildSettingName: String, buildSettingValue: String, delegate: some TaskGenerationDelegate) {
switch entitlementsDictionary[entitlement] {
case let .plBool(entitlementValue):
if expectedValue != entitlementValue {
let message: String
let childDiagnostics: [Diagnostic]

if isAppSandboxEnabled {
let entitlementsLocation: Diagnostic.Location
if let entitlementsPath {
entitlementsLocation = .path(entitlementsPath)
} else {
entitlementsLocation = .unknown
}

if expectedValue {
// This is when build setting is true and entitlement is false
message = "The \(appSandboxBuildSettingName) build setting is set to YES, but is set to NO in your entitlements file."
message = "The '\(buildSettingName)' build setting is set to '\(buildSettingValue)', but entitlement '\(entitlement)' is set to '\(entitlementValue ? "YES" : "NO")' in your entitlements file."
childDiagnostics = [
.init(behavior: .note, location: .unknown, data: .init("To enable App Sandbox, remove the entitlement from your entitlements file.")),
.init(behavior: .note, location: .unknown, data: .init("To disable App Sandbox, remove the entitlement from your entitlements file, and set the \(appSandboxBuildSettingName) build setting to NO."))
.init(behavior: .note, location: entitlementsLocation, data: .init("To enable '\(buildSettingName)', remove the entitlement from your entitlements file.")),
.init(behavior: .note, location: .buildSettings(names: [buildSettingName]), data: .init("To disable '\(buildSettingName)', remove the entitlement from your entitlements file and disable '\(buildSettingName)' in build settings."))
]
} else {
message = "The \(appSandboxBuildSettingName) build setting is set to NO, but is set to YES in your entitlements file."
message = "The '\(buildSettingName)' build setting is set to '\(buildSettingValue)', but entitlement '\(entitlement)' is set to '\(entitlementValue ? "YES" : "NO")' in your entitlements file."
childDiagnostics = [
.init(behavior: .note, location: .unknown, data: .init("To enable App Sandbox, remove the entitlement from your entitlements file, and set the \(appSandboxBuildSettingName) build setting to YES.")),
.init(behavior: .note, location: .unknown, data: .init("To disable App Sandbox, remove the entitlement from your entitlements file."))
.init(behavior: .note, location: .buildSettings(names: [buildSettingName]), data: .init("To enable '\(buildSettingName)', remove the entitlement from your entitlements file, and enable '\(buildSettingName)' in build settings.")),
.init(behavior: .note, location: entitlementsLocation, data: .init("To disable '\(buildSettingName)', remove the entitlement from your entitlements file."))
]
}

Expand All @@ -250,5 +244,34 @@ private extension ProductPackagingToolSpec {
break
}
}

static func checkForConflicts(_ cbc: CommandBuildContext, _ delegate: some TaskGenerationDelegate, entitlementsDictionary: [String: PropertyListItem], entitlementsPath: Path?) {
for (buildSetting, entitlementPrefix) in sandboxFileAccessSettingsAndEntitlements {
let fileAccessValue = cbc.scope.evaluate(buildSetting)
let buildSettingName = buildSetting.name

let readOnlyEntitlement = "\(entitlementPrefix).read-only"
let readWriteEntitlement = "\(entitlementPrefix).read-write"

switch fileAccessValue {
case .readOnly:
validate(entitlement: readOnlyEntitlement, withExpectedValue: true, entitlementsDictionary: entitlementsDictionary, entitlementsPath: entitlementsPath, buildSettingName: buildSettingName, buildSettingValue: "readOnly", delegate: delegate)

case .readWrite:
validate(entitlement: readWriteEntitlement, withExpectedValue: true, entitlementsDictionary: entitlementsDictionary, entitlementsPath: entitlementsPath, buildSettingName: buildSettingName, buildSettingValue: "readWrite", delegate: delegate)
case .none:
validate(entitlement: readOnlyEntitlement, withExpectedValue: false, entitlementsDictionary: entitlementsDictionary, entitlementsPath: entitlementsPath, buildSettingName: buildSettingName, buildSettingValue: "None", delegate: delegate)
validate(entitlement: readWriteEntitlement, withExpectedValue: false, entitlementsDictionary: entitlementsDictionary, entitlementsPath: entitlementsPath, buildSettingName: buildSettingName, buildSettingValue: "None", delegate: delegate)

}
}

for (buildSetting, entitlement) in ProductPackagingToolSpec.sandboxAndHardenedRuntimeBooleanEntitlements {
let buildSettingValue = cbc.scope.evaluate(buildSetting)
let buildSettingName = buildSetting.name

validate(entitlement: entitlement, withExpectedValue: buildSettingValue, entitlementsDictionary: entitlementsDictionary, entitlementsPath: entitlementsPath, buildSettingName: buildSettingName, buildSettingValue: "\(buildSettingValue ? "YES" : "NO")", delegate: delegate)
}
}
}
}
Loading