Skip to content

Commit

Permalink
Support secure client validation by exposing the audit token (#32)
Browse files Browse the repository at this point in the history
* Support validating clients
* Fix deprecated bindgen warnings (whitelist/allowlist)
  • Loading branch information
Steven Joruk authored Jun 7, 2021
1 parent cfbdc8d commit 7158124
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
override: true

- name: Build and run the echo server
run: cd examples/echo-server && make && sudo make install
run: cd examples/echo-server && make && make install

- name: Run cargo test
uses: actions-rs/cargo@v1
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ serialized property lists for Mac OS. Read more at the

[apple developer]: https://developer.apple.com/documentation/xpc

## Features

* `audit_token` enables retrieving the client's audit token. This requires
using a private API, but it's the simplest way to securely validate clients.
See [CVE-2020-0984](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0984)
and [this useful blog post](https://theevilbit.github.io/posts/secure_coding_xpc_part2/).
The [example echo server](examples/echo-server/src/main.rs) makes use of this.

## Supported Data Types

* `array`: `Vec<Message>`
Expand Down
2 changes: 0 additions & 2 deletions examples/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ authors = ["Steven Joruk <[email protected]>"]
edition = "2018"
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures = { version = "0.3" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Expand Down
5 changes: 4 additions & 1 deletion examples/echo-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ 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" }
xpc-connection = { path = "../../xpc-connection", features = ["audit_token"] }
22 changes: 10 additions & 12 deletions examples/echo-server/Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
CFLAGS+=-O2
all: ../../target/debug/echo-server

all: ../../target/release/echo-server
../../target/debug/echo-server:
cargo build

../../target/release/echo-server: src/main.rs
cargo build --release

install: ../../target/release/echo-server
cp ../../target/release/echo-server /Library/PrivilegedHelperTools/com.example.echo
cp com.example.echo.plist /Library/LaunchDaemons/
launchctl load /Library/LaunchDaemons/com.example.echo.plist
install: ../../target/debug/echo-server
sudo cp ../../target/debug/echo-server /Library/PrivilegedHelperTools/com.example.echo
sudo cp com.example.echo.plist /Library/LaunchDaemons/
sudo launchctl load /Library/LaunchDaemons/com.example.echo.plist

uninstall:
launchctl unload /Library/LaunchDaemons/com.example.echo.plist
rm /Library/PrivilegedHelperTools/com.example.echo
rm /Library/LaunchDaemons/com.example.echo.plist
sudo launchctl unload /Library/LaunchDaemons/com.example.echo.plist
sudo rm /Library/PrivilegedHelperTools/com.example.echo
sudo rm /Library/LaunchDaemons/com.example.echo.plist
8 changes: 5 additions & 3 deletions examples/echo-server/com.example.echo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
<true/>
</dict>
<key>Program</key>
<string>/Library/PrivilegedHelperTools/com.example.echo</string>
<key>StandardOutPath</key>
<string>/var/log/com.example.echo.log</string>
<string>/Library/PrivilegedHelperTools/com.example.echo</string>
<key>StandardOutPath</key>
<string>/var/log/com.example.echo.log</string>
<key>StandardErrorPath</key>
<string>/var/log/com.example.echo.log</string>
</dict>
</plist>
48 changes: 48 additions & 0 deletions examples/echo-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
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
12 changes: 6 additions & 6 deletions xpc-connection-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ fn main() {
bindgen::Builder::default()
.header("wrapper.h")
.rustfmt_bindings(true)
.whitelist_function("dispatch_queue_create")
.whitelist_function("xpc.*")
.whitelist_var("xpc.*")
.whitelist_var("_xpc.*")
.whitelist_var("XPC.*")
.whitelist_type("uuid_t")
.allowlist_function("dispatch_queue_create")
.allowlist_function("xpc.*")
.allowlist_var("xpc.*")
.allowlist_var("_xpc.*")
.allowlist_var("XPC.*")
.allowlist_type("uuid_t")
.generate()
.expect("Unable to generate bindings")
.write_to_file(out_path.join("bindings.rs"))
Expand Down
7 changes: 6 additions & 1 deletion xpc-connection/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ repository = "https://github.com/dfrankland/xpc-connection-rs"
keywords = ["xpc", "mac", "macOS"]
categories = ["os", "api-bindings", "concurrency", "encoding"]

[features]
audit_token = []
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" }
xpc-connection-sys = { path = "../xpc-connection-sys", version = "0.1.0" }

[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
32 changes: 25 additions & 7 deletions xpc-connection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,16 @@
extern crate xpc_connection_sys;

mod message;

use std::ffi::CStr;
use std::ops::Deref;
use std::{pin::Pin, task::Poll};
pub use message::*;

use block::ConcreteBlock;

use futures::{
channel::mpsc::{unbounded as unbounded_channel, UnboundedReceiver, UnboundedSender},
Stream,
};

pub use self::message::*;
use std::ffi::CStr;
use std::{ffi::c_void, ops::Deref};
use std::{pin::Pin, 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,
Expand Down Expand Up @@ -211,6 +208,27 @@ impl XpcClient {
xpc_release(xpc_object);
}
}

#[cfg(feature = "audit_token")]
pub fn audit_token(&self) -> [u8; 32] {
// 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.
extern "C" {
fn xpc_connection_get_audit_token(con: xpc_connection_t, token: *mut c_void);
}

let mut token_buffer: [u8; 32] = [0; 32];

unsafe {
xpc_connection_get_audit_token(
self.connection as xpc_connection_t,
token_buffer.as_mut_ptr() as _,
)
}

token_buffer
}
}

#[cfg(test)]
Expand Down

0 comments on commit 7158124

Please sign in to comment.