Commit d1d991ef authored by Thiébaud Weksteen's avatar Thiébaud Weksteen Committed by Paul Moore
Browse files

selinux: Add netlink xperm support



Reuse the existing extended permissions infrastructure to support
policies based on the netlink message types.

A new policy capability "netlink_xperm" is introduced. When disabled,
the previous behaviour is preserved. That is, netlink_send will rely on
the permission mappings defined in nlmsgtab.c (e.g, nlmsg_read for
RTM_GETADDR on NETLINK_ROUTE). When enabled, the mappings are ignored
and the generic "nlmsg" permission is used instead.

The new "nlmsg" permission is an extended permission. The 16 bits of the
extended permission are mapped to the nlmsg_type field.

Example policy on Android, preventing regular apps from accessing the
device's MAC address and ARP table, but allowing this access to
privileged apps, looks as follows:

allow netdomain self:netlink_route_socket {
	create read getattr write setattr lock append connect getopt
	setopt shutdown nlmsg
};
allowxperm netdomain self:netlink_route_socket nlmsg ~{
	RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
};
allowxperm priv_app self:netlink_route_socket nlmsg {
	RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
};

The constants in the example above (e.g., RTM_GETLINK) are explicitly
defined in the policy.

It is possible to generate policies to support kernels that may or
may not have the capability enabled by generating a rule for each
scenario. For instance:

allow domain self:netlink_audit_socket nlmsg_read;
allow domain self:netlink_audit_socket nlmsg;
allowxperm domain self:netlink_audit_socket nlmsg { AUDIT_GET };

The approach of defining a new permission ("nlmsg") instead of relying
on the existing permissions (e.g., "nlmsg_read", "nlmsg_readpriv" or
"nlmsg_tty_audit") has been preferred because:
  1. This is similar to the other extended permission ("ioctl");
  2. With the new extended permission, the coarse-grained mapping is not
     necessary anymore. It could eventually be removed, which would be
     impossible if the extended permission was defined below these.
  3. Having a single extra extended permission considerably simplifies
     the implementation here and in libselinux.

Signed-off-by: default avatarThiébaud Weksteen <tweek@google.com>
Signed-off-by: default avatarBram Bonné <brambonne@google.com>
[PM: manual merge fixes for sock_skip_has_perm()]
Signed-off-by: default avatarPaul Moore <paul@paul-moore.com>
parent 3b70b66e
Loading
Loading
Loading
Loading
+42 −9
Original line number Diff line number Diff line
@@ -4590,14 +4590,10 @@ static int socket_sockcreate_sid(const struct task_security_struct *tsec,
				       secclass, NULL, socksid);
}

static int sock_has_perm(struct sock *sk, u32 perms)
static bool sock_skip_has_perm(u32 sid)
{
	struct sk_security_struct *sksec = selinux_sock(sk);
	struct common_audit_data ad;
	struct lsm_network_audit net;

	if (sksec->sid == SECINITSID_KERNEL)
		return 0;
	if (sid == SECINITSID_KERNEL)
		return true;

	/*
	 * Before POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT, sockets that
@@ -4611,7 +4607,19 @@ static int sock_has_perm(struct sock *sk, u32 perms)
	 * setting.
	 */
	if (!selinux_policycap_userspace_initial_context() &&
	    sksec->sid == SECINITSID_INIT)
	    sid == SECINITSID_INIT)
		return true;
	return false;
}


static int sock_has_perm(struct sock *sk, u32 perms)
{
	struct sk_security_struct *sksec = sk->sk_security;
	struct common_audit_data ad;
	struct lsm_network_audit net;

	if (sock_skip_has_perm(sksec->sid))
		return 0;

	ad_net_init_from_sk(&ad, &net, sk);
@@ -5920,6 +5928,26 @@ static unsigned int selinux_ip_postroute(void *priv,
}
#endif	/* CONFIG_NETFILTER */

static int nlmsg_sock_has_extended_perms(struct sock *sk, u32 perms, u16 nlmsg_type)
{
	struct sk_security_struct *sksec = sk->sk_security;
	struct common_audit_data ad;
	struct lsm_network_audit net;
	u8 driver;
	u8 xperm;

	if (sock_skip_has_perm(sksec->sid))
		return 0;

	ad_net_init_from_sk(&ad, &net, sk);

	driver = nlmsg_type >> 8;
	xperm = nlmsg_type & 0xff;

	return avc_has_extended_perms(current_sid(), sksec->sid, sksec->sclass,
			perms, driver, xperm, &ad);
}

static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb)
{
	int rc = 0;
@@ -5945,7 +5973,12 @@ static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb)

		rc = selinux_nlmsg_lookup(sclass, nlh->nlmsg_type, &perm);
		if (rc == 0) {
			if (selinux_policycap_netlink_xperm()) {
				rc = nlmsg_sock_has_extended_perms(
					sk, perm, nlh->nlmsg_type);
			} else {
				rc = sock_has_perm(sk, perm);
			}
			if (rc)
				return rc;
		} else if (rc == -EINVAL) {
+4 −4
Original line number Diff line number Diff line
@@ -97,17 +97,17 @@ const struct security_class_mapping secclass_map[] = {
	{ "shm", { COMMON_IPC_PERMS, "lock", NULL } },
	{ "ipc", { COMMON_IPC_PERMS, NULL } },
	{ "netlink_route_socket",
	  { COMMON_SOCK_PERMS, "nlmsg_read", "nlmsg_write", NULL } },
	  { COMMON_SOCK_PERMS, "nlmsg_read", "nlmsg_write", "nlmsg", NULL } },
	{ "netlink_tcpdiag_socket",
	  { COMMON_SOCK_PERMS, "nlmsg_read", "nlmsg_write", NULL } },
	  { COMMON_SOCK_PERMS, "nlmsg_read", "nlmsg_write", "nlmsg", NULL } },
	{ "netlink_nflog_socket", { COMMON_SOCK_PERMS, NULL } },
	{ "netlink_xfrm_socket",
	  { COMMON_SOCK_PERMS, "nlmsg_read", "nlmsg_write", NULL } },
	  { COMMON_SOCK_PERMS, "nlmsg_read", "nlmsg_write", "nlmsg", NULL } },
	{ "netlink_selinux_socket", { COMMON_SOCK_PERMS, NULL } },
	{ "netlink_iscsi_socket", { COMMON_SOCK_PERMS, NULL } },
	{ "netlink_audit_socket",
	  { COMMON_SOCK_PERMS, "nlmsg_read", "nlmsg_write", "nlmsg_relay",
	    "nlmsg_readpriv", "nlmsg_tty_audit", NULL } },
	    "nlmsg_readpriv", "nlmsg_tty_audit", "nlmsg", NULL } },
	{ "netlink_fib_lookup_socket", { COMMON_SOCK_PERMS, NULL } },
	{ "netlink_connector_socket", { COMMON_SOCK_PERMS, NULL } },
	{ "netlink_netfilter_socket", { COMMON_SOCK_PERMS, NULL } },
+1 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ enum {
	POLICYDB_CAP_GENFS_SECLABEL_SYMLINKS,
	POLICYDB_CAP_IOCTL_SKIP_CLOEXEC,
	POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT,
	POLICYDB_CAP_NETLINK_XPERM,
	__POLICYDB_CAP_MAX
};
#define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
	"genfs_seclabel_symlinks",
	"ioctl_skip_cloexec",
	"userspace_initial_context",
	"netlink_xperm",
};
/* clang-format on */

+6 −0
Original line number Diff line number Diff line
@@ -195,6 +195,12 @@ static inline bool selinux_policycap_userspace_initial_context(void)
		selinux_state.policycap[POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT]);
}

static inline bool selinux_policycap_netlink_xperm(void)
{
	return READ_ONCE(
		selinux_state.policycap[POLICYDB_CAP_NETLINK_XPERM]);
}

struct selinux_policy_convert_data;

struct selinux_load_state {
Loading