summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2025-08-15 09:51:40 +0200
committerMarkus Pettersson <markus.pettersson@mullvad.net>2025-08-28 14:34:02 +0200
commit8b7e2dba790e90a0ac885adfeded15eb97fb99f0 (patch)
tree320dd43be7f2681a0b70a1fe71ff06fb89b8e29c
parent57e41952f8d0b02d0d433c12879e7e537807cb3f (diff)
downloadmullvadvpn-8b7e2dba790e90a0ac885adfeded15eb97fb99f0.tar.xz
mullvadvpn-8b7e2dba790e90a0ac885adfeded15eb97fb99f0.zip
Use objc2_service_management to get rid of a lot of unsafe
-rw-r--r--Cargo.lock29
-rw-r--r--mullvad-daemon/Cargo.toml4
-rw-r--r--mullvad-daemon/src/macos_launch_daemon.rs81
3 files changed, 55 insertions, 59 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0acd63ee00..dded76c819 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2986,8 +2986,8 @@ dependencies = [
"mullvad-version",
"nix 0.30.1",
"notify 8.0.0",
- "objc2 0.5.2",
"objc2-foundation 0.3.1",
+ "objc2-service-management",
"rand 0.8.5",
"regex",
"serde",
@@ -3610,9 +3610,6 @@ name = "objc-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
-dependencies = [
- "cc",
-]
[[package]]
name = "objc2"
@@ -3741,6 +3738,30 @@ dependencies = [
]
[[package]]
+name = "objc2-security"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44"
+dependencies = [
+ "bitflags 2.9.0",
+ "objc2 0.6.2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-service-management"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ca650c581044d08572b3c21d9ce8c730f2eefdc1683f033849f305ea8212be"
+dependencies = [
+ "block2 0.6.1",
+ "objc2 0.6.2",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.1",
+ "objc2-security",
+]
+
+[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml
index 4844400257..7abf9bf7cc 100644
--- a/mullvad-daemon/Cargo.toml
+++ b/mullvad-daemon/Cargo.toml
@@ -71,8 +71,8 @@ simple-signal = "1.1"
talpid-dbus = { path = "../talpid-dbus" }
[target.'cfg(target_os="macos")'.dependencies]
-objc2 = { version = "0.5.2", features = ["exception"] }
-objc2-foundation = { version = "0.3.1", features = ["NSProcessInfo"] }
+objc2-foundation = { version = "0.3.1", features = ["NSProcessInfo", "NSString", "NSURL"] }
+objc2-service-management = { version = "0.3.1", features = [ "SMAppService", "objc2" ] }
notify = "8.0.0"
talpid-macos = { path = "../talpid-macos" }
diff --git a/mullvad-daemon/src/macos_launch_daemon.rs b/mullvad-daemon/src/macos_launch_daemon.rs
index 53f9ba2af4..080e771565 100644
--- a/mullvad-daemon/src/macos_launch_daemon.rs
+++ b/mullvad-daemon/src/macos_launch_daemon.rs
@@ -5,19 +5,13 @@
//! must be checked so that the user can be directed to approve the launch
//! daemon in the system settings.
-#![allow(clippy::undocumented_unsafe_blocks)] // Remove me if you dare.
+use objc2_foundation::{NSOperatingSystemVersion, NSProcessInfo, NSURL, ns_string};
+use objc2_service_management::{SMAppService, SMAppServiceStatus};
-use std::ffi::CStr;
-
-use objc2::{class, msg_send, runtime::AnyObject};
-use objc2_foundation::{NSOperatingSystemVersion, NSProcessInfo};
-
-type Id = *mut AnyObject;
-
-// TODO: Replace with obcj2-service-management
-// Framework that contains `SMAppService`.
-#[link(name = "ServiceManagement", kind = "framework")]
-unsafe extern "C" {}
+/// Path to the plist that defines the Mullvad launch daemon.
+/// It must be kept in sync with the path defined in
+/// `dist-assets/pkg-scripts/postinstall`.
+const DAEMON_PLIST_PATH: &str = "/Library/LaunchDaemons/net.mullvad.daemon.plist";
/// Authorization status of the Mullvad daemon.
#[repr(i32)]
@@ -36,20 +30,31 @@ pub fn get_status() -> LaunchDaemonStatus {
if os_version.majorVersion < 13 {
return LaunchDaemonStatus::Ok;
}
- get_status_for_url(&daemon_plist_url())
+ // SAFETY: daemon_plist_path is not an empty path & it is a valid system path.
+ // https://developer.apple.com/documentation/foundation/nsurl/fileurl(withpath:)#parameters
+ let daemon_plist_url = unsafe { NSURL::fileURLWithPath(ns_string!(DAEMON_PLIST_PATH)) };
+ get_status_for_url(&daemon_plist_url)
}
-fn get_status_for_url(url: &Object) -> LaunchDaemonStatus {
- let status: libc::c_long =
- unsafe { msg_send![class!(SMAppService), statusForLegacyURL: url.0] };
-
+fn get_status_for_url(url: &NSURL) -> LaunchDaemonStatus {
+ // SAFETY: Apple does not state *anything* regarding safety requirements of this function:
+ // https://developer.apple.com/documentation/servicemanagement/smappservice/statusforlegacyplist(at:)
+ // But using a bit of reasoning & the [guidelines of objc2](https://github.com/madsmtm/objc2/blob/master/crates/header-translator/README.md#what-is-required-for-a-method-to-be-safe):
+ // """
+ // What is required for a method to be safe?
+ // 1. The method must not take a raw pointer; one could trivially pass ptr::invalid() and cause UB with that.
+ // 2. Any extra requirements that the method states in its documentation must be upheld.
+ // """
+ // we can conclude that:
+ // (1.) is upheld by the virtue of url being a reference, since references are always valid.
+ // (2.) is trivially upheld since Apple does not state safety requirements.
+ let status = unsafe { SMAppService::statusForLegacyURL(url) };
match status {
- // SMAppServiceStatusNotRegistered | SMAppServiceStatusNotFound
- 0 | 3 => LaunchDaemonStatus::NotFound,
- // SMAppServiceStatusEnabled
- 1 => LaunchDaemonStatus::Ok,
- // SMAppServiceStatusRequiresApproval
- 2 => LaunchDaemonStatus::NotAuthorized,
+ SMAppServiceStatus::NotRegistered | SMAppServiceStatus::NotFound => {
+ LaunchDaemonStatus::NotFound
+ }
+ SMAppServiceStatus::Enabled => LaunchDaemonStatus::Ok,
+ SMAppServiceStatus::RequiresApproval => LaunchDaemonStatus::NotAuthorized,
// Unknown status
_ => LaunchDaemonStatus::Unknown,
}
@@ -59,33 +64,3 @@ fn get_os_version() -> NSOperatingSystemVersion {
let process_info = NSProcessInfo::processInfo();
process_info.operatingSystemVersion()
}
-
-/// Returns an `NSURL` instance for `DAEMON_PLIST_PATH`.
-fn daemon_plist_url() -> Object {
- /// Path to the plist that defines the Mullvad launch daemon.
- /// It must be kept in sync with the path defined in
- /// `dist-assets/pkg-scripts/postinstall`.
- const DAEMON_PLIST_PATH: &CStr = c"/Library/LaunchDaemons/net.mullvad.daemon.plist";
-
- let nsstr_inst: Id = unsafe { msg_send![class!(NSString), alloc] };
- let nsstr_inst: Id =
- unsafe { msg_send![nsstr_inst, initWithUTF8String: DAEMON_PLIST_PATH.as_ptr()] };
-
- let nsurl_inst: Id = unsafe { msg_send![class!(NSURL), alloc] };
- let nsurl_inst: Id = unsafe { msg_send![nsurl_inst, initWithString: nsstr_inst] };
-
- let _: () = unsafe { msg_send![nsstr_inst, release] };
-
- assert!(!nsurl_inst.is_null());
-
- Object(nsurl_inst)
-}
-
-/// Calls `[self.0 release]` when the wrapped instance is dropped.
-struct Object(Id);
-
-impl Drop for Object {
- fn drop(&mut self) {
- let _: () = unsafe { msg_send![self.0, release] };
- }
-}