trilemma-dev / SecureXPC

A simple and secure XPC framework for Swift
MIT License
77 stars 15 forks source link

Add support for encoding/decoding XPC connections #12

Closed amomchilov closed 2 years ago

amomchilov commented 2 years ago

Originally posted by @amomchilov in https://github.com/trilemma-dev/SecureXPC/issues/6#issuecomment-960865476:

File descriptors and shared memory are intentionally not supported as they are concepts incompatible with Codable which is designed for indefinite serialization and cross-device transfer. As such choosing Codable for SecureXPC had tradeoffs because XPC does not have the same requirements — it can represent live resources on a single device. However, I decided the tradeoff was worth it because of Swift's excellent built in support for Codable (particularly the compiler automatically generating conformance in many cases). If there was a desire to support file descriptors and shared memory, I can envision ways to add them in the future, although it would mean expanding the API quite considerably.

I don't have an immediate need for passing FDs or shared memory regions in my program yet, but my app does need to pass an xpc_endpoint_t. My main app is sandboxed, so the only way it can use SMBless is by delegating that work to an unsandboxed XPC helper service. Once the installation is done, it opens the XPC connection to the privileged helper service, and passes the endpoint back to the main app.

This is the approach that's recommended in Apple's EvenBetterAuthorizationSample, so I think supporting xpc_endpoint_t is pretty crucial. So at a minimum, at least some level of special-casing needs to happen.

amomchilov commented 2 years ago

Some observations:

The only solution approaches I can think of involve wrapping the xpc_connection_t (or xpc_endpoint_t; I propose we make both codable). Either explicitly by the user, or implicitly via a property wrapper. I don't see any down-side to the latter over the former, so I propose we go with a property wrapper.

I see two ways we can make this work:

  1. "Fake" the implementation of Codable on the property wrapper
    • Make the init(from:) and encode(to:) call fatalError().
      • The message should inform users that these values can only ever be encoded/decoded by the SecureXPC library.
    • Add special-cases to the XPCEncoder/XPCDecoder (well really, their containers), such that they intercept these values and never actually call their init(from:) or encode(to:) methods.
    • I haven't had time to spike this one out yet
  2. Conform to Codable for real
    • Make the init(from:) and encode(to:) cast their Encoder/Decoder param to a concrete XPCEncoderImpl/XPCDecoderImpl. If the cast fails, fatalError() out.
      • Again, the message should inform users that these values can only ever be encoded/decoded by the SecureXPC library.
    • Make a single value container (in either case), and down cast to the XPCSingleValueEncodingContainer/XPCSingleValueDecodingContainer (this is safe to force-cast)
    • Don't special case anything. Let the default implementation of Codable take its course. init(from:) and encode(to:) will be called like normal.
    • Add methods to XPCEncoder/XPCDecoder (their containers) that handle xpc_connection_t and xpc_endpoint_t. Use these to implement init(from:) and encode(to:)
    • I made a proof of concept of this for xpc_connection_t, here: #13