Skip to content

Commit 3e220ac

Browse files
committed
Support xpc_connection_set_peer_sig on macOS 12
Also support validating client code requirements using the audit token using the same APIs.
1 parent 3ba4b76 commit 3e220ac

File tree

6 files changed

+176
-68
lines changed

6 files changed

+176
-68
lines changed

examples/echo-server/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ publish = false
88
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
99

1010
[dependencies]
11-
core-foundation = "0.9"
1211
futures = { version = "0.3" }
13-
# Support for SecCode was added in 2.3.1
14-
security-framework = "^2.3.1"
1512
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
1613
xpc-connection = { path = "../../xpc-connection", features = ["audit_token"] }

examples/echo-server/src/main.rs

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,10 @@
1-
use core_foundation::{base::TCFType, data::CFData};
21
use futures::stream::StreamExt;
3-
use security_framework::os::macos::code_signing::{Flags, GuestAttributes, SecCode};
42
use std::{error::Error, ffi::CString};
53
use xpc_connection::{Message, MessageError, XpcClient, XpcListener};
64

7-
fn get_code_object_for_client(client: &XpcClient) -> SecCode {
8-
let token_data = CFData::from_buffer(&client.audit_token());
9-
let mut attrs = GuestAttributes::new();
10-
attrs.set_audit_token(token_data.as_concrete_TypeRef());
11-
SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE).unwrap()
12-
}
13-
14-
#[allow(dead_code)]
15-
/// This isn't used because we don't sign our builds, but it's a useful example.
16-
fn validate_client_by_code_signing_requirement(client: &XpcClient) -> bool {
17-
let requirement = "anchor apple".parse().unwrap();
18-
19-
if get_code_object_for_client(client)
20-
.check_validity(Flags::NONE, &requirement)
21-
.is_ok()
22-
{
23-
println!("The client's code signature matches");
24-
return true;
25-
}
26-
27-
println!("The client's code signature doesn't match");
28-
false
29-
}
30-
31-
fn validate_client_by_path(client: &XpcClient) -> bool {
32-
if get_code_object_for_client(client)
33-
.path(Flags::NONE)
34-
.unwrap()
35-
// It'd be better to use to_path
36-
.get_string()
37-
.to_string()
38-
// This is insecure, it's just so the tests can be run from anywhere
39-
.contains("message_round_trip")
40-
{
41-
println!("The client was validated using its path");
42-
return true;
43-
}
44-
45-
println!("The client's path doesn't contain 'message_round_trip'");
46-
false
47-
}
48-
495
async fn handle_client(mut client: XpcClient) {
506
println!("New connection");
517

52-
if !validate_client_by_path(&client) {
53-
return;
54-
}
55-
568
loop {
579
match client.next().await {
5810
None => {
@@ -80,7 +32,16 @@ async fn main() -> Result<(), Box<dyn Error>> {
8032
mach_port_name.to_string_lossy()
8133
);
8234

83-
let mut listener = XpcListener::listen(&mach_port_name);
35+
let mut listener = XpcListener::listen(
36+
&mach_port_name,
37+
// An example requirement. Whe matching on the common name it's
38+
// important to anchor to a trusted authority that you know doesn't
39+
// allow for user-defined common names, otherwise it would be trivial
40+
// to bypass.
41+
// Some("anchor apple and certificate leaf[subject.CN] = \"Apple Development: Steven Joruk (Z84S59N9K4)\""),
42+
None,
43+
None,
44+
);
8445

8546
while let Some(client) = listener.next().await {
8647
tokio::spawn(handle_client(client));

xpc-connection-sys/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![allow(
22
dead_code,
3-
safe_packed_borrows,
3+
unaligned_references,
44
non_upper_case_globals,
55
non_camel_case_types,
66
non_snake_case,

xpc-connection/Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@ keywords = ["xpc", "mac", "macOS"]
1111
categories = ["os", "api-bindings", "concurrency", "encoding"]
1212

1313
[features]
14-
audit_token = []
14+
audit_token = ["core-foundation", "security-framework"]
1515
default = []
1616

1717
[dependencies]
1818
block = "0.1.6"
19-
core-foundation = { version = "0.9", optional = true }
2019
futures = "0.3.4"
2120
xpc-connection-sys = { path = "../xpc-connection-sys", version = "0.1.0" }
2221

22+
# Remove when weak linkage is stable
23+
libc = "0.2.97"
24+
25+
# For the audit_token feature
26+
core-foundation = { version = "0.9", optional = true }
27+
security-framework = { version = "^2.3.1", optional = true }
28+
2329
[dev-dependencies]
2430
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

xpc-connection/src/dlsym.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// This is copied from mio which is MIT licensed, it's to work around the
2+
// linkage feature not yet being stable.
3+
//
4+
// https://github.com/carllerche/mio/blob/master/src/sys/unix/dlsym.rs
5+
6+
use std::marker;
7+
use std::mem;
8+
use std::sync::atomic::{AtomicUsize, Ordering};
9+
10+
macro_rules! dlsym {
11+
(fn $name:ident($($t:ty),*) -> $ret:ty) => (
12+
#[allow(bad_style)]
13+
static $name: crate::dlsym::DlSym<unsafe extern fn($($t),*) -> $ret> =
14+
crate::dlsym::DlSym {
15+
name: concat!(stringify!($name), "\0"),
16+
addr: AtomicUsize::new(0),
17+
_marker: ::std::marker::PhantomData,
18+
};
19+
)
20+
}
21+
22+
pub struct DlSym<F> {
23+
pub name: &'static str,
24+
pub addr: AtomicUsize,
25+
pub _marker: marker::PhantomData<F>,
26+
}
27+
28+
impl<F> DlSym<F> {
29+
pub fn get(&self) -> Option<&F> {
30+
assert_eq!(mem::size_of::<F>(), mem::size_of::<usize>());
31+
unsafe {
32+
if self.addr.load(Ordering::SeqCst) == 0 {
33+
self.addr.store(fetch(self.name), Ordering::SeqCst);
34+
}
35+
if self.addr.load(Ordering::SeqCst) == 1 {
36+
None
37+
} else {
38+
mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr)
39+
}
40+
}
41+
}
42+
}
43+
44+
unsafe fn fetch(name: &str) -> usize {
45+
assert_eq!(name.as_bytes()[name.len() - 1], 0);
46+
match libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize {
47+
0 => 1,
48+
n => n,
49+
}
50+
}

xpc-connection/src/lib.rs

Lines changed: 107 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[allow(
22
dead_code,
3-
safe_packed_borrows,
3+
unaligned_references,
44
non_upper_case_globals,
55
non_camel_case_types,
66
non_snake_case,
@@ -11,20 +11,34 @@ extern crate xpc_connection_sys;
1111
mod message;
1212
pub use message::*;
1313

14+
#[macro_use]
15+
mod dlsym;
16+
1417
use block::ConcreteBlock;
1518
use futures::{
1619
channel::mpsc::{unbounded as unbounded_channel, UnboundedReceiver, UnboundedSender},
1720
Stream,
1821
};
19-
use std::ffi::CStr;
20-
use std::{ffi::c_void, ops::Deref};
21-
use std::{pin::Pin, task::Poll};
22+
use std::{
23+
ffi::{CStr, CString},
24+
ops::Deref,
25+
os::raw::{c_char, c_int},
26+
pin::Pin,
27+
sync::atomic::AtomicUsize,
28+
task::Poll,
29+
};
30+
2231
use xpc_connection_sys::{
23-
xpc_connection_cancel, xpc_connection_create_mach_service, xpc_connection_resume,
24-
xpc_connection_send_message, xpc_connection_set_event_handler, xpc_connection_t, xpc_object_t,
25-
xpc_release, XPC_CONNECTION_MACH_SERVICE_LISTENER, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED,
32+
dispatch_queue_t, xpc_connection_cancel, xpc_connection_create_mach_service,
33+
xpc_connection_resume, xpc_connection_send_message, xpc_connection_set_event_handler,
34+
xpc_connection_t, xpc_object_t, xpc_release, XPC_CONNECTION_MACH_SERVICE_LISTENER,
35+
XPC_CONNECTION_MACH_SERVICE_PRIVILEGED,
2636
};
2737

38+
dlsym! {
39+
fn xpc_connection_set_peer_code_sig(*const c_char) -> c_int
40+
}
41+
2842
// A connection's event handler could still be waiting or running when we want
2943
// to drop a connection. We must cancel the handler and wait for the final
3044
// call to a handler to occur, which is always a message containing an
@@ -87,12 +101,35 @@ impl Stream for XpcListener {
87101

88102
impl XpcListener {
89103
/// The connection must be a listener.
90-
fn from_raw(connection: xpc_connection_t) -> XpcListener {
104+
fn from_raw(connection: xpc_connection_t, requirement: Option<&'static str>) -> XpcListener {
91105
let (sender, receiver) = unbounded_channel();
92106
let sender_clone = sender.clone();
93107

108+
let mut already_validated = false;
109+
110+
if let Some(requirement) = requirement {
111+
if let Some(f) = crate::xpc_connection_set_peer_code_sig.get() {
112+
let requirement = CString::new(requirement).expect("Invalid requirement string");
113+
unsafe {
114+
f(requirement.as_ptr());
115+
}
116+
117+
already_validated = true;
118+
}
119+
}
120+
94121
let block = ConcreteBlock::new(move |event| match xpc_object_to_message(event) {
95-
Message::Client(client) => sender_clone.unbounded_send(client).ok(),
122+
Message::Client(mut client) => {
123+
if already_validated
124+
|| Self::validate_client_using_audit_token(&client, &requirement)
125+
{
126+
sender_clone.unbounded_send(client).ok()
127+
} else {
128+
unsafe { xpc_connection_cancel(client.connection) };
129+
client.event_handler_is_running = false;
130+
None
131+
}
132+
}
96133
_ => None,
97134
});
98135

@@ -113,13 +150,68 @@ impl XpcListener {
113150
}
114151
}
115152

116-
pub fn listen(name: impl AsRef<CStr>) -> Self {
153+
/// If `requirement` is set then clients will have their code signature
154+
/// validated before being available. See Apple's documentation on the
155+
/// language [here](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/RequirementLang/RequirementLang.html).
156+
///
157+
/// On macOS 12 this uses `xpc_connection_set_peer_code_sig`, and if the
158+
/// `audit_token` feature is enabled then this will use a custom
159+
/// implementation on older versions of macOS.
160+
///
161+
/// # Panics
162+
///
163+
/// * If `audit_token` feature is used and the `requirement` isn't parsable
164+
/// as a `SecRequirement`. This will occur during client validation.
165+
pub fn listen(
166+
name: impl AsRef<CStr>,
167+
requirement: Option<&'static str>,
168+
queue: Option<dispatch_queue_t>,
169+
) -> XpcListener {
117170
let name = name.as_ref();
118171
let flags = XPC_CONNECTION_MACH_SERVICE_LISTENER as u64;
119-
let connection = unsafe {
120-
xpc_connection_create_mach_service(name.as_ref().as_ptr(), std::ptr::null_mut(), flags)
172+
let queue = queue.unwrap_or(std::ptr::null_mut());
173+
174+
let connection =
175+
unsafe { xpc_connection_create_mach_service(name.as_ref().as_ptr(), queue, flags) };
176+
177+
Self::from_raw(connection, requirement)
178+
}
179+
180+
#[inline]
181+
#[cfg(feature = "audit_token")]
182+
fn validate_client_using_audit_token(client: &XpcClient, requirement: &Option<&str>) -> bool {
183+
use core_foundation::{base::TCFType, data::CFData};
184+
use security_framework::os::macos::code_signing::{Flags, GuestAttributes, SecCode};
185+
186+
let requirement = match requirement {
187+
Some(r) => r,
188+
None => return true,
121189
};
122-
Self::from_raw(connection)
190+
191+
let requirement = requirement
192+
.parse()
193+
.expect("Unable to parse the requirement");
194+
195+
let token_data = CFData::from_buffer(&client.audit_token());
196+
let mut attrs = GuestAttributes::new();
197+
attrs.set_audit_token(token_data.as_concrete_TypeRef());
198+
199+
if let Ok(code_object) = SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) {
200+
return code_object
201+
.check_validity(Flags::NONE, &requirement)
202+
.is_ok();
203+
}
204+
205+
false
206+
}
207+
208+
#[inline]
209+
#[cfg(not(feature = "audit_token"))]
210+
fn validate_client_using_audit_token(_client: &XpcClient, _requirement: &Option<&str>) -> bool {
211+
// TODO: log an error:
212+
// Attempted to use code signature requirements on an unsupported
213+
// version of macOS without the `audit_token` feature enabled
214+
false
123215
}
124216
}
125217

@@ -223,6 +315,8 @@ impl XpcClient {
223315

224316
#[cfg(feature = "audit_token")]
225317
pub fn audit_token(&self) -> [u8; 32] {
318+
use libc::c_void;
319+
226320
// This is a private API, but it's also required in order to
227321
// authenticate XPC clients without requiring a handshake.
228322
// See https://developer.apple.com/forums/thread/72881 for more info.

0 commit comments

Comments
 (0)