+ u_int32_t tlv_size = (sizeof(u_int8_t) + sizeof(u_int32_t) + sizeof(necp_policy_id));
+ u_int32_t response_size = 0;
+ u_int8_t *response = NULL;
+ int num_policies = 0;
+ int cur_policy_index = 0;
+ int error = 0;
+ struct necp_session_policy *policy;
+
+ LIST_FOREACH(policy, &session->policies, chain) {
+ if (!policy->pending_deletion) {
+ num_policies++;
+ }
+ }
+
+ if (num_policies > NECP_MAX_POLICY_LIST_COUNT) {
+ NECPLOG(LOG_ERR, "necp_session_list_all size too large to copy (%u policies)", num_policies);
+ error = EINVAL;
+ goto done;
+ }
+
+ response_size = num_policies * tlv_size;
+ if (uap->out_buffer_length < response_size || uap->out_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_list_all buffer not large enough (%u < %u)", uap->out_buffer_length, response_size);
+ error = EINVAL;
+ goto done;
+ }
+
+ // Create a response with one Policy ID TLV for each policy
+ MALLOC(response, u_int8_t *, response_size, M_NECP, M_WAITOK | M_ZERO);
+ if (response == NULL) {
+ error = ENOMEM;
+ goto done;
+ }
+
+ u_int8_t *cursor = response;
+ LIST_FOREACH(policy, &session->policies, chain) {
+ if (!policy->pending_deletion && cur_policy_index < num_policies) {
+ cursor = necp_buffer_write_tlv(cursor, NECP_TLV_POLICY_ID, sizeof(u_int32_t), &policy->local_id, response, response_size);
+ cur_policy_index++;
+ }
+ }
+
+ error = copyout(response, uap->out_buffer, response_size);
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_list_all TLV copyout error (%d)", error);
+ goto done;
+ }
+
+done:
+ if (response != NULL) {
+ FREE(response, M_NECP);
+ response = NULL;
+ }
+ *retval = error;
+
+ return error;
+}
+
+
+static int
+necp_session_delete_all(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+#pragma unused(uap)
+ necp_policy_mark_all_for_deletion(session);
+ *retval = 0;
+ return 0;
+}
+
+static int
+necp_session_set_session_priority(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+ int error = 0;
+ struct necp_session_policy *policy = NULL;
+ struct necp_session_policy *temp_policy = NULL;
+
+ if (uap->in_buffer_length < sizeof(necp_session_priority) || uap->in_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_set_session_priority invalid input (%zu)", uap->in_buffer_length);
+ error = EINVAL;
+ goto done;
+ }
+
+ necp_session_priority requested_session_priority = 0;
+ error = copyin(uap->in_buffer, &requested_session_priority, sizeof(requested_session_priority));
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_set_session_priority priority copyin error (%d)", error);
+ goto done;
+ }
+
+ // Enforce special session priorities with entitlements
+ if (requested_session_priority == NECP_SESSION_PRIORITY_CONTROL ||
+ requested_session_priority == NECP_SESSION_PRIORITY_PRIVILEGED_TUNNEL) {
+ errno_t cred_result = priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NECP_POLICIES, 0);
+ if (cred_result != 0) {
+ NECPLOG(LOG_ERR, "Session does not hold necessary entitlement to claim priority level %d", requested_session_priority);
+ error = EPERM;
+ goto done;
+ }
+ }
+
+ if (session->session_priority != requested_session_priority) {
+ session->session_priority = requested_session_priority;
+ session->session_order = necp_allocate_new_session_order(session->session_priority, session->control_unit);
+ session->dirty = TRUE;
+
+ // Mark all policies as needing updates
+ LIST_FOREACH_SAFE(policy, &session->policies, chain, temp_policy) {
+ policy->pending_update = TRUE;
+ }
+ }
+
+done:
+ *retval = error;
+ return error;
+}
+
+static int
+necp_session_lock_to_process(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+#pragma unused(uap)
+ session->proc_locked = TRUE;
+ *retval = 0;
+ return 0;
+}
+
+static int
+necp_session_register_service(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+ int error = 0;
+ struct necp_service_registration *new_service = NULL;
+
+ if (uap->in_buffer_length < sizeof(uuid_t) || uap->in_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_register_service invalid input (%zu)", uap->in_buffer_length);
+ error = EINVAL;
+ goto done;
+ }
+
+ uuid_t service_uuid;
+ error = copyin(uap->in_buffer, service_uuid, sizeof(service_uuid));
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_register_service uuid copyin error (%d)", error);
+ goto done;
+ }
+
+ MALLOC(new_service, struct necp_service_registration *, sizeof(*new_service), M_NECP, M_WAITOK | M_ZERO);
+ if (new_service == NULL) {
+ NECPLOG0(LOG_ERR, "Failed to allocate service registration");
+ error = ENOMEM;
+ goto done;
+ }
+
+ lck_rw_lock_exclusive(&necp_kernel_policy_lock);
+ new_service->service_id = necp_create_uuid_service_id_mapping(service_uuid);
+ LIST_INSERT_HEAD(&session->services, new_service, session_chain);
+ LIST_INSERT_HEAD(&necp_registered_service_list, new_service, kernel_chain);
+ lck_rw_done(&necp_kernel_policy_lock);
+
+done:
+ *retval = error;
+ return error;
+}
+
+static int
+necp_session_unregister_service(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+ int error = 0;
+ struct necp_service_registration *service = NULL;
+ struct necp_service_registration *temp_service = NULL;
+ struct necp_uuid_id_mapping *mapping = NULL;
+
+ if (uap->in_buffer_length < sizeof(uuid_t) || uap->in_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_unregister_service invalid input (%zu)", uap->in_buffer_length);
+ error = EINVAL;
+ goto done;
+ }
+
+ uuid_t service_uuid;
+ error = copyin(uap->in_buffer, service_uuid, sizeof(service_uuid));
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_unregister_service uuid copyin error (%d)", error);
+ goto done;
+ }
+
+ // Remove all matching services for this session
+ lck_rw_lock_exclusive(&necp_kernel_policy_lock);
+ mapping = necp_uuid_lookup_service_id_locked(service_uuid);
+ if (mapping != NULL) {
+ LIST_FOREACH_SAFE(service, &session->services, session_chain, temp_service) {
+ if (service->service_id == mapping->id) {
+ LIST_REMOVE(service, session_chain);
+ LIST_REMOVE(service, kernel_chain);
+ FREE(service, M_NECP);
+ }
+ }
+ necp_remove_uuid_service_id_mapping(service_uuid);
+ }
+ lck_rw_done(&necp_kernel_policy_lock);
+
+done:
+ *retval = error;
+ return error;
+}
+
+static int
+necp_session_dump_all(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+#pragma unused(session)
+ int error = 0;
+
+ if (uap->out_buffer_length == 0 || uap->out_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_dump_all invalid output buffer (%zu)", uap->out_buffer_length);
+ error = EINVAL;
+ goto done;
+ }
+
+ error = necp_handle_policy_dump_all(uap->out_buffer, uap->out_buffer_length);
+done:
+ *retval = error;
+ return error;
+}
+
+int
+necp_session_action(struct proc *p, struct necp_session_action_args *uap, int *retval)
+{
+#pragma unused(p)
+ int error = 0;
+ int return_value = 0;
+ struct necp_session *session = NULL;
+ error = necp_session_find_from_fd(uap->necp_fd, &session);
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_action find fd error (%d)", error);
+ return error;
+ }
+
+ NECP_SESSION_LOCK(session);
+
+ if (session->proc_locked) {
+ // Verify that the calling process is allowed to do actions
+ uuid_t proc_uuid;
+ proc_getexecutableuuid(current_proc(), proc_uuid, sizeof(proc_uuid));
+ if (uuid_compare(proc_uuid, session->proc_uuid) != 0) {
+ error = EPERM;
+ goto done;
+ }
+ } else {
+ // If not locked, update the proc_uuid and proc_pid of the session
+ proc_getexecutableuuid(current_proc(), session->proc_uuid, sizeof(session->proc_uuid));
+ session->proc_pid = proc_pid(current_proc());
+ }
+
+ u_int32_t action = uap->action;
+ switch (action) {
+ case NECP_SESSION_ACTION_POLICY_ADD: {
+ return_value = necp_session_add_policy(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_POLICY_GET: {
+ return_value = necp_session_get_policy(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_POLICY_DELETE: {
+ return_value = necp_session_delete_policy(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_POLICY_APPLY_ALL: {
+ return_value = necp_session_apply_all(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_POLICY_LIST_ALL: {
+ return_value = necp_session_list_all(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_POLICY_DELETE_ALL: {
+ return_value = necp_session_delete_all(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_SET_SESSION_PRIORITY: {
+ return_value = necp_session_set_session_priority(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_LOCK_SESSION_TO_PROC: {
+ return_value = necp_session_lock_to_process(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_REGISTER_SERVICE: {
+ return_value = necp_session_register_service(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_UNREGISTER_SERVICE: {
+ return_value = necp_session_unregister_service(session, uap, retval);
+ break;
+ }
+ case NECP_SESSION_ACTION_POLICY_DUMP_ALL: {
+ return_value = necp_session_dump_all(session, uap, retval);
+ break;
+ }
+ default: {
+ NECPLOG(LOG_ERR, "necp_session_action unknown action (%u)", action);
+ return_value = EINVAL;
+ break;
+ }
+ }
+
+done:
+ NECP_SESSION_UNLOCK(session);
+ file_drop(uap->necp_fd);
+
+ return return_value;
+}
+
+struct necp_resolver_key_state {
+ const struct ccdigest_info *digest_info;
+ uint8_t key[CCSHA256_OUTPUT_SIZE];
+};
+static struct necp_resolver_key_state s_necp_resolver_key_state;
+
+static void
+necp_generate_resolver_key(void)
+{
+ s_necp_resolver_key_state.digest_info = ccsha256_di();
+ cc_rand_generate(s_necp_resolver_key_state.key, sizeof(s_necp_resolver_key_state.key));
+}
+
+static void
+necp_sign_update_context(const struct ccdigest_info *di,
+ cchmac_ctx_t ctx,
+ uuid_t client_id,
+ u_int8_t *query,
+ u_int32_t query_length,
+ u_int8_t *answer,
+ u_int32_t answer_length)
+{
+ const uint8_t context[32] = {[0 ... 31] = 0x20}; // 0x20 repeated 32 times
+ const char *context_string = "NECP Resolver Binder";
+ uint8_t separator = 0;
+ cchmac_update(di, ctx, sizeof(context), context);
+ cchmac_update(di, ctx, strlen(context_string), context_string);
+ cchmac_update(di, ctx, sizeof(separator), &separator);
+ cchmac_update(di, ctx, sizeof(uuid_t), client_id);
+ cchmac_update(di, ctx, sizeof(query_length), &query_length);
+ cchmac_update(di, ctx, query_length, query);
+ cchmac_update(di, ctx, sizeof(answer_length), &answer_length);
+ cchmac_update(di, ctx, answer_length, answer);
+}
+
+int
+necp_sign_resolver_answer(uuid_t client_id, u_int8_t *query, u_int32_t query_length,
+ u_int8_t *answer, u_int32_t answer_length,
+ u_int8_t *tag, u_int32_t *out_tag_length)
+{
+ if (s_necp_resolver_key_state.digest_info == NULL) {
+ return EINVAL;
+ }
+
+ if (query == NULL ||
+ query_length == 0 ||
+ answer == NULL ||
+ answer_length == 0 ||
+ tag == NULL ||
+ out_tag_length == NULL) {
+ return EINVAL;
+ }
+
+ size_t required_tag_length = s_necp_resolver_key_state.digest_info->output_size;
+ if (*out_tag_length < required_tag_length) {
+ return ERANGE;
+ }
+
+ *out_tag_length = required_tag_length;
+
+ cchmac_ctx_decl(s_necp_resolver_key_state.digest_info->state_size,
+ s_necp_resolver_key_state.digest_info->block_size, ctx);
+ cchmac_init(s_necp_resolver_key_state.digest_info, ctx,
+ sizeof(s_necp_resolver_key_state.key),
+ s_necp_resolver_key_state.key);
+ necp_sign_update_context(s_necp_resolver_key_state.digest_info,
+ ctx, client_id, query, query_length,
+ answer, answer_length);
+ cchmac_final(s_necp_resolver_key_state.digest_info, ctx, tag);
+
+ return 0;
+}
+
+bool
+necp_validate_resolver_answer(uuid_t client_id, u_int8_t *query, u_int32_t query_length,
+ u_int8_t *answer, u_int32_t answer_length,
+ u_int8_t *tag, u_int32_t tag_length)
+{
+ if (s_necp_resolver_key_state.digest_info == NULL) {
+ return false;
+ }
+
+ if (query == NULL ||
+ query_length == 0 ||
+ answer == NULL ||
+ answer_length == 0 ||
+ tag == NULL ||
+ tag_length == 0) {
+ return false;
+ }
+
+ size_t required_tag_length = s_necp_resolver_key_state.digest_info->output_size;
+ if (tag_length != required_tag_length) {
+ return false;
+ }
+
+ uint8_t actual_tag[required_tag_length];
+
+ cchmac_ctx_decl(s_necp_resolver_key_state.digest_info->state_size,
+ s_necp_resolver_key_state.digest_info->block_size, ctx);
+ cchmac_init(s_necp_resolver_key_state.digest_info, ctx,
+ sizeof(s_necp_resolver_key_state.key),
+ s_necp_resolver_key_state.key);
+ necp_sign_update_context(s_necp_resolver_key_state.digest_info,
+ ctx, client_id, query, query_length,
+ answer, answer_length);
+ cchmac_final(s_necp_resolver_key_state.digest_info, ctx, actual_tag);
+
+ return cc_cmp_safe(s_necp_resolver_key_state.digest_info->output_size, tag, actual_tag) == 0;
+}
+
+errno_t
+necp_init(void)
+{
+ errno_t result = 0;
+
+ necp_kernel_policy_grp_attr = lck_grp_attr_alloc_init();
+ if (necp_kernel_policy_grp_attr == NULL) {
+ NECPLOG0(LOG_ERR, "lck_grp_attr_alloc_init failed");
+ result = ENOMEM;
+ goto done;
+ }
+
+ necp_kernel_policy_mtx_grp = lck_grp_alloc_init(NECP_CONTROL_NAME, necp_kernel_policy_grp_attr);
+ if (necp_kernel_policy_mtx_grp == NULL) {
+ NECPLOG0(LOG_ERR, "lck_grp_alloc_init failed");
+ result = ENOMEM;
+ goto done;
+ }
+
+ necp_kernel_policy_mtx_attr = lck_attr_alloc_init();
+ if (necp_kernel_policy_mtx_attr == NULL) {
+ NECPLOG0(LOG_ERR, "lck_attr_alloc_init failed");
+ result = ENOMEM;
+ goto done;
+ }
+
+ lck_rw_init(&necp_kernel_policy_lock, necp_kernel_policy_mtx_grp, necp_kernel_policy_mtx_attr);
+
+ necp_route_rule_grp_attr = lck_grp_attr_alloc_init();
+ if (necp_route_rule_grp_attr == NULL) {
+ NECPLOG0(LOG_ERR, "lck_grp_attr_alloc_init failed");
+ result = ENOMEM;
+ goto done;
+ }
+
+ necp_route_rule_mtx_grp = lck_grp_alloc_init("necp_route_rule", necp_route_rule_grp_attr);
+ if (necp_route_rule_mtx_grp == NULL) {
+ NECPLOG0(LOG_ERR, "lck_grp_alloc_init failed");
+ result = ENOMEM;
+ goto done;
+ }
+
+ necp_route_rule_mtx_attr = lck_attr_alloc_init();
+ if (necp_route_rule_mtx_attr == NULL) {
+ NECPLOG0(LOG_ERR, "lck_attr_alloc_init failed");
+ result = ENOMEM;
+ goto done;
+ }
+
+ lck_rw_init(&necp_route_rule_lock, necp_route_rule_mtx_grp, necp_route_rule_mtx_attr);
+
+ necp_client_init();
+
+ TAILQ_INIT(&necp_session_list);
+
+ LIST_INIT(&necp_kernel_socket_policies);
+ LIST_INIT(&necp_kernel_ip_output_policies);
+
+ LIST_INIT(&necp_account_id_list);
+
+ LIST_INIT(&necp_uuid_service_id_list);
+
+ LIST_INIT(&necp_registered_service_list);
+
+ LIST_INIT(&necp_route_rules);
+ LIST_INIT(&necp_aggregate_route_rules);
+
+ necp_generate_resolver_key();
+
+ necp_uuid_app_id_hashtbl = hashinit(NECP_UUID_APP_ID_HASH_SIZE, M_NECP, &necp_uuid_app_id_hash_mask);
+ necp_uuid_app_id_hash_num_buckets = necp_uuid_app_id_hash_mask + 1;
+ necp_num_uuid_app_id_mappings = 0;
+ necp_uuid_app_id_mappings_dirty = FALSE;
+
+ necp_kernel_application_policies_condition_mask = 0;
+ necp_kernel_socket_policies_condition_mask = 0;
+ necp_kernel_ip_output_policies_condition_mask = 0;
+
+ necp_kernel_application_policies_count = 0;
+ necp_kernel_socket_policies_count = 0;
+ necp_kernel_socket_policies_non_app_count = 0;
+ necp_kernel_ip_output_policies_count = 0;
+ necp_kernel_ip_output_policies_non_id_count = 0;
+
+ necp_kernel_socket_policies_gencount = 1;
+
+ memset(&necp_kernel_socket_policies_map, 0, sizeof(necp_kernel_socket_policies_map));
+ memset(&necp_kernel_ip_output_policies_map, 0, sizeof(necp_kernel_ip_output_policies_map));
+ necp_kernel_socket_policies_app_layer_map = NULL;
+
+ necp_drop_unentitled_order = necp_get_first_order_for_priority(necp_drop_unentitled_level);
+
+done:
+ if (result != 0) {
+ if (necp_kernel_policy_mtx_attr != NULL) {
+ lck_attr_free(necp_kernel_policy_mtx_attr);
+ necp_kernel_policy_mtx_attr = NULL;
+ }
+ if (necp_kernel_policy_mtx_grp != NULL) {
+ lck_grp_free(necp_kernel_policy_mtx_grp);
+ necp_kernel_policy_mtx_grp = NULL;
+ }
+ if (necp_kernel_policy_grp_attr != NULL) {
+ lck_grp_attr_free(necp_kernel_policy_grp_attr);
+ necp_kernel_policy_grp_attr = NULL;
+ }
+ if (necp_route_rule_mtx_attr != NULL) {
+ lck_attr_free(necp_route_rule_mtx_attr);
+ necp_route_rule_mtx_attr = NULL;
+ }
+ if (necp_route_rule_mtx_grp != NULL) {
+ lck_grp_free(necp_route_rule_mtx_grp);
+ necp_route_rule_mtx_grp = NULL;
+ }
+ if (necp_route_rule_grp_attr != NULL) {
+ lck_grp_attr_free(necp_route_rule_grp_attr);
+ necp_route_rule_grp_attr = NULL;
+ }
+ }
+ return result;
+}
+
+static void
+necp_post_change_event(struct kev_necp_policies_changed_data *necp_event_data)
+{
+ struct kev_msg ev_msg;
+ memset(&ev_msg, 0, sizeof(ev_msg));
+
+ ev_msg.vendor_code = KEV_VENDOR_APPLE;
+ ev_msg.kev_class = KEV_NETWORK_CLASS;
+ ev_msg.kev_subclass = KEV_NECP_SUBCLASS;
+ ev_msg.event_code = KEV_NECP_POLICIES_CHANGED;
+
+ ev_msg.dv[0].data_ptr = necp_event_data;
+ ev_msg.dv[0].data_length = sizeof(necp_event_data->changed_count);
+ ev_msg.dv[1].data_length = 0;
+
+ kev_post_msg(&ev_msg);
+}
+
+static inline bool
+necp_buffer_write_tlv_validate(u_int8_t *cursor, u_int8_t type, u_int32_t length,
+ u_int8_t *buffer, u_int32_t buffer_length)
+{
+ if (cursor < buffer || (uintptr_t)(cursor - buffer) > buffer_length) {
+ NECPLOG0(LOG_ERR, "Cannot write TLV in buffer (invalid cursor)");
+ return false;
+ }
+ u_int8_t *next_tlv = (u_int8_t *)(cursor + sizeof(type) + sizeof(length) + length);
+ if (next_tlv <= buffer || // make sure the next TLV start doesn't overflow
+ (uintptr_t)(next_tlv - buffer) > buffer_length) { // make sure the next TLV has enough room in buffer
+ NECPLOG(LOG_ERR, "Cannot write TLV in buffer (TLV length %u, buffer length %u)",
+ length, buffer_length);
+ return false;
+ }
+ return true;
+}
+
+u_int8_t *
+necp_buffer_write_tlv_if_different(u_int8_t *cursor, u_int8_t type,
+ u_int32_t length, const void *value, bool *updated,
+ u_int8_t *buffer, u_int32_t buffer_length)
+{
+ if (!necp_buffer_write_tlv_validate(cursor, type, length, buffer, buffer_length)) {
+ // If we can't fit this TLV, return the current cursor
+ return cursor;
+ }
+ u_int8_t *next_tlv = (u_int8_t *)(cursor + sizeof(type) + sizeof(length) + length);
+ if (*updated || *(u_int8_t *)(cursor) != type) {
+ *(u_int8_t *)(cursor) = type;
+ *updated = TRUE;
+ }
+ if (*updated || *(u_int32_t *)(void *)(cursor + sizeof(type)) != length) {
+ *(u_int32_t *)(void *)(cursor + sizeof(type)) = length;
+ *updated = TRUE;
+ }
+ if (length > 0) {
+ if (*updated || memcmp((u_int8_t *)(cursor + sizeof(type) + sizeof(length)), value, length) != 0) {
+ memcpy((u_int8_t *)(cursor + sizeof(type) + sizeof(length)), value, length);
+ *updated = TRUE;
+ }
+ }
+ return next_tlv;
+}
+
+u_int8_t *
+necp_buffer_write_tlv(u_int8_t *cursor, u_int8_t type,
+ u_int32_t length, const void *value,
+ u_int8_t *buffer, u_int32_t buffer_length)
+{
+ if (!necp_buffer_write_tlv_validate(cursor, type, length, buffer, buffer_length)) {
+ return NULL;
+ }
+ u_int8_t *next_tlv = (u_int8_t *)(cursor + sizeof(type) + sizeof(length) + length);
+ *(u_int8_t *)(cursor) = type;
+ *(u_int32_t *)(void *)(cursor + sizeof(type)) = length;