restricted_in_port.c [plain text]
#include <sys/types.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <kern/task.h>
#include <IOKit/IOBSD.h>
#include <net/restricted_in_port.h>
#include <netinet/in.h>
#include <os/log.h>
#define ENTITLEMENT_TEST_PORT "com.apple.private.network.restricted.port.test"
#define ENTITLEMENT_TEST_CONTROL "com.apple.private.network.restricted.port.control"
bitmap_t *restricted_port_bitmap = NULL;
struct restricted_port_entry {
const char *rpe_entitlement; in_port_t rpe_port; uint16_t rpe_flags; };
#define RPE_FLAG_SUPERUSER 0x01 // superuser can use the port
#define RPE_FLAG_ENTITLEMENT 0x02 // can use the port with the required entitlement
#define RPE_FLAG_TCP 0x04 // require entitlement for TCP
#define RPE_FLAG_UDP 0x08 // require entitlement for TCP
#define RPE_FLAG_TEST 0x10 // entry for testing
static struct restricted_port_entry restricted_port_list[] = {
#if CONFIG_EMBEDDED
{
.rpe_port = 62742,
.rpe_flags = RPE_FLAG_ENTITLEMENT | RPE_FLAG_TCP | RPE_FLAG_UDP,
.rpe_entitlement = "com.apple.private.network.restricted.port.nr_proxy",
},
{
.rpe_port = 62743,
.rpe_flags = RPE_FLAG_ENTITLEMENT | RPE_FLAG_UDP,
.rpe_entitlement = "com.apple.private.network.restricted.port.nr_control",
},
{
.rpe_port = 61314,
.rpe_flags = RPE_FLAG_ENTITLEMENT | RPE_FLAG_TCP | RPE_FLAG_UDP,
.rpe_entitlement = "com.apple.private.network.restricted.port.ids_service_connector",
},
{
.rpe_port = 61315,
.rpe_flags = RPE_FLAG_ENTITLEMENT | RPE_FLAG_TCP | RPE_FLAG_UDP,
.rpe_entitlement = "com.apple.private.network.restricted.port.ids_cloud_service_connector",
},
#endif
#if (DEBUG || DEVELOPMENT)
{
.rpe_port = 0,
.rpe_flags = RPE_FLAG_TCP | RPE_FLAG_TEST,
.rpe_entitlement = ENTITLEMENT_TEST_PORT,
},
{
.rpe_port = 0,
.rpe_flags = RPE_FLAG_UDP | RPE_FLAG_TEST,
.rpe_entitlement = ENTITLEMENT_TEST_PORT,
},
#endif
{
.rpe_port = 0,
.rpe_flags = 0,
.rpe_entitlement = NULL,
}
};
#define RPE_ENTRY_COUNT (sizeof(restricted_port_list) / sizeof(restricted_port_list[0]))
SYSCTL_NODE(_net, OID_AUTO, restricted_port,
CTLFLAG_RW | CTLFLAG_LOCKED, 0, "restricted port");
static int sysctl_restricted_port_bitmap SYSCTL_HANDLER_ARGS;
static int sysctl_restricted_port_enforced SYSCTL_HANDLER_ARGS;
static int sysctl_restricted_port_verbose SYSCTL_HANDLER_ARGS;
SYSCTL_PROC(_net_restricted_port, OID_AUTO, bitmap,
CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED,
0, 0, &sysctl_restricted_port_bitmap, "", "");
#if (DEBUG || DEVELOPMENT)
static int restricted_port_enforced = 1;
SYSCTL_PROC(_net_restricted_port, OID_AUTO, enforced,
CTLTYPE_INT | CTLFLAG_LOCKED | CTLFLAG_RW | CTLFLAG_ANYBODY,
0, 0, &sysctl_restricted_port_enforced, "I", "");
#else
const int restricted_port_enforced = 1;
SYSCTL_PROC(_net_restricted_port, OID_AUTO, enforced,
CTLTYPE_INT | CTLFLAG_LOCKED | CTLFLAG_RD,
0, 0, &sysctl_restricted_port_enforced, "I", "");
#endif
static int restricted_port_verbose = 0;
SYSCTL_PROC(_net_restricted_port, OID_AUTO, verbose,
CTLTYPE_INT | CTLFLAG_LOCKED | CTLFLAG_RW | CTLFLAG_ANYBODY,
0, 0, &sysctl_restricted_port_verbose, "I", "");
#if (DEBUG || DEVELOPMENT)
static uint16_t restricted_port_test = 0;
static int sysctl_restricted_port_test_entitlement SYSCTL_HANDLER_ARGS;
static int sysctl_restricted_port_test_superuser SYSCTL_HANDLER_ARGS;
SYSCTL_PROC(_net_restricted_port, OID_AUTO, test_entitlement,
CTLTYPE_INT | CTLFLAG_LOCKED | CTLFLAG_RW | CTLFLAG_ANYBODY,
0, 0, &sysctl_restricted_port_test_entitlement, "UI", "");
SYSCTL_PROC(_net_restricted_port, OID_AUTO, test_superuser,
CTLTYPE_INT | CTLFLAG_LOCKED | CTLFLAG_RW | CTLFLAG_ANYBODY,
0, 0, &sysctl_restricted_port_test_superuser, "UI", "");
#endif
static int
sysctl_restricted_port_bitmap SYSCTL_HANDLER_ARGS
{
#pragma unused(oidp, arg1, arg2)
if (req->newptr) {
return EPERM;
}
int error = SYSCTL_OUT(req, restricted_port_bitmap, BITMAP_SIZE(UINT16_MAX));
return error;
}
static int
sysctl_restricted_port_enforced SYSCTL_HANDLER_ARGS
{
#pragma unused(arg1, arg2)
int old_value = restricted_port_enforced;
int value = old_value;
int error = sysctl_handle_int(oidp, &value, 0, req);
if (error != 0 || !req->newptr) {
return error;
}
#if (DEBUG || DEVELOPMENT)
if (proc_suser(current_proc()) != 0 &&
!IOTaskHasEntitlement(current_task(), ENTITLEMENT_TEST_CONTROL)) {
return EPERM;
}
restricted_port_enforced = value;
os_log(OS_LOG_DEFAULT,
"%s:%u sysctl net.restricted_port.enforced: %d -> %d",
proc_best_name(current_proc()), proc_selfpid(),
old_value, restricted_port_enforced);
return error;
#else
return EPERM;
#endif
}
static int
sysctl_restricted_port_verbose SYSCTL_HANDLER_ARGS
{
#pragma unused(arg1, arg2)
int old_value = restricted_port_verbose;
int value = old_value;
int error = sysctl_handle_int(oidp, &value, 0, req);
if (error != 0 || !req->newptr) {
return error;
}
if (proc_suser(current_proc()) != 0 &&
!IOTaskHasEntitlement(current_task(), ENTITLEMENT_TEST_CONTROL)) {
return EPERM;
}
restricted_port_verbose = value;
os_log(OS_LOG_DEFAULT,
"%s:%u sysctl net.restricted_port.verbose: %d -> %d)",
proc_best_name(current_proc()), proc_selfpid(),
old_value, restricted_port_verbose);
return error;
}
#if (DEBUG || DEVELOPMENT)
static int
sysctl_restricted_port_test_common(struct sysctl_oid *oidp,
struct sysctl_req *req, bool test_superuser)
{
uint16_t old_value = restricted_port_test;
int value = old_value;
unsigned int i;
int error = sysctl_handle_int(oidp, &value, 0, req);
if (error != 0 || !req->newptr) {
return error;
}
if (proc_suser(current_proc()) != 0 &&
!IOTaskHasEntitlement(current_task(), ENTITLEMENT_TEST_CONTROL)) {
return EPERM;
}
if (value < 0 || value > UINT16_MAX) {
return EINVAL;
}
if (value == 0) {
if (restricted_port_test != 0) {
for (i = 0; i < RPE_ENTRY_COUNT; i++) {
struct restricted_port_entry *rpe = &restricted_port_list[i];
if (rpe->rpe_entitlement == NULL) {
break;
}
if (!(rpe->rpe_flags & RPE_FLAG_TEST)) {
continue;
}
rpe->rpe_port = 0;
rpe->rpe_flags &= ~(RPE_FLAG_ENTITLEMENT | RPE_FLAG_SUPERUSER);
}
bitmap_clear(restricted_port_bitmap, restricted_port_test);
restricted_port_test = 0;
}
} else {
for (i = 0; i < RPE_ENTRY_COUNT; i++) {
struct restricted_port_entry *rpe = &restricted_port_list[i];
if (rpe->rpe_entitlement == NULL) {
break;
}
if (!(rpe->rpe_flags & RPE_FLAG_TEST)) {
continue;
}
rpe->rpe_port = value;
if (test_superuser) {
rpe->rpe_flags |= RPE_FLAG_SUPERUSER;
rpe->rpe_flags &= ~RPE_FLAG_ENTITLEMENT;
} else {
rpe->rpe_flags |= RPE_FLAG_ENTITLEMENT;
rpe->rpe_flags &= ~RPE_FLAG_SUPERUSER;
}
}
restricted_port_test = (uint16_t)value;
bitmap_set(restricted_port_bitmap, restricted_port_test);
}
return 0;
}
static int
sysctl_restricted_port_test_entitlement SYSCTL_HANDLER_ARGS
{
#pragma unused(arg1, arg2)
uint16_t old_value = restricted_port_test;
int error;
error = sysctl_restricted_port_test_common(oidp, req, false);
if (error == 0) {
os_log(OS_LOG_DEFAULT,
"%s:%u sysctl net.restricted_port.test_entitlement: %u -> %u)",
proc_best_name(current_proc()), proc_selfpid(),
old_value, restricted_port_test);
}
return error;
}
static int
sysctl_restricted_port_test_superuser SYSCTL_HANDLER_ARGS
{
#pragma unused(arg1, arg2)
uint16_t old_value = restricted_port_test;
int error;
error = sysctl_restricted_port_test_common(oidp, req, true);
if (error == 0) {
os_log(OS_LOG_DEFAULT,
"%s:%u sysctl net.restricted_port.test_superuser: %u -> %u)",
proc_best_name(current_proc()), proc_selfpid(),
old_value, restricted_port_test);
}
return error;
}
#endif
void
restricted_in_port_init(void)
{
unsigned int i;
restricted_port_bitmap = bitmap_alloc(UINT16_MAX);
if (restricted_port_bitmap == NULL) {
panic("restricted_port_init: bitmap allocation failed");
}
for (i = 0; i < RPE_ENTRY_COUNT; i++) {
struct restricted_port_entry *rpe = &restricted_port_list[i];
if (rpe->rpe_entitlement == NULL) {
break;
}
if (rpe->rpe_port == 0) {
continue;
}
bitmap_set(restricted_port_bitmap, rpe->rpe_port);
}
}
static const char *
port_flag_str(uint32_t port_flags)
{
switch (port_flags) {
case PORT_FLAGS_LISTENER:
return "listener";
case PORT_FLAGS_BSD:
return "bsd";
case PORT_FLAGS_PF:
return "pf";
default:
break;
}
return "?";
}
bool
current_task_can_use_restricted_in_port(in_port_t port, uint8_t protocol, uint32_t port_flags)
{
unsigned int i;
struct proc *p = current_proc();
pid_t pid = proc_pid(p);
if (!IS_RESTRICTED_IN_PORT(port) || restricted_port_enforced == 0) {
if (restricted_port_verbose > 1) {
os_log(OS_LOG_DEFAULT,
"port %u for protocol %u via %s can be used by process %s:%u",
ntohs(port), protocol, port_flag_str(port_flags), proc_best_name(p), pid);
}
return true;
}
for (i = 0; i < RPE_ENTRY_COUNT; i++) {
struct restricted_port_entry *rpe = &restricted_port_list[i];
if (rpe->rpe_entitlement == NULL) {
break;
}
if (rpe->rpe_port == 0) {
continue;
}
if ((protocol == IPPROTO_TCP && !(rpe->rpe_flags & RPE_FLAG_TCP)) ||
(protocol == IPPROTO_UDP && !(rpe->rpe_flags & RPE_FLAG_UDP))) {
continue;
}
if (rpe->rpe_port != ntohs(port)) {
continue;
}
task_t task = current_task();
if (rpe->rpe_flags & RPE_FLAG_SUPERUSER) {
if (task == kernel_task || proc_suser(current_proc()) == 0) {
os_log(OS_LOG_DEFAULT,
"root restricted port %u for protocol %u via %s can be used by superuser process %s:%u",
ntohs(port), protocol, port_flag_str(port_flags), proc_best_name(p), pid);
return true;
}
}
if (rpe->rpe_flags & RPE_FLAG_ENTITLEMENT) {
if (task == kernel_task) {
os_log(OS_LOG_DEFAULT,
"entitlement restricted port %u for protocol %u via %s cannot be used by kernel",
ntohs(port), protocol, port_flag_str(port_flags));
return false;
}
if (!IOTaskHasEntitlement(current_task(), rpe->rpe_entitlement)) {
os_log(OS_LOG_DEFAULT,
"entitlement restricted port %u for protocol %u via %s cannot be used by process %s:%u -- IOTaskHasEntitlement(%s) failed",
ntohs(port), protocol, port_flag_str(port_flags), proc_best_name(p), pid, rpe->rpe_entitlement);
return false;
}
os_log(OS_LOG_DEFAULT,
"entitlement restricted port %u for protocol %u via %s can be used by process %s:%u",
ntohs(port), protocol, port_flag_str(port_flags), proc_best_name(p), pid);
return true;
}
os_log(OS_LOG_DEFAULT,
"root restricted port %u for protocol %u via %s cannot be used by process %s:%u",
ntohs(port), protocol, port_flag_str(port_flags), proc_best_name(p), pid);
return false;
}
if (restricted_port_verbose > 1) {
os_log(OS_LOG_DEFAULT,
"port %u for protocol %u via %s can be used by process %s:%u",
ntohs(port), protocol, port_flag_str(port_flags), proc_best_name(p), pid);
}
return true;
}