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; }
void do_service_mitm(mach_port_t real_service_port, mach_port_t replacer_portset) { mach_msg_size_t max_request_size = 0x10000; mach_msg_header_t* request = malloc(max_request_size); for(;;) { memset(request, 0, max_request_size); kern_return_t err = mach_msg(request, MACH_RCV_MSG | MACH_RCV_LARGE, // leave larger messages in the queue 0, max_request_size, replacer_portset, 0, 0); if (err == MACH_RCV_TOO_LARGE) { // bump up the buffer size mach_msg_size_t new_size = request->msgh_size + 0x1000; request = realloc(request, new_size); // try to receive again continue; } if (err != KERN_SUCCESS) { printf("error receiving on port set: %s\n", mach_error_string(err)); exit(EXIT_FAILURE); } got_replaced_with = request->msgh_local_port; printf("got a request, fixing it up...\n"); // fix up the message such that it can be forwarded: // get the rights we were sent for each port the header mach_port_right_t remote = MACH_MSGH_BITS_REMOTE(request->msgh_bits); mach_port_right_t voucher = MACH_MSGH_BITS_VOUCHER(request->msgh_bits); // fixup the header ports: // swap the remote port we received into the local port we'll forward // this means we're only mitm'ing in one direction - we could also // intercept these replies if necessary request->msgh_local_port = request->msgh_remote_port; request->msgh_remote_port = real_service_port; // voucher port stays the same int is_complex = MACH_MSGH_BITS_IS_COMPLEX(request->msgh_bits); // (remote, local, voucher) request->msgh_bits = MACH_MSGH_BITS_SET_PORTS(MACH_MSG_TYPE_COPY_SEND, right_fixup(remote), right_fixup(voucher)); if (is_complex) { request->msgh_bits |= MACH_MSGH_BITS_COMPLEX; // if it's complex we also need to fixup all the descriptors... mach_msg_body_t* body = (mach_msg_body_t*)(request+1); mach_msg_type_descriptor_t* desc = (mach_msg_type_descriptor_t*)(body+1); for (mach_msg_size_t i = 0; i < body->msgh_descriptor_count; i++) { switch (desc->type) { case MACH_MSG_PORT_DESCRIPTOR: { mach_msg_port_descriptor_t* port_desc = (mach_msg_port_descriptor_t*)desc; inspect_port(port_desc->name); port_desc->disposition = right_fixup(port_desc->disposition); desc = (mach_msg_type_descriptor_t*)(port_desc+1); break; } case MACH_MSG_OOL_VOLATILE_DESCRIPTOR: case MACH_MSG_OOL_DESCRIPTOR: { mach_msg_ool_descriptor_t* ool_desc = (mach_msg_ool_descriptor_t*)desc; // make sure that deallocate is true; we don't want to keep this memory: ool_desc->deallocate = 1; desc = (mach_msg_type_descriptor_t*)(ool_desc+1); break; } case MACH_MSG_OOL_PORTS_DESCRIPTOR: { mach_msg_ool_ports_descriptor_t* ool_ports_desc = (mach_msg_ool_ports_descriptor_t*)desc; // make sure that deallocate is true: ool_ports_desc->deallocate = 1; ool_ports_desc->disposition = right_fixup(ool_ports_desc->disposition); desc = (mach_msg_type_descriptor_t*)(ool_ports_desc+1); break; } } } } printf("fixed up request, forwarding it\n"); // forward the message: err = mach_msg(request, MACH_SEND_MSG|MACH_MSG_OPTION_NONE, request->msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (err != KERN_SUCCESS) { printf("error forwarding service message: %s\n", mach_error_string(err)); exit(EXIT_FAILURE); } } }
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; }