X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/0a7de7458d150b5d4dffc935ba399be265ef0a1a..4ba76501152d51ccb5647018f3192c6096367d48:/bsd/net/necp.c diff --git a/bsd/net/necp.c b/bsd/net/necp.c index 513cea4ae..2133f31f3 100644 --- a/bsd/net/necp.c +++ b/bsd/net/necp.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2018 Apple Inc. All rights reserved. + * Copyright (c) 2013-2019 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -43,6 +43,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -61,6 +64,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -68,7 +75,7 @@ * NECP - Network Extension Control Policy database * ------------------------------------------------ * The goal of this module is to allow clients connecting via a - * kernel control socket to create high-level policy sessions, which + * policy file descriptor to create high-level policy sessions, which * are ingested into low-level kernel policies that control and tag * traffic at the application, socket, and IP layers. * @@ -84,7 +91,7 @@ * can specify the sub-order for each policy it creates which will be * used to further sort the kernel policies. * - * Kernel Control Socket --> 1 necp_session --> list of necp_session_policy structs + * Policy fd --> 1 necp_session --> list of necp_session_policy structs * * ------------------------------------------------ * Kernel Policies @@ -136,6 +143,14 @@ u_int32_t necp_drop_all_level = 0; u_int32_t necp_pass_loopback = 1; // 0=Off, 1=On u_int32_t necp_pass_keepalives = 1; // 0=Off, 1=On +u_int32_t necp_pass_interpose = 1; // 0=Off, 1=On + +u_int32_t necp_drop_unentitled_order = 0; +#ifdef XNU_TARGET_OS_WATCH +u_int32_t necp_drop_unentitled_level = NECP_SESSION_PRIORITY_CONTROL + 1; // Block all unentitled traffic from policies below control level +#else // XNU_TARGET_OS_WATCH +u_int32_t necp_drop_unentitled_level = 0; +#endif // XNU_TARGET_OS_WATCH u_int32_t necp_debug = 0; // 0=None, 1=Basic, 2=EveryMatch @@ -182,6 +197,9 @@ u_int32_t necp_session_count = 0; #define IS_NECP_ROUTE_RULE_ALLOW_OR_DENY(x) ((x) == NECP_ROUTE_RULE_DENY_INTERFACE || (x) == NECP_ROUTE_RULE_ALLOW_INTERFACE) +#define IS_NECP_DEST_IN_LOCAL_NETWORKS(rt) \ + ((rt) != NULL && !((rt)->rt_flags & RTF_GATEWAY) && ((rt)->rt_ifa && (rt)->rt_ifa->ifa_ifp && !((rt)->rt_ifa->ifa_ifp->if_flags & IFF_POINTOPOINT))) + #define NECP_KERNEL_CONDITION_ALL_INTERFACES 0x000001 #define NECP_KERNEL_CONDITION_BOUND_INTERFACE 0x000002 #define NECP_KERNEL_CONDITION_PROTOCOL 0x000004 @@ -203,6 +221,12 @@ u_int32_t necp_session_count = 0; #define NECP_KERNEL_CONDITION_ENTITLEMENT 0x040000 #define NECP_KERNEL_CONDITION_CUSTOM_ENTITLEMENT 0x080000 #define NECP_KERNEL_CONDITION_AGENT_TYPE 0x100000 +#define NECP_KERNEL_CONDITION_HAS_CLIENT 0x200000 +#define NECP_KERNEL_CONDITION_LOCAL_NETWORKS 0x400000 +#define NECP_KERNEL_CONDITION_CLIENT_FLAGS 0x800000 +#define NECP_KERNEL_CONDITION_LOCAL_EMPTY 0x1000000 +#define NECP_KERNEL_CONDITION_REMOTE_EMPTY 0x2000000 +#define NECP_KERNEL_CONDITION_PLATFORM_BINARY 0x4000000 #define NECP_MAX_POLICY_RESULT_SIZE 512 #define NECP_MAX_ROUTE_RULES_ARRAY_SIZE 1024 @@ -256,13 +280,15 @@ struct necp_socket_info { u_int32_t application_id; u_int32_t real_application_id; u_int32_t account_id; + u_int32_t drop_order; + u_int32_t client_flags; char *domain; errno_t cred_result; + unsigned has_client : 1; + unsigned is_platform_binary : 1; + unsigned __pad_bits : 6; }; -static kern_ctl_ref necp_kctlref; -static u_int32_t necp_family; -static OSMallocTag necp_malloc_tag; static lck_grp_attr_t *necp_kernel_policy_grp_attr = NULL; static lck_attr_t *necp_kernel_policy_mtx_attr = NULL; static lck_grp_t *necp_kernel_policy_mtx_grp = NULL; @@ -273,6 +299,8 @@ static lck_attr_t *necp_route_rule_mtx_attr = NULL; static lck_grp_t *necp_route_rule_mtx_grp = NULL; decl_lck_rw_data(static, necp_route_rule_lock); +os_refgrp_decl(static, necp_refgrp, "NECPRefGroup", NULL); + /* * On modification, invalidate cached lookups by bumping the generation count. * Other calls will need to take the slowpath of taking @@ -285,6 +313,22 @@ static volatile int32_t necp_kernel_socket_policies_gencount; } \ } while (0) +/* + * Drop-all Bypass: + * Allow priviledged processes to bypass the default drop-all + * via entitlement check. For OSX, since entitlement check is + * not supported for configd, configd signing identity is checked + * instead. + */ +#define SIGNING_ID_CONFIGD "com.apple.configd" +#define SIGNING_ID_CONFIGD_LEN (sizeof(SIGNING_ID_CONFIGD) - 1) + +typedef enum { + NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE = 0, + NECP_DROP_ALL_BYPASS_CHECK_RESULT_TRUE = 1, + NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE = 2, +} necp_drop_all_bypass_check_result_t; + static u_int32_t necp_kernel_application_policies_condition_mask; static size_t necp_kernel_application_policies_count; static u_int32_t necp_kernel_socket_policies_condition_mask; @@ -310,23 +354,18 @@ static LIST_HEAD(_necpkernelipoutputpolicies, necp_kernel_ip_output_policy) necp #define NECP_KERNEL_IP_OUTPUT_POLICIES_MAP_NUM_ID_BUCKETS 5 #define NECP_IP_OUTPUT_MAP_ID_TO_BUCKET(id) (id ? (id%(NECP_KERNEL_IP_OUTPUT_POLICIES_MAP_NUM_ID_BUCKETS - 1) + 1) : 0) static struct necp_kernel_ip_output_policy **necp_kernel_ip_output_policies_map[NECP_KERNEL_IP_OUTPUT_POLICIES_MAP_NUM_ID_BUCKETS]; +static struct necp_kernel_socket_policy pass_policy = +{ + .id = NECP_KERNEL_POLICY_ID_NO_MATCH, + .result = NECP_KERNEL_POLICY_RESULT_PASS, +}; static struct necp_session *necp_create_session(void); static void necp_delete_session(struct necp_session *session); -static necp_policy_id necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_t packet, +static necp_policy_id necp_handle_policy_add(struct necp_session *session, u_int8_t *tlv_buffer, size_t tlv_buffer_length, int offset, int *error); -static void necp_handle_policy_get(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); -static void necp_handle_policy_delete(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); -static void necp_handle_policy_apply_all(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); -static void necp_handle_policy_list_all(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); -static void necp_handle_policy_delete_all(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); -static int necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, mbuf_t packet, - user_addr_t out_buffer, size_t out_buffer_length, int offset); -static void necp_handle_set_session_priority(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); -static void necp_handle_lock_session_to_proc(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); -static void necp_handle_register_service(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); -static void necp_handle_unregister_service(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset); +static int necp_handle_policy_dump_all(user_addr_t out_buffer, size_t out_buffer_length); #define MAX_RESULT_STRING_LEN 64 static inline const char * necp_get_result_description(char *result_string, necp_kernel_policy_result result, necp_kernel_policy_result_parameter result_parameter); @@ -338,11 +377,11 @@ static bool necp_policy_mark_all_for_deletion(struct necp_session *session); static bool necp_policy_delete(struct necp_session *session, struct necp_session_policy *policy); static void necp_policy_apply_all(struct necp_session *session); -static necp_kernel_policy_id necp_kernel_socket_policy_add(necp_policy_order order, u_int32_t session_order, int session_pid, u_int32_t condition_mask, u_int32_t condition_negated_mask, necp_app_id cond_app_id, necp_app_id cond_real_app_id, char *cond_custom_entitlement, u_int32_t cond_account_id, char *domain, pid_t cond_pid, uid_t cond_uid, ifnet_t cond_bound_interface, struct necp_policy_condition_tc_range cond_traffic_class, u_int16_t cond_protocol, union necp_sockaddr_union *cond_local_start, union necp_sockaddr_union *cond_local_end, u_int8_t cond_local_prefix, union necp_sockaddr_union *cond_remote_start, union necp_sockaddr_union *cond_remote_end, u_int8_t cond_remote_prefix, struct necp_policy_condition_agent_type *cond_agent_type, necp_kernel_policy_result result, necp_kernel_policy_result_parameter result_parameter); +static necp_kernel_policy_id necp_kernel_socket_policy_add(necp_policy_order order, u_int32_t session_order, int session_pid, u_int32_t condition_mask, u_int32_t condition_negated_mask, necp_app_id cond_app_id, necp_app_id cond_real_app_id, char *cond_custom_entitlement, u_int32_t cond_account_id, char *domain, pid_t cond_pid, uid_t cond_uid, ifnet_t cond_bound_interface, struct necp_policy_condition_tc_range cond_traffic_class, u_int16_t cond_protocol, union necp_sockaddr_union *cond_local_start, union necp_sockaddr_union *cond_local_end, u_int8_t cond_local_prefix, union necp_sockaddr_union *cond_remote_start, union necp_sockaddr_union *cond_remote_end, u_int8_t cond_remote_prefix, struct necp_policy_condition_agent_type *cond_agent_type, u_int32_t cond_client_flags, necp_kernel_policy_result result, necp_kernel_policy_result_parameter result_parameter); static bool necp_kernel_socket_policy_delete(necp_kernel_policy_id policy_id); static bool necp_kernel_socket_policies_reprocess(void); static bool necp_kernel_socket_policies_update_uuid_table(void); -static inline struct necp_kernel_socket_policy *necp_socket_find_policy_match_with_info_locked(struct necp_kernel_socket_policy **policy_search_array, struct necp_socket_info *info, necp_kernel_policy_filter *return_filter, u_int32_t *return_route_rule_id, necp_kernel_policy_result *return_service_action, necp_kernel_policy_service *return_service, u_int32_t *return_netagent_array, u_int32_t *return_netagent_use_flags_array, size_t netagent_array_count, struct necp_client_parameter_netagent_type *required_agent_types, u_int32_t num_required_agent_types, proc_t proc, necp_kernel_policy_id *skip_policy_id); +static inline struct necp_kernel_socket_policy *necp_socket_find_policy_match_with_info_locked(struct necp_kernel_socket_policy **policy_search_array, struct necp_socket_info *info, necp_kernel_policy_filter *return_filter, u_int32_t *return_route_rule_id_array, size_t *return_route_rule_id_array_count, size_t route_rule_id_array_count, necp_kernel_policy_result *return_service_action, necp_kernel_policy_service *return_service, u_int32_t *return_netagent_array, u_int32_t *return_netagent_use_flags_array, size_t netagent_array_count, struct necp_client_parameter_netagent_type *required_agent_types, u_int32_t num_required_agent_types, proc_t proc, necp_kernel_policy_id *skip_policy_id, struct rtentry *rt, necp_kernel_policy_result *return_drop_dest_policy_result, necp_drop_all_bypass_check_result_t *return_drop_all_bypass); static necp_kernel_policy_id necp_kernel_ip_output_policy_add(necp_policy_order order, necp_policy_order suborder, u_int32_t session_order, int session_pid, u_int32_t condition_mask, u_int32_t condition_negated_mask, necp_kernel_policy_id cond_policy_id, ifnet_t cond_bound_interface, u_int32_t cond_last_interface_index, u_int16_t cond_protocol, union necp_sockaddr_union *cond_local_start, union necp_sockaddr_union *cond_local_end, u_int8_t cond_local_prefix, union necp_sockaddr_union *cond_remote_start, union necp_sockaddr_union *cond_remote_end, u_int8_t cond_remote_prefix, necp_kernel_policy_result result, necp_kernel_policy_result_parameter result_parameter); static bool necp_kernel_ip_output_policy_delete(necp_kernel_policy_id policy_id); @@ -353,15 +392,16 @@ static bool necp_is_range_in_range(struct sockaddr *inner_range_start, struct so static bool necp_is_addr_in_subnet(struct sockaddr *addr, struct sockaddr *subnet_addr, u_int8_t subnet_prefix); static int necp_addr_compare(struct sockaddr *sa1, struct sockaddr *sa2, int check_port); static bool necp_buffer_compare_with_bit_prefix(u_int8_t *p1, u_int8_t *p2, u_int32_t bits); -static bool necp_is_loopback(struct sockaddr *local_addr, struct sockaddr *remote_addr, struct inpcb *inp, struct mbuf *packet); +static bool necp_addr_is_empty(struct sockaddr *addr); +static bool necp_is_loopback(struct sockaddr *local_addr, struct sockaddr *remote_addr, struct inpcb *inp, struct mbuf *packet, u_int32_t bound_interface_index); static bool necp_is_intcoproc(struct inpcb *inp, struct mbuf *packet); struct necp_uuid_id_mapping { LIST_ENTRY(necp_uuid_id_mapping) chain; uuid_t uuid; u_int32_t id; - u_int32_t refcount; - u_int32_t table_refcount; // Add to UUID policy table count + os_refcnt_t refcount; + u_int32_t table_usecount; // Add to UUID policy table count }; static size_t necp_num_uuid_app_id_mappings; static bool necp_uuid_app_id_mappings_dirty; @@ -383,7 +423,7 @@ struct necp_string_id_mapping { LIST_ENTRY(necp_string_id_mapping) chain; char *string; necp_app_id id; - u_int32_t refcount; + os_refcnt_t refcount; }; static LIST_HEAD(necp_string_id_mapping_list, necp_string_id_mapping) necp_account_id_list; static u_int32_t necp_create_string_to_id_mapping(struct necp_string_id_mapping_list *list, char *domain); @@ -412,9 +452,10 @@ struct necp_route_rule { u_int8_t wifi_action; u_int8_t wired_action; u_int8_t expensive_action; + u_int8_t constrained_action; u_int exception_if_indices[MAX_ROUTE_RULE_INTERFACES]; u_int8_t exception_if_actions[MAX_ROUTE_RULE_INTERFACES]; - u_int32_t refcount; + os_refcnt_t refcount; }; static LIST_HEAD(necp_route_rule_list, necp_route_rule) necp_route_rules; static u_int32_t necp_create_route_rule(struct necp_route_rule_list *list, u_int8_t *route_rules_array, u_int32_t route_rules_array_size); @@ -434,17 +475,30 @@ static u_int32_t necp_create_aggregate_route_rule(u_int32_t *rule_ids); // Sysctl definitions static int sysctl_handle_necp_level SYSCTL_HANDLER_ARGS; +static int sysctl_handle_necp_unentitled_level SYSCTL_HANDLER_ARGS; SYSCTL_NODE(_net, OID_AUTO, necp, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "NECP"); SYSCTL_INT(_net_necp, NECPCTL_PASS_LOOPBACK, pass_loopback, CTLFLAG_LOCKED | CTLFLAG_RW, &necp_pass_loopback, 0, ""); SYSCTL_INT(_net_necp, NECPCTL_PASS_KEEPALIVES, pass_keepalives, CTLFLAG_LOCKED | CTLFLAG_RW, &necp_pass_keepalives, 0, ""); +SYSCTL_INT(_net_necp, NECPCTL_PASS_INTERPOSE, pass_interpose, CTLFLAG_LOCKED | CTLFLAG_RW, &necp_pass_interpose, 0, ""); SYSCTL_INT(_net_necp, NECPCTL_DEBUG, debug, CTLFLAG_LOCKED | CTLFLAG_RW, &necp_debug, 0, ""); +SYSCTL_PROC(_net_necp, NECPCTL_DROP_UNENTITLED_LEVEL, drop_unentitled_level, CTLTYPE_INT | CTLFLAG_LOCKED | CTLFLAG_RW, &necp_drop_unentitled_level, 0, &sysctl_handle_necp_unentitled_level, "IU", ""); SYSCTL_PROC(_net_necp, NECPCTL_DROP_ALL_LEVEL, drop_all_level, CTLTYPE_INT | CTLFLAG_LOCKED | CTLFLAG_RW, &necp_drop_all_level, 0, &sysctl_handle_necp_level, "IU", ""); SYSCTL_LONG(_net_necp, NECPCTL_SOCKET_POLICY_COUNT, socket_policy_count, CTLFLAG_LOCKED | CTLFLAG_RD, &necp_kernel_socket_policies_count, ""); SYSCTL_LONG(_net_necp, NECPCTL_SOCKET_NON_APP_POLICY_COUNT, socket_non_app_policy_count, CTLFLAG_LOCKED | CTLFLAG_RD, &necp_kernel_socket_policies_non_app_count, ""); SYSCTL_LONG(_net_necp, NECPCTL_IP_POLICY_COUNT, ip_policy_count, CTLFLAG_LOCKED | CTLFLAG_RD, &necp_kernel_ip_output_policies_count, ""); SYSCTL_INT(_net_necp, NECPCTL_SESSION_COUNT, session_count, CTLFLAG_LOCKED | CTLFLAG_RD, &necp_session_count, 0, ""); +static struct necp_drop_dest_policy necp_drop_dest_policy; +static int necp_drop_dest_debug = 0; // 0: off, 1: match, >1: every evaluation +SYSCTL_INT(_net_necp, OID_AUTO, drop_dest_debug, CTLFLAG_LOCKED | CTLFLAG_RW, &necp_drop_dest_debug, 0, ""); + +static int sysctl_handle_necp_drop_dest_level SYSCTL_HANDLER_ARGS; +SYSCTL_PROC(_net_necp, OID_AUTO, drop_dest_level, CTLTYPE_STRUCT | CTLFLAG_LOCKED | CTLFLAG_ANYBODY | CTLFLAG_RW, + 0, 0, &sysctl_handle_necp_drop_dest_level, "S,necp_drop_dest_level", ""); + +static bool necp_address_matches_drop_dest_policy(union necp_sockaddr_union *, u_int32_t); + // Session order allocation static u_int32_t necp_allocate_new_session_order(u_int32_t priority, u_int32_t control_unit) @@ -465,6 +519,9 @@ necp_allocate_new_session_order(u_int32_t priority, u_int32_t control_unit) static inline u_int32_t necp_get_first_order_for_priority(u_int32_t priority) { + if (priority == 0) { + return 0; + } return ((priority - 1) * 1000) + 1; } @@ -474,72 +531,76 @@ sysctl_handle_necp_level SYSCTL_HANDLER_ARGS { #pragma unused(arg1, arg2) int error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req); - if (necp_drop_all_level == 0) { - necp_drop_all_order = 0; + necp_drop_all_order = necp_get_first_order_for_priority(necp_drop_all_level); + return error; +} + +static int +sysctl_handle_necp_unentitled_level SYSCTL_HANDLER_ARGS +{ +#pragma unused(arg1, arg2) + int error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req); + necp_drop_unentitled_order = necp_get_first_order_for_priority(necp_drop_unentitled_level); + return error; +} + +// Use a macro here to avoid computing the kauth_cred_t when necp_drop_unentitled_level is 0 +static inline u_int32_t +_necp_process_drop_order_inner(kauth_cred_t cred) +{ + if (priv_check_cred(cred, PRIV_NET_PRIVILEGED_CLIENT_ACCESS, 0) != 0 && + priv_check_cred(cred, PRIV_NET_PRIVILEGED_SERVER_ACCESS, 0) != 0) { + return necp_drop_unentitled_order; } else { - necp_drop_all_order = necp_get_first_order_for_priority(necp_drop_all_level); + return 0; } - return error; } +#define necp_process_drop_order(_cred) (necp_drop_unentitled_order != 0 ? _necp_process_drop_order_inner(_cred) : necp_drop_unentitled_order) +#pragma GCC poison _necp_process_drop_order_inner + // 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, + .fo_type = DTYPE_NETPOLICY, + .fo_read = fo_no_read, + .fo_write = fo_no_write, + .fo_ioctl = fo_no_ioctl, + .fo_select = fo_no_select, + .fo_close = necp_session_op_close, + .fo_drain = fo_no_drain, + .fo_kqfilter = fo_no_kqfilter, }; -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) +static inline necp_drop_all_bypass_check_result_t +necp_check_drop_all_bypass_result(proc_t proc) { -#pragma unused(fp, com, data, ctx) - return ENOTTY; -} + if (proc == NULL) { + proc = current_proc(); + if (proc == NULL) { + return NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE; + } + } -static int -noop_select(struct fileproc *fp, int which, void *wql, vfs_context_t ctx) -{ -#pragma unused(fp, which, wql, ctx) - return ENXIO; -} +#if defined(XNU_TARGET_OS_OSX) + const char *signing_id = NULL; + const bool isConfigd = (csproc_get_platform_binary(proc) && + (signing_id = cs_identity_get(proc)) && + (strlen(signing_id) == SIGNING_ID_CONFIGD_LEN) && + (memcmp(signing_id, SIGNING_ID_CONFIGD, SIGNING_ID_CONFIGD_LEN) == 0)); + if (isConfigd) { + return NECP_DROP_ALL_BYPASS_CHECK_RESULT_TRUE; + } +#endif -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; + const task_t task = proc_task(proc); + if (task == NULL || !IOTaskHasEntitlement(task, "com.apple.private.necp.drop_all_bypass")) { + return NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE; + } else { + return NECP_DROP_ALL_BYPASS_CHECK_RESULT_TRUE; + } } int @@ -667,7 +728,7 @@ necp_session_add_policy(struct necp_session *session, struct necp_session_action goto done; } - necp_policy_id new_policy_id = necp_handle_policy_add(session, 0, NULL, tlv_buffer, uap->in_buffer_length, 0, &error); + necp_policy_id new_policy_id = necp_handle_policy_add(session, 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; @@ -1010,6 +1071,7 @@ done: 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) { @@ -1018,7 +1080,7 @@ necp_session_dump_all(struct necp_session *session, struct necp_session_action_a goto done; } - error = necp_handle_policy_dump_all(session, 0, NULL, uap->out_buffer, uap->out_buffer_length, 0); + error = necp_handle_policy_dump_all(uap->out_buffer, uap->out_buffer_length); done: *retval = error; return error; @@ -1113,27 +1175,122 @@ done: return return_value; } -// Kernel Control functions -static errno_t necp_register_control(void); -static errno_t necp_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac, void **unitinfo); -static errno_t necp_ctl_disconnect(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo); -static errno_t necp_ctl_send(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, mbuf_t m, int flags); -static void necp_ctl_rcvd(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int flags); -static errno_t necp_ctl_getopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int opt, void *data, size_t *len); -static errno_t necp_ctl_setopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int opt, void *data, size_t len); +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); -static bool necp_send_ctl_data(struct necp_session *session, u_int8_t *buffer, size_t buffer_size); + 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; - result = necp_register_control(); - if (result != 0) { - goto done; - } - 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"); @@ -1196,6 +1353,8 @@ necp_init(void) 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; @@ -1217,6 +1376,8 @@ necp_init(void) 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) { @@ -1243,52 +1404,10 @@ done: lck_grp_attr_free(necp_route_rule_grp_attr); necp_route_rule_grp_attr = NULL; } - if (necp_kctlref != NULL) { - ctl_deregister(necp_kctlref); - necp_kctlref = NULL; - } } return result; } -static errno_t -necp_register_control(void) -{ - struct kern_ctl_reg kern_ctl; - errno_t result = 0; - - // Create a tag to allocate memory - necp_malloc_tag = OSMalloc_Tagalloc(NECP_CONTROL_NAME, OSMT_DEFAULT); - - // Find a unique value for our interface family - result = mbuf_tag_id_find(NECP_CONTROL_NAME, &necp_family); - if (result != 0) { - NECPLOG(LOG_ERR, "mbuf_tag_id_find_internal failed: %d", result); - return result; - } - - bzero(&kern_ctl, sizeof(kern_ctl)); - strlcpy(kern_ctl.ctl_name, NECP_CONTROL_NAME, sizeof(kern_ctl.ctl_name)); - kern_ctl.ctl_name[sizeof(kern_ctl.ctl_name) - 1] = 0; - kern_ctl.ctl_flags = CTL_FLAG_PRIVILEGED; // Require root - kern_ctl.ctl_sendsize = 64 * 1024; - kern_ctl.ctl_recvsize = 64 * 1024; - kern_ctl.ctl_connect = necp_ctl_connect; - kern_ctl.ctl_disconnect = necp_ctl_disconnect; - kern_ctl.ctl_send = necp_ctl_send; - kern_ctl.ctl_rcvd = necp_ctl_rcvd; - kern_ctl.ctl_setopt = necp_ctl_setopt; - kern_ctl.ctl_getopt = necp_ctl_getopt; - - result = ctl_register(&kern_ctl, &necp_kctlref); - if (result != 0) { - NECPLOG(LOG_ERR, "ctl_register failed: %d", result); - return result; - } - - return 0; -} - static void necp_post_change_event(struct kev_necp_policies_changed_data *necp_event_data) { @@ -1307,117 +1426,6 @@ necp_post_change_event(struct kev_necp_policies_changed_data *necp_event_data) kev_post_msg(&ev_msg); } -static errno_t -necp_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac, void **unitinfo) -{ -#pragma unused(kctlref, sac) - *unitinfo = necp_create_session(); - if (*unitinfo == NULL) { - // Could not allocate session - return ENOBUFS; - } - - return 0; -} - -static errno_t -necp_ctl_disconnect(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo) -{ -#pragma unused(kctlref, unit) - struct necp_session *session = (struct necp_session *)unitinfo; - if (session != NULL) { - necp_policy_mark_all_for_deletion(session); - necp_policy_apply_all(session); - necp_delete_session((struct necp_session *)unitinfo); - } - - return 0; -} - - -// Message handling -static int -necp_packet_find_tlv(mbuf_t packet, int offset, u_int8_t type, int *err, int next) -{ - size_t cursor = offset; - int error = 0; - u_int32_t curr_length; - u_int8_t curr_type; - - *err = 0; - - do { - if (!next) { - error = mbuf_copydata(packet, cursor, sizeof(curr_type), &curr_type); - if (error) { - *err = ENOENT; - return -1; - } - } else { - next = 0; - curr_type = NECP_TLV_NIL; - } - - if (curr_type != type) { - cursor += sizeof(curr_type); - error = mbuf_copydata(packet, cursor, sizeof(curr_length), &curr_length); - if (error) { - *err = error; - return -1; - } - cursor += (sizeof(curr_length) + curr_length); - } - } while (curr_type != type); - - return cursor; -} - -static int -necp_packet_get_tlv_at_offset(mbuf_t packet, int tlv_offset, u_int32_t buff_len, void *buff, u_int32_t *value_size) -{ - int error = 0; - u_int32_t length; - - if (tlv_offset < 0) { - return EINVAL; - } - - error = mbuf_copydata(packet, tlv_offset + sizeof(u_int8_t), sizeof(length), &length); - if (error) { - return error; - } - - u_int32_t total_len = m_length2(packet, NULL); - if (total_len < (tlv_offset + sizeof(u_int8_t) + sizeof(length) + length)) { - NECPLOG(LOG_ERR, "Got a bad TLV, length (%u) + offset (%d) < total length (%u)", - length, (tlv_offset + sizeof(u_int8_t) + sizeof(length)), total_len); - return EINVAL; - } - - if (value_size != NULL) { - *value_size = length; - } - - if (buff != NULL && buff_len > 0) { - u_int32_t to_copy = (length < buff_len) ? length : buff_len; - error = mbuf_copydata(packet, tlv_offset + sizeof(u_int8_t) + sizeof(length), to_copy, buff); - if (error) { - return error; - } - } - - return 0; -} - -static u_int8_t * -necp_buffer_write_packet_header(u_int8_t *buffer, u_int8_t packet_type, u_int8_t flags, u_int32_t message_id) -{ - ((struct necp_packet_header *)(void *)buffer)->packet_type = packet_type; - ((struct necp_packet_header *)(void *)buffer)->flags = flags; - ((struct necp_packet_header *)(void *)buffer)->message_id = message_id; - return buffer + sizeof(struct necp_packet_header); -} - 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) @@ -1442,7 +1450,8 @@ necp_buffer_write_tlv_if_different(u_int8_t *cursor, u_int8_t type, u_int8_t *buffer, u_int32_t buffer_length) { if (!necp_buffer_write_tlv_validate(cursor, type, length, buffer, buffer_length)) { - return NULL; + // 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) { @@ -1524,9 +1533,15 @@ necp_buffer_get_tlv_value(u_int8_t *buffer, int tlv_offset, u_int32_t *value_siz } int -necp_buffer_find_tlv(u_int8_t *buffer, u_int32_t buffer_length, int offset, u_int8_t type, int next) +necp_buffer_find_tlv(u_int8_t *buffer, u_int32_t buffer_length, int offset, u_int8_t type, int *err, int next) { + if (err != NULL) { + *err = ENOENT; + } if (offset < 0) { + if (err != NULL) { + *err = EINVAL; + } return -1; } int cursor = offset; @@ -1553,6 +1568,9 @@ necp_buffer_find_tlv(u_int8_t *buffer, u_int32_t buffer_length, int offset, u_in if (curr_type == type) { // check if entire TLV fits inside buffer if (((u_int32_t)next_cursor) <= buffer_length) { + if (err != NULL) { + *err = 0; + } return cursor; } else { return -1; @@ -1563,26 +1581,19 @@ necp_buffer_find_tlv(u_int8_t *buffer, u_int32_t buffer_length, int offset, u_in } static int -necp_find_tlv(mbuf_t packet, u_int8_t *buffer, u_int32_t buffer_length, int offset, u_int8_t type, int *err, int next) +necp_find_tlv(u_int8_t *buffer, u_int32_t buffer_length, int offset, u_int8_t type, int *err, int next) { int cursor = -1; - if (packet != NULL) { - cursor = necp_packet_find_tlv(packet, offset, type, err, next); - } else if (buffer != NULL) { - cursor = necp_buffer_find_tlv(buffer, buffer_length, offset, type, next); + if (buffer != NULL) { + cursor = necp_buffer_find_tlv(buffer, buffer_length, offset, type, err, next); } return cursor; } static int -necp_get_tlv_at_offset(mbuf_t packet, u_int8_t *buffer, u_int32_t buffer_length, +necp_get_tlv_at_offset(u_int8_t *buffer, u_int32_t buffer_length, int tlv_offset, u_int32_t out_buffer_length, void *out_buffer, u_int32_t *value_size) { - if (packet != NULL) { - // Handle mbuf parsing - return necp_packet_get_tlv_at_offset(packet, tlv_offset, out_buffer_length, out_buffer, value_size); - } - if (buffer == NULL) { NECPLOG0(LOG_ERR, "necp_get_tlv_at_offset buffer is NULL"); return EINVAL; @@ -1633,300 +1644,98 @@ necp_get_tlv_at_offset(mbuf_t packet, u_int8_t *buffer, u_int32_t buffer_length, } static int -necp_get_tlv(mbuf_t packet, u_int8_t *buffer, u_int32_t buffer_length, +necp_get_tlv(u_int8_t *buffer, u_int32_t buffer_length, int offset, u_int8_t type, u_int32_t buff_len, void *buff, u_int32_t *value_size) { int error = 0; - int tlv_offset = necp_find_tlv(packet, buffer, buffer_length, offset, type, &error, 0); + int tlv_offset = necp_find_tlv(buffer, buffer_length, offset, type, &error, 0); if (tlv_offset < 0) { return error; } - return necp_get_tlv_at_offset(packet, buffer, buffer_length, tlv_offset, buff_len, buff, value_size); + return necp_get_tlv_at_offset(buffer, buffer_length, tlv_offset, buff_len, buff, value_size); } -static bool -necp_send_ctl_data(struct necp_session *session, u_int8_t *buffer, size_t buffer_size) +// Session Management + +static struct necp_session * +necp_create_session(void) { - int error; + struct necp_session *new_session = NULL; - if (necp_kctlref == NULL || session == NULL || buffer == NULL || buffer_size == 0) { - return FALSE; + MALLOC(new_session, struct necp_session *, sizeof(*new_session), M_NECP, M_WAITOK | M_ZERO); + if (new_session == NULL) { + goto done; } - error = ctl_enqueuedata(necp_kctlref, session->control_unit, buffer, buffer_size, CTL_DATA_EOR); + new_session->necp_fd_type = necp_fd_type_session; + new_session->session_priority = NECP_SESSION_PRIORITY_UNKNOWN; + new_session->dirty = FALSE; + LIST_INIT(&new_session->policies); + lck_mtx_init(&new_session->lock, necp_kernel_policy_mtx_grp, necp_kernel_policy_mtx_attr); - return error == 0; -} + // Take the lock + lck_rw_lock_exclusive(&necp_kernel_policy_lock); -static bool -necp_send_success_response(struct necp_session *session, u_int8_t packet_type, u_int32_t message_id) -{ - bool success = TRUE; - u_int8_t *response = NULL; - u_int8_t *cursor = NULL; - size_t response_size = sizeof(struct necp_packet_header) + sizeof(u_int8_t) + sizeof(u_int32_t); - MALLOC(response, u_int8_t *, response_size, M_NECP, M_WAITOK); - if (response == NULL) { - return FALSE; - } - cursor = response; - cursor = necp_buffer_write_packet_header(cursor, packet_type, NECP_PACKET_FLAGS_RESPONSE, message_id); - cursor = necp_buffer_write_tlv(cursor, NECP_TLV_NIL, 0, NULL, response, response_size); + // Find the next available control unit + u_int32_t control_unit = 1; + struct necp_session *next_session = NULL; + TAILQ_FOREACH(next_session, &necp_session_list, chain) { + if (next_session->control_unit > control_unit) { + // Found a gap, grab this control unit + break; + } - if (!(success = necp_send_ctl_data(session, (u_int8_t *)response, response_size))) { - NECPLOG0(LOG_ERR, "Failed to send response"); + // Try the next control unit, loop around + control_unit = next_session->control_unit + 1; } - FREE(response, M_NECP); - return success; -} + new_session->control_unit = control_unit; + new_session->session_order = necp_allocate_new_session_order(new_session->session_priority, control_unit); -static bool -necp_send_error_response(struct necp_session *session, u_int8_t packet_type, u_int32_t message_id, u_int32_t error) -{ - bool success = TRUE; - u_int8_t *response = NULL; - u_int8_t *cursor = NULL; - size_t response_size = sizeof(struct necp_packet_header) + sizeof(u_int8_t) + sizeof(u_int32_t) + sizeof(u_int32_t); - MALLOC(response, u_int8_t *, response_size, M_NECP, M_WAITOK); - if (response == NULL) { - return FALSE; + if (next_session != NULL) { + TAILQ_INSERT_BEFORE(next_session, new_session, chain); + } else { + TAILQ_INSERT_TAIL(&necp_session_list, new_session, chain); } - cursor = response; - cursor = necp_buffer_write_packet_header(cursor, packet_type, NECP_PACKET_FLAGS_RESPONSE, message_id); - cursor = necp_buffer_write_tlv(cursor, NECP_TLV_ERROR, sizeof(error), &error, response, response_size); - if (!(success = necp_send_ctl_data(session, (u_int8_t *)response, response_size))) { - NECPLOG0(LOG_ERR, "Failed to send response"); + necp_session_count++; + lck_rw_done(&necp_kernel_policy_lock); + + if (necp_debug) { + NECPLOG(LOG_DEBUG, "Created NECP session, control unit %d", control_unit); } - FREE(response, M_NECP); - return success; +done: + return new_session; } -static bool -necp_send_policy_id_response(struct necp_session *session, u_int8_t packet_type, u_int32_t message_id, necp_policy_id policy_id) +static void +necp_delete_session(struct necp_session *session) { - bool success = TRUE; - u_int8_t *response = NULL; - u_int8_t *cursor = NULL; - size_t response_size = sizeof(struct necp_packet_header) + sizeof(u_int8_t) + sizeof(u_int32_t) + sizeof(u_int32_t); - MALLOC(response, u_int8_t *, response_size, M_NECP, M_WAITOK); - if (response == NULL) { - return FALSE; - } - cursor = response; - cursor = necp_buffer_write_packet_header(cursor, packet_type, NECP_PACKET_FLAGS_RESPONSE, message_id); - cursor = necp_buffer_write_tlv(cursor, NECP_TLV_POLICY_ID, sizeof(policy_id), &policy_id, response, response_size); + if (session != NULL) { + struct necp_service_registration *service = NULL; + struct necp_service_registration *temp_service = NULL; + LIST_FOREACH_SAFE(service, &session->services, session_chain, temp_service) { + LIST_REMOVE(service, session_chain); + lck_rw_lock_exclusive(&necp_kernel_policy_lock); + LIST_REMOVE(service, kernel_chain); + lck_rw_done(&necp_kernel_policy_lock); + FREE(service, M_NECP); + } + if (necp_debug) { + NECPLOG0(LOG_DEBUG, "Deleted NECP session"); + } - if (!(success = necp_send_ctl_data(session, (u_int8_t *)response, response_size))) { - NECPLOG0(LOG_ERR, "Failed to send response"); - } + lck_rw_lock_exclusive(&necp_kernel_policy_lock); + TAILQ_REMOVE(&necp_session_list, session, chain); + necp_session_count--; + lck_rw_done(&necp_kernel_policy_lock); - FREE(response, M_NECP); - return success; -} - -static errno_t -necp_ctl_send(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, mbuf_t packet, int flags) -{ -#pragma unused(kctlref, unit, flags) - struct necp_session *session = (struct necp_session *)unitinfo; - struct necp_packet_header header; - int error = 0; - - if (session == NULL) { - NECPLOG0(LOG_ERR, "Got a NULL session"); - error = EINVAL; - goto done; - } - - if (mbuf_pkthdr_len(packet) < sizeof(header)) { - NECPLOG(LOG_ERR, "Got a bad packet, length (%lu) < sizeof header (%lu)", mbuf_pkthdr_len(packet), sizeof(header)); - error = EINVAL; - goto done; - } - - error = mbuf_copydata(packet, 0, sizeof(header), &header); - if (error) { - NECPLOG(LOG_ERR, "mbuf_copydata failed for the header: %d", error); - error = ENOBUFS; - goto done; - } - - if (session->proc_locked) { - // Verify that the calling process is allowed to send messages - uuid_t proc_uuid; - proc_getexecutableuuid(current_proc(), proc_uuid, sizeof(proc_uuid)); - if (uuid_compare(proc_uuid, session->proc_uuid) != 0) { - necp_send_error_response(session, header.packet_type, header.message_id, NECP_ERROR_INVALID_PROCESS); - 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()); - } - - switch (header.packet_type) { - case NECP_PACKET_TYPE_POLICY_ADD: { - necp_handle_policy_add(session, header.message_id, packet, NULL, 0, sizeof(header), NULL); - break; - } - case NECP_PACKET_TYPE_POLICY_GET: { - necp_handle_policy_get(session, header.message_id, packet, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_POLICY_DELETE: { - necp_handle_policy_delete(session, header.message_id, packet, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_POLICY_APPLY_ALL: { - necp_handle_policy_apply_all(session, header.message_id, packet, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_POLICY_LIST_ALL: { - necp_handle_policy_list_all(session, header.message_id, packet, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_POLICY_DELETE_ALL: { - necp_handle_policy_delete_all(session, header.message_id, packet, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_POLICY_DUMP_ALL: { - necp_handle_policy_dump_all(session, header.message_id, packet, 0, 0, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_SET_SESSION_PRIORITY: { - necp_handle_set_session_priority(session, header.message_id, packet, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_LOCK_SESSION_TO_PROC: { - necp_handle_lock_session_to_proc(session, header.message_id, packet, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_REGISTER_SERVICE: { - necp_handle_register_service(session, header.message_id, packet, sizeof(header)); - break; - } - case NECP_PACKET_TYPE_UNREGISTER_SERVICE: { - necp_handle_unregister_service(session, header.message_id, packet, sizeof(header)); - break; - } - default: { - NECPLOG(LOG_ERR, "Received unknown message type %d", header.packet_type); - necp_send_error_response(session, header.packet_type, header.message_id, NECP_ERROR_UNKNOWN_PACKET_TYPE); - break; - } - } - -done: - mbuf_freem(packet); - return error; -} - -static void -necp_ctl_rcvd(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int flags) -{ -#pragma unused(kctlref, unit, unitinfo, flags) - return; -} - -static errno_t -necp_ctl_getopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int opt, void *data, size_t *len) -{ -#pragma unused(kctlref, unit, unitinfo, opt, data, len) - return 0; -} - -static errno_t -necp_ctl_setopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int opt, void *data, size_t len) -{ -#pragma unused(kctlref, unit, unitinfo, opt, data, len) - return 0; -} - -// Session Management - -static struct necp_session * -necp_create_session(void) -{ - struct necp_session *new_session = NULL; - - MALLOC(new_session, struct necp_session *, sizeof(*new_session), M_NECP, M_WAITOK | M_ZERO); - if (new_session == NULL) { - goto done; - } - - new_session->necp_fd_type = necp_fd_type_session; - new_session->session_priority = NECP_SESSION_PRIORITY_UNKNOWN; - new_session->dirty = FALSE; - LIST_INIT(&new_session->policies); - lck_mtx_init(&new_session->lock, necp_kernel_policy_mtx_grp, necp_kernel_policy_mtx_attr); - - // Take the lock - lck_rw_lock_exclusive(&necp_kernel_policy_lock); - - // Find the next available control unit - u_int32_t control_unit = 1; - struct necp_session *next_session = NULL; - TAILQ_FOREACH(next_session, &necp_session_list, chain) { - if (next_session->control_unit > control_unit) { - // Found a gap, grab this control unit - break; - } - - // Try the next control unit, loop around - control_unit = next_session->control_unit + 1; - } - - new_session->control_unit = control_unit; - new_session->session_order = necp_allocate_new_session_order(new_session->session_priority, control_unit); - - if (next_session != NULL) { - TAILQ_INSERT_BEFORE(next_session, new_session, chain); - } else { - TAILQ_INSERT_TAIL(&necp_session_list, new_session, chain); - } - - necp_session_count++; - lck_rw_done(&necp_kernel_policy_lock); - - if (necp_debug) { - NECPLOG(LOG_DEBUG, "Created NECP session, control unit %d", control_unit); - } - -done: - return new_session; -} - -static void -necp_delete_session(struct necp_session *session) -{ - if (session != NULL) { - struct necp_service_registration *service = NULL; - struct necp_service_registration *temp_service = NULL; - LIST_FOREACH_SAFE(service, &session->services, session_chain, temp_service) { - LIST_REMOVE(service, session_chain); - lck_rw_lock_exclusive(&necp_kernel_policy_lock); - LIST_REMOVE(service, kernel_chain); - lck_rw_done(&necp_kernel_policy_lock); - FREE(service, M_NECP); - } - if (necp_debug) { - NECPLOG0(LOG_DEBUG, "Deleted NECP session"); - } - - lck_rw_lock_exclusive(&necp_kernel_policy_lock); - TAILQ_REMOVE(&necp_session_list, session, chain); - necp_session_count--; - lck_rw_done(&necp_kernel_policy_lock); - - lck_mtx_destroy(&session->lock, necp_kernel_policy_mtx_grp); - FREE(session, M_NECP); - } + lck_mtx_destroy(&session->lock, necp_kernel_policy_mtx_grp); + FREE(session, M_NECP); + } } // Session Policy Management @@ -1981,7 +1790,8 @@ necp_policy_result_is_valid(u_int8_t *buffer, u_int32_t length) case NECP_POLICY_RESULT_PASS: case NECP_POLICY_RESULT_DROP: case NECP_POLICY_RESULT_ROUTE_RULES: - case NECP_POLICY_RESULT_SCOPED_DIRECT: { + case NECP_POLICY_RESULT_SCOPED_DIRECT: + case NECP_POLICY_RESULT_ALLOW_UNENTITLED: { validated = TRUE; break; } @@ -2099,7 +1909,8 @@ necp_policy_condition_is_valid(u_int8_t *buffer, u_int32_t length, u_int8_t poli policy_result_type == NECP_POLICY_RESULT_ROUTE_RULES || policy_result_type == NECP_POLICY_RESULT_USE_NETAGENT || policy_result_type == NECP_POLICY_RESULT_NETAGENT_SCOPED || - policy_result_type == NECP_POLICY_RESULT_SCOPED_DIRECT) ? TRUE : FALSE; + policy_result_type == NECP_POLICY_RESULT_SCOPED_DIRECT || + policy_result_type == NECP_POLICY_RESULT_ALLOW_UNENTITLED) ? TRUE : FALSE; u_int32_t condition_length = necp_policy_condition_get_value_length_from_buffer(buffer, length); u_int8_t *condition_value = necp_policy_condition_get_value_pointer_from_buffer(buffer, length); u_int8_t type = necp_policy_condition_get_type_from_buffer(buffer, length); @@ -2131,7 +1942,10 @@ necp_policy_condition_is_valid(u_int8_t *buffer, u_int32_t length, u_int8_t poli } case NECP_POLICY_CONDITION_DEFAULT: case NECP_POLICY_CONDITION_ALL_INTERFACES: - case NECP_POLICY_CONDITION_ENTITLEMENT: { + case NECP_POLICY_CONDITION_ENTITLEMENT: + case NECP_POLICY_CONDITION_PLATFORM_BINARY: + case NECP_POLICY_CONDITION_HAS_CLIENT: + case NECP_POLICY_CONDITION_LOCAL_NETWORKS: { if (!(flags & NECP_POLICY_CONDITION_FLAGS_NEGATIVE)) { validated = TRUE; } @@ -2181,6 +1995,43 @@ necp_policy_condition_is_valid(u_int8_t *buffer, u_int32_t length, u_int8_t poli } break; } + case NECP_POLICY_CONDITION_FLOW_IP_PROTOCOL: { + if (condition_length >= sizeof(u_int16_t)) { + validated = TRUE; + } + break; + } + case NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR: + case NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR: { + if (condition_length >= sizeof(struct necp_policy_condition_addr) && + necp_address_is_valid(&((struct necp_policy_condition_addr *)(void *)condition_value)->address.sa)) { + validated = TRUE; + } + break; + } + case NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR_RANGE: + case NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR_RANGE: { + if (condition_length >= sizeof(struct necp_policy_condition_addr_range) && + necp_address_is_valid(&((struct necp_policy_condition_addr_range *)(void *)condition_value)->start_address.sa) && + necp_address_is_valid(&((struct necp_policy_condition_addr_range *)(void *)condition_value)->end_address.sa)) { + validated = TRUE; + } + break; + } + case NECP_POLICY_CONDITION_CLIENT_FLAGS: { + if (condition_length == 0 || condition_length >= sizeof(u_int32_t)) { + validated = TRUE; + } + break; + } + case NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR_EMPTY: { + validated = TRUE; + break; + } + case NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR_EMPTY: { + validated = TRUE; + break; + } default: { validated = FALSE; break; @@ -2260,164 +2111,8 @@ necp_get_posix_error_for_necp_error(int response_error) } } -static void -necp_handle_set_session_priority(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ - int error; - struct necp_session_policy *policy = NULL; - struct necp_session_policy *temp_policy = NULL; - u_int32_t response_error = NECP_ERROR_INTERNAL; - u_int32_t requested_session_priority = NECP_SESSION_PRIORITY_UNKNOWN; - - // Read policy id - error = necp_get_tlv(packet, NULL, 0, offset, NECP_TLV_SESSION_PRIORITY, sizeof(requested_session_priority), &requested_session_priority, NULL); - if (error) { - NECPLOG(LOG_ERR, "Failed to get session priority: %d", error); - response_error = NECP_ERROR_INVALID_TLV; - goto fail; - } - - if (session == NULL) { - NECPLOG0(LOG_ERR, "Failed to find session"); - response_error = NECP_ERROR_INTERNAL; - goto fail; - } - - // 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); - goto fail; - } - } - - 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; - } - } - - necp_send_success_response(session, NECP_PACKET_TYPE_SET_SESSION_PRIORITY, message_id); - return; - -fail: - necp_send_error_response(session, NECP_PACKET_TYPE_SET_SESSION_PRIORITY, message_id, response_error); -} - -static void -necp_handle_lock_session_to_proc(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ -#pragma unused(packet, offset) - // proc_uuid already filled out - session->proc_locked = TRUE; - necp_send_success_response(session, NECP_PACKET_TYPE_LOCK_SESSION_TO_PROC, message_id); -} - -static void -necp_handle_register_service(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ - int error; - struct necp_service_registration *new_service = NULL; - u_int32_t response_error = NECP_ERROR_INTERNAL; - uuid_t service_uuid; - uuid_clear(service_uuid); - - if (session == NULL) { - NECPLOG0(LOG_ERR, "Failed to find session"); - response_error = NECP_ERROR_INTERNAL; - goto fail; - } - - // Enforce entitlements - errno_t cred_result = priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NECP_POLICIES, 0); - if (cred_result != 0) { - NECPLOG0(LOG_ERR, "Session does not hold necessary entitlement to register service"); - goto fail; - } - - // Read service uuid - error = necp_get_tlv(packet, NULL, 0, offset, NECP_TLV_SERVICE_UUID, sizeof(uuid_t), service_uuid, NULL); - if (error) { - NECPLOG(LOG_ERR, "Failed to get service UUID: %d", error); - response_error = NECP_ERROR_INVALID_TLV; - goto fail; - } - - MALLOC(new_service, struct necp_service_registration *, sizeof(*new_service), M_NECP, M_WAITOK); - if (new_service == NULL) { - NECPLOG0(LOG_ERR, "Failed to allocate service registration"); - response_error = NECP_ERROR_INTERNAL; - goto fail; - } - - lck_rw_lock_exclusive(&necp_kernel_policy_lock); - memset(new_service, 0, sizeof(*new_service)); - 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); - - necp_send_success_response(session, NECP_PACKET_TYPE_REGISTER_SERVICE, message_id); - return; -fail: - necp_send_error_response(session, NECP_PACKET_TYPE_REGISTER_SERVICE, message_id, response_error); -} - -static void -necp_handle_unregister_service(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ - int error; - struct necp_service_registration *service = NULL; - struct necp_service_registration *temp_service = NULL; - u_int32_t response_error = NECP_ERROR_INTERNAL; - struct necp_uuid_id_mapping *mapping = NULL; - uuid_t service_uuid; - uuid_clear(service_uuid); - - if (session == NULL) { - NECPLOG0(LOG_ERR, "Failed to find session"); - response_error = NECP_ERROR_INTERNAL; - goto fail; - } - - // Read service uuid - error = necp_get_tlv(packet, NULL, 0, offset, NECP_TLV_SERVICE_UUID, sizeof(uuid_t), service_uuid, NULL); - if (error) { - NECPLOG(LOG_ERR, "Failed to get service UUID: %d", error); - response_error = NECP_ERROR_INVALID_TLV; - goto fail; - } - - // Mark 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); - - necp_send_success_response(session, NECP_PACKET_TYPE_UNREGISTER_SERVICE, message_id); - return; -fail: - necp_send_error_response(session, NECP_PACKET_TYPE_UNREGISTER_SERVICE, message_id, response_error); -} - static necp_policy_id -necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_t packet, +necp_handle_policy_add(struct necp_session *session, u_int8_t *tlv_buffer, size_t tlv_buffer_length, int offset, int *return_error) { bool has_default_condition = FALSE; @@ -2445,7 +2140,7 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ u_int32_t policy_result_size = 0; // Read policy order - error = necp_get_tlv(packet, tlv_buffer, tlv_buffer_length, offset, NECP_TLV_POLICY_ORDER, sizeof(order), &order, NULL); + error = necp_get_tlv(tlv_buffer, tlv_buffer_length, offset, NECP_TLV_POLICY_ORDER, sizeof(order), &order, NULL); if (error) { NECPLOG(LOG_ERR, "Failed to get policy order: %d", error); response_error = NECP_ERROR_INVALID_TLV; @@ -2453,8 +2148,13 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ } // Read policy result - cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, offset, NECP_TLV_POLICY_RESULT, &error, 0); - error = necp_get_tlv_at_offset(packet, tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &policy_result_size); + cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, offset, NECP_TLV_POLICY_RESULT, &error, 0); + if (error || cursor < 0) { + NECPLOG(LOG_ERR, "Failed to find policy result TLV: %d", error); + response_error = NECP_ERROR_INVALID_TLV; + goto fail; + } + error = necp_get_tlv_at_offset(tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &policy_result_size); if (error || policy_result_size == 0) { NECPLOG(LOG_ERR, "Failed to get policy result length: %d", error); response_error = NECP_ERROR_INVALID_TLV; @@ -2471,7 +2171,7 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ response_error = NECP_ERROR_INTERNAL; goto fail; } - error = necp_get_tlv_at_offset(packet, tlv_buffer, tlv_buffer_length, cursor, policy_result_size, policy_result, NULL); + error = necp_get_tlv_at_offset(tlv_buffer, tlv_buffer_length, cursor, policy_result_size, policy_result, NULL); if (error) { NECPLOG(LOG_ERR, "Failed to get policy result: %d", error); response_error = NECP_ERROR_POLICY_RESULT_INVALID; @@ -2485,13 +2185,17 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ if (necp_policy_result_requires_route_rules(policy_result, policy_result_size)) { // Read route rules conditions - for (cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, offset, NECP_TLV_ROUTE_RULE, &error, 0); + for (cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, offset, NECP_TLV_ROUTE_RULE, &error, 0); cursor >= 0; - cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, cursor, NECP_TLV_ROUTE_RULE, &error, 1)) { + cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, cursor, NECP_TLV_ROUTE_RULE, &error, 1)) { u_int32_t route_rule_size = 0; - necp_get_tlv_at_offset(packet, tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &route_rule_size); - if (route_rule_size > 0) { - route_rules_array_size += (sizeof(u_int8_t) + sizeof(u_int32_t) + route_rule_size); + necp_get_tlv_at_offset(tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &route_rule_size); + if (os_add_overflow(route_rules_array_size, + (sizeof(u_int8_t) + sizeof(u_int32_t) + route_rule_size), + &route_rules_array_size)) { + NECPLOG0(LOG_ERR, "Route rules size overflowed, too large"); + response_error = NECP_ERROR_INVALID_TLV; + goto fail; } } @@ -2513,13 +2217,14 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ } route_rules_array_cursor = 0; - for (cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, offset, NECP_TLV_ROUTE_RULE, &error, 0); + for (cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, offset, NECP_TLV_ROUTE_RULE, &error, 0); cursor >= 0; - cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, cursor, NECP_TLV_ROUTE_RULE, &error, 1)) { + cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, cursor, NECP_TLV_ROUTE_RULE, &error, 1)) { u_int8_t route_rule_type = NECP_TLV_ROUTE_RULE; u_int32_t route_rule_size = 0; - necp_get_tlv_at_offset(packet, tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &route_rule_size); - if (route_rule_size > 0 && route_rule_size <= (route_rules_array_size - route_rules_array_cursor)) { + necp_get_tlv_at_offset(tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &route_rule_size); + if (route_rule_size > 0 && + (sizeof(route_rule_type) + sizeof(route_rule_size) + route_rule_size) <= (route_rules_array_size - route_rules_array_cursor)) { // Add type memcpy((route_rules_array + route_rules_array_cursor), &route_rule_type, sizeof(route_rule_type)); route_rules_array_cursor += sizeof(route_rule_type); @@ -2529,7 +2234,7 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ route_rules_array_cursor += sizeof(route_rule_size); // Add value - necp_get_tlv_at_offset(packet, tlv_buffer, tlv_buffer_length, cursor, route_rule_size, (route_rules_array + route_rules_array_cursor), NULL); + necp_get_tlv_at_offset(tlv_buffer, tlv_buffer_length, cursor, route_rule_size, (route_rules_array + route_rules_array_cursor), NULL); if (!necp_policy_route_rule_is_valid((route_rules_array + route_rules_array_cursor), route_rule_size)) { NECPLOG0(LOG_ERR, "Failed to validate policy route rule"); @@ -2552,14 +2257,20 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ } // Read policy conditions - for (cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, offset, NECP_TLV_POLICY_CONDITION, &error, 0); + for (cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, offset, NECP_TLV_POLICY_CONDITION, &error, 0); cursor >= 0; - cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) { + cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) { u_int32_t condition_size = 0; - necp_get_tlv_at_offset(packet, tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &condition_size); + necp_get_tlv_at_offset(tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &condition_size); if (condition_size > 0) { - conditions_array_size += (sizeof(u_int8_t) + sizeof(u_int32_t) + condition_size); + if (os_add_overflow(conditions_array_size, + (sizeof(u_int8_t) + sizeof(u_int32_t) + condition_size), + &conditions_array_size)) { + NECPLOG0(LOG_ERR, "Conditions size overflowed, too large"); + response_error = NECP_ERROR_INVALID_TLV; + goto fail; + } } } @@ -2581,13 +2292,14 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ } conditions_array_cursor = 0; - for (cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, offset, NECP_TLV_POLICY_CONDITION, &error, 0); + for (cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, offset, NECP_TLV_POLICY_CONDITION, &error, 0); cursor >= 0; - cursor = necp_find_tlv(packet, tlv_buffer, tlv_buffer_length, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) { + cursor = necp_find_tlv(tlv_buffer, tlv_buffer_length, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) { u_int8_t condition_type = NECP_TLV_POLICY_CONDITION; u_int32_t condition_size = 0; - necp_get_tlv_at_offset(packet, tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &condition_size); - if (condition_size > 0 && condition_size <= (conditions_array_size - conditions_array_cursor)) { + necp_get_tlv_at_offset(tlv_buffer, tlv_buffer_length, cursor, 0, NULL, &condition_size); + if (condition_size > 0 && + (sizeof(condition_type) + sizeof(condition_size) + condition_size) <= (conditions_array_size - conditions_array_cursor)) { // Add type memcpy((conditions_array + conditions_array_cursor), &condition_type, sizeof(condition_type)); conditions_array_cursor += sizeof(condition_type); @@ -2597,7 +2309,7 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ conditions_array_cursor += sizeof(condition_size); // Add value - necp_get_tlv_at_offset(packet, tlv_buffer, tlv_buffer_length, cursor, condition_size, (conditions_array + conditions_array_cursor), NULL); + necp_get_tlv_at_offset(tlv_buffer, tlv_buffer_length, cursor, condition_size, (conditions_array + conditions_array_cursor), NULL); if (!necp_policy_condition_is_valid((conditions_array + conditions_array_cursor), condition_size, necp_policy_result_get_type_from_buffer(policy_result, policy_result_size))) { NECPLOG0(LOG_ERR, "Failed to validate policy condition"); response_error = NECP_ERROR_POLICY_CONDITIONS_INVALID; @@ -2652,9 +2364,6 @@ necp_handle_policy_add(struct necp_session *session, u_int32_t message_id, mbuf_ goto fail; } - if (packet != NULL) { - necp_send_policy_id_response(session, NECP_PACKET_TYPE_POLICY_ADD, message_id, policy->local_id); - } return policy->local_id; fail: @@ -2668,168 +2377,12 @@ fail: FREE(route_rules_array, M_NECP); } - if (packet != NULL) { - necp_send_error_response(session, NECP_PACKET_TYPE_POLICY_ADD, message_id, response_error); - } if (return_error != NULL) { *return_error = necp_get_posix_error_for_necp_error(response_error); } return 0; } -static void -necp_handle_policy_get(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ -#pragma unused(offset) - int error; - u_int8_t *response = NULL; - u_int8_t *cursor = NULL; - u_int32_t response_error = NECP_ERROR_INTERNAL; - necp_policy_id policy_id = 0; - u_int32_t order_tlv_size = 0; - u_int32_t result_tlv_size = 0; - u_int32_t response_size = 0; - - struct necp_session_policy *policy = NULL; - - // Read policy id - error = necp_get_tlv(packet, NULL, 0, offset, NECP_TLV_POLICY_ID, sizeof(policy_id), &policy_id, NULL); - if (error) { - NECPLOG(LOG_ERR, "Failed to get policy id: %d", error); - response_error = NECP_ERROR_INVALID_TLV; - goto fail; - } - - 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); - response_error = NECP_ERROR_POLICY_ID_NOT_FOUND; - goto fail; - } - - order_tlv_size = sizeof(u_int8_t) + sizeof(u_int32_t) + sizeof(necp_policy_order); - result_tlv_size = (policy->result_size ? (sizeof(u_int8_t) + sizeof(u_int32_t) + policy->result_size) : 0); - response_size = sizeof(struct necp_packet_header) + order_tlv_size + result_tlv_size + policy->conditions_size; - MALLOC(response, u_int8_t *, response_size, M_NECP, M_WAITOK); - if (response == NULL) { - necp_send_error_response(session, NECP_PACKET_TYPE_POLICY_GET, message_id, NECP_ERROR_INTERNAL); - return; - } - - cursor = response; - cursor = necp_buffer_write_packet_header(cursor, NECP_PACKET_TYPE_POLICY_GET, NECP_PACKET_FLAGS_RESPONSE, message_id); - 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); - } - - if (!necp_send_ctl_data(session, (u_int8_t *)response, response_size)) { - NECPLOG0(LOG_ERR, "Failed to send response"); - } - - FREE(response, M_NECP); - return; - -fail: - necp_send_error_response(session, NECP_PACKET_TYPE_POLICY_GET, message_id, response_error); -} - -static void -necp_handle_policy_delete(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ - int error; - u_int32_t response_error = NECP_ERROR_INTERNAL; - necp_policy_id policy_id = 0; - - struct necp_session_policy *policy = NULL; - - // Read policy id - error = necp_get_tlv(packet, NULL, 0, offset, NECP_TLV_POLICY_ID, sizeof(policy_id), &policy_id, NULL); - if (error) { - NECPLOG(LOG_ERR, "Failed to get policy id: %d", error); - response_error = NECP_ERROR_INVALID_TLV; - goto fail; - } - - 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); - response_error = NECP_ERROR_POLICY_ID_NOT_FOUND; - goto fail; - } - - necp_policy_mark_for_deletion(session, policy); - - necp_send_success_response(session, NECP_PACKET_TYPE_POLICY_DELETE, message_id); - return; - -fail: - necp_send_error_response(session, NECP_PACKET_TYPE_POLICY_DELETE, message_id, response_error); -} - -static void -necp_handle_policy_apply_all(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ -#pragma unused(packet, offset) - necp_policy_apply_all(session); - necp_send_success_response(session, NECP_PACKET_TYPE_POLICY_APPLY_ALL, message_id); -} - -static void -necp_handle_policy_list_all(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ -#pragma unused(packet, offset) - u_int32_t tlv_size = (sizeof(u_int8_t) + sizeof(u_int32_t) + sizeof(u_int32_t)); - u_int32_t response_size = 0; - u_int8_t *response = NULL; - u_int8_t *cursor = NULL; - int num_policies = 0; - int cur_policy_index = 0; - struct necp_session_policy *policy; - - LIST_FOREACH(policy, &session->policies, chain) { - if (!policy->pending_deletion) { - num_policies++; - } - } - - // Create a response with one Policy ID TLV for each policy - response_size = sizeof(struct necp_packet_header) + num_policies * tlv_size; - MALLOC(response, u_int8_t *, response_size, M_NECP, M_WAITOK); - if (response == NULL) { - necp_send_error_response(session, NECP_PACKET_TYPE_POLICY_LIST_ALL, message_id, NECP_ERROR_INTERNAL); - return; - } - - cursor = response; - cursor = necp_buffer_write_packet_header(cursor, NECP_PACKET_TYPE_POLICY_LIST_ALL, NECP_PACKET_FLAGS_RESPONSE, message_id); - - 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++; - } - } - - if (!necp_send_ctl_data(session, (u_int8_t *)response, response_size)) { - NECPLOG0(LOG_ERR, "Failed to send response"); - } - - FREE(response, M_NECP); -} - -static void -necp_handle_policy_delete_all(struct necp_session *session, u_int32_t message_id, mbuf_t packet, int offset) -{ -#pragma unused(packet, offset) - necp_policy_mark_all_for_deletion(session); - necp_send_success_response(session, NECP_PACKET_TYPE_POLICY_DELETE_ALL, message_id); -} - static necp_policy_id necp_policy_get_new_id(struct necp_session *session) { @@ -2945,10 +2498,8 @@ necp_policy_get_new_id(struct necp_session *session) * ... */ static int -necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, mbuf_t packet, - user_addr_t out_buffer, size_t out_buffer_length, int offset) +necp_handle_policy_dump_all(user_addr_t out_buffer, size_t out_buffer_length) { -#pragma unused(offset) struct necp_kernel_socket_policy *policy = NULL; int policy_i; int policy_count = 0; @@ -3035,6 +2586,9 @@ necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, if (condition_mask & NECP_KERNEL_CONDITION_ALL_INTERFACES) { num_conditions++; } + if (condition_mask & NECP_KERNEL_CONDITION_HAS_CLIENT) { + num_conditions++; + } if (condition_mask & NECP_KERNEL_CONDITION_BOUND_INTERFACE) { snprintf(if_name, IFXNAMSIZ, "%s%d", ifnet_name(policy->cond_bound_interface), ifnet_unit(policy->cond_bound_interface)); condition_tlv_length += strlen(if_name) + 1; @@ -3086,6 +2640,12 @@ necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, condition_tlv_length += entitlement_len; num_conditions++; } + if (condition_mask & NECP_KERNEL_CONDITION_PLATFORM_BINARY) { + num_conditions++; + } + if (condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS) { + num_conditions++; + } if (condition_mask & NECP_KERNEL_CONDITION_LOCAL_START) { if (condition_mask & NECP_KERNEL_CONDITION_LOCAL_END) { condition_tlv_length += sizeof(struct necp_policy_condition_addr_range); @@ -3106,6 +2666,16 @@ necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, condition_tlv_length += sizeof(struct necp_policy_condition_agent_type); num_conditions++; } + if (condition_mask & NECP_KERNEL_CONDITION_CLIENT_FLAGS) { + condition_tlv_length += sizeof(u_int32_t); + num_conditions++; + } + if (condition_mask & NECP_KERNEL_CONDITION_LOCAL_EMPTY) { + num_conditions++; + } + if (condition_mask & NECP_KERNEL_CONDITION_REMOTE_EMPTY) { + num_conditions++; + } } condition_tlv_length += num_conditions * (sizeof(u_int8_t) + sizeof(u_int32_t)); // These are for the condition TLVs. The space for "value" is already accounted for above. @@ -3148,6 +2718,12 @@ necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, if (condition_mask & NECP_KERNEL_CONDITION_ALL_INTERFACES) { cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_ALL_INTERFACES, 0, "", cond_buf, condition_tlv_length); } + if (condition_mask & NECP_KERNEL_CONDITION_HAS_CLIENT) { + cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_HAS_CLIENT, 0, "", cond_buf, condition_tlv_length); + } + if (condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS) { + cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_LOCAL_NETWORKS, 0, "", cond_buf, condition_tlv_length); + } if (condition_mask & NECP_KERNEL_CONDITION_BOUND_INTERFACE) { cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_BOUND_INTERFACE, strlen(if_name) + 1, if_name, cond_buf, condition_tlv_length); @@ -3200,6 +2776,9 @@ necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_ENTITLEMENT, strlen(policy->cond_custom_entitlement) + 1, policy->cond_custom_entitlement, cond_buf, condition_tlv_length); } + if (condition_mask & NECP_KERNEL_CONDITION_PLATFORM_BINARY) { + cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_PLATFORM_BINARY, 0, "", cond_buf, condition_tlv_length); + } if (condition_mask & NECP_KERNEL_CONDITION_LOCAL_START) { if (condition_mask & NECP_KERNEL_CONDITION_LOCAL_END) { struct necp_policy_condition_addr_range range; @@ -3235,6 +2814,15 @@ necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, sizeof(policy->cond_agent_type), &policy->cond_agent_type, cond_buf, condition_tlv_length); } + if (condition_mask & NECP_KERNEL_CONDITION_CLIENT_FLAGS) { + cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_CLIENT_FLAGS, sizeof(policy->cond_client_flags), &policy->cond_client_flags, cond_buf, condition_tlv_length); + } + if (condition_mask & NECP_KERNEL_CONDITION_LOCAL_EMPTY) { + cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR_EMPTY, 0, "", cond_buf, condition_tlv_length); + } + if (condition_mask & NECP_KERNEL_CONDITION_REMOTE_EMPTY) { + cond_buf_cursor = necp_buffer_write_tlv(cond_buf_cursor, NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR_EMPTY, 0, "", cond_buf, condition_tlv_length); + } } cursor = necp_buffer_write_tlv(cursor, NECP_TLV_POLICY_CONDITION, cond_buf_cursor - cond_buf, cond_buf, tlv_buffer, total_allocated_bytes); @@ -3252,33 +2840,6 @@ necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, // UNLOCK lck_rw_done(&necp_kernel_policy_lock); - // Send packet - if (packet != NULL) { - u_int32_t total_result_length = sizeof(struct necp_packet_header) + total_tlv_len; - - // Allow malloc to wait, since the total buffer may be large and we are not holding any locks - MALLOC(result_buf, u_int8_t *, total_result_length, M_NECP, M_WAITOK | M_ZERO); - if (result_buf == NULL) { - NECPLOG(LOG_DEBUG, "Failed to allocate result_buffer (%u bytes)", total_result_length); - REPORT_ERROR(NECP_ERROR_INTERNAL); - } - - result_buf_cursor = result_buf; - result_buf_cursor = necp_buffer_write_packet_header(result_buf_cursor, NECP_PACKET_TYPE_POLICY_DUMP_ALL, NECP_PACKET_FLAGS_RESPONSE, message_id); - - for (int i = 0; i < policy_count; i++) { - if (tlv_buffer_pointers[i] != NULL) { - result_buf_cursor = necp_buffer_write_tlv(result_buf_cursor, NECP_TLV_POLICY_DUMP, tlv_buffer_lengths[i], tlv_buffer_pointers[i], result_buf, total_result_length); - } - } - - if (!necp_send_ctl_data(session, result_buf, result_buf_cursor - result_buf)) { - NECPLOG(LOG_ERR, "Failed to send response (%u bytes)", result_buf_cursor - result_buf); - } else { - NECPLOG(LOG_ERR, "Sent data worth %u bytes. Total result buffer length was %u bytes", result_buf_cursor - result_buf, total_result_length); - } - } - // Copy out if (out_buffer != 0) { if (out_buffer_length < total_tlv_len + sizeof(u_int32_t)) { @@ -3315,13 +2876,6 @@ necp_handle_policy_dump_all(struct necp_session *session, u_int32_t message_id, done: if (error_occured) { - if (packet != NULL) { - if (!necp_send_error_response(session, NECP_PACKET_TYPE_POLICY_DUMP_ALL, message_id, response_error)) { - NECPLOG0(LOG_ERR, "Failed to send error response"); - } else { - NECPLOG0(LOG_ERR, "Sent error response"); - } - } error_code = necp_get_posix_error_for_necp_error(response_error); } @@ -3606,6 +3160,7 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli union necp_sockaddr_union cond_remote_start; union necp_sockaddr_union cond_remote_end; u_int8_t cond_remote_prefix = 0; + u_int32_t cond_client_flags = 0; u_int32_t offset = 0; u_int8_t ultimate_result = 0; u_int32_t secondary_result = 0; @@ -3642,6 +3197,11 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli socket_ip_conditions = TRUE; break; } + case NECP_POLICY_CONDITION_HAS_CLIENT: { + master_condition_mask |= NECP_KERNEL_CONDITION_HAS_CLIENT; + socket_only_conditions = TRUE; + break; + } case NECP_POLICY_CONDITION_ENTITLEMENT: { if (condition_length > 0) { if (cond_custom_entitlement == NULL) { @@ -3657,6 +3217,11 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli } break; } + case NECP_POLICY_CONDITION_PLATFORM_BINARY: { + master_condition_mask |= NECP_KERNEL_CONDITION_PLATFORM_BINARY; + socket_only_conditions = TRUE; + break; + } case NECP_POLICY_CONDITION_DOMAIN: { // Make sure there is only one such rule if (condition_length > 0 && cond_domain == NULL) { @@ -3781,18 +3346,29 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli } break; } - case NECP_POLICY_CONDITION_IP_PROTOCOL: { + case NECP_POLICY_CONDITION_IP_PROTOCOL: + case NECP_POLICY_CONDITION_FLOW_IP_PROTOCOL: { if (condition_length >= sizeof(u_int16_t)) { master_condition_mask |= NECP_KERNEL_CONDITION_PROTOCOL; if (condition_is_negative) { master_condition_negated_mask |= NECP_KERNEL_CONDITION_PROTOCOL; } - memcpy(&cond_protocol, condition_value, sizeof(cond_protocol)); - socket_ip_conditions = TRUE; + memcpy(&cond_protocol, condition_value, sizeof(cond_protocol)); + if (condition_type == NECP_POLICY_CONDITION_FLOW_IP_PROTOCOL) { + socket_only_conditions = TRUE; + } else { + socket_ip_conditions = TRUE; + } } break; } - case NECP_POLICY_CONDITION_LOCAL_ADDR: { + case NECP_POLICY_CONDITION_LOCAL_NETWORKS: { + master_condition_mask |= NECP_KERNEL_CONDITION_LOCAL_NETWORKS; + socket_ip_conditions = TRUE; + break; + } + case NECP_POLICY_CONDITION_LOCAL_ADDR: + case NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR: { struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)condition_value; if (!necp_address_is_valid(&address_struct->address.sa)) { break; @@ -3806,10 +3382,15 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli master_condition_negated_mask |= NECP_KERNEL_CONDITION_LOCAL_START; master_condition_negated_mask |= NECP_KERNEL_CONDITION_LOCAL_PREFIX; } - socket_ip_conditions = TRUE; + if (condition_type == NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR) { + socket_only_conditions = TRUE; + } else { + socket_ip_conditions = TRUE; + } break; } - case NECP_POLICY_CONDITION_REMOTE_ADDR: { + case NECP_POLICY_CONDITION_REMOTE_ADDR: + case NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR: { struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)condition_value; if (!necp_address_is_valid(&address_struct->address.sa)) { break; @@ -3823,10 +3404,15 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli master_condition_negated_mask |= NECP_KERNEL_CONDITION_REMOTE_START; master_condition_negated_mask |= NECP_KERNEL_CONDITION_REMOTE_PREFIX; } - socket_ip_conditions = TRUE; + if (condition_type == NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR) { + socket_only_conditions = TRUE; + } else { + socket_ip_conditions = TRUE; + } break; } - case NECP_POLICY_CONDITION_LOCAL_ADDR_RANGE: { + case NECP_POLICY_CONDITION_LOCAL_ADDR_RANGE: + case NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR_RANGE: { struct necp_policy_condition_addr_range *address_struct = (struct necp_policy_condition_addr_range *)(void *)condition_value; if (!necp_address_is_valid(&address_struct->start_address.sa) || !necp_address_is_valid(&address_struct->end_address.sa)) { @@ -3841,10 +3427,15 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli master_condition_negated_mask |= NECP_KERNEL_CONDITION_LOCAL_START; master_condition_negated_mask |= NECP_KERNEL_CONDITION_LOCAL_END; } - socket_ip_conditions = TRUE; + if (condition_type == NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR_RANGE) { + socket_only_conditions = TRUE; + } else { + socket_ip_conditions = TRUE; + } break; } - case NECP_POLICY_CONDITION_REMOTE_ADDR_RANGE: { + case NECP_POLICY_CONDITION_REMOTE_ADDR_RANGE: + case NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR_RANGE: { struct necp_policy_condition_addr_range *address_struct = (struct necp_policy_condition_addr_range *)(void *)condition_value; if (!necp_address_is_valid(&address_struct->start_address.sa) || !necp_address_is_valid(&address_struct->end_address.sa)) { @@ -3859,7 +3450,11 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli master_condition_negated_mask |= NECP_KERNEL_CONDITION_REMOTE_START; master_condition_negated_mask |= NECP_KERNEL_CONDITION_REMOTE_END; } - socket_ip_conditions = TRUE; + if (condition_type == NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR_RANGE) { + socket_only_conditions = TRUE; + } else { + socket_ip_conditions = TRUE; + } break; } case NECP_POLICY_CONDITION_AGENT_TYPE: { @@ -3870,6 +3465,36 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli } break; } + case NECP_POLICY_CONDITION_CLIENT_FLAGS: { + if (condition_is_negative) { + master_condition_negated_mask |= NECP_KERNEL_CONDITION_CLIENT_FLAGS; + } + master_condition_mask |= NECP_KERNEL_CONDITION_CLIENT_FLAGS; + socket_only_conditions = TRUE; + if (condition_length >= sizeof(u_int32_t)) { + memcpy(&cond_client_flags, condition_value, sizeof(cond_client_flags)); + } else { + // Empty means match on fallback traffic + cond_client_flags = NECP_CLIENT_PARAMETER_FLAG_FALLBACK_TRAFFIC; + } + break; + } + case NECP_POLICY_CONDITION_FLOW_LOCAL_ADDR_EMPTY: { + master_condition_mask |= NECP_KERNEL_CONDITION_LOCAL_EMPTY; + if (condition_is_negative) { + master_condition_negated_mask |= NECP_KERNEL_CONDITION_LOCAL_EMPTY; + } + socket_only_conditions = TRUE; + break; + } + case NECP_POLICY_CONDITION_FLOW_REMOTE_ADDR_EMPTY: { + master_condition_mask |= NECP_KERNEL_CONDITION_REMOTE_EMPTY; + if (condition_is_negative) { + master_condition_negated_mask |= NECP_KERNEL_CONDITION_REMOTE_EMPTY; + } + socket_only_conditions = TRUE; + break; + } default: { break; } @@ -4017,13 +3642,23 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli socket_layer_non_id_conditions = TRUE; break; } + case NECP_POLICY_RESULT_ALLOW_UNENTITLED: { + socket_layer_non_id_conditions = TRUE; + break; + } case NECP_POLICY_RESULT_ROUTE_RULES: { if (policy->route_rules != NULL && policy->route_rules_size > 0) { u_int32_t route_rule_id = necp_create_route_rule(&necp_route_rules, policy->route_rules, policy->route_rules_size); if (route_rule_id > 0) { policy->applied_route_rules_id = route_rule_id; ultimate_result_parameter.route_rule_id = route_rule_id; - socket_layer_non_id_conditions = TRUE; + if (socket_only_conditions) { // socket_ip_conditions can be TRUE or FALSE + socket_layer_non_id_conditions = TRUE; + } else if (socket_ip_conditions) { + socket_layer_non_id_conditions = TRUE; + ip_output_layer_non_id_conditions = TRUE; + ip_output_layer_non_id_only = TRUE; // Only apply route rules to packets that didn't go through socket layer + } } } break; @@ -4034,7 +3669,7 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli } if (socket_layer_non_id_conditions) { - necp_kernel_policy_id policy_id = necp_kernel_socket_policy_add(policy->order, session->session_order, session->proc_pid, master_condition_mask, master_condition_negated_mask, cond_app_id, cond_real_app_id, cond_custom_entitlement, cond_account_id, cond_domain, cond_pid, cond_uid, cond_bound_interface, cond_traffic_class, cond_protocol, &cond_local_start, &cond_local_end, cond_local_prefix, &cond_remote_start, &cond_remote_end, cond_remote_prefix, &cond_agent_type, ultimate_result, ultimate_result_parameter); + necp_kernel_policy_id policy_id = necp_kernel_socket_policy_add(policy->order, session->session_order, session->proc_pid, master_condition_mask, master_condition_negated_mask, cond_app_id, cond_real_app_id, cond_custom_entitlement, cond_account_id, cond_domain, cond_pid, cond_uid, cond_bound_interface, cond_traffic_class, cond_protocol, &cond_local_start, &cond_local_end, cond_local_prefix, &cond_remote_start, &cond_remote_end, cond_remote_prefix, &cond_agent_type, cond_client_flags, ultimate_result, ultimate_result_parameter); if (policy_id == 0) { NECPLOG0(LOG_DEBUG, "Error applying socket kernel policy"); @@ -4050,6 +3685,7 @@ necp_policy_apply(struct necp_session *session, struct necp_session_policy *poli if (ip_output_layer_non_id_only) { condition_mask |= NECP_KERNEL_CONDITION_POLICY_ID; } + necp_kernel_policy_id policy_id = necp_kernel_ip_output_policy_add(policy->order, NECP_KERNEL_POLICY_SUBORDER_NON_ID_CONDITIONS, session->session_order, session->proc_pid, condition_mask, master_condition_negated_mask, NECP_KERNEL_POLICY_ID_NONE, cond_bound_interface, 0, cond_protocol, &cond_local_start, &cond_local_end, cond_local_prefix, &cond_remote_start, &cond_remote_end, cond_remote_prefix, ultimate_result, ultimate_result_parameter); if (policy_id == 0) { @@ -4202,9 +3838,10 @@ necp_kernel_policy_get_new_id(bool socket_level) return newid; } -#define NECP_KERNEL_VALID_SOCKET_CONDITIONS (NECP_KERNEL_CONDITION_APP_ID | NECP_KERNEL_CONDITION_REAL_APP_ID | NECP_KERNEL_CONDITION_DOMAIN | NECP_KERNEL_CONDITION_ACCOUNT_ID | NECP_KERNEL_CONDITION_PID | NECP_KERNEL_CONDITION_UID | NECP_KERNEL_CONDITION_ALL_INTERFACES | NECP_KERNEL_CONDITION_BOUND_INTERFACE | NECP_KERNEL_CONDITION_TRAFFIC_CLASS | NECP_KERNEL_CONDITION_PROTOCOL | NECP_KERNEL_CONDITION_LOCAL_START | NECP_KERNEL_CONDITION_LOCAL_END | NECP_KERNEL_CONDITION_LOCAL_PREFIX | NECP_KERNEL_CONDITION_REMOTE_START | NECP_KERNEL_CONDITION_REMOTE_END | NECP_KERNEL_CONDITION_REMOTE_PREFIX | NECP_KERNEL_CONDITION_ENTITLEMENT | NECP_KERNEL_CONDITION_CUSTOM_ENTITLEMENT | NECP_KERNEL_CONDITION_AGENT_TYPE) +#define NECP_KERNEL_VALID_SOCKET_CONDITIONS (NECP_KERNEL_CONDITION_APP_ID | NECP_KERNEL_CONDITION_REAL_APP_ID | NECP_KERNEL_CONDITION_DOMAIN | NECP_KERNEL_CONDITION_ACCOUNT_ID | NECP_KERNEL_CONDITION_PID | NECP_KERNEL_CONDITION_UID | NECP_KERNEL_CONDITION_ALL_INTERFACES | NECP_KERNEL_CONDITION_BOUND_INTERFACE | NECP_KERNEL_CONDITION_TRAFFIC_CLASS | NECP_KERNEL_CONDITION_PROTOCOL | NECP_KERNEL_CONDITION_LOCAL_START | NECP_KERNEL_CONDITION_LOCAL_END | NECP_KERNEL_CONDITION_LOCAL_PREFIX | NECP_KERNEL_CONDITION_REMOTE_START | NECP_KERNEL_CONDITION_REMOTE_END | NECP_KERNEL_CONDITION_REMOTE_PREFIX | NECP_KERNEL_CONDITION_ENTITLEMENT | NECP_KERNEL_CONDITION_CUSTOM_ENTITLEMENT | NECP_KERNEL_CONDITION_AGENT_TYPE | NECP_KERNEL_CONDITION_HAS_CLIENT | NECP_KERNEL_CONDITION_LOCAL_NETWORKS | NECP_KERNEL_CONDITION_CLIENT_FLAGS | NECP_KERNEL_CONDITION_LOCAL_EMPTY | NECP_KERNEL_CONDITION_REMOTE_EMPTY | NECP_KERNEL_CONDITION_PLATFORM_BINARY) + static necp_kernel_policy_id -necp_kernel_socket_policy_add(necp_policy_order order, u_int32_t session_order, int session_pid, u_int32_t condition_mask, u_int32_t condition_negated_mask, necp_app_id cond_app_id, necp_app_id cond_real_app_id, char *cond_custom_entitlement, u_int32_t cond_account_id, char *cond_domain, pid_t cond_pid, uid_t cond_uid, ifnet_t cond_bound_interface, struct necp_policy_condition_tc_range cond_traffic_class, u_int16_t cond_protocol, union necp_sockaddr_union *cond_local_start, union necp_sockaddr_union *cond_local_end, u_int8_t cond_local_prefix, union necp_sockaddr_union *cond_remote_start, union necp_sockaddr_union *cond_remote_end, u_int8_t cond_remote_prefix, struct necp_policy_condition_agent_type *cond_agent_type, necp_kernel_policy_result result, necp_kernel_policy_result_parameter result_parameter) +necp_kernel_socket_policy_add(necp_policy_order order, u_int32_t session_order, int session_pid, u_int32_t condition_mask, u_int32_t condition_negated_mask, necp_app_id cond_app_id, necp_app_id cond_real_app_id, char *cond_custom_entitlement, u_int32_t cond_account_id, char *cond_domain, pid_t cond_pid, uid_t cond_uid, ifnet_t cond_bound_interface, struct necp_policy_condition_tc_range cond_traffic_class, u_int16_t cond_protocol, union necp_sockaddr_union *cond_local_start, union necp_sockaddr_union *cond_local_end, u_int8_t cond_local_prefix, union necp_sockaddr_union *cond_remote_start, union necp_sockaddr_union *cond_remote_end, u_int8_t cond_remote_prefix, struct necp_policy_condition_agent_type *cond_agent_type, u_int32_t cond_client_flags, necp_kernel_policy_result result, necp_kernel_policy_result_parameter result_parameter) { struct necp_kernel_socket_policy *new_kernel_policy = NULL; struct necp_kernel_socket_policy *tmp_kernel_policy = NULL; @@ -4237,6 +3874,12 @@ necp_kernel_socket_policy_add(necp_policy_order order, u_int32_t session_order, if ((new_kernel_policy->condition_mask & NECP_KERNEL_CONDITION_REMOTE_END) && (new_kernel_policy->condition_mask & NECP_KERNEL_CONDITION_REMOTE_PREFIX)) { new_kernel_policy->condition_mask &= ~NECP_KERNEL_CONDITION_REMOTE_PREFIX; } + if (new_kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_EMPTY) { + new_kernel_policy->condition_mask &= ~(NECP_KERNEL_CONDITION_LOCAL_PREFIX | NECP_KERNEL_CONDITION_LOCAL_END); + } + if ((new_kernel_policy->condition_mask & NECP_KERNEL_CONDITION_REMOTE_EMPTY)) { + new_kernel_policy->condition_mask &= ~(NECP_KERNEL_CONDITION_REMOTE_PREFIX | NECP_KERNEL_CONDITION_REMOTE_END); + } new_kernel_policy->condition_negated_mask = condition_negated_mask & new_kernel_policy->condition_mask; // Set condition values @@ -4296,6 +3939,9 @@ necp_kernel_socket_policy_add(necp_policy_order order, u_int32_t session_order, if (new_kernel_policy->condition_mask & NECP_KERNEL_CONDITION_AGENT_TYPE) { memcpy(&new_kernel_policy->cond_agent_type, cond_agent_type, sizeof(*cond_agent_type)); } + if (new_kernel_policy->condition_mask & NECP_KERNEL_CONDITION_CLIENT_FLAGS) { + new_kernel_policy->cond_client_flags = cond_client_flags; + } new_kernel_policy->result = result; memcpy(&new_kernel_policy->result_parameter, &result_parameter, sizeof(result_parameter)); @@ -4407,9 +4053,13 @@ necp_get_result_description(char *result_string, necp_kernel_policy_result resul snprintf(result_string, MAX_RESULT_STRING_LEN, "ScopedDirect"); break; } + case NECP_KERNEL_POLICY_RESULT_ALLOW_UNENTITLED: { + snprintf(result_string, MAX_RESULT_STRING_LEN, "AllowUnentitled"); + break; + } case NECP_KERNEL_POLICY_RESULT_ROUTE_RULES: { int index = 0; - char interface_names[IFXNAMSIZ][MAX_ROUTE_RULE_INTERFACES]; + char interface_names[MAX_ROUTE_RULE_INTERFACES][IFXNAMSIZ]; struct necp_route_rule *route_rule = necp_lookup_route_rule_locked(&necp_route_rules, result_parameter.route_rule_id); if (route_rule != NULL) { for (index = 0; index < MAX_ROUTE_RULE_INTERFACES; index++) { @@ -4422,11 +4072,12 @@ necp_get_result_description(char *result_string, necp_kernel_policy_result resul } switch (route_rule->default_action) { case NECP_ROUTE_RULE_DENY_INTERFACE: - snprintf(result_string, MAX_RESULT_STRING_LEN, "RouteRules (Only %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s)", + snprintf(result_string, MAX_RESULT_STRING_LEN, "RouteRules (Only %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s)", (route_rule->cellular_action == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? "Cell " : "", (route_rule->wifi_action == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? "WiFi " : "", (route_rule->wired_action == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? "Wired " : "", (route_rule->expensive_action == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? "Exp " : "", + (route_rule->constrained_action == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? "Constrained " : "", (route_rule->exception_if_actions[0] == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? interface_names[0] : "", (route_rule->exception_if_actions[0] == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? " " : "", (route_rule->exception_if_actions[1] == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? interface_names[1] : "", @@ -4448,11 +4099,12 @@ necp_get_result_description(char *result_string, necp_kernel_policy_result resul (route_rule->exception_if_actions[9] == NECP_ROUTE_RULE_ALLOW_INTERFACE) ? interface_names[9] : ""); break; case NECP_ROUTE_RULE_ALLOW_INTERFACE: - snprintf(result_string, MAX_RESULT_STRING_LEN, "RouteRules (%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s)", + snprintf(result_string, MAX_RESULT_STRING_LEN, "RouteRules (%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s)", (route_rule->cellular_action == NECP_ROUTE_RULE_DENY_INTERFACE) ? "!Cell " : "", (route_rule->wifi_action == NECP_ROUTE_RULE_DENY_INTERFACE) ? "!WiFi " : "", (route_rule->wired_action == NECP_ROUTE_RULE_DENY_INTERFACE) ? "!Wired " : "", (route_rule->expensive_action == NECP_ROUTE_RULE_DENY_INTERFACE) ? "!Exp " : "", + (route_rule->constrained_action == NECP_ROUTE_RULE_DENY_INTERFACE) ? "!Constrained " : "", (route_rule->exception_if_actions[0] == NECP_ROUTE_RULE_DENY_INTERFACE) ? "!" : "", (route_rule->exception_if_actions[0] == NECP_ROUTE_RULE_DENY_INTERFACE) ? interface_names[0] : "", (route_rule->exception_if_actions[1] == NECP_ROUTE_RULE_DENY_INTERFACE) ? "!" : "", @@ -4475,11 +4127,12 @@ necp_get_result_description(char *result_string, necp_kernel_policy_result resul (route_rule->exception_if_actions[9] == NECP_ROUTE_RULE_DENY_INTERFACE) ? interface_names[9] : ""); break; case NECP_ROUTE_RULE_QOS_MARKING: - snprintf(result_string, MAX_RESULT_STRING_LEN, "RouteRules (QoSMarking %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s)", + snprintf(result_string, MAX_RESULT_STRING_LEN, "RouteRules (QoSMarking %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s)", (route_rule->cellular_action == NECP_ROUTE_RULE_QOS_MARKING) ? "Cell " : "", (route_rule->wifi_action == NECP_ROUTE_RULE_QOS_MARKING) ? "WiFi " : "", (route_rule->wired_action == NECP_ROUTE_RULE_QOS_MARKING) ? "Wired " : "", (route_rule->expensive_action == NECP_ROUTE_RULE_QOS_MARKING) ? "Exp " : "", + (route_rule->constrained_action == NECP_ROUTE_RULE_QOS_MARKING) ? "Constrained " : "", (route_rule->exception_if_actions[0] == NECP_ROUTE_RULE_QOS_MARKING) ? interface_names[0] : "", (route_rule->exception_if_actions[0] == NECP_ROUTE_RULE_QOS_MARKING) ? " " : "", (route_rule->exception_if_actions[1] == NECP_ROUTE_RULE_QOS_MARKING) ? interface_names[1] : "", @@ -4627,7 +4280,8 @@ necp_kernel_socket_policy_results_overlap(struct necp_kernel_socket_policy *uppe } else if (upper_policy->result == NECP_KERNEL_POLICY_RESULT_SOCKET_FILTER || upper_policy->result == NECP_KERNEL_POLICY_RESULT_ROUTE_RULES || upper_policy->result == NECP_KERNEL_POLICY_RESULT_USE_NETAGENT || - upper_policy->result == NECP_KERNEL_POLICY_RESULT_NETAGENT_SCOPED) { + upper_policy->result == NECP_KERNEL_POLICY_RESULT_NETAGENT_SCOPED || + upper_policy->result == NECP_KERNEL_POLICY_RESULT_ALLOW_UNENTITLED) { // Filters and route rules never cancel out lower policies return FALSE; } else if (necp_kernel_socket_result_is_trigger_service_type(upper_policy)) { @@ -4699,6 +4353,11 @@ necp_kernel_socket_policy_is_unnecessary(struct necp_kernel_socket_policy *polic continue; } + // If new policy matches Local Networks, compared policy must also + if ((policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS) && !(compared_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS)) { + continue; + } + // Default makes lower policies unecessary always if (compared_policy->condition_mask == 0) { return TRUE; @@ -4764,6 +4423,11 @@ necp_kernel_socket_policy_is_unnecessary(struct necp_kernel_socket_policy *polic continue; } + if (compared_policy->condition_mask & NECP_KERNEL_CONDITION_CLIENT_FLAGS && + compared_policy->cond_client_flags != policy->cond_client_flags) { + continue; + } + if (compared_policy->condition_mask & NECP_KERNEL_CONDITION_TRAFFIC_CLASS && !(compared_policy->cond_traffic_class.start_tc <= policy->cond_traffic_class.start_tc && compared_policy->cond_traffic_class.end_tc >= policy->cond_traffic_class.end_tc)) { @@ -5020,7 +4684,7 @@ necp_create_string_to_id_mapping(struct necp_string_id_mapping_list *list, char existing_mapping = necp_lookup_string_to_id_locked(list, string); if (existing_mapping != NULL) { string_id = existing_mapping->id; - existing_mapping->refcount++; + os_ref_retain_locked(&existing_mapping->refcount); } else { struct necp_string_id_mapping *new_mapping = NULL; MALLOC(new_mapping, struct necp_string_id_mapping *, sizeof(struct necp_string_id_mapping), M_NECP, M_WAITOK); @@ -5032,7 +4696,7 @@ necp_create_string_to_id_mapping(struct necp_string_id_mapping_list *list, char if (new_mapping->string != NULL) { memcpy(new_mapping->string, string, length); new_mapping->id = necp_get_new_string_id(); - new_mapping->refcount = 1; + os_ref_init(&new_mapping->refcount, &necp_refgrp); LIST_INSERT_HEAD(list, new_mapping, chain); string_id = new_mapping->id; } else { @@ -5053,7 +4717,7 @@ necp_remove_string_to_id_mapping(struct necp_string_id_mapping_list *list, char existing_mapping = necp_lookup_string_to_id_locked(list, string); if (existing_mapping != NULL) { - if (--existing_mapping->refcount == 0) { + if (os_ref_release_locked(&existing_mapping->refcount) == 0) { LIST_REMOVE(existing_mapping, chain); FREE(existing_mapping->string, M_NECP); FREE(existing_mapping, M_NECP); @@ -5138,7 +4802,7 @@ necp_lookup_route_rule_locked(struct necp_route_rule_list *list, u_int32_t route } static struct necp_route_rule * -necp_lookup_route_rule_by_contents_locked(struct necp_route_rule_list *list, u_int32_t default_action, u_int8_t cellular_action, u_int8_t wifi_action, u_int8_t wired_action, u_int8_t expensive_action, u_int32_t *if_indices, u_int8_t *if_actions) +necp_lookup_route_rule_by_contents_locked(struct necp_route_rule_list *list, u_int32_t default_action, u_int8_t cellular_action, u_int8_t wifi_action, u_int8_t wired_action, u_int8_t expensive_action, u_int8_t constrained_action, u_int32_t *if_indices, u_int8_t *if_actions) { struct necp_route_rule *searchentry = NULL; struct necp_route_rule *foundentry = NULL; @@ -5148,7 +4812,8 @@ necp_lookup_route_rule_by_contents_locked(struct necp_route_rule_list *list, u_i searchentry->cellular_action == cellular_action && searchentry->wifi_action == wifi_action && searchentry->wired_action == wired_action && - searchentry->expensive_action == expensive_action) { + searchentry->expensive_action == expensive_action && + searchentry->constrained_action == constrained_action) { bool match_failed = FALSE; size_t index_a = 0; size_t index_b = 0; @@ -5199,6 +4864,7 @@ necp_create_route_rule(struct necp_route_rule_list *list, u_int8_t *route_rules_ u_int8_t wifi_action = NECP_ROUTE_RULE_NONE; u_int8_t wired_action = NECP_ROUTE_RULE_NONE; u_int8_t expensive_action = NECP_ROUTE_RULE_NONE; + u_int8_t constrained_action = NECP_ROUTE_RULE_NONE; u_int32_t if_indices[MAX_ROUTE_RULE_INTERFACES]; size_t num_valid_indices = 0; memset(&if_indices, 0, sizeof(if_indices)); @@ -5241,6 +4907,9 @@ necp_create_route_rule(struct necp_route_rule_list *list, u_int8_t *route_rules_ if (rule_flags & NECP_ROUTE_RULE_FLAG_EXPENSIVE) { expensive_action = rule_type; } + if (rule_flags & NECP_ROUTE_RULE_FLAG_CONSTRAINED) { + constrained_action = rule_type; + } if (rule_flags == 0) { default_action = rule_type; } @@ -5265,10 +4934,10 @@ necp_create_route_rule(struct necp_route_rule_list *list, u_int8_t *route_rules_ offset += sizeof(u_int8_t) + sizeof(u_int32_t) + length; } - existing_rule = necp_lookup_route_rule_by_contents_locked(list, default_action, cellular_action, wifi_action, wired_action, expensive_action, if_indices, if_actions); + existing_rule = necp_lookup_route_rule_by_contents_locked(list, default_action, cellular_action, wifi_action, wired_action, expensive_action, constrained_action, if_indices, if_actions); if (existing_rule != NULL) { route_rule_id = existing_rule->id; - existing_rule->refcount++; + os_ref_retain_locked(&existing_rule->refcount); } else { struct necp_route_rule *new_rule = NULL; MALLOC(new_rule, struct necp_route_rule *, sizeof(struct necp_route_rule), M_NECP, M_WAITOK); @@ -5280,9 +4949,10 @@ necp_create_route_rule(struct necp_route_rule_list *list, u_int8_t *route_rules_ new_rule->wifi_action = wifi_action; new_rule->wired_action = wired_action; new_rule->expensive_action = expensive_action; + new_rule->constrained_action = constrained_action; memcpy(&new_rule->exception_if_indices, &if_indices, sizeof(if_indices)); memcpy(&new_rule->exception_if_actions, &if_actions, sizeof(if_actions)); - new_rule->refcount = 1; + os_ref_init(&new_rule->refcount, &necp_refgrp); LIST_INSERT_HEAD(list, new_rule, chain); } } @@ -5323,7 +4993,7 @@ necp_remove_route_rule(struct necp_route_rule_list *list, u_int32_t route_rule_i existing_rule = necp_lookup_route_rule_locked(list, route_rule_id); if (existing_rule != NULL) { - if (--existing_rule->refcount == 0) { + if (os_ref_release_locked(&existing_rule->refcount) == 0) { necp_remove_aggregate_route_rule_for_id(existing_rule->id); LIST_REMOVE(existing_rule, chain); FREE(existing_rule, M_NECP); @@ -5361,16 +5031,10 @@ necp_create_aggregate_route_rule(u_int32_t *rule_ids) struct necp_aggregate_route_rule *new_rule = NULL; struct necp_aggregate_route_rule *existing_rule = NULL; - LIST_FOREACH(existing_rule, &necp_aggregate_route_rules, chain) { - if (memcmp(existing_rule->rule_ids, rule_ids, (sizeof(u_int32_t) * MAX_AGGREGATE_ROUTE_RULES)) == 0) { - return existing_rule->id; - } - } - lck_rw_lock_exclusive(&necp_route_rule_lock); + // Check if the rule already exists LIST_FOREACH(existing_rule, &necp_aggregate_route_rules, chain) { - // Re-check, in case something else created the rule while we are waiting to lock if (memcmp(existing_rule->rule_ids, rule_ids, (sizeof(u_int32_t) * MAX_AGGREGATE_ROUTE_RULES)) == 0) { lck_rw_done(&necp_route_rule_lock); return existing_rule->id; @@ -5494,9 +5158,9 @@ necp_create_uuid_app_id_mapping(uuid_t uuid, bool *allocated_mapping, bool uuid_ existing_mapping = necp_uuid_lookup_app_id_locked(uuid); if (existing_mapping != NULL) { local_id = existing_mapping->id; - existing_mapping->refcount++; + os_ref_retain_locked(&existing_mapping->refcount); if (uuid_policy_table) { - existing_mapping->table_refcount++; + existing_mapping->table_usecount++; } } else { struct necp_uuid_id_mapping *new_mapping = NULL; @@ -5504,11 +5168,11 @@ necp_create_uuid_app_id_mapping(uuid_t uuid, bool *allocated_mapping, bool uuid_ if (new_mapping != NULL) { uuid_copy(new_mapping->uuid, uuid); new_mapping->id = necp_get_new_uuid_id(false); - new_mapping->refcount = 1; + os_ref_init(&new_mapping->refcount, &necp_refgrp); if (uuid_policy_table) { - new_mapping->table_refcount = 1; + new_mapping->table_usecount = 1; } else { - new_mapping->table_refcount = 0; + new_mapping->table_usecount = 0; } LIST_INSERT_HEAD(APPUUIDHASH(uuid), new_mapping, chain); @@ -5538,9 +5202,9 @@ necp_remove_uuid_app_id_mapping(uuid_t uuid, bool *removed_mapping, bool uuid_po existing_mapping = necp_uuid_lookup_app_id_locked(uuid); if (existing_mapping != NULL) { if (uuid_policy_table) { - existing_mapping->table_refcount--; + existing_mapping->table_usecount--; } - if (--existing_mapping->refcount == 0) { + if (os_ref_release_locked(&existing_mapping->refcount) == 0) { LIST_REMOVE(existing_mapping, chain); FREE(existing_mapping, M_NECP); if (removed_mapping) { @@ -5618,14 +5282,14 @@ necp_create_uuid_service_id_mapping(uuid_t uuid) existing_mapping = necp_uuid_lookup_service_id_locked(uuid); if (existing_mapping != NULL) { local_id = existing_mapping->id; - existing_mapping->refcount++; + os_ref_retain_locked(&existing_mapping->refcount); } else { struct necp_uuid_id_mapping *new_mapping = NULL; MALLOC(new_mapping, struct necp_uuid_id_mapping *, sizeof(*new_mapping), M_NECP, M_WAITOK); if (new_mapping != NULL) { uuid_copy(new_mapping->uuid, uuid); new_mapping->id = necp_get_new_uuid_id(true); - new_mapping->refcount = 1; + os_ref_init(&new_mapping->refcount, &necp_refgrp); LIST_INSERT_HEAD(&necp_uuid_service_id_list, new_mapping, chain); @@ -5647,9 +5311,9 @@ necp_remove_uuid_service_id_mapping(uuid_t uuid) LCK_RW_ASSERT(&necp_kernel_policy_lock, LCK_RW_ASSERT_EXCLUSIVE); - existing_mapping = necp_uuid_lookup_app_id_locked(uuid); + existing_mapping = necp_uuid_lookup_service_id_locked(uuid); if (existing_mapping != NULL) { - if (--existing_mapping->refcount == 0) { + if (os_ref_release_locked(&existing_mapping->refcount) == 0) { LIST_REMOVE(existing_mapping, chain); FREE(existing_mapping, M_NECP); } @@ -5676,7 +5340,7 @@ necp_kernel_socket_policies_update_uuid_table(void) for (uuid_list_head = &necp_uuid_app_id_hashtbl[necp_uuid_app_id_hash_num_buckets - 1]; uuid_list_head >= necp_uuid_app_id_hashtbl; uuid_list_head--) { struct necp_uuid_id_mapping *mapping = NULL; LIST_FOREACH(mapping, uuid_list_head, chain) { - if (mapping->table_refcount > 0 && + if (mapping->table_usecount > 0 && proc_uuid_policy_kernel(PROC_UUID_POLICY_OPERATION_ADD, mapping->uuid, PROC_UUID_NECP_APP_POLICY) < 0) { NECPLOG0(LOG_DEBUG, "Error adding uuid to policy table\n"); } @@ -5690,7 +5354,7 @@ necp_kernel_socket_policies_update_uuid_table(void) return TRUE; } -#define NECP_KERNEL_VALID_IP_OUTPUT_CONDITIONS (NECP_KERNEL_CONDITION_ALL_INTERFACES | NECP_KERNEL_CONDITION_BOUND_INTERFACE | NECP_KERNEL_CONDITION_PROTOCOL | NECP_KERNEL_CONDITION_LOCAL_START | NECP_KERNEL_CONDITION_LOCAL_END | NECP_KERNEL_CONDITION_LOCAL_PREFIX | NECP_KERNEL_CONDITION_REMOTE_START | NECP_KERNEL_CONDITION_REMOTE_END | NECP_KERNEL_CONDITION_REMOTE_PREFIX | NECP_KERNEL_CONDITION_POLICY_ID | NECP_KERNEL_CONDITION_LAST_INTERFACE) +#define NECP_KERNEL_VALID_IP_OUTPUT_CONDITIONS (NECP_KERNEL_CONDITION_ALL_INTERFACES | NECP_KERNEL_CONDITION_BOUND_INTERFACE | NECP_KERNEL_CONDITION_PROTOCOL | NECP_KERNEL_CONDITION_LOCAL_START | NECP_KERNEL_CONDITION_LOCAL_END | NECP_KERNEL_CONDITION_LOCAL_PREFIX | NECP_KERNEL_CONDITION_REMOTE_START | NECP_KERNEL_CONDITION_REMOTE_END | NECP_KERNEL_CONDITION_REMOTE_PREFIX | NECP_KERNEL_CONDITION_POLICY_ID | NECP_KERNEL_CONDITION_LAST_INTERFACE | NECP_KERNEL_CONDITION_LOCAL_NETWORKS) static necp_kernel_policy_id necp_kernel_ip_output_policy_add(necp_policy_order order, necp_policy_order suborder, u_int32_t session_order, int session_pid, u_int32_t condition_mask, u_int32_t condition_negated_mask, necp_kernel_policy_id cond_policy_id, ifnet_t cond_bound_interface, u_int32_t cond_last_interface_index, u_int16_t cond_protocol, union necp_sockaddr_union *cond_local_start, union necp_sockaddr_union *cond_local_end, u_int8_t cond_local_prefix, union necp_sockaddr_union *cond_remote_start, union necp_sockaddr_union *cond_remote_end, u_int8_t cond_remote_prefix, necp_kernel_policy_result result, necp_kernel_policy_result_parameter result_parameter) { @@ -5905,6 +5569,11 @@ necp_kernel_ip_output_policy_is_unnecessary(struct necp_kernel_ip_output_policy continue; } + // If new policy matches Local Networks, compared policy must also + if ((policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS) && !(compared_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS)) { + continue; + } + // Default makes lower policies unecessary always if (compared_policy->condition_mask == 0) { return TRUE; @@ -5999,8 +5668,10 @@ necp_kernel_ip_output_policies_reprocess(void) /* Update bucket counts: * Non-id and SKIP policies will be added to all buckets + * Add local networks policy to all buckets for incoming IP */ if (!(kernel_policy->condition_mask & NECP_KERNEL_CONDITION_POLICY_ID) || + (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS) || kernel_policy->result == NECP_KERNEL_POLICY_RESULT_SKIP) { for (i = 0; i < NECP_KERNEL_IP_OUTPUT_POLICIES_MAP_NUM_ID_BUCKETS; i++) { bucket_allocation_counts[i]++; @@ -6030,6 +5701,7 @@ necp_kernel_ip_output_policies_reprocess(void) LIST_FOREACH(kernel_policy, &necp_kernel_ip_output_policies, chain) { // Insert pointers into map if (!(kernel_policy->condition_mask & NECP_KERNEL_CONDITION_POLICY_ID) || + (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS) || kernel_policy->result == NECP_KERNEL_POLICY_RESULT_SKIP) { for (i = 0; i < NECP_KERNEL_IP_OUTPUT_POLICIES_MAP_NUM_ID_BUCKETS; i++) { if (!necp_kernel_ip_output_policy_is_unnecessary(kernel_policy, necp_kernel_ip_output_policies_map[i], bucket_current_free_index[i])) { @@ -6167,11 +5839,35 @@ necp_hostname_matches_domain(struct substring hostname_substring, u_int8_t hostn return FALSE; } +bool +net_domain_contains_hostname(char *hostname_string, char *domain_string) +{ + if (hostname_string == NULL || + domain_string == NULL) { + return false; + } + + struct substring hostname_substring; + hostname_substring.string = hostname_string; + hostname_substring.length = strlen(hostname_string); + + return necp_hostname_matches_domain(hostname_substring, + necp_count_dots(hostname_string, hostname_substring.length), + domain_string, + necp_count_dots(domain_string, strlen(domain_string))); +} + +#define NECP_MAX_STRING_LEN 1024 + static char * necp_copy_string(char *string, size_t length) { char *copied_string = NULL; + if (length > NECP_MAX_STRING_LEN) { + return NULL; + } + MALLOC(copied_string, char *, length + 1, M_NECP, M_WAITOK); if (copied_string == NULL) { return NULL; @@ -6208,34 +5904,31 @@ static inline void necp_get_parent_cred_result(proc_t proc, struct necp_socket_info *info) { task_t task = proc_task(proc ? proc : current_proc()); - coalition_t coal = COALITION_NULL; - Boolean is_leader = coalition_is_leader(task, COALITION_TYPE_JETSAM, &coal); + coalition_t coal = task_get_coalition(task, COALITION_TYPE_JETSAM); - if (is_leader == TRUE) { + if (coal == COALITION_NULL || coalition_is_leader(task, coal)) { // No parent, nothing to do return; } - if (coal != NULL) { - task_t lead_task = coalition_get_leader(coal); - if (lead_task != NULL) { - proc_t lead_proc = get_bsdtask_info(lead_task); - if (lead_proc != NULL) { - kauth_cred_t lead_cred = kauth_cred_proc_ref(lead_proc); - if (lead_cred != NULL) { - errno_t cred_result = priv_check_cred(lead_cred, PRIV_NET_PRIVILEGED_NECP_MATCH, 0); - kauth_cred_unref(&lead_cred); - info->cred_result = cred_result; - } + task_t lead_task = coalition_get_leader(coal); + if (lead_task != NULL) { + proc_t lead_proc = get_bsdtask_info(lead_task); + if (lead_proc != NULL) { + kauth_cred_t lead_cred = kauth_cred_proc_ref(lead_proc); + if (lead_cred != NULL) { + errno_t cred_result = priv_check_cred(lead_cred, PRIV_NET_PRIVILEGED_NECP_MATCH, 0); + kauth_cred_unref(&lead_cred); + info->cred_result = cred_result; } - task_deallocate(lead_task); } + task_deallocate(lead_task); } } -#define NECP_KERNEL_ADDRESS_TYPE_CONDITIONS (NECP_KERNEL_CONDITION_LOCAL_START | NECP_KERNEL_CONDITION_LOCAL_END | NECP_KERNEL_CONDITION_LOCAL_PREFIX | NECP_KERNEL_CONDITION_REMOTE_START | NECP_KERNEL_CONDITION_REMOTE_END | NECP_KERNEL_CONDITION_REMOTE_PREFIX) +#define NECP_KERNEL_ADDRESS_TYPE_CONDITIONS (NECP_KERNEL_CONDITION_LOCAL_START | NECP_KERNEL_CONDITION_LOCAL_END | NECP_KERNEL_CONDITION_LOCAL_PREFIX | NECP_KERNEL_CONDITION_REMOTE_START | NECP_KERNEL_CONDITION_REMOTE_END | NECP_KERNEL_CONDITION_REMOTE_PREFIX | NECP_KERNEL_CONDITION_LOCAL_EMPTY | NECP_KERNEL_CONDITION_REMOTE_EMPTY | NECP_KERNEL_CONDITION_LOCAL_NETWORKS) static void -necp_application_fillout_info_locked(uuid_t application_uuid, uuid_t real_application_uuid, char *account, char *domain, pid_t pid, uid_t uid, u_int16_t protocol, u_int32_t bound_interface_index, u_int32_t traffic_class, union necp_sockaddr_union *local_addr, union necp_sockaddr_union *remote_addr, proc_t proc, struct necp_socket_info *info) +necp_application_fillout_info_locked(uuid_t application_uuid, uuid_t real_application_uuid, char *account, char *domain, pid_t pid, uid_t uid, u_int16_t protocol, u_int32_t bound_interface_index, u_int32_t traffic_class, union necp_sockaddr_union *local_addr, union necp_sockaddr_union *remote_addr, u_int16_t local_port, u_int16_t remote_port, bool has_client, proc_t proc, u_int32_t drop_order, u_int32_t client_flags, struct necp_socket_info *info) { memset(info, 0, sizeof(struct necp_socket_info)); @@ -6244,6 +5937,9 @@ necp_application_fillout_info_locked(uuid_t application_uuid, uuid_t real_applic info->protocol = protocol; info->bound_interface_index = bound_interface_index; info->traffic_class = traffic_class; + info->has_client = has_client; + info->drop_order = drop_order; + info->client_flags = client_flags; if (necp_kernel_application_policies_condition_mask & NECP_KERNEL_CONDITION_ENTITLEMENT && proc != NULL) { info->cred_result = priv_check_cred(proc_ucred(proc), PRIV_NET_PRIVILEGED_NECP_MATCH, 0); @@ -6253,6 +5949,10 @@ necp_application_fillout_info_locked(uuid_t application_uuid, uuid_t real_applic } } + if (necp_kernel_application_policies_condition_mask & NECP_KERNEL_CONDITION_PLATFORM_BINARY && proc != NULL) { + info->is_platform_binary = csproc_get_platform_binary(proc) ? true : false; + } + if (necp_kernel_application_policies_condition_mask & NECP_KERNEL_CONDITION_APP_ID && !uuid_is_null(application_uuid)) { struct necp_uuid_id_mapping *existing_mapping = necp_uuid_lookup_app_id_locked(application_uuid); if (existing_mapping) { @@ -6285,9 +5985,23 @@ necp_application_fillout_info_locked(uuid_t application_uuid, uuid_t real_applic if (necp_kernel_application_policies_condition_mask & NECP_KERNEL_ADDRESS_TYPE_CONDITIONS) { if (local_addr && local_addr->sa.sa_len > 0) { memcpy(&info->local_addr, local_addr, local_addr->sa.sa_len); + if (local_port != 0) { + info->local_addr.sin6.sin6_port = local_port; + } + } else if (local_port != 0) { + info->local_addr.sin6.sin6_len = sizeof(struct sockaddr_in6); + info->local_addr.sin6.sin6_family = AF_INET6; + info->local_addr.sin6.sin6_port = local_port; } if (remote_addr && remote_addr->sa.sa_len > 0) { memcpy(&info->remote_addr, remote_addr, remote_addr->sa.sa_len); + if (remote_port != 0) { + info->remote_addr.sin6.sin6_port = remote_port; + } + } else if (remote_port != 0) { + info->remote_addr.sin6.sin6_len = sizeof(struct sockaddr_in6); + info->remote_addr.sin6.sin6_family = AF_INET6; + info->remote_addr.sin6.sin6_port = remote_port; } } } @@ -6325,10 +6039,14 @@ necp_application_find_policy_match_internal(proc_t proc, u_int32_t parameters_size, struct necp_aggregate_result *returned_result, u_int32_t *flags, + u_int32_t *reason, u_int required_interface_index, const union necp_sockaddr_union *override_local_addr, const union necp_sockaddr_union *override_remote_addr, - struct rtentry **returned_route, bool ignore_address) + struct necp_client_endpoint *returned_v4_gateway, + struct necp_client_endpoint *returned_v6_gateway, + struct rtentry **returned_route, bool ignore_address, + bool has_client) { int error = 0; size_t offset = 0; @@ -6336,7 +6054,6 @@ necp_application_find_policy_match_internal(proc_t proc, struct necp_kernel_socket_policy *matched_policy = NULL; struct necp_socket_info info; necp_kernel_policy_filter filter_control_unit = 0; - u_int32_t route_rule_id = 0; necp_kernel_policy_result service_action = 0; necp_kernel_policy_service service = { 0, 0 }; @@ -6349,6 +6066,9 @@ necp_application_find_policy_match_internal(proc_t proc, bool no_remote_addr = FALSE; u_int8_t remote_family = 0; bool no_local_addr = FALSE; + u_int16_t local_port = 0; + u_int16_t remote_port = 0; + necp_drop_all_bypass_check_result_t drop_all_bypass = NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE; if (override_local_addr) { memcpy(&local_addr, override_local_addr, sizeof(local_addr)); @@ -6392,11 +6112,23 @@ necp_application_find_policy_match_internal(proc_t proc, return EINVAL; } + if (returned_v4_gateway != NULL) { + memset(returned_v4_gateway, 0, sizeof(struct necp_client_endpoint)); + } + + if (returned_v6_gateway != NULL) { + memset(returned_v6_gateway, 0, sizeof(struct necp_client_endpoint)); + } + memset(returned_result, 0, sizeof(struct necp_aggregate_result)); + u_int32_t drop_order = necp_process_drop_order(proc_ucred(proc)); + + necp_kernel_policy_result drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_NONE; + lck_rw_lock_shared(&necp_kernel_policy_lock); if (necp_kernel_application_policies_count == 0) { - if (necp_drop_all_order > 0) { + if (necp_drop_all_order > 0 || drop_order > 0) { returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_DROP; lck_rw_done(&necp_kernel_policy_lock); return 0; @@ -6489,6 +6221,8 @@ necp_application_find_policy_match_internal(proc_t proc, case NECP_CLIENT_PARAMETER_IP_PROTOCOL: { if (length >= sizeof(u_int16_t)) { memcpy(&protocol, value, sizeof(u_int16_t)); + } else if (length >= sizeof(u_int8_t)) { + memcpy(&protocol, value, sizeof(u_int8_t)); } break; } @@ -6506,7 +6240,7 @@ necp_application_find_policy_match_internal(proc_t proc, break; } case NECP_CLIENT_PARAMETER_LOCAL_ADDRESS: { - if (ignore_address) { + if (ignore_address || override_local_addr) { break; } @@ -6519,7 +6253,7 @@ necp_application_find_policy_match_internal(proc_t proc, break; } case NECP_CLIENT_PARAMETER_REMOTE_ADDRESS: { - if (ignore_address) { + if (ignore_address || override_remote_addr) { break; } @@ -6531,13 +6265,44 @@ necp_application_find_policy_match_internal(proc_t proc, } break; } + case NECP_CLIENT_PARAMETER_LOCAL_ENDPOINT: { + if (ignore_address || override_local_addr) { + break; + } + + if (length >= sizeof(struct necp_client_endpoint)) { + struct necp_client_endpoint *endpoint = (struct necp_client_endpoint *)(void *)value; + if (endpoint->u.endpoint.endpoint_family == AF_UNSPEC && + endpoint->u.endpoint.endpoint_port != 0) { + // Save port + local_port = endpoint->u.endpoint.endpoint_port; + } + } + break; + } + case NECP_CLIENT_PARAMETER_REMOTE_ENDPOINT: { + if (ignore_address || override_remote_addr) { + break; + } + + if (length >= sizeof(struct necp_client_endpoint)) { + struct necp_client_endpoint *endpoint = (struct necp_client_endpoint *)(void *)value; + if (endpoint->u.endpoint.endpoint_family == AF_UNSPEC && + endpoint->u.endpoint.endpoint_port != 0) { + // Save port + remote_port = endpoint->u.endpoint.endpoint_port; + } + } + break; + } case NECP_CLIENT_PARAMETER_FLAGS: { if (length >= sizeof(client_flags)) { memcpy(&client_flags, value, sizeof(client_flags)); } break; } - case NECP_CLIENT_PARAMETER_REQUIRE_AGENT_TYPE: { + case NECP_CLIENT_PARAMETER_REQUIRE_AGENT_TYPE: + case NECP_CLIENT_PARAMETER_PREFER_AGENT_TYPE: { if (num_required_agent_types >= NECP_MAX_REQUIRED_AGENTS) { break; } @@ -6557,24 +6322,48 @@ necp_application_find_policy_match_internal(proc_t proc, offset += sizeof(u_int8_t) + sizeof(u_int32_t) + length; } + // Check for loopback exception + if (necp_pass_loopback > 0 && necp_is_loopback(&local_addr.sa, &remote_addr.sa, NULL, NULL, bound_interface_index)) { + returned_result->policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_PASS; + returned_result->routed_interface_index = lo_ifp->if_index; + *flags |= (NECP_CLIENT_RESULT_FLAG_IS_LOCAL | NECP_CLIENT_RESULT_FLAG_IS_DIRECT); + return 0; + } + // Lock lck_rw_lock_shared(&necp_kernel_policy_lock); - necp_application_fillout_info_locked(application_uuid, real_application_uuid, account, domain, pid, uid, protocol, bound_interface_index, traffic_class, &local_addr, &remote_addr, proc, &info); - matched_policy = necp_socket_find_policy_match_with_info_locked(necp_kernel_socket_policies_app_layer_map, &info, &filter_control_unit, &route_rule_id, &service_action, &service, netagent_ids, netagent_use_flags, NECP_MAX_NETAGENTS, required_agent_types, num_required_agent_types, proc, NULL); + u_int32_t route_rule_id_array[MAX_AGGREGATE_ROUTE_RULES]; + size_t route_rule_id_array_count = 0; + necp_application_fillout_info_locked(application_uuid, real_application_uuid, account, domain, pid, uid, protocol, bound_interface_index, traffic_class, &local_addr, &remote_addr, local_port, remote_port, has_client, proc, drop_order, client_flags, &info); + matched_policy = necp_socket_find_policy_match_with_info_locked(necp_kernel_socket_policies_app_layer_map, &info, &filter_control_unit, route_rule_id_array, &route_rule_id_array_count, MAX_AGGREGATE_ROUTE_RULES, &service_action, &service, netagent_ids, netagent_use_flags, NECP_MAX_NETAGENTS, required_agent_types, num_required_agent_types, proc, NULL, NULL, &drop_dest_policy_result, &drop_all_bypass); if (matched_policy) { returned_result->policy_id = matched_policy->id; returned_result->routing_result = matched_policy->result; memcpy(&returned_result->routing_result_parameter, &matched_policy->result_parameter, sizeof(returned_result->routing_result_parameter)); - } else if (necp_drop_all_order > 0) { - // Mark socket as a drop if drop_all is set - returned_result->policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; - returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_DROP; } else { - returned_result->policy_id = 0; - returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_NONE; + bool drop_all = false; + if (necp_drop_all_order > 0 || info.drop_order > 0 || drop_dest_policy_result == NECP_KERNEL_POLICY_RESULT_DROP) { + // Mark socket as a drop if drop_all is set + drop_all = true; + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE) { + drop_all_bypass = necp_check_drop_all_bypass_result(proc); + } + } + if (drop_all && drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE) { + returned_result->policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_DROP; + } else { + returned_result->policy_id = 0; + returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_NONE; + } + } + if (filter_control_unit == NECP_FILTER_UNIT_NO_FILTER) { + returned_result->filter_control_unit = 0; + } else { + returned_result->filter_control_unit = filter_control_unit; } - returned_result->filter_control_unit = filter_control_unit; returned_result->service_action = service_action; // Handle trigger service @@ -6671,7 +6460,7 @@ necp_application_find_policy_match_internal(proc_t proc, returned_result->routed_interface_index = 0; } - if (no_remote_addr && remote_family == 0 && + if (no_remote_addr && remote_family == AF_UNSPEC && (rt == NULL || rt->rt_ifp == NULL)) { // Route lookup for default IPv4 failed, try IPv6 @@ -6834,13 +6623,28 @@ necp_application_find_policy_match_internal(proc_t proc, } // Check QoS marking (fastlane) - if (necp_update_qos_marking(rt->rt_ifp, route_rule_id)) { - *flags |= NECP_CLIENT_RESULT_FLAG_ALLOW_QOS_MARKING; + for (size_t route_rule_index = 0; route_rule_index < route_rule_id_array_count; route_rule_index++) { + if (necp_update_qos_marking(rt->rt_ifp, route_rule_id_array[route_rule_index])) { + *flags |= NECP_CLIENT_RESULT_FLAG_ALLOW_QOS_MARKING; + // If the route can use QoS markings, stop iterating route rules + break; + } } if (IFNET_IS_LOW_POWER(rt->rt_ifp)) { *flags |= NECP_CLIENT_RESULT_FLAG_INTERFACE_LOW_POWER; } + + if (traffic_class == SO_TC_BK_SYS) { + // Block BK_SYS traffic if interface is throttled + u_int32_t throttle_level = 0; + if (ifnet_get_throttle(rt->rt_ifp, &throttle_level) == 0) { + if (throttle_level == IFNET_THROTTLE_OPPORTUNISTIC) { + returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_DROP; + memset(&returned_result->routing_result_parameter, 0, sizeof(returned_result->routing_result_parameter)); + } + } + } } } @@ -6867,6 +6671,12 @@ necp_application_find_policy_match_internal(proc_t proc, if (v4Route->rt_ifp != NULL && !IS_INTF_CLAT46(v4Route->rt_ifp)) { *flags |= NECP_CLIENT_RESULT_FLAG_HAS_IPV4; } + if (returned_v4_gateway != NULL && + v4Route->rt_gateway != NULL && + v4Route->rt_gateway->sa_len == sizeof(returned_v4_gateway->u.sin)) { + memcpy(&returned_v4_gateway->u.sin, v4Route->rt_gateway, sizeof(returned_v4_gateway->u.sin)); + memset(&returned_v4_gateway->u.sin.sin_zero, 0, sizeof(returned_v4_gateway->u.sin.sin_zero)); + } rtfree(v4Route); v4Route = NULL; } @@ -6879,21 +6689,56 @@ necp_application_find_policy_match_internal(proc_t proc, *flags |= NECP_CLIENT_RESULT_FLAG_HAS_NAT64; } } + if (returned_v6_gateway != NULL && + v6Route->rt_gateway != NULL && + v6Route->rt_gateway->sa_len == sizeof(returned_v6_gateway->u.sin6)) { + memcpy(&returned_v6_gateway->u.sin6, v6Route->rt_gateway, sizeof(returned_v6_gateway->u.sin6)); + } rtfree(v6Route); v6Route = NULL; } } } - u_int32_t interface_type_denied = IFRTYPE_FUNCTIONAL_UNKNOWN; - bool route_is_allowed = necp_route_is_allowed(rt, NULL, route_rule_id, &interface_type_denied); - if (!route_is_allowed) { - // If the route is blocked, treat the lookup as a drop - returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_DROP; - memset(&returned_result->routing_result_parameter, 0, sizeof(returned_result->routing_result_parameter)); + for (size_t route_rule_index = 0; route_rule_index < route_rule_id_array_count; route_rule_index++) { + u_int32_t interface_type_denied = IFRTYPE_FUNCTIONAL_UNKNOWN; + bool route_is_allowed = necp_route_is_allowed(rt, NULL, route_rule_id_array[route_rule_index], &interface_type_denied); + if (!route_is_allowed) { + // If the route is blocked, treat the lookup as a drop + returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_DROP; + memset(&returned_result->routing_result_parameter, 0, sizeof(returned_result->routing_result_parameter)); + + if (interface_type_denied != IFRTYPE_FUNCTIONAL_UNKNOWN) { + if (reason != NULL) { + if (interface_type_denied == IFRTYPE_FUNCTIONAL_CELLULAR) { + *reason = NECP_CLIENT_RESULT_REASON_CELLULAR_DENIED; + } else if (interface_type_denied == IFRTYPE_FUNCTIONAL_WIFI_INFRA) { + *reason = NECP_CLIENT_RESULT_REASON_WIFI_DENIED; + } + } + necp_send_application_interface_denied_event(pid, application_uuid, interface_type_denied); + } + // If the route gets denied, stop matching rules + break; + } + } - if (interface_type_denied != IFRTYPE_FUNCTIONAL_UNKNOWN) { - necp_send_application_interface_denied_event(pid, application_uuid, interface_type_denied); + if (rt != NULL && rt->rt_ifp != NULL) { + const bool expensive_prohibited = ((client_flags & NECP_CLIENT_PARAMETER_FLAG_PROHIBIT_EXPENSIVE) && + IFNET_IS_EXPENSIVE(rt->rt_ifp)); + const bool constrained_prohibited = ((client_flags & NECP_CLIENT_PARAMETER_FLAG_PROHIBIT_CONSTRAINED) && + IFNET_IS_CONSTRAINED(rt->rt_ifp)); + if (reason != NULL) { + if (expensive_prohibited) { + *reason = NECP_CLIENT_RESULT_REASON_EXPENSIVE_PROHIBITED; + } else if (constrained_prohibited) { + *reason = NECP_CLIENT_RESULT_REASON_CONSTRAINED_PROHIBITED; + } + } + if (expensive_prohibited || constrained_prohibited) { + // If the client flags prohibited a property of the interface, treat it as a drop + returned_result->routing_result = NECP_KERNEL_POLICY_RESULT_DROP; + memset(&returned_result->routing_result_parameter, 0, sizeof(returned_result->routing_result_parameter)); } } @@ -6912,7 +6757,58 @@ necp_application_find_policy_match_internal(proc_t proc, } static bool -necp_socket_check_policy(struct necp_kernel_socket_policy *kernel_policy, necp_app_id app_id, necp_app_id real_app_id, errno_t cred_result, u_int32_t account_id, struct substring domain, u_int8_t domain_dot_count, pid_t pid, uid_t uid, u_int32_t bound_interface_index, u_int32_t traffic_class, u_int16_t protocol, union necp_sockaddr_union *local, union necp_sockaddr_union *remote, struct necp_client_parameter_netagent_type *required_agent_types, u_int32_t num_required_agent_types, proc_t proc) +necp_is_route_local(union necp_sockaddr_union *remote_addr) +{ + bool no_remote_addr = FALSE; + u_int8_t remote_family = 0; + struct rtentry *rt = NULL; + bool is_local = FALSE; + + if (remote_addr == NULL) { + return NULL; + } + + if (remote_addr->sa.sa_len == 0 || + (remote_addr->sa.sa_family == AF_INET && remote_addr->sin.sin_addr.s_addr == 0) || + (remote_addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&remote_addr->sin6.sin6_addr))) { + no_remote_addr = TRUE; + remote_family = remote_addr->sa.sa_family; + } + + if (no_remote_addr) { + memset(remote_addr, 0, sizeof(union necp_sockaddr_union)); + if (remote_family == AF_INET6) { + // Reset address to :: + remote_addr->sa.sa_family = AF_INET6; + remote_addr->sa.sa_len = sizeof(struct sockaddr_in6); + } else { + // Reset address to 0.0.0.0 + remote_addr->sa.sa_family = AF_INET; + remote_addr->sa.sa_len = sizeof(struct sockaddr_in); + } + } + + // Lookup route regardless of the scoped interface to check if + // remote address is in a local network. + rt = rtalloc1_scoped((struct sockaddr *)remote_addr, 0, 0, 0); + + if (rt == NULL) { + goto done; + } + if (remote_addr->sa.sa_family == AF_INET && IS_INTF_CLAT46(rt->rt_ifp)) { + goto free_rt; + } + is_local = IS_NECP_DEST_IN_LOCAL_NETWORKS(rt); + +free_rt: + rtfree(rt); + +done: + return is_local; +} + +static bool +necp_socket_check_policy(struct necp_kernel_socket_policy *kernel_policy, necp_app_id app_id, necp_app_id real_app_id, errno_t cred_result, u_int32_t account_id, struct substring domain, u_int8_t domain_dot_count, pid_t pid, uid_t uid, u_int32_t bound_interface_index, u_int32_t traffic_class, u_int16_t protocol, union necp_sockaddr_union *local, union necp_sockaddr_union *remote, struct necp_client_parameter_netagent_type *required_agent_types, u_int32_t num_required_agent_types, bool has_client, uint32_t client_flags, int is_platform_binary, proc_t proc, struct rtentry *rt) { if (!(kernel_policy->condition_mask & NECP_KERNEL_CONDITION_ALL_INTERFACES)) { if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_BOUND_INTERFACE) { @@ -6968,6 +6864,12 @@ necp_socket_check_policy(struct necp_kernel_socket_policy *kernel_policy, necp_a } } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_HAS_CLIENT) { + if (!has_client) { + return FALSE; + } + } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_ENTITLEMENT) { if (cred_result != 0) { // Process is missing entitlement @@ -6975,6 +6877,13 @@ necp_socket_check_policy(struct necp_kernel_socket_policy *kernel_policy, necp_a } } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_PLATFORM_BINARY) { + if (is_platform_binary == 0) { + // Process is not platform binary + return FALSE; + } + } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_CUSTOM_ENTITLEMENT) { if (kernel_policy->cond_custom_entitlement_matched == necp_boolean_state_false) { // Process is missing entitlement based on previous check @@ -7103,6 +7012,21 @@ necp_socket_check_policy(struct necp_kernel_socket_policy *kernel_policy, necp_a } } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS) { + bool is_local = FALSE; + + if (rt != NULL) { + is_local = IS_NECP_DEST_IN_LOCAL_NETWORKS(rt); + } else { + is_local = necp_is_route_local(remote); + } + + if (!is_local) { + // Either no route to validate or no match for local networks + return FALSE; + } + } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_START) { if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_END) { bool inRange = necp_is_addr_in_range((struct sockaddr *)local, (struct sockaddr *)&kernel_policy->cond_local_start, (struct sockaddr *)&kernel_policy->cond_local_end); @@ -7155,6 +7079,46 @@ necp_socket_check_policy(struct necp_kernel_socket_policy *kernel_policy, necp_a } } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_CLIENT_FLAGS) { + if (kernel_policy->condition_negated_mask & NECP_KERNEL_CONDITION_CLIENT_FLAGS) { + if ((client_flags & kernel_policy->cond_client_flags) == kernel_policy->cond_client_flags) { + // Flags do match, and condition is negative, fail. + return FALSE; + } + } else { + if ((client_flags & kernel_policy->cond_client_flags) != kernel_policy->cond_client_flags) { + // Flags do not match, fail. + return FALSE; + } + } + } + + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_EMPTY) { + bool isEmpty = necp_addr_is_empty((struct sockaddr *)local); + if (kernel_policy->condition_negated_mask & NECP_KERNEL_CONDITION_LOCAL_EMPTY) { + if (isEmpty) { + return FALSE; + } + } else { + if (!isEmpty) { + return FALSE; + } + } + } + + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_REMOTE_EMPTY) { + bool isEmpty = necp_addr_is_empty((struct sockaddr *)remote); + if (kernel_policy->condition_negated_mask & NECP_KERNEL_CONDITION_REMOTE_EMPTY) { + if (isEmpty) { + return FALSE; + } + } else { + if (!isEmpty) { + return FALSE; + } + } + } + return TRUE; } @@ -7165,7 +7129,7 @@ necp_socket_calc_flowhash_locked(struct necp_socket_info *info) } static void -necp_socket_fillout_info_locked(struct inpcb *inp, struct sockaddr *override_local_addr, struct sockaddr *override_remote_addr, u_int32_t override_bound_interface, struct necp_socket_info *info) +necp_socket_fillout_info_locked(struct inpcb *inp, struct sockaddr *override_local_addr, struct sockaddr *override_remote_addr, u_int32_t override_bound_interface, u_int32_t drop_order, struct necp_socket_info *info) { struct socket *so = NULL; @@ -7173,6 +7137,8 @@ necp_socket_fillout_info_locked(struct inpcb *inp, struct sockaddr *override_loc so = inp->inp_socket; + info->drop_order = drop_order; + if (necp_kernel_socket_policies_condition_mask & NECP_KERNEL_CONDITION_PID) { info->pid = ((so->so_flags & SOF_DELEGATED) ? so->e_pid : so->last_pid); } @@ -7185,6 +7151,30 @@ necp_socket_fillout_info_locked(struct inpcb *inp, struct sockaddr *override_loc info->traffic_class = so->so_traffic_class; } + if (necp_kernel_socket_policies_condition_mask & NECP_KERNEL_CONDITION_HAS_CLIENT) { + info->has_client = !uuid_is_null(inp->necp_client_uuid); + } + + if (necp_kernel_socket_policies_condition_mask & NECP_KERNEL_CONDITION_CLIENT_FLAGS) { + info->client_flags = 0; + if (INP_NO_CONSTRAINED(inp)) { + info->client_flags |= NECP_CLIENT_PARAMETER_FLAG_PROHIBIT_CONSTRAINED; + } + if (INP_NO_EXPENSIVE(inp)) { + info->client_flags |= NECP_CLIENT_PARAMETER_FLAG_PROHIBIT_EXPENSIVE; + } + if (inp->inp_socket->so_flags1 & SOF1_CELLFALLBACK) { + info->client_flags |= NECP_CLIENT_PARAMETER_FLAG_FALLBACK_TRAFFIC; + } + if (inp->inp_socket->so_flags1 & SOF1_INBOUND) { + info->client_flags |= NECP_CLIENT_PARAMETER_FLAG_INBOUND; + } + if (inp->inp_socket->so_options & SO_ACCEPTCONN || + inp->inp_flags2 & INP2_EXTERNAL_PORT) { + info->client_flags |= NECP_CLIENT_PARAMETER_FLAG_LISTENER; + } + } + if (necp_kernel_socket_policies_condition_mask & NECP_KERNEL_CONDITION_PROTOCOL) { if (inp->inp_ip_p) { info->protocol = inp->inp_ip_p; @@ -7215,6 +7205,10 @@ necp_socket_fillout_info_locked(struct inpcb *inp, struct sockaddr *override_loc necp_get_parent_cred_result(NULL, info); } } + + if (necp_kernel_socket_policies_condition_mask & NECP_KERNEL_CONDITION_PLATFORM_BINARY) { + info->is_platform_binary = csproc_get_platform_binary(current_proc()) ? true : false; + } } if (necp_kernel_socket_policies_condition_mask & NECP_KERNEL_CONDITION_ACCOUNT_ID && inp->inp_necp_attributes.inp_account != NULL) { @@ -7237,45 +7231,51 @@ necp_socket_fillout_info_locked(struct inpcb *inp, struct sockaddr *override_loc } if (necp_kernel_socket_policies_condition_mask & NECP_KERNEL_ADDRESS_TYPE_CONDITIONS) { - if (inp->inp_vflag & INP_IPV4) { - if (override_local_addr) { - if (override_local_addr->sa_len <= sizeof(struct sockaddr_in)) { - memcpy(&info->local_addr, override_local_addr, override_local_addr->sa_len); - } - } else { + if (override_local_addr != NULL) { + if (override_local_addr->sa_family == AF_INET6 && override_local_addr->sa_len <= sizeof(struct sockaddr_in6)) { + memcpy(&info->local_addr, override_local_addr, override_local_addr->sa_len); + if (IN6_IS_ADDR_V4MAPPED(&(info->local_addr.sin6.sin6_addr))) { + struct sockaddr_in sin; + in6_sin6_2_sin(&sin, &(info->local_addr.sin6)); + memset(&info->local_addr, 0, sizeof(union necp_sockaddr_union)); + memcpy(&info->local_addr, &sin, sin.sin_len); + } + } else if (override_local_addr->sa_family == AF_INET && override_local_addr->sa_len <= sizeof(struct sockaddr_in)) { + memcpy(&info->local_addr, override_local_addr, override_local_addr->sa_len); + } + } else { + if (inp->inp_vflag & INP_IPV4) { ((struct sockaddr_in *)&info->local_addr)->sin_family = AF_INET; ((struct sockaddr_in *)&info->local_addr)->sin_len = sizeof(struct sockaddr_in); ((struct sockaddr_in *)&info->local_addr)->sin_port = inp->inp_lport; memcpy(&((struct sockaddr_in *)&info->local_addr)->sin_addr, &inp->inp_laddr, sizeof(struct in_addr)); - } - - if (override_remote_addr) { - if (override_remote_addr->sa_len <= sizeof(struct sockaddr_in)) { - memcpy(&info->remote_addr, override_remote_addr, override_remote_addr->sa_len); - } - } else { - ((struct sockaddr_in *)&info->remote_addr)->sin_family = AF_INET; - ((struct sockaddr_in *)&info->remote_addr)->sin_len = sizeof(struct sockaddr_in); - ((struct sockaddr_in *)&info->remote_addr)->sin_port = inp->inp_fport; - memcpy(&((struct sockaddr_in *)&info->remote_addr)->sin_addr, &inp->inp_faddr, sizeof(struct in_addr)); - } - } else if (inp->inp_vflag & INP_IPV6) { - if (override_local_addr) { - if (override_local_addr->sa_len <= sizeof(struct sockaddr_in6)) { - memcpy(&info->local_addr, override_local_addr, override_local_addr->sa_len); - } - } else { + } else if (inp->inp_vflag & INP_IPV6) { ((struct sockaddr_in6 *)&info->local_addr)->sin6_family = AF_INET6; ((struct sockaddr_in6 *)&info->local_addr)->sin6_len = sizeof(struct sockaddr_in6); ((struct sockaddr_in6 *)&info->local_addr)->sin6_port = inp->inp_lport; memcpy(&((struct sockaddr_in6 *)&info->local_addr)->sin6_addr, &inp->in6p_laddr, sizeof(struct in6_addr)); } + } - if (override_remote_addr) { - if (override_remote_addr->sa_len <= sizeof(struct sockaddr_in6)) { - memcpy(&info->remote_addr, override_remote_addr, override_remote_addr->sa_len); + if (override_remote_addr != NULL) { + if (override_remote_addr->sa_family == AF_INET6 && override_remote_addr->sa_len <= sizeof(struct sockaddr_in6)) { + memcpy(&info->remote_addr, override_remote_addr, override_remote_addr->sa_len); + if (IN6_IS_ADDR_V4MAPPED(&(info->remote_addr.sin6.sin6_addr))) { + struct sockaddr_in sin; + in6_sin6_2_sin(&sin, &(info->remote_addr.sin6)); + memset(&info->remote_addr, 0, sizeof(union necp_sockaddr_union)); + memcpy(&info->remote_addr, &sin, sin.sin_len); } - } else { + } else if (override_remote_addr->sa_family == AF_INET && override_remote_addr->sa_len <= sizeof(struct sockaddr_in)) { + memcpy(&info->remote_addr, override_remote_addr, override_remote_addr->sa_len); + } + } else { + if (inp->inp_vflag & INP_IPV4) { + ((struct sockaddr_in *)&info->remote_addr)->sin_family = AF_INET; + ((struct sockaddr_in *)&info->remote_addr)->sin_len = sizeof(struct sockaddr_in); + ((struct sockaddr_in *)&info->remote_addr)->sin_port = inp->inp_fport; + memcpy(&((struct sockaddr_in *)&info->remote_addr)->sin_addr, &inp->inp_faddr, sizeof(struct in_addr)); + } else if (inp->inp_vflag & INP_IPV6) { ((struct sockaddr_in6 *)&info->remote_addr)->sin6_family = AF_INET6; ((struct sockaddr_in6 *)&info->remote_addr)->sin6_len = sizeof(struct sockaddr_in6); ((struct sockaddr_in6 *)&info->remote_addr)->sin6_port = inp->inp_fport; @@ -7287,45 +7287,75 @@ necp_socket_fillout_info_locked(struct inpcb *inp, struct sockaddr *override_loc static inline struct necp_kernel_socket_policy * necp_socket_find_policy_match_with_info_locked(struct necp_kernel_socket_policy **policy_search_array, struct necp_socket_info *info, - necp_kernel_policy_filter *return_filter, u_int32_t *return_route_rule_id, + necp_kernel_policy_filter *return_filter, + u_int32_t *return_route_rule_id_array, size_t *return_route_rule_id_array_count, size_t route_rule_id_array_count, necp_kernel_policy_result *return_service_action, necp_kernel_policy_service *return_service, u_int32_t *return_netagent_array, u_int32_t *return_netagent_use_flags_array, size_t netagent_array_count, struct necp_client_parameter_netagent_type *required_agent_types, - u_int32_t num_required_agent_types, proc_t proc, necp_kernel_policy_id *skip_policy_id) + u_int32_t num_required_agent_types, proc_t proc, necp_kernel_policy_id *skip_policy_id, struct rtentry *rt, + necp_kernel_policy_result *return_drop_dest_policy_result, necp_drop_all_bypass_check_result_t *return_drop_all_bypass) { struct necp_kernel_socket_policy *matched_policy = NULL; u_int32_t skip_order = 0; u_int32_t skip_session_order = 0; - u_int32_t route_rule_id_array[MAX_AGGREGATE_ROUTE_RULES]; size_t route_rule_id_count = 0; int i; size_t netagent_cursor = 0; + necp_drop_all_bypass_check_result_t drop_all_bypass = NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE; + if (return_drop_all_bypass != NULL) { + *return_drop_all_bypass = drop_all_bypass; + } // Pre-process domain for quick matching struct substring domain_substring = necp_trim_dots_and_stars(info->domain, info->domain ? strlen(info->domain) : 0); u_int8_t domain_dot_count = necp_count_dots(domain_substring.string, domain_substring.length); - if (return_filter) { + if (return_filter != NULL) { *return_filter = 0; } - if (return_route_rule_id) { - *return_route_rule_id = 0; + if (return_route_rule_id_array_count != NULL) { + *return_route_rule_id_array_count = 0; } - if (return_service_action) { + if (return_service_action != NULL) { *return_service_action = 0; } - if (return_service) { + if (return_service != NULL) { return_service->identifier = 0; return_service->data = 0; } + // Do not subject layer-2 filter to NECP policies, return a PASS policy + if (necp_pass_interpose > 0 && info->client_flags & NECP_CLIENT_PARAMETER_FLAG_INTERPOSE) { + return &pass_policy; + } + + *return_drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_NONE; + if (policy_search_array != NULL) { for (i = 0; policy_search_array[i] != NULL; i++) { if (necp_drop_all_order != 0 && policy_search_array[i]->session_order >= necp_drop_all_order) { // We've hit a drop all rule + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE) { + drop_all_bypass = necp_check_drop_all_bypass_result(proc); + if (return_drop_all_bypass != NULL) { + *return_drop_all_bypass = drop_all_bypass; + } + } + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE) { + break; + } + } + if (necp_drop_dest_policy.entry_count != 0 && + necp_address_matches_drop_dest_policy(&info->remote_addr, policy_search_array[i]->session_order)) { + // We've hit a drop by destination address rule + *return_drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_DROP; + break; + } + if (info->drop_order != 0 && policy_search_array[i]->session_order >= info->drop_order) { + // We've hit a drop order for this socket break; } if (skip_session_order && policy_search_array[i]->session_order >= skip_session_order) { @@ -7346,18 +7376,24 @@ necp_socket_find_policy_match_with_info_locked(struct necp_kernel_socket_policy // Skip this policy continue; } - if (necp_socket_check_policy(policy_search_array[i], info->application_id, info->real_application_id, info->cred_result, info->account_id, domain_substring, domain_dot_count, info->pid, info->uid, info->bound_interface_index, info->traffic_class, info->protocol, &info->local_addr, &info->remote_addr, required_agent_types, num_required_agent_types, proc)) { + + if (necp_socket_check_policy(policy_search_array[i], info->application_id, info->real_application_id, info->cred_result, info->account_id, domain_substring, domain_dot_count, info->pid, info->uid, info->bound_interface_index, info->traffic_class, info->protocol, &info->local_addr, &info->remote_addr, required_agent_types, num_required_agent_types, info->has_client, info->client_flags, info->is_platform_binary, proc, rt)) { if (policy_search_array[i]->result == NECP_KERNEL_POLICY_RESULT_SOCKET_FILTER) { - if (return_filter && *return_filter == 0) { - *return_filter = policy_search_array[i]->result_parameter.filter_control_unit; + if (return_filter && *return_filter != NECP_FILTER_UNIT_NO_FILTER) { + necp_kernel_policy_filter control_unit = policy_search_array[i]->result_parameter.filter_control_unit; + if (control_unit == NECP_FILTER_UNIT_NO_FILTER) { + *return_filter = control_unit; + } else { + *return_filter |= control_unit; + } if (necp_debug > 1) { NECPLOG(LOG_DEBUG, "Socket Policy: (Application %d Real Application %d BoundInterface %d Proto %d) Filter %d", info->application_id, info->real_application_id, info->bound_interface_index, info->protocol, policy_search_array[i]->result_parameter.filter_control_unit); } } continue; } else if (policy_search_array[i]->result == NECP_KERNEL_POLICY_RESULT_ROUTE_RULES) { - if (return_route_rule_id && route_rule_id_count < MAX_AGGREGATE_ROUTE_RULES) { - route_rule_id_array[route_rule_id_count++] = policy_search_array[i]->result_parameter.route_rule_id; + if (return_route_rule_id_array && route_rule_id_count < route_rule_id_array_count) { + return_route_rule_id_array[route_rule_id_count++] = policy_search_array[i]->result_parameter.route_rule_id; if (necp_debug > 1) { NECPLOG(LOG_DEBUG, "Socket Policy: (Application %d Real Application %d BoundInterface %d Proto %d) Route Rule %d", info->application_id, info->real_application_id, info->bound_interface_index, info->protocol, policy_search_array[i]->result_parameter.route_rule_id); } @@ -7408,6 +7444,12 @@ necp_socket_find_policy_match_with_info_locked(struct necp_kernel_socket_policy continue; } + // Matched an allow unentitled, which clears any drop order + if (policy_search_array[i]->result == NECP_KERNEL_POLICY_RESULT_ALLOW_UNENTITLED) { + info->drop_order = 0; + continue; + } + // Passed all tests, found a match matched_policy = policy_search_array[i]; break; @@ -7415,10 +7457,8 @@ necp_socket_find_policy_match_with_info_locked(struct necp_kernel_socket_policy } } - if (route_rule_id_count == 1) { - *return_route_rule_id = route_rule_id_array[0]; - } else if (route_rule_id_count > 1) { - *return_route_rule_id = necp_create_aggregate_route_rule(route_rule_id_array); + if (return_route_rule_id_array_count != NULL) { + *return_route_rule_id_array_count = route_rule_id_count; } return matched_policy; } @@ -7481,7 +7521,7 @@ necp_socket_is_connected(struct inpcb *inp) static inline bool necp_socket_bypass(struct sockaddr *override_local_addr, struct sockaddr *override_remote_addr, struct inpcb *inp) { - if (necp_pass_loopback > 0 && necp_is_loopback(override_local_addr, override_remote_addr, inp, NULL)) { + if (necp_pass_loopback > 0 && necp_is_loopback(override_local_addr, override_remote_addr, inp, NULL, IFSCOPE_NONE)) { return true; } else if (necp_is_intcoproc(inp, NULL)) { return true; @@ -7495,11 +7535,12 @@ necp_socket_find_policy_match(struct inpcb *inp, struct sockaddr *override_local { struct socket *so = NULL; necp_kernel_policy_filter filter_control_unit = 0; - u_int32_t route_rule_id = 0; struct necp_kernel_socket_policy *matched_policy = NULL; necp_kernel_policy_id matched_policy_id = NECP_KERNEL_POLICY_ID_NONE; necp_kernel_policy_result service_action = 0; necp_kernel_policy_service service = { 0, 0 }; + u_int32_t drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_NONE; + necp_drop_all_bypass_check_result_t drop_all_bypass = NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE; u_int32_t netagent_ids[NECP_MAX_NETAGENTS]; memset(&netagent_ids, 0, sizeof(netagent_ids)); @@ -7523,10 +7564,12 @@ necp_socket_find_policy_match(struct inpcb *inp, struct sockaddr *override_local so = inp->inp_socket; + u_int32_t drop_order = necp_process_drop_order(so->so_cred); + // Don't lock. Possible race condition, but we don't want the performance hit. if (necp_kernel_socket_policies_count == 0 || (!(inp->inp_flags2 & INP2_WANT_APP_POLICY) && necp_kernel_socket_policies_non_app_count == 0)) { - if (necp_drop_all_order > 0) { + if (necp_drop_all_order > 0 || drop_order > 0) { inp->inp_policyresult.policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; inp->inp_policyresult.skip_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; inp->inp_policyresult.policy_gencount = 0; @@ -7560,7 +7603,7 @@ necp_socket_find_policy_match(struct inpcb *inp, struct sockaddr *override_local // Lock lck_rw_lock_shared(&necp_kernel_policy_lock); - necp_socket_fillout_info_locked(inp, override_local_addr, override_remote_addr, override_bound_interface, &info); + necp_socket_fillout_info_locked(inp, override_local_addr, override_remote_addr, override_bound_interface, drop_order, &info); inp->inp_policyresult.app_id = info.application_id; // Check info @@ -7578,7 +7621,10 @@ necp_socket_find_policy_match(struct inpcb *inp, struct sockaddr *override_local // Match socket to policy necp_kernel_policy_id skip_policy_id; - matched_policy = necp_socket_find_policy_match_with_info_locked(necp_kernel_socket_policies_map[NECP_SOCKET_MAP_APP_ID_TO_BUCKET(info.application_id)], &info, &filter_control_unit, &route_rule_id, &service_action, &service, netagent_ids, NULL, NECP_MAX_NETAGENTS, NULL, 0, current_proc(), &skip_policy_id); + u_int32_t route_rule_id_array[MAX_AGGREGATE_ROUTE_RULES]; + size_t route_rule_id_array_count = 0; + matched_policy = necp_socket_find_policy_match_with_info_locked(necp_kernel_socket_policies_map[NECP_SOCKET_MAP_APP_ID_TO_BUCKET(info.application_id)], &info, &filter_control_unit, route_rule_id_array, &route_rule_id_array_count, MAX_AGGREGATE_ROUTE_RULES, &service_action, &service, netagent_ids, NULL, NECP_MAX_NETAGENTS, NULL, 0, current_proc(), &skip_policy_id, inp->inp_route.ro_rt, &drop_dest_policy_result, &drop_all_bypass); + // If the socket matched a scoped service policy, mark as Drop if not registered. // This covers the cases in which a service is required (on demand) but hasn't started yet. if ((service_action == NECP_KERNEL_POLICY_RESULT_TRIGGER_SCOPED || @@ -7655,6 +7701,15 @@ necp_socket_find_policy_match(struct inpcb *inp, struct sockaddr *override_local } } } + + u_int32_t route_rule_id = 0; + if (route_rule_id_array_count == 1) { + route_rule_id = route_rule_id_array[0]; + } else if (route_rule_id_array_count > 1) { + route_rule_id = necp_create_aggregate_route_rule(route_rule_id_array); + } + + bool reset_tcp_mss = false; if (matched_policy) { matched_policy_id = matched_policy->id; inp->inp_policyresult.policy_id = matched_policy->id; @@ -7677,40 +7732,54 @@ necp_socket_find_policy_match(struct inpcb *inp, struct sockaddr *override_local matched_policy->result == NECP_KERNEL_POLICY_RESULT_IP_TUNNEL && info.protocol == IPPROTO_TCP) { // Reset MSS on TCP socket if tunnel policy changes - tcp_mtudisc(inp, 0); + reset_tcp_mss = true; } if (necp_debug > 1) { NECPLOG(LOG_DEBUG, "Socket Policy: %p (BoundInterface %d Proto %d) Policy %d Result %d Parameter %d", inp->inp_socket, info.bound_interface_index, info.protocol, matched_policy->id, matched_policy->result, matched_policy->result_parameter.tunnel_interface_index); } - } else if (necp_drop_all_order > 0) { - // Mark socket as a drop if set - inp->inp_policyresult.policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; - inp->inp_policyresult.skip_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; - inp->inp_policyresult.policy_gencount = necp_kernel_socket_policies_gencount; - inp->inp_policyresult.flowhash = flowhash; - inp->inp_policyresult.results.filter_control_unit = 0; - inp->inp_policyresult.results.route_rule_id = 0; - inp->inp_policyresult.results.result = NECP_KERNEL_POLICY_RESULT_DROP; } else { - // Mark non-matching socket so we don't re-check it - inp->inp_policyresult.policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; - inp->inp_policyresult.skip_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; - inp->inp_policyresult.policy_gencount = necp_kernel_socket_policies_gencount; - inp->inp_policyresult.flowhash = flowhash; - inp->inp_policyresult.results.filter_control_unit = filter_control_unit; // We may have matched a filter, so mark it! - inp->inp_policyresult.results.route_rule_id = route_rule_id; // We may have matched a route rule, so mark it! - inp->inp_policyresult.results.result = NECP_KERNEL_POLICY_RESULT_NONE; + bool drop_all = false; + if (necp_drop_all_order > 0 || info.drop_order > 0 || drop_dest_policy_result == NECP_KERNEL_POLICY_RESULT_DROP) { + // Mark socket as a drop if set + drop_all = true; + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE) { + drop_all_bypass = necp_check_drop_all_bypass_result(NULL); + } + } + if (drop_all && drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE) { + inp->inp_policyresult.policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + inp->inp_policyresult.skip_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + inp->inp_policyresult.policy_gencount = necp_kernel_socket_policies_gencount; + inp->inp_policyresult.flowhash = flowhash; + inp->inp_policyresult.results.filter_control_unit = 0; + inp->inp_policyresult.results.route_rule_id = 0; + inp->inp_policyresult.results.result = NECP_KERNEL_POLICY_RESULT_DROP; + } else { + // Mark non-matching socket so we don't re-check it + inp->inp_policyresult.policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + inp->inp_policyresult.skip_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + inp->inp_policyresult.policy_gencount = necp_kernel_socket_policies_gencount; + inp->inp_policyresult.flowhash = flowhash; + inp->inp_policyresult.results.filter_control_unit = filter_control_unit; // We may have matched a filter, so mark it! + inp->inp_policyresult.results.route_rule_id = route_rule_id; // We may have matched a route rule, so mark it! + inp->inp_policyresult.results.result = NECP_KERNEL_POLICY_RESULT_NONE; + } } // Unlock lck_rw_done(&necp_kernel_policy_lock); + if (reset_tcp_mss) { + // Update MSS when not holding the policy lock to avoid recursive locking + tcp_mtudisc(inp, 0); + } + return matched_policy_id; } static bool -necp_ip_output_check_policy(struct necp_kernel_ip_output_policy *kernel_policy, necp_kernel_policy_id socket_policy_id, necp_kernel_policy_id socket_skip_policy_id, u_int32_t bound_interface_index, u_int32_t last_interface_index, u_int16_t protocol, union necp_sockaddr_union *local, union necp_sockaddr_union *remote) +necp_ip_output_check_policy(struct necp_kernel_ip_output_policy *kernel_policy, necp_kernel_policy_id socket_policy_id, necp_kernel_policy_id socket_skip_policy_id, u_int32_t bound_interface_index, u_int32_t last_interface_index, u_int16_t protocol, union necp_sockaddr_union *local, union necp_sockaddr_union *remote, struct rtentry *rt) { if (!(kernel_policy->condition_mask & NECP_KERNEL_CONDITION_ALL_INTERFACES)) { if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_BOUND_INTERFACE) { @@ -7767,6 +7836,21 @@ necp_ip_output_check_policy(struct necp_kernel_ip_output_policy *kernel_policy, } } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_NETWORKS) { + bool is_local = FALSE; + + if (rt != NULL) { + is_local = IS_NECP_DEST_IN_LOCAL_NETWORKS(rt); + } else { + is_local = necp_is_route_local(remote); + } + + if (!is_local) { + // Either no route to validate or no match for local networks + return FALSE; + } + } + if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_START) { if (kernel_policy->condition_mask & NECP_KERNEL_CONDITION_LOCAL_END) { bool inRange = necp_is_addr_in_range((struct sockaddr *)local, (struct sockaddr *)&kernel_policy->cond_local_start, (struct sockaddr *)&kernel_policy->cond_local_end); @@ -7823,17 +7907,43 @@ necp_ip_output_check_policy(struct necp_kernel_ip_output_policy *kernel_policy, } static inline struct necp_kernel_ip_output_policy * -necp_ip_output_find_policy_match_locked(necp_kernel_policy_id socket_policy_id, necp_kernel_policy_id socket_skip_policy_id, u_int32_t bound_interface_index, u_int32_t last_interface_index, u_int16_t protocol, union necp_sockaddr_union *local_addr, union necp_sockaddr_union *remote_addr) +necp_ip_output_find_policy_match_locked(necp_kernel_policy_id socket_policy_id, necp_kernel_policy_id socket_skip_policy_id, u_int32_t bound_interface_index, u_int32_t last_interface_index, u_int16_t protocol, union necp_sockaddr_union *local_addr, union necp_sockaddr_union *remote_addr, struct rtentry *rt, u_int32_t *return_route_rule_id, necp_kernel_policy_result *return_drop_dest_policy_result, necp_drop_all_bypass_check_result_t *return_drop_all_bypass) { u_int32_t skip_order = 0; u_int32_t skip_session_order = 0; - int i; struct necp_kernel_ip_output_policy *matched_policy = NULL; struct necp_kernel_ip_output_policy **policy_search_array = necp_kernel_ip_output_policies_map[NECP_IP_OUTPUT_MAP_ID_TO_BUCKET(socket_policy_id)]; + u_int32_t route_rule_id_array[MAX_AGGREGATE_ROUTE_RULES]; + size_t route_rule_id_count = 0; + necp_drop_all_bypass_check_result_t drop_all_bypass = NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE; + if (return_drop_all_bypass != NULL) { + *return_drop_all_bypass = drop_all_bypass; + } + + if (return_route_rule_id != NULL) { + *return_route_rule_id = 0; + } + + *return_drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_NONE; + if (policy_search_array != NULL) { - for (i = 0; policy_search_array[i] != NULL; i++) { + for (int i = 0; policy_search_array[i] != NULL; i++) { if (necp_drop_all_order != 0 && policy_search_array[i]->session_order >= necp_drop_all_order) { // We've hit a drop all rule + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE) { + drop_all_bypass = necp_check_drop_all_bypass_result(NULL); + if (return_drop_all_bypass != NULL) { + *return_drop_all_bypass = drop_all_bypass; + } + } + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE) { + break; + } + } + if (necp_drop_dest_policy.entry_count > 0 && + necp_address_matches_drop_dest_policy(remote_addr, policy_search_array[i]->session_order)) { + // We've hit a drop by destination address rule + *return_drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_DROP; break; } if (skip_session_order && policy_search_array[i]->session_order >= skip_session_order) { @@ -7854,28 +7964,39 @@ necp_ip_output_find_policy_match_locked(necp_kernel_policy_id socket_policy_id, // Skip this policy continue; } - if (necp_ip_output_check_policy(policy_search_array[i], socket_policy_id, socket_skip_policy_id, bound_interface_index, last_interface_index, protocol, local_addr, remote_addr)) { - // Passed all tests, found a match - matched_policy = policy_search_array[i]; - if (policy_search_array[i]->result == NECP_KERNEL_POLICY_RESULT_SKIP) { + if (necp_ip_output_check_policy(policy_search_array[i], socket_policy_id, socket_skip_policy_id, bound_interface_index, last_interface_index, protocol, local_addr, remote_addr, rt)) { + if (policy_search_array[i]->result == NECP_KERNEL_POLICY_RESULT_ROUTE_RULES) { + if (return_route_rule_id != NULL && route_rule_id_count < MAX_AGGREGATE_ROUTE_RULES) { + route_rule_id_array[route_rule_id_count++] = policy_search_array[i]->result_parameter.route_rule_id; + } + continue; + } else if (policy_search_array[i]->result == NECP_KERNEL_POLICY_RESULT_SKIP) { skip_order = policy_search_array[i]->result_parameter.skip_policy_order; skip_session_order = policy_search_array[i]->session_order + 1; continue; } + // Passed all tests, found a match + matched_policy = policy_search_array[i]; break; } } } + if (route_rule_id_count == 1) { + *return_route_rule_id = route_rule_id_array[0]; + } else if (route_rule_id_count > 1) { + *return_route_rule_id = necp_create_aggregate_route_rule(route_rule_id_array); + } + return matched_policy; } static inline bool necp_output_bypass(struct mbuf *packet) { - if (necp_pass_loopback > 0 && necp_is_loopback(NULL, NULL, NULL, packet)) { + if (necp_pass_loopback > 0 && necp_is_loopback(NULL, NULL, NULL, packet, IFSCOPE_NONE)) { return true; } if (necp_pass_keepalives > 0 && necp_get_is_keepalive_from_packet(packet)) { @@ -7888,7 +8009,8 @@ necp_output_bypass(struct mbuf *packet) } necp_kernel_policy_id -necp_ip_output_find_policy_match(struct mbuf *packet, int flags, struct ip_out_args *ipoa, necp_kernel_policy_result *result, necp_kernel_policy_result_parameter *result_parameter) +necp_ip_output_find_policy_match(struct mbuf *packet, int flags, struct ip_out_args *ipoa, struct rtentry *rt, + necp_kernel_policy_result *result, necp_kernel_policy_result_parameter *result_parameter) { struct ip *ip = NULL; int hlen = sizeof(struct ip); @@ -7901,6 +8023,8 @@ necp_ip_output_find_policy_match(struct mbuf *packet, int flags, struct ip_out_a u_int32_t last_interface_index = 0; union necp_sockaddr_union local_addr; union necp_sockaddr_union remote_addr; + u_int32_t drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_NONE; + necp_drop_all_bypass_check_result_t drop_all_bypass = NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE; if (result) { *result = 0; @@ -7920,7 +8044,7 @@ necp_ip_output_find_policy_match(struct mbuf *packet, int flags, struct ip_out_a // Exit early for an empty list // Don't lock. Possible race condition, but we don't want the performance hit. if (necp_kernel_ip_output_policies_count == 0 || - ((socket_policy_id == NECP_KERNEL_POLICY_ID_NONE) && necp_kernel_ip_output_policies_non_id_count == 0)) { + (socket_policy_id == NECP_KERNEL_POLICY_ID_NONE && necp_kernel_ip_output_policies_non_id_count == 0 && necp_drop_dest_policy.entry_count == 0)) { if (necp_drop_all_order > 0) { matched_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; if (result) { @@ -7998,7 +8122,8 @@ necp_ip_output_find_policy_match(struct mbuf *packet, int flags, struct ip_out_a // Match packet to policy lck_rw_lock_shared(&necp_kernel_policy_lock); - matched_policy = necp_ip_output_find_policy_match_locked(socket_policy_id, socket_skip_policy_id, bound_interface_index, last_interface_index, protocol, &local_addr, &remote_addr); + u_int32_t route_rule_id = 0; + matched_policy = necp_ip_output_find_policy_match_locked(socket_policy_id, socket_skip_policy_id, bound_interface_index, last_interface_index, protocol, &local_addr, &remote_addr, rt, &route_rule_id, &drop_dest_policy_result, &drop_all_bypass); if (matched_policy) { matched_policy_id = matched_policy->id; if (result) { @@ -8009,13 +8134,36 @@ necp_ip_output_find_policy_match(struct mbuf *packet, int flags, struct ip_out_a memcpy(result_parameter, &matched_policy->result_parameter, sizeof(matched_policy->result_parameter)); } + if (route_rule_id != 0 && + packet->m_pkthdr.necp_mtag.necp_route_rule_id == 0) { + packet->m_pkthdr.necp_mtag.necp_route_rule_id = route_rule_id; + } + if (necp_debug > 1) { - NECPLOG(LOG_DEBUG, "IP Output: (ID %d BoundInterface %d LastInterface %d Proto %d) Policy %d Result %d Parameter %d", socket_policy_id, bound_interface_index, last_interface_index, protocol, matched_policy->id, matched_policy->result, matched_policy->result_parameter.tunnel_interface_index); + NECPLOG(LOG_DEBUG, "IP Output: (ID %d BoundInterface %d LastInterface %d Proto %d) Policy %d Result %d Parameter %d Route Rule %u", socket_policy_id, bound_interface_index, last_interface_index, protocol, matched_policy->id, matched_policy->result, matched_policy->result_parameter.tunnel_interface_index, route_rule_id); } - } else if (necp_drop_all_order > 0) { - matched_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; - if (result) { - *result = NECP_KERNEL_POLICY_RESULT_DROP; + } else { + bool drop_all = false; + /* + * Apply drop-all only to packets which have never matched a primary policy (check + * if the packet saved policy id is none or falls within the socket policy id range). + */ + if (socket_policy_id < NECP_KERNEL_POLICY_ID_FIRST_VALID_IP && + (necp_drop_all_order > 0 || drop_dest_policy_result == NECP_KERNEL_POLICY_RESULT_DROP)) { + drop_all = true; + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE) { + drop_all_bypass = necp_check_drop_all_bypass_result(NULL); + } + } + if (drop_all && drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE) { + matched_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + if (result) { + *result = NECP_KERNEL_POLICY_RESULT_DROP; + } + } else if (route_rule_id != 0 && + packet->m_pkthdr.necp_mtag.necp_route_rule_id == 0) { + // If we matched a route rule, mark it + packet->m_pkthdr.necp_mtag.necp_route_rule_id = route_rule_id; } } @@ -8025,7 +8173,8 @@ necp_ip_output_find_policy_match(struct mbuf *packet, int flags, struct ip_out_a } necp_kernel_policy_id -necp_ip6_output_find_policy_match(struct mbuf *packet, int flags, struct ip6_out_args *ip6oa, necp_kernel_policy_result *result, necp_kernel_policy_result_parameter *result_parameter) +necp_ip6_output_find_policy_match(struct mbuf *packet, int flags, struct ip6_out_args *ip6oa, struct rtentry *rt, + necp_kernel_policy_result *result, necp_kernel_policy_result_parameter *result_parameter) { struct ip6_hdr *ip6 = NULL; int next = -1; @@ -8039,6 +8188,8 @@ necp_ip6_output_find_policy_match(struct mbuf *packet, int flags, struct ip6_out u_int32_t last_interface_index = 0; union necp_sockaddr_union local_addr; union necp_sockaddr_union remote_addr; + u_int32_t drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_NONE; + necp_drop_all_bypass_check_result_t drop_all_bypass = NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE; if (result) { *result = 0; @@ -8058,7 +8209,7 @@ necp_ip6_output_find_policy_match(struct mbuf *packet, int flags, struct ip6_out // Exit early for an empty list // Don't lock. Possible race condition, but we don't want the performance hit. if (necp_kernel_ip_output_policies_count == 0 || - ((socket_policy_id == NECP_KERNEL_POLICY_ID_NONE) && necp_kernel_ip_output_policies_non_id_count == 0)) { + (socket_policy_id == NECP_KERNEL_POLICY_ID_NONE && necp_kernel_ip_output_policies_non_id_count == 0 && necp_drop_dest_policy.entry_count == 0)) { if (necp_drop_all_order > 0) { matched_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; if (result) { @@ -8133,7 +8284,8 @@ necp_ip6_output_find_policy_match(struct mbuf *packet, int flags, struct ip6_out // Match packet to policy lck_rw_lock_shared(&necp_kernel_policy_lock); - matched_policy = necp_ip_output_find_policy_match_locked(socket_policy_id, socket_skip_policy_id, bound_interface_index, last_interface_index, protocol, &local_addr, &remote_addr); + u_int32_t route_rule_id = 0; + matched_policy = necp_ip_output_find_policy_match_locked(socket_policy_id, socket_skip_policy_id, bound_interface_index, last_interface_index, protocol, &local_addr, &remote_addr, rt, &route_rule_id, &drop_dest_policy_result, &drop_all_bypass); if (matched_policy) { matched_policy_id = matched_policy->id; if (result) { @@ -8144,13 +8296,36 @@ necp_ip6_output_find_policy_match(struct mbuf *packet, int flags, struct ip6_out memcpy(result_parameter, &matched_policy->result_parameter, sizeof(matched_policy->result_parameter)); } + if (route_rule_id != 0 && + packet->m_pkthdr.necp_mtag.necp_route_rule_id == 0) { + packet->m_pkthdr.necp_mtag.necp_route_rule_id = route_rule_id; + } + if (necp_debug > 1) { - NECPLOG(LOG_DEBUG, "IP6 Output: (ID %d BoundInterface %d LastInterface %d Proto %d) Policy %d Result %d Parameter %d", socket_policy_id, bound_interface_index, last_interface_index, protocol, matched_policy->id, matched_policy->result, matched_policy->result_parameter.tunnel_interface_index); + NECPLOG(LOG_DEBUG, "IP6 Output: (ID %d BoundInterface %d LastInterface %d Proto %d) Policy %d Result %d Parameter %d Route Rule %u", socket_policy_id, bound_interface_index, last_interface_index, protocol, matched_policy->id, matched_policy->result, matched_policy->result_parameter.tunnel_interface_index, route_rule_id); } - } else if (necp_drop_all_order > 0) { - matched_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; - if (result) { - *result = NECP_KERNEL_POLICY_RESULT_DROP; + } else { + bool drop_all = false; + /* + * Apply drop-all only to packets which have never matched a primary policy (check + * if the packet saved policy id is none or falls within the socket policy id range). + */ + if (socket_policy_id < NECP_KERNEL_POLICY_ID_FIRST_VALID_IP && + (necp_drop_all_order > 0 || drop_dest_policy_result == NECP_KERNEL_POLICY_RESULT_DROP)) { + drop_all = true; + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE) { + drop_all_bypass = necp_check_drop_all_bypass_result(NULL); + } + } + if (drop_all && drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE) { + matched_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + if (result) { + *result = NECP_KERNEL_POLICY_RESULT_DROP; + } + } else if (route_rule_id != 0 && + packet->m_pkthdr.necp_mtag.necp_route_rule_id == 0) { + // If we matched a route rule, mark it + packet->m_pkthdr.necp_mtag.necp_route_rule_id = route_rule_id; } } @@ -8360,6 +8535,54 @@ necp_buffer_compare_with_bit_prefix(u_int8_t *p1, u_int8_t *p2, u_int32_t bits) return TRUE; } +static bool +necp_addr_is_empty(struct sockaddr *addr) +{ + if (addr == NULL) { + return TRUE; + } + + if (addr->sa_len == 0) { + return TRUE; + } + + switch (addr->sa_family) { + case AF_INET: { + static struct sockaddr_in ipv4_empty_address = { + .sin_len = sizeof(struct sockaddr_in), + .sin_family = AF_INET, + .sin_port = 0, + .sin_addr = { .s_addr = 0 }, // 0.0.0.0 + .sin_zero = {0}, + }; + if (necp_addr_compare(addr, (struct sockaddr *)&ipv4_empty_address, 0) == 0) { + return TRUE; + } else { + return FALSE; + } + } + case AF_INET6: { + static struct sockaddr_in6 ipv6_empty_address = { + .sin6_len = sizeof(struct sockaddr_in6), + .sin6_family = AF_INET6, + .sin6_port = 0, + .sin6_flowinfo = 0, + .sin6_addr = IN6ADDR_ANY_INIT, // :: + .sin6_scope_id = 0, + }; + if (necp_addr_compare(addr, (struct sockaddr *)&ipv6_empty_address, 0) == 0) { + return TRUE; + } else { + return FALSE; + } + } + default: + return FALSE; + } + + return FALSE; +} + static bool necp_update_qos_marking(struct ifnet *ifp, u_int32_t route_rule_id) { @@ -8399,12 +8622,13 @@ necp_update_qos_marking(struct ifnet *ifp, u_int32_t route_rule_id) if ((route_rule->cellular_action == NECP_ROUTE_RULE_QOS_MARKING && IFNET_IS_CELLULAR(ifp)) || (route_rule->wifi_action == NECP_ROUTE_RULE_QOS_MARKING && IFNET_IS_WIFI(ifp)) || (route_rule->wired_action == NECP_ROUTE_RULE_QOS_MARKING && IFNET_IS_WIRED(ifp)) || - (route_rule->expensive_action == NECP_ROUTE_RULE_QOS_MARKING && IFNET_IS_EXPENSIVE(ifp))) { + (route_rule->expensive_action == NECP_ROUTE_RULE_QOS_MARKING && IFNET_IS_EXPENSIVE(ifp)) || + (route_rule->constrained_action == NECP_ROUTE_RULE_QOS_MARKING && IFNET_IS_CONSTRAINED(ifp))) { qos_marking = TRUE; if (necp_debug > 2) { - NECPLOG(LOG_DEBUG, "QoS Marking: C:%d WF:%d W:%d E:%d for Rule %d Allowed %d", + NECPLOG(LOG_DEBUG, "QoS Marking: C:%d WF:%d W:%d E:%d Cn:%d for Rule %d Allowed %d", route_rule->cellular_action, route_rule->wifi_action, route_rule->wired_action, - route_rule->expensive_action, route_rule_id, qos_marking); + route_rule->expensive_action, route_rule->constrained_action, route_rule_id, qos_marking); } goto done; } @@ -8630,6 +8854,22 @@ necp_route_is_allowed_inner(struct rtentry *route, struct ifnet *ifp, u_int32_t } } + if (IFNET_IS_CONSTRAINED(ifp)) { + if (route_rule->constrained_action == NECP_ROUTE_RULE_DENY_LQM_ABORT) { + if (necp_route_is_lqm_abort(ifp, delegated_ifp)) { + // Mark aggregate action as deny + type_aggregate_action = NECP_ROUTE_RULE_DENY_INTERFACE; + } + } else if (IS_NECP_ROUTE_RULE_ALLOW_OR_DENY(route_rule->constrained_action)) { + if (type_aggregate_action == NECP_ROUTE_RULE_NONE || + (type_aggregate_action == NECP_ROUTE_RULE_ALLOW_INTERFACE && + route_rule->constrained_action == NECP_ROUTE_RULE_DENY_INTERFACE)) { + // Deny wins if there is a conflict + type_aggregate_action = route_rule->constrained_action; + } + } + } + if (type_aggregate_action != NECP_ROUTE_RULE_NONE) { if (necp_debug > 1) { NECPLOG(LOG_DEBUG, "Route Allowed: C:%d WF:%d W:%d E:%d for Rule %d Allowed %d", route_rule->cellular_action, route_rule->wifi_action, route_rule->wired_action, route_rule->expensive_action, route_rule_id, ((type_aggregate_action == NECP_ROUTE_RULE_DENY_INTERFACE) ? FALSE : TRUE)); @@ -8726,7 +8966,8 @@ necp_socket_is_allowed_to_send_recv_internal(struct inpcb *inp, struct sockaddr u_int32_t route_rule_id = 0; struct rtentry *route = NULL; u_int32_t interface_type_denied = IFRTYPE_FUNCTIONAL_UNKNOWN; - + necp_kernel_policy_result drop_dest_policy_result = NECP_KERNEL_POLICY_RESULT_NONE; + necp_drop_all_bypass_check_result_t drop_all_bypass = NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE; u_int32_t netagent_ids[NECP_MAX_NETAGENTS]; memset(&netagent_ids, 0, sizeof(netagent_ids)); @@ -8746,10 +8987,14 @@ necp_socket_is_allowed_to_send_recv_internal(struct inpcb *inp, struct sockaddr route = inp->inp_route.ro_rt; + struct socket *so = inp->inp_socket; + + u_int32_t drop_order = necp_process_drop_order(so->so_cred); + // Don't lock. Possible race condition, but we don't want the performance hit. if (necp_kernel_socket_policies_count == 0 || (!(inp->inp_flags2 & INP2_WANT_APP_POLICY) && necp_kernel_socket_policies_non_app_count == 0)) { - if (necp_drop_all_order > 0) { + if (necp_drop_all_order > 0 || drop_order > 0) { if (necp_socket_bypass(override_local_addr, override_remote_addr, inp)) { allowed_to_receive = TRUE; } else { @@ -8806,7 +9051,7 @@ necp_socket_is_allowed_to_send_recv_internal(struct inpcb *inp, struct sockaddr // Actually calculate policy result lck_rw_lock_shared(&necp_kernel_policy_lock); - necp_socket_fillout_info_locked(inp, override_local_addr, override_remote_addr, 0, &info); + necp_socket_fillout_info_locked(inp, override_local_addr, override_remote_addr, 0, drop_order, &info); flowhash = necp_socket_calc_flowhash_locked(&info); if (inp->inp_policyresult.policy_id != NECP_KERNEL_POLICY_ID_NONE && @@ -8834,7 +9079,16 @@ necp_socket_is_allowed_to_send_recv_internal(struct inpcb *inp, struct sockaddr goto done; } - struct necp_kernel_socket_policy *matched_policy = necp_socket_find_policy_match_with_info_locked(necp_kernel_socket_policies_map[NECP_SOCKET_MAP_APP_ID_TO_BUCKET(info.application_id)], &info, NULL, &route_rule_id, &service_action, &service, netagent_ids, NULL, NECP_MAX_NETAGENTS, NULL, 0, current_proc(), return_skip_policy_id); + u_int32_t route_rule_id_array[MAX_AGGREGATE_ROUTE_RULES]; + size_t route_rule_id_array_count = 0; + struct necp_kernel_socket_policy *matched_policy = necp_socket_find_policy_match_with_info_locked(necp_kernel_socket_policies_map[NECP_SOCKET_MAP_APP_ID_TO_BUCKET(info.application_id)], &info, NULL, route_rule_id_array, &route_rule_id_array_count, MAX_AGGREGATE_ROUTE_RULES, &service_action, &service, netagent_ids, NULL, NECP_MAX_NETAGENTS, NULL, 0, current_proc(), return_skip_policy_id, inp->inp_route.ro_rt, &drop_dest_policy_result, &drop_all_bypass); + + if (route_rule_id_array_count == 1) { + route_rule_id = route_rule_id_array[0]; + } else if (route_rule_id_array_count > 1) { + route_rule_id = necp_create_aggregate_route_rule(route_rule_id_array); + } + if (matched_policy != NULL) { if (matched_policy->result == NECP_KERNEL_POLICY_RESULT_DROP || matched_policy->result == NECP_KERNEL_POLICY_RESULT_SOCKET_DIVERT || @@ -8861,14 +9115,23 @@ necp_socket_is_allowed_to_send_recv_internal(struct inpcb *inp, struct sockaddr NECPLOG(LOG_DEBUG, "Socket Send/Recv Policy: Policy %d Allowed %d", return_policy_id ? *return_policy_id : 0, allowed_to_receive); } goto done; - } else if (necp_drop_all_order > 0) { - allowed_to_receive = FALSE; } else { - if (return_policy_id) { - *return_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + bool drop_all = false; + if (necp_drop_all_order > 0 || info.drop_order > 0 || drop_dest_policy_result == NECP_KERNEL_POLICY_RESULT_DROP) { + drop_all = true; + if (drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_NONE) { + drop_all_bypass = necp_check_drop_all_bypass_result(NULL); + } } - if (return_route_rule_id) { - *return_route_rule_id = route_rule_id; + if (drop_all && drop_all_bypass == NECP_DROP_ALL_BYPASS_CHECK_RESULT_FALSE) { + allowed_to_receive = FALSE; + } else { + if (return_policy_id) { + *return_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + } + if (return_route_rule_id) { + *return_route_rule_id = route_rule_id; + } } } @@ -8915,10 +9178,13 @@ necp_socket_is_allowed_to_send_recv_v6(struct inpcb *inp, u_int16_t local_port, } bool -necp_socket_is_allowed_to_send_recv(struct inpcb *inp, necp_kernel_policy_id *return_policy_id, u_int32_t *return_route_rule_id, +necp_socket_is_allowed_to_send_recv(struct inpcb *inp, ifnet_t interface, necp_kernel_policy_id *return_policy_id, + u_int32_t *return_route_rule_id, necp_kernel_policy_id *return_skip_policy_id) { - return necp_socket_is_allowed_to_send_recv_internal(inp, NULL, NULL, NULL, return_policy_id, return_route_rule_id, return_skip_policy_id); + return necp_socket_is_allowed_to_send_recv_internal(inp, NULL, NULL, interface, + return_policy_id, return_route_rule_id, + return_skip_policy_id); } int @@ -8946,8 +9212,18 @@ necp_mark_packet_from_socket(struct mbuf *packet, struct inpcb *inp, necp_kernel } packet->m_pkthdr.necp_mtag.necp_app_id = inp->inp_policyresult.app_id; - if (skip_policy_id != NECP_KERNEL_POLICY_ID_NONE) { + if (skip_policy_id != NECP_KERNEL_POLICY_ID_NONE && + skip_policy_id != NECP_KERNEL_POLICY_ID_NO_MATCH) { + // Only mark the skip policy if it is a valid policy ID packet->m_pkthdr.necp_mtag.necp_skip_policy_id = skip_policy_id; + } else if (inp->inp_policyresult.results.filter_control_unit == NECP_FILTER_UNIT_NO_FILTER) { + // Overload the meaning of "NECP_KERNEL_POLICY_ID_NO_MATCH" + // to indicate that NECP_FILTER_UNIT_NO_FILTER was set + // See necp_get_skip_policy_id_from_packet() and + // necp_packet_should_skip_filters(). + packet->m_pkthdr.necp_mtag.necp_skip_policy_id = NECP_KERNEL_POLICY_ID_NO_MATCH; + } else { + packet->m_pkthdr.necp_mtag.necp_skip_policy_id = NECP_KERNEL_POLICY_ID_NONE; } return 0; @@ -9018,9 +9294,25 @@ necp_get_skip_policy_id_from_packet(struct mbuf *packet) return NECP_KERNEL_POLICY_ID_NONE; } + // Check for overloaded value. See necp_mark_packet_from_socket(). + if (packet->m_pkthdr.necp_mtag.necp_skip_policy_id == NECP_KERNEL_POLICY_ID_NO_MATCH) { + return NECP_KERNEL_POLICY_ID_NONE; + } + return packet->m_pkthdr.necp_mtag.necp_skip_policy_id; } +bool +necp_packet_should_skip_filters(struct mbuf *packet) +{ + if (packet == NULL || !(packet->m_flags & M_PKTHDR)) { + return false; + } + + // Check for overloaded value. See necp_mark_packet_from_socket(). + return packet->m_pkthdr.necp_mtag.necp_skip_policy_id == NECP_KERNEL_POLICY_ID_NO_MATCH; +} + u_int32_t necp_get_last_interface_index_from_packet(struct mbuf *packet) { @@ -9273,7 +9565,7 @@ necp_addr_is_loopback(struct sockaddr *address) } static bool -necp_is_loopback(struct sockaddr *local_addr, struct sockaddr *remote_addr, struct inpcb *inp, struct mbuf *packet) +necp_is_loopback(struct sockaddr *local_addr, struct sockaddr *remote_addr, struct inpcb *inp, struct mbuf *packet, u_int32_t bound_interface_index) { // Note: This function only checks for the loopback addresses. // In the future, we may want to expand to also allow any traffic @@ -9303,6 +9595,8 @@ necp_is_loopback(struct sockaddr *local_addr, struct sockaddr *remote_addr, stru return TRUE; } } + } else if (bound_interface_index != IFSCOPE_NONE && lo_ifp->if_index == bound_interface_index) { + return TRUE; } if (packet != NULL) { @@ -9332,7 +9626,17 @@ static bool necp_is_intcoproc(struct inpcb *inp, struct mbuf *packet) { if (inp != NULL) { - return sflt_permission_check(inp) ? true : false; + if (!(inp->inp_vflag & INP_IPV6)) { + return false; + } + if (INP_INTCOPROC_ALLOWED(inp)) { + return true; + } + if ((inp->inp_flags & INP_BOUND_IF) && + IFNET_IS_INTCOPROC(inp->inp_boundifp)) { + return true; + } + return false; } if (packet != NULL) { struct ip6_hdr *ip6 = mtod(packet, struct ip6_hdr *); @@ -9346,3 +9650,176 @@ necp_is_intcoproc(struct inpcb *inp, struct mbuf *packet) return false; } + +static bool +necp_address_matches_drop_dest_policy(union necp_sockaddr_union *sau, u_int32_t session_order) +{ + char dest_str[MAX_IPv6_STR_LEN]; + + if (necp_drop_dest_debug > 0) { + if (sau->sa.sa_family == AF_INET) { + (void) inet_ntop(AF_INET, &sau->sin.sin_addr, dest_str, sizeof(dest_str)); + } else if (sau->sa.sa_family == AF_INET6) { + (void) inet_ntop(AF_INET6, &sau->sin6.sin6_addr, dest_str, sizeof(dest_str)); + } else { + dest_str[0] = 0; + } + } + for (u_int32_t i = 0; i < necp_drop_dest_policy.entry_count; i++) { + struct necp_drop_dest_entry *necp_drop_dest_entry = &necp_drop_dest_policy.entries[i]; + struct necp_policy_condition_addr *npca = &necp_drop_dest_entry->cond_addr; + + if (session_order >= necp_drop_dest_entry->order && necp_is_addr_in_subnet(&sau->sa, &npca->address.sa, npca->prefix)) { + if (necp_drop_dest_debug > 0) { + char subnet_str[MAX_IPv6_STR_LEN]; + struct proc *p = current_proc(); + pid_t pid = proc_pid(p); + + if (sau->sa.sa_family == AF_INET) { + (void) inet_ntop(AF_INET, &npca->address.sin, subnet_str, sizeof(subnet_str)); + os_log(OS_LOG_DEFAULT, "%s (process %s:%u) %s matches %s/%u", __func__, proc_best_name(p), pid, dest_str, subnet_str, npca->prefix); + } else if (sau->sa.sa_family == AF_INET6) { + (void) inet_ntop(AF_INET6, &npca->address.sin6, subnet_str, sizeof(subnet_str)); + os_log(OS_LOG_DEFAULT, "%s (process %s:%u) %s matches %s/%u", __func__, proc_best_name(p), pid, dest_str, subnet_str, npca->prefix); + } + } + return true; + } + } + if (necp_drop_dest_debug > 1) { + struct proc *p = current_proc(); + pid_t pid = proc_pid(p); + + os_log(OS_LOG_DEFAULT, "%s (process %s:%u) %s no match", __func__, proc_best_name(p), pid, dest_str); + } + return false; +} + +static int +sysctl_handle_necp_drop_dest_level SYSCTL_HANDLER_ARGS +{ +#pragma unused(arg1, arg2, oidp) + int changed = 0; + int error = 0; + struct necp_drop_dest_policy tmp_drop_dest_policy; + struct proc *p = current_proc(); + pid_t pid = proc_pid(p); + + if (req->newptr != USER_ADDR_NULL && proc_suser(current_proc()) != 0 && + priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NECP_POLICIES, 0) != 0) { + NECPLOG(LOG_ERR, "%s (process %s:%u) not permitted", __func__, proc_best_name(p), pid); + return EPERM; + } + if (req->newptr != USER_ADDR_NULL && req->newlen != sizeof(struct necp_drop_dest_policy)) { + NECPLOG(LOG_ERR, "%s (process %s:%u) bad newlen %lu", __func__, proc_best_name(p), pid, req->newlen); + return EINVAL; + } + + memcpy(&tmp_drop_dest_policy, &necp_drop_dest_policy, sizeof(struct necp_drop_dest_policy)); + error = sysctl_io_opaque(req, &tmp_drop_dest_policy, sizeof(struct necp_drop_dest_policy), &changed); + if (error != 0) { + NECPLOG(LOG_ERR, "%s (process %s:%u) sysctl_io_opaque() error %d", __func__, proc_best_name(p), pid, error); + return error; + } + if (changed == 0 || req->newptr == USER_ADDR_NULL) { + return error; + } + + // + // Validate the passed parameters + // + if (tmp_drop_dest_policy.entry_count >= MAX_NECP_DROP_DEST_LEVEL_ADDRS) { + NECPLOG(LOG_ERR, "%s (process %s:%u) bad entry_count %u", __func__, proc_best_name(p), pid, tmp_drop_dest_policy.entry_count); + return EINVAL; + } + for (u_int32_t i = 0; i < tmp_drop_dest_policy.entry_count; i++) { + struct necp_drop_dest_entry *tmp_drop_dest_entry = &tmp_drop_dest_policy.entries[i]; + struct necp_policy_condition_addr *npca = &tmp_drop_dest_entry->cond_addr; + + switch (tmp_drop_dest_entry->level) { + case NECP_SESSION_PRIORITY_UNKNOWN: + if (tmp_drop_dest_policy.entry_count != 0) { + NECPLOG(LOG_ERR, "%s (process %s:%u) NECP_SESSION_PRIORITY_UNKNOWN bad entry_count %u", __func__, proc_best_name(p), pid, tmp_drop_dest_policy.entry_count); + return EINVAL; + } + break; + case NECP_SESSION_PRIORITY_CONTROL: + case NECP_SESSION_PRIORITY_PRIVILEGED_TUNNEL: + case NECP_SESSION_PRIORITY_HIGH: + case NECP_SESSION_PRIORITY_DEFAULT: + case NECP_SESSION_PRIORITY_LOW: + if (tmp_drop_dest_policy.entry_count == 0) { + NECPLOG(LOG_ERR, "%s (process %s:%u) priority %u entry_count 0", __func__, proc_best_name(p), pid, tmp_drop_dest_entry->level); + return EINVAL; + } + break; + default: { + NECPLOG(LOG_ERR, "%s (process %s:%u) bad level %u", __func__, proc_best_name(p), pid, tmp_drop_dest_entry->level); + return EINVAL; + } + } + + switch (npca->address.sa.sa_family) { + case AF_INET: { + if (npca->prefix > 32) { + NECPLOG(LOG_ERR, "%s (process %s:%u) AF_INET bad prefix %u", __func__, proc_best_name(p), pid, npca->prefix); + return EINVAL; + } + if (npca->address.sin.sin_len != sizeof(struct sockaddr_in)) { + NECPLOG(LOG_ERR, "%s (process %s:%u) AF_INET bad sin_len %u", __func__, proc_best_name(p), pid, npca->address.sin.sin_len); + return EINVAL; + } + if (npca->address.sin.sin_port != 0) { + NECPLOG(LOG_ERR, "%s (process %s:%u) AF_INET bad sin_port %u, not zero", __func__, proc_best_name(p), pid, npca->address.sin.sin_port); + return EINVAL; + } + break; + } + case AF_INET6: { + if (npca->prefix > 128) { + NECPLOG(LOG_ERR, "%s (process %s:%u) AF_INET6 bad prefix %u", __func__, proc_best_name(p), pid, npca->prefix); + return EINVAL; + } + if (npca->address.sin6.sin6_len != sizeof(struct sockaddr_in6)) { + NECPLOG(LOG_ERR, "%s (process %s:%u) AF_INET6 bad sin6_len %u", __func__, proc_best_name(p), pid, npca->address.sin6.sin6_len); + return EINVAL; + } + if (npca->address.sin6.sin6_port != 0) { + NECPLOG(LOG_ERR, "%s (process %s:%u) AF_INET6 bad sin6_port %u, not zero", __func__, proc_best_name(p), pid, npca->address.sin6.sin6_port); + return EINVAL; + } + if (npca->address.sin6.sin6_flowinfo != 0) { + NECPLOG(LOG_ERR, "%s (process %s:%u) AF_INET6 bad sin6_flowinfo %u, not zero", __func__, proc_best_name(p), pid, npca->address.sin6.sin6_flowinfo); + return EINVAL; + } + if (npca->address.sin6.sin6_scope_id != 0) { + NECPLOG(LOG_ERR, "%s (process %s:%u) AF_INET6 bad sin6_scope_id %u, not zero", __func__, proc_best_name(p), pid, npca->address.sin6.sin6_scope_id); + return EINVAL; + } + break; + } + default: { + return EINVAL; + } + } + } + + // + // Commit the changed policy + // + lck_rw_lock_exclusive(&necp_kernel_policy_lock); + memset(&necp_drop_dest_policy, 0, sizeof(struct necp_drop_dest_policy)); + + necp_drop_dest_policy.entry_count = tmp_drop_dest_policy.entry_count; + for (u_int32_t i = 0; i < tmp_drop_dest_policy.entry_count; i++) { + struct necp_drop_dest_entry *tmp_drop_dest_entry = &tmp_drop_dest_policy.entries[i]; + struct necp_drop_dest_entry *necp_drop_dest_entry = &necp_drop_dest_policy.entries[i]; + + memcpy(necp_drop_dest_entry, tmp_drop_dest_entry, sizeof(struct necp_drop_dest_entry)); + + necp_drop_dest_entry->order = necp_get_first_order_for_priority(necp_drop_dest_entry->level); + } + lck_rw_done(&necp_kernel_policy_lock); + + return 0; +}