macOS XPC: Can incoming message size be bounded before payload allocation?

Hello,

I'm designing a local macOS helper service (minimum deployment target: macOS 13) that performs one fixed syntax-validation operation. The submitted source is never executed.

The security contract requires:

  • A maximum incoming transport request size of 327,680 bytes.
  • Immediate rejection when the 327,681st byte is encountered, without an allocation proportional to an unbounded sender-controlled payload.
  • No application-level response for an oversized transport request, followed by termination of the affected connection.
  • Complete application responses limited to 65,536 bytes, without treating truncated output as successful.

I've reviewed the public XPC APIs, including xpc_data_get_length, xpc_connection_cancel, and xpc_connection_set_peer_code_signing_requirement.

The remaining uncertainty is whether XPC can enforce a receiver-side message-size limit before materializing the incoming payload.

My questions are:

  1. Does any public XPC API on macOS 13+ provide a documented maximum incoming message size that is enforced before payload materialization or proportional allocation?
  2. If not, is the entire incoming XPC message necessarily materialized before the application event handler receives it?
  3. Is there a supported XPC design for guaranteeing bounded receiver memory with untrusted input, or should this requirement use a different local transport, such as an AF_UNIX SOCK_STREAM socket?
  4. What guarantees does xpc_connection_cancel provide regarding connection termination, pending messages, and prevention of connection reuse after detecting malformed or oversized input?

I'm specifically looking for documented platform guarantees rather than behavior that happens to work in testing.

Thank you!

Answered by DTS Engineer in 908989022

Due to the way that XPC is layered on top of the Mach messaging API, data of this size will be transferred out-of-line. So while the client can send you a large amount of data, the won’t affect the memory footprint of your process. If you want to enforce a limit, get the data size using xpc_data_get_length and fail the request (or the connection) if it’s too large.

I don’t think we document the inline size limit for XPC data values, but it’s gonna be way less than the sorts of values you’re talking about. IIRC it’s something in the 16 KiB range.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Due to the way that XPC is layered on top of the Mach messaging API, data of this size will be transferred out-of-line. So while the client can send you a large amount of data, the won’t affect the memory footprint of your process. If you want to enforce a limit, get the data size using xpc_data_get_length and fail the request (or the connection) if it’s too large.

I don’t think we document the inline size limit for XPC data values, but it’s gonna be way less than the sorts of values you’re talking about. IIRC it’s something in the 16 KiB range.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you, Quinn! That's very helpful.

Just to clarify one security-critical point: does the out-of-line behavior and bounded receiver memory footprint apply specifically to individual XPC data values, or can it also be relied upon for an entire incoming XPC message containing multiple data values or nested containers?

Our requirement is a strict 327,680-byte limit on the complete incoming transport request, with no allocation proportional to an unbounded sender-controlled payload before rejection.

Would you recommend treating xpc_data_get_length as sufficient only when the protocol enforces a single data value in a fixed message structure?

Thanks again!

Thank you for the earlier clarification about Mach out-of-line data.

I would like to clarify one remaining point for a macOS 13+ application.

Our security contract requires rejecting a complete incoming transport message larger than 327,680 bytes, before any unbounded allocation proportional to its size.

Does Apple provide a documented API or receiver pattern to enforce this limit on the entire XPC message, including multiple or nested values, before materialization or proportional allocation?

I understand that xpc_data_get_length provides the length of an individual XPC_TYPE_DATA value, but that does not appear to establish a bound on the complete XPC message.

If this guarantee is not available through public XPC APIs, could you confirm that and indicate what allocation and connection-termination guarantees a receiver can rely on?

Thank you for your help.

Well, that was interesting.

I did a bunch of research on this topic yesterday, and the conclusion was that there isn’t a way to stop a malicious client sending you a message that runs you out of memory O-:

One way around this is to create an XPC service that acts as a proxy to your main service. The XPC service checks the validity of incoming messages before passing them along to your main service. And the main service uses a peer requirement to ensure that it only accepts messages from the XPC service.

The mechanics of how you’d apply this approach is gonna depend on your packaging. Is your named XPC endpoint run by an XPC service or a launchd daemon or agent? And if it’s the second case, is it invoked solely by apps that you create? Or do you expect it to be invoked by other third-party code?


Oh, and we’d welcome an enhancement request for a way to limit the message size. It’s not clear exactly what’s feasible in this space, but feel free to put your own requirements into the bug and the XPC team can consider those in their future plans.

If you do file an ER, please post your bug number, just for the record.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"