+// Session fd
+
+static int noop_read(struct fileproc *, struct uio *, int, vfs_context_t);
+static int noop_write(struct fileproc *, struct uio *, int, vfs_context_t);
+static int noop_ioctl(struct fileproc *, unsigned long, caddr_t,
+ vfs_context_t);
+static int noop_select(struct fileproc *, int, void *, vfs_context_t);
+static int necp_session_op_close(struct fileglob *, vfs_context_t);
+static int noop_kqfilter(struct fileproc *, struct knote *,
+ struct kevent_internal_s *, vfs_context_t);
+
+static const struct fileops necp_session_fd_ops = {
+ .fo_type = DTYPE_NETPOLICY,
+ .fo_read = noop_read,
+ .fo_write = noop_write,
+ .fo_ioctl = noop_ioctl,
+ .fo_select = noop_select,
+ .fo_close = necp_session_op_close,
+ .fo_kqfilter = noop_kqfilter,
+ .fo_drain = NULL,
+};
+
+static int
+noop_read(struct fileproc *fp, struct uio *uio, int flags, vfs_context_t ctx)
+{
+#pragma unused(fp, uio, flags, ctx)
+ return (ENXIO);
+}
+
+static int
+noop_write(struct fileproc *fp, struct uio *uio, int flags,
+ vfs_context_t ctx)
+{
+#pragma unused(fp, uio, flags, ctx)
+ return (ENXIO);
+}
+
+static int
+noop_ioctl(struct fileproc *fp, unsigned long com, caddr_t data,
+ vfs_context_t ctx)
+{
+#pragma unused(fp, com, data, ctx)
+ return (ENOTTY);
+}
+
+static int
+noop_select(struct fileproc *fp, int which, void *wql, vfs_context_t ctx)
+{
+#pragma unused(fp, which, wql, ctx)
+ return (ENXIO);
+}
+
+static int
+noop_kqfilter(struct fileproc *fp, struct knote *kn,
+ struct kevent_internal_s *kev, vfs_context_t ctx)
+{
+#pragma unused(fp, kn, kev, ctx)
+ return (ENXIO);
+}
+
+int
+necp_session_open(struct proc *p, struct necp_session_open_args *uap, int *retval)
+{
+#pragma unused(uap)
+ int error = 0;
+ struct necp_session *session = NULL;
+ struct fileproc *fp = NULL;
+ int fd = -1;
+
+ uid_t uid = kauth_cred_getuid(proc_ucred(p));
+ if (uid != 0 && priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NECP_POLICIES, 0) != 0) {
+ NECPLOG0(LOG_ERR, "Process does not hold necessary entitlement to open NECP session");
+ error = EACCES;
+ goto done;
+ }
+
+ error = falloc(p, &fp, &fd, vfs_context_current());
+ if (error != 0) {
+ goto done;
+ }
+
+ session = necp_create_session();
+ if (session == NULL) {
+ error = ENOMEM;
+ goto done;
+ }
+
+ fp->f_fglob->fg_flag = 0;
+ fp->f_fglob->fg_ops = &necp_session_fd_ops;
+ fp->f_fglob->fg_data = session;
+
+ proc_fdlock(p);
+ FDFLAGS_SET(p, fd, (UF_EXCLOSE | UF_FORKCLOSE));
+ procfdtbl_releasefd(p, fd, NULL);
+ fp_drop(p, fd, fp, 1);
+ proc_fdunlock(p);
+
+ *retval = fd;
+done:
+ if (error != 0) {
+ if (fp != NULL) {
+ fp_free(p, fd, fp);
+ fp = NULL;
+ }
+ }
+
+ return (error);
+}
+
+static int
+necp_session_op_close(struct fileglob *fg, vfs_context_t ctx)
+{
+#pragma unused(ctx)
+ struct necp_session *session = (struct necp_session *)fg->fg_data;
+ fg->fg_data = NULL;
+
+ if (session != NULL) {
+ necp_policy_mark_all_for_deletion(session);
+ necp_policy_apply_all(session);
+ necp_delete_session(session);
+ return (0);
+ } else {
+ return (ENOENT);
+ }
+}
+
+static int
+necp_session_find_from_fd(int fd, struct necp_session **session)
+{
+ proc_t p = current_proc();
+ struct fileproc *fp = NULL;
+ int error = 0;
+
+ proc_fdlock_spin(p);
+ if ((error = fp_lookup(p, fd, &fp, 1)) != 0) {
+ goto done;
+ }
+ if (fp->f_fglob->fg_ops->fo_type != DTYPE_NETPOLICY) {
+ fp_drop(p, fd, fp, 1);
+ error = ENODEV;
+ goto done;
+ }
+ *session = (struct necp_session *)fp->f_fglob->fg_data;
+
+done:
+ proc_fdunlock(p);
+ return (error);
+}
+
+static int
+necp_session_add_policy(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+ int error = 0;
+ u_int8_t *tlv_buffer = NULL;
+
+ if (uap->in_buffer_length == 0 || uap->in_buffer_length > NECP_MAX_POLICY_SIZE || uap->in_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_add_policy invalid input (%zu)", uap->in_buffer_length);
+ error = EINVAL;
+ goto done;
+ }
+
+ if (uap->out_buffer_length < sizeof(necp_policy_id) || uap->out_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_add_policy invalid output buffer (%zu)", uap->out_buffer_length);
+ error = EINVAL;
+ goto done;
+ }
+
+ if ((tlv_buffer = _MALLOC(uap->in_buffer_length, M_NECP, M_WAITOK | M_ZERO)) == NULL) {
+ error = ENOMEM;
+ goto done;
+ }
+
+ error = copyin(uap->in_buffer, tlv_buffer, uap->in_buffer_length);
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_add_policy tlv copyin error (%d)", error);
+ goto done;
+ }
+
+ necp_policy_id new_policy_id = necp_handle_policy_add(session, 0, NULL, tlv_buffer, uap->in_buffer_length, 0, &error);
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_add_policy failed to add policy (%d)", error);
+ goto done;
+ }
+
+ error = copyout(&new_policy_id, uap->out_buffer, sizeof(new_policy_id));
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_add_policy policy_id copyout error (%d)", error);
+ goto done;
+ }
+
+done:
+ if (tlv_buffer != NULL) {
+ FREE(tlv_buffer, M_NECP);
+ tlv_buffer = NULL;
+ }
+ *retval = error;
+
+ return (error);
+}
+
+static int
+necp_session_get_policy(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+ int error = 0;
+ u_int8_t *response = NULL;
+
+ if (uap->in_buffer_length < sizeof(necp_policy_id) || uap->in_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_get_policy invalid input (%zu)", uap->in_buffer_length);
+ error = EINVAL;
+ goto done;
+ }
+
+ necp_policy_id policy_id = 0;
+ error = copyin(uap->in_buffer, &policy_id, sizeof(policy_id));
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_get_policy policy_id copyin error (%d)", error);
+ goto done;
+ }
+
+ struct necp_session_policy *policy = necp_policy_find(session, policy_id);
+ if (policy == NULL || policy->pending_deletion) {
+ NECPLOG(LOG_ERR, "Failed to find policy with id %d", policy_id);
+ error = ENOENT;
+ goto done;
+ }
+
+ u_int32_t order_tlv_size = sizeof(u_int8_t) + sizeof(u_int32_t) + sizeof(necp_policy_order);
+ u_int32_t result_tlv_size = (policy->result_size ? (sizeof(u_int8_t) + sizeof(u_int32_t) + policy->result_size) : 0);
+ u_int32_t response_size = order_tlv_size + result_tlv_size + policy->conditions_size;
+
+ if (uap->out_buffer_length < response_size || uap->out_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_get_policy buffer not large enough (%u < %u)", uap->out_buffer_length, response_size);
+ error = EINVAL;
+ goto done;
+ }
+
+ if (response_size > NECP_MAX_POLICY_SIZE) {
+ NECPLOG(LOG_ERR, "necp_session_get_policy size too large to copy (%u)", response_size);
+ error = EINVAL;
+ goto done;
+ }
+
+ 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;
+ cursor = necp_buffer_write_tlv(cursor, NECP_TLV_POLICY_ORDER, sizeof(necp_policy_order), &policy->order, response, response_size);
+ if (result_tlv_size) {
+ cursor = necp_buffer_write_tlv(cursor, NECP_TLV_POLICY_RESULT, policy->result_size, &policy->result, response, response_size);
+ }
+ if (policy->conditions_size) {
+ memcpy(((u_int8_t *)(void *)(cursor)), policy->conditions, policy->conditions_size);
+ }
+
+ error = copyout(response, uap->out_buffer, response_size);
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_get_policy 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_policy(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+ int error = 0;
+
+ if (uap->in_buffer_length < sizeof(necp_policy_id) || uap->in_buffer == 0) {
+ NECPLOG(LOG_ERR, "necp_session_delete_policy invalid input (%zu)", uap->in_buffer_length);
+ error = EINVAL;
+ goto done;
+ }
+
+ necp_policy_id delete_policy_id = 0;
+ error = copyin(uap->in_buffer, &delete_policy_id, sizeof(delete_policy_id));
+ if (error != 0) {
+ NECPLOG(LOG_ERR, "necp_session_delete_policy policy_id copyin error (%d)", error);
+ goto done;
+ }
+
+ struct necp_session_policy *policy = necp_policy_find(session, delete_policy_id);
+ if (policy == NULL || policy->pending_deletion) {
+ NECPLOG(LOG_ERR, "necp_session_delete_policy failed to find policy with id %u", delete_policy_id);
+ error = ENOENT;
+ goto done;
+ }
+
+ necp_policy_mark_for_deletion(session, policy);
+done:
+ *retval = error;
+ return (error);
+}
+
+static int
+necp_session_apply_all(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+#pragma unused(uap)
+ necp_policy_apply_all(session);
+ *retval = 0;
+ return (0);
+}
+
+static int
+necp_session_list_all(struct necp_session *session, struct necp_session_action_args *uap, int *retval)
+{
+ 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->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)
+{
+ 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(session, 0, NULL, uap->out_buffer, uap->out_buffer_length, 0);
+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);
+}
+