From bc0c26891a53445f21ebedfc826e5393202ed8bd Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 31 Aug 2022 16:16:38 +0800 Subject: [PATCH] Repo // Sandbox entitlements and hardened runtime. --- Installer/AppDelegate.swift | 68 ++++++++++++++++++++--------- Source/Modules/IMEModules/IME.swift | 24 ++++++---- vChewing.entitlements | 22 ++++++++++ vChewing.xcodeproj/project.pbxproj | 18 ++++++++ vChewingInstaller.entitlements | 14 ++++++ vChewingPhraseEditor.entitlements | 12 +++++ 6 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 vChewing.entitlements create mode 100644 vChewingInstaller.entitlements create mode 100644 vChewingPhraseEditor.entitlements diff --git a/Installer/AppDelegate.swift b/Installer/AppDelegate.swift index 0eb095c8..f474cea3 100644 --- a/Installer/AppDelegate.swift +++ b/Installer/AppDelegate.swift @@ -13,12 +13,14 @@ import Cocoa private let kTargetBin = "vChewing" private let kTargetType = "app" private let kTargetBundle = "vChewing.app" +private let kTargetBundleWithComponents = "Library/Input%20Methods/vChewing.app" -private let urlDestinationPartial = FileManager.default.urls( - for: .inputMethodsDirectory, in: .userDomainMask -)[0] -private let urlTargetPartial = urlDestinationPartial.appendingPathComponent(kTargetBundle) -private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS/") +private let realHomeDir = URL( + fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil +) +private let urlDestinationPartial = realHomeDir.appendingPathComponent("Library/Input Methods") +private let urlTargetPartial = realHomeDir.appendingPathComponent(kTargetBundleWithComponents) +private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS") .appendingPathComponent(kTargetBin) private let kDestinationPartial = urlDestinationPartial.path @@ -93,9 +95,9 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { window?.standardWindowButton(.zoomButton)?.isHidden = true if FileManager.default.fileExists( - atPath: (kTargetPartialPath as NSString).expandingTildeInPath) + atPath: kTargetPartialPath) { - let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath) + let currentBundle = Bundle(path: kTargetPartialPath) let shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String let currentVersion = @@ -136,15 +138,12 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { } func removeThenInstallInputMethod() { - if FileManager.default.fileExists( - atPath: (kTargetPartialPath as NSString).expandingTildeInPath) - == false - { - installInputMethod( - previousExists: false, previousVersionNotFullyDeactivatedWarning: false - ) - return - } + // if !FileManager.default.fileExists(atPath: kTargetPartialPath) { + // installInputMethod( + // previousExists: false, previousVersionNotFullyDeactivatedWarning: false + // ) + // return + // } let shouldWaitForTranslocationRemoval = isAppBundleTranslocated(atPath: kTargetPartialPath) @@ -152,7 +151,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { // 將既存輸入法扔到垃圾桶內 do { - let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath + let sourceDir = kDestinationPartial let fileManager = FileManager.default let fileURLString = String(format: "%@/%@", sourceDir, kTargetBundle) let fileURL = URL(fileURLWithPath: fileURLString) @@ -216,8 +215,9 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { } let cpTask = Process() cpTask.launchPath = "/bin/cp" + print(kDestinationPartial) cpTask.arguments = [ - "-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath, + "-R", targetBundle, kDestinationPartial, ] cpTask.launch() cpTask.waitUntilExit() @@ -231,7 +231,11 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { endAppWithDelay() } - guard let imeBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath), + let imeURLInstalled = realHomeDir.appendingPathComponent("Library/Input Methods/vChewing.app") + + _ = try? shell("/usr/bin/xattr -drs com.apple.quarantine \(kTargetPartialPath)") + + guard let imeBundle = Bundle(url: imeURLInstalled), let imeIdentifier = imeBundle.bundleIdentifier else { endAppWithDelay() @@ -343,6 +347,30 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { NSApp.terminate(self) } + private func shell(_ command: String) throws -> String { + let task = Process() + let pipe = Pipe() + + task.standardOutput = pipe + task.standardError = pipe + task.arguments = ["-c", command] + if #available(macOS 10.13, *) { + task.executableURL = URL(fileURLWithPath: "/bin/zsh") + } else { + task.launchPath = "/bin/zsh" + } + task.standardInput = nil + + if #available(macOS 10.13, *) { + try task.run() + } + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)! + + return output + } + // Determines if an app is translocated by Gatekeeper to a randomized path. // See https://weblog.rogueamoeba.com/2016/06/29/sierra-and-gatekeeper-path-randomization/ // Originally written by Zonble Yang in Objective-C (MIT License). @@ -350,7 +378,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { func isAppBundleTranslocated(atPath bundlePath: String) -> Bool { var entryCount = getfsstat(nil, 0, 0) var entries: [statfs] = .init(repeating: .init(), count: Int(entryCount)) - let absPath = (bundlePath as NSString).expandingTildeInPath.cString(using: .utf8) + let absPath = bundlePath.cString(using: .utf8) entryCount = getfsstat(&entries, entryCount * Int32(MemoryLayout.stride), MNT_NOWAIT) for entry in entries.prefix(Int(entryCount)) { let isMatch = withUnsafeBytes(of: entry.f_mntfromname) { mntFromName in diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index ecde1e14..e6e62313 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -22,6 +22,9 @@ public enum InputMode: String { public enum IME { static let arrSupportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] static let dlgOpenPath = NSOpenPanel() + public static let realHomeDir = URL( + fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil + ) // MARK: - 瀏覽器 Bundle Identifier 關鍵詞匹配黑名單 @@ -173,16 +176,23 @@ public enum IME { return -1 } - let kTargetBin = "vChewing" + // 自威注音 v2.3.0 開始,沙箱限制了威注音的某些行為,所以該函式不再受理 sudo 模式下的操作。 + if isSudo { + print( + "vChewing binary does not support sudo-uninstall since v2.3.0. Please use the bundled uninstall.sh instead.\n\nIf you want to fix the installation (i.e. removing all incorrectly installed files outside of the current user folder), please use the bundled fixinstall.sh instead.\n\nIf you don't know how to proceed, please bring either the uninstall.sh / install.sh or the instruction article https://vchewing.github.io/UNINSTALL.html to Apple Support (support.apple.com) for help. Their senior advisors can understand these uninstall instructions." + ) + return -1 + } + let kTargetBundle = "/vChewing.app" let pathLibrary = isSudo ? "/Library" - : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path + : IME.realHomeDir.appendingPathComponent("Library/").path let pathIMELibrary = isSudo ? "/Library/Input Methods" - : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path + : IME.realHomeDir.appendingPathComponent("Library/Input Methods/").path let pathUnitKeyboardLayouts = "/Keyboard Layouts" let arrKeyLayoutFiles = [ "/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", @@ -203,15 +213,13 @@ public enum IME { // 目前暫時無法應對 symbol link 的情況。 IME.trashTargetIfExists(mgrLangModel.dataFolderPath(isDefaultFolder: true)) IME.trashTargetIfExists(pathLibrary + "/Preferences/" + bundleID + ".plist") // 之後移除 App 偏好設定 + IME.trashTargetIfExists(pathLibrary + "/Receipts/org.atelierInmu.vChewing.bom") // pkg 垃圾 + IME.trashTargetIfExists(pathLibrary + "/Receipts/org.atelierInmu.vChewing.plist") // pkg 垃圾 } if !IME.trashTargetIfExists(pathIMELibrary + kTargetBundle) { return -1 } // 最後移除 App 自身 // 幹掉殘留在記憶體內的執行緒。 if selfKill { - let killTask = Process() - killTask.launchPath = "/usr/bin/killall" - killTask.arguments = ["-9", kTargetBin] - killTask.launch() - killTask.waitUntilExit() + NSApplication.shared.terminate(nil) } return 0 } diff --git a/vChewing.entitlements b/vChewing.entitlements new file mode 100644 index 00000000..ada9860e --- /dev/null +++ b/vChewing.entitlements @@ -0,0 +1,22 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.bookmarks.app-scope + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.temporary-exception.files.home-relative-path.read-write + + / + + com.apple.security.temporary-exception.mach-register.global-name + org.atelierInmu.inputmethod.vChewing_Connection + com.apple.security.temporary-exception.shared-preference.read-only + org.atelierInmu.inputmethod.vChewing + + diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 76583ce6..5294da42 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -219,6 +219,9 @@ 5B18BA7327C7BD8C0056EB19 /* LICENSE-JPN.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LICENSE-JPN.txt"; sourceTree = ""; }; 5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LICENSE-CHT.txt"; sourceTree = ""; }; 5B20430628BEE30900BFC6FD /* BookmarkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkManager.swift; sourceTree = ""; }; + 5B20430B28BEFC0C00BFC6FD /* vChewing.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vChewing.entitlements; sourceTree = ""; }; + 5B20430C28BEFC1200BFC6FD /* vChewingPhraseEditor.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vChewingPhraseEditor.entitlements; sourceTree = ""; }; + 5B20430D28BF279900BFC6FD /* vChewingInstaller.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vChewingInstaller.entitlements; sourceTree = ""; }; 5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleStates.swift; sourceTree = ""; }; 5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleDisplay.swift; sourceTree = ""; }; 5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Delegates.swift; sourceTree = ""; }; @@ -789,6 +792,9 @@ 6A0D4E9215FC0CFA00ABF4B3 = { isa = PBXGroup; children = ( + 5B20430D28BF279900BFC6FD /* vChewingInstaller.entitlements */, + 5B20430C28BEFC1200BFC6FD /* vChewingPhraseEditor.entitlements */, + 5B20430B28BEFC0C00BFC6FD /* vChewing.entitlements */, 5BBD627827B6C4D900271480 /* Update-Info.plist */, 5B18BA7027C7BD8B0056EB19 /* Makefile */, 5B0C5EDE27C7D94C0078037C /* DataCompiler */, @@ -1534,11 +1540,13 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = vChewingPhraseEditor.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 2201; DEAD_CODE_STRIPPING = YES; + ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -1584,11 +1592,13 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = vChewingPhraseEditor.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 2201; DEAD_CODE_STRIPPING = YES; + ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -1716,6 +1726,7 @@ CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = vChewing.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -1723,6 +1734,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -1773,6 +1785,7 @@ CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = vChewing.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -1780,6 +1793,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -1818,12 +1832,14 @@ CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = vChewingInstaller.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 2201; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -1860,12 +1876,14 @@ CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = vChewingInstaller.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 2201; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES; diff --git a/vChewingInstaller.entitlements b/vChewingInstaller.entitlements new file mode 100644 index 00000000..d0c50061 --- /dev/null +++ b/vChewingInstaller.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.files.bookmarks.app-scope + + com.apple.security.temporary-exception.files.home-relative-path.read-write + + /Library/Input Methods/ + + com.apple.security.temporary-exception.shared-preference.read-write + org.atelierInmu.vChewing.vChewingInstaller + + diff --git a/vChewingPhraseEditor.entitlements b/vChewingPhraseEditor.entitlements new file mode 100644 index 00000000..db28c6a8 --- /dev/null +++ b/vChewingPhraseEditor.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.bookmarks.app-scope + + com.apple.security.files.user-selected.read-write + + +