Skip to content
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

Support xpc_connection_set_peer_sig on macOS 12 #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,21 @@ serialized property lists for Mac OS. Read more at the
## Supported Data Types

* `array`: `Vec<Message>`
* `bool`: `bool`
* `data`: `Vec<u8>`
* `date`: `SystemTime`
* `dictionary`: `HashMap<String, Message>`
* `double`: `f64`
* `error`: `MessageError`
* `fd`: `RawFd`
* `int64`: `i64`
* `string`: `String`
* `uint64`: `u64`
* `uuid`: `Vec<u8>`
* `null`

## Yet to Be Supported Data Types

* `activity`
* `bool`
* `date`
* `double`
* `endpoint`
* `null`
* `shmem`
3 changes: 0 additions & 3 deletions examples/echo-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
core-foundation = "0.9"
futures = { version = "0.3" }
# Support for SecCode was added in 2.3.1
security-framework = "^2.3.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
xpc-connection = { path = "../../xpc-connection", features = ["audit_token"] }
59 changes: 10 additions & 49 deletions examples/echo-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,10 @@
use core_foundation::{base::TCFType, data::CFData};
use futures::stream::StreamExt;
use security_framework::os::macos::code_signing::{Flags, GuestAttributes, SecCode};
use std::{error::Error, ffi::CString};
use xpc_connection::{Message, MessageError, XpcClient, XpcListener};

fn get_code_object_for_client(client: &XpcClient) -> SecCode {
let token_data = CFData::from_buffer(&client.audit_token());
let mut attrs = GuestAttributes::new();
attrs.set_audit_token(token_data.as_concrete_TypeRef());
SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE).unwrap()
}

#[allow(dead_code)]
/// This isn't used because we don't sign our builds, but it's a useful example.
fn validate_client_by_code_signing_requirement(client: &XpcClient) -> bool {
let requirement = "anchor apple".parse().unwrap();

if get_code_object_for_client(client)
.check_validity(Flags::NONE, &requirement)
.is_ok()
{
println!("The client's code signature matches");
return true;
}

println!("The client's code signature doesn't match");
false
}

fn validate_client_by_path(client: &XpcClient) -> bool {
if get_code_object_for_client(client)
.path(Flags::NONE)
.unwrap()
// It'd be better to use to_path
.get_string()
.to_string()
// This is insecure, it's just so the tests can be run from anywhere
.contains("message_round_trip")
{
println!("The client was validated using its path");
return true;
}

println!("The client's path doesn't contain 'message_round_trip'");
false
}

async fn handle_client(mut client: XpcClient) {
println!("New connection");

if !validate_client_by_path(&client) {
return;
}

loop {
match client.next().await {
None => {
Expand Down Expand Up @@ -80,7 +32,16 @@ async fn main() -> Result<(), Box<dyn Error>> {
mach_port_name.to_string_lossy()
);

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

while let Some(client) = listener.next().await {
tokio::spawn(handle_client(client));
Expand Down
2 changes: 1 addition & 1 deletion xpc-connection-sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(
dead_code,
safe_packed_borrows,
unaligned_references,

Check failure on line 3 in xpc-connection-sys/src/lib.rs

View workflow job for this annotation

GitHub Actions / Lints

lint `unaligned_references` has been removed: converted into hard error, see issue #82523 <https://github.com/rust-lang/rust/issues/82523> for more information
non_upper_case_globals,
non_camel_case_types,
non_snake_case,
Expand Down
10 changes: 8 additions & 2 deletions xpc-connection/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ keywords = ["xpc", "mac", "macOS"]
categories = ["os", "api-bindings", "concurrency", "encoding"]

[features]
audit_token = []
audit_token = ["core-foundation", "security-framework"]
default = []

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

# Remove when weak linkage is stable
libc = "0.2.97"

# For the audit_token feature
core-foundation = { version = "0.9", optional = true }
security-framework = { version = "^2.3.1", optional = true }

[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
50 changes: 50 additions & 0 deletions xpc-connection/src/dlsym.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// This is copied from mio which is MIT licensed, it's to work around the
// linkage feature not yet being stable.
//
// https://github.com/carllerche/mio/blob/master/src/sys/unix/dlsym.rs

use std::marker;
use std::mem;
use std::sync::atomic::{AtomicUsize, Ordering};

macro_rules! dlsym {
(fn $name:ident($($t:ty),*) -> $ret:ty) => (
#[allow(bad_style)]
static $name: crate::dlsym::DlSym<unsafe extern fn($($t),*) -> $ret> =
crate::dlsym::DlSym {
name: concat!(stringify!($name), "\0"),
addr: AtomicUsize::new(0),
_marker: ::std::marker::PhantomData,
};
)
}

pub struct DlSym<F> {
pub name: &'static str,
pub addr: AtomicUsize,
pub _marker: marker::PhantomData<F>,
}

impl<F> DlSym<F> {
pub fn get(&self) -> Option<&F> {
assert_eq!(mem::size_of::<F>(), mem::size_of::<usize>());
unsafe {
if self.addr.load(Ordering::SeqCst) == 0 {
self.addr.store(fetch(self.name), Ordering::SeqCst);
}
if self.addr.load(Ordering::SeqCst) == 1 {
None
} else {
mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr)
}
}
}
}

unsafe fn fetch(name: &str) -> usize {
assert_eq!(name.as_bytes()[name.len() - 1], 0);
match libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize {
0 => 1,
n => n,
}
}
132 changes: 119 additions & 13 deletions xpc-connection/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[allow(
dead_code,
safe_packed_borrows,
unaligned_references,
non_upper_case_globals,
non_camel_case_types,
non_snake_case,
Expand All @@ -11,20 +11,34 @@ extern crate xpc_connection_sys;
mod message;
pub use message::*;

#[macro_use]
mod dlsym;

use block::ConcreteBlock;
use futures::{
channel::mpsc::{unbounded as unbounded_channel, UnboundedReceiver, UnboundedSender},
Stream,
};
use std::ffi::CStr;
use std::{ffi::c_void, ops::Deref};
use std::{pin::Pin, task::Poll};
use std::{
ffi::{CStr, CString},
ops::Deref,
os::raw::{c_char, c_int},
pin::Pin,
sync::atomic::AtomicUsize,
task::Poll,
};

use xpc_connection_sys::{
xpc_connection_cancel, xpc_connection_create_mach_service, xpc_connection_resume,
xpc_connection_send_message, xpc_connection_set_event_handler, xpc_connection_t, xpc_object_t,
xpc_release, XPC_CONNECTION_MACH_SERVICE_LISTENER, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED,
dispatch_queue_t, xpc_connection_cancel, xpc_connection_create_mach_service,
xpc_connection_resume, xpc_connection_send_message, xpc_connection_set_event_handler,
xpc_connection_t, xpc_object_t, xpc_release, XPC_CONNECTION_MACH_SERVICE_LISTENER,
XPC_CONNECTION_MACH_SERVICE_PRIVILEGED,
};

dlsym! {
fn xpc_connection_set_peer_code_sig(*const c_char) -> c_int
}

// A connection's event handler could still be waiting or running when we want
// to drop a connection. We must cancel the handler and wait for the final
// call to a handler to occur, which is always a message containing an
Expand Down Expand Up @@ -59,6 +73,12 @@ pub struct XpcListener {
sender: UnboundedSender<XpcClient>,
}

impl PartialEq for XpcListener {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.connection, other.connection)
}
}

impl Drop for XpcListener {
fn drop(&mut self) {
unsafe {
Expand All @@ -81,12 +101,35 @@ impl Stream for XpcListener {

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

let mut already_validated = false;

if let Some(requirement) = requirement {
if let Some(f) = crate::xpc_connection_set_peer_code_sig.get() {
let requirement = CString::new(requirement).expect("Invalid requirement string");
unsafe {
f(requirement.as_ptr());
}

already_validated = true;
}
}

let block = ConcreteBlock::new(move |event| match xpc_object_to_message(event) {
Message::Client(client) => sender_clone.unbounded_send(client).ok(),
Message::Client(mut client) => {
if already_validated
|| Self::validate_client_using_audit_token(&client, &requirement)
{
sender_clone.unbounded_send(client).ok()
} else {
unsafe { xpc_connection_cancel(client.connection) };
client.event_handler_is_running = false;
None
}
}
_ => None,
});

Expand All @@ -107,13 +150,68 @@ impl XpcListener {
}
}

pub fn listen(name: impl AsRef<CStr>) -> Self {
/// If `requirement` is set then clients will have their code signature
/// validated before being available. See Apple's documentation on the
/// language [here](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/RequirementLang/RequirementLang.html).
///
/// On macOS 12 this uses `xpc_connection_set_peer_code_sig`, and if the
/// `audit_token` feature is enabled then this will use a custom
/// implementation on older versions of macOS.
///
/// # Panics
///
/// * If `audit_token` feature is used and the `requirement` isn't parsable
/// as a `SecRequirement`. This will occur during client validation.
pub fn listen(
name: impl AsRef<CStr>,
requirement: Option<&'static str>,
queue: Option<dispatch_queue_t>,
) -> XpcListener {
let name = name.as_ref();
let flags = XPC_CONNECTION_MACH_SERVICE_LISTENER as u64;
let connection = unsafe {
xpc_connection_create_mach_service(name.as_ref().as_ptr(), std::ptr::null_mut(), flags)
let queue = queue.unwrap_or(std::ptr::null_mut());

let connection =
unsafe { xpc_connection_create_mach_service(name.as_ref().as_ptr(), queue, flags) };

Self::from_raw(connection, requirement)
}

#[inline]
#[cfg(feature = "audit_token")]
fn validate_client_using_audit_token(client: &XpcClient, requirement: &Option<&str>) -> bool {
use core_foundation::{base::TCFType, data::CFData};
use security_framework::os::macos::code_signing::{Flags, GuestAttributes, SecCode};

let requirement = match requirement {
Some(r) => r,
None => return true,
};
Self::from_raw(connection)

let requirement = requirement
.parse()
.expect("Unable to parse the requirement");

let token_data = CFData::from_buffer(&client.audit_token());
let mut attrs = GuestAttributes::new();
attrs.set_audit_token(token_data.as_concrete_TypeRef());

if let Ok(code_object) = SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) {
return code_object
.check_validity(Flags::NONE, &requirement)
.is_ok();
}

false
}

#[inline]
#[cfg(not(feature = "audit_token"))]
fn validate_client_using_audit_token(_client: &XpcClient, _requirement: &Option<&str>) -> bool {
// TODO: log an error:
// Attempted to use code signature requirements on an unsupported
// version of macOS without the `audit_token` feature enabled
false
}
}

Expand All @@ -127,6 +225,12 @@ pub struct XpcClient {

unsafe impl Send for XpcClient {}

impl PartialEq for XpcClient {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.connection, other.connection)
}
}

impl Drop for XpcClient {
fn drop(&mut self) {
if self.event_handler_is_running {
Expand Down Expand Up @@ -211,6 +315,8 @@ impl XpcClient {

#[cfg(feature = "audit_token")]
pub fn audit_token(&self) -> [u8; 32] {
use libc::c_void;

// This is a private API, but it's also required in order to
// authenticate XPC clients without requiring a handshake.
// See https://developer.apple.com/forums/thread/72881 for more info.
Expand Down
Loading
Loading