Skip to contentSkip to conversation

Generic netlink multicast fixes

PATCH New Ido Schimmel 1 participant 5 messages Dec 11, 2023 Stable branch

lore
Share & tools
lore
#0 lore raw

cover Generic netlink multicast fixes

Restrict two generic netlink multicast groups - in the "psample" and "NET_DM" families - to be root-only with the appropriate capabilities.

Patch #1 is a dependency of patch #2 which is needed by the actual fixes in patches #3 and #4.

Florian Westphal (1):
netlink: don't call ->netlink_bind with table lock held

Ido Schimmel (3):
genetlink: add CAP_NET_ADMIN test for multicast bind
psample: Require 'CAP_NET_ADMIN' when joining "packets" group
drop_monitor: Require 'CAP_SYS_ADMIN' when joining "events" group

 include/net/genetlink.h  |  3 +++
 net/core/drop_monitor.c  |  4 +++-
 net/netlink/af_netlink.c |  4 ++--
 net/netlink/genetlink.c  | 35 +++++++++++++++++++++++++++++++++++
 net/psample/psample.c    |  3 ++-
 5 files changed, 45 insertions(+), 4 deletions(-)
Signature · 1 line
2.40.1
#1 lore raw

PATCH 1/4 netlink: don't call ->netlink_bind with table lock held

From: Florian Westphal <[email protected]>

commit f2764bd4f6a8dffaec3e220728385d9756b3c2cb upstream.

When I added support to allow generic netlink multicast groups to be restricted to subscribers with CAP_NET_ADMIN I was unaware that a genl_bind implementation already existed in the past.

It was reverted due to ABBA deadlock:

1. ->netlink_bind gets called with the table lock held.
2. genetlink bind callback is invoked, it grabs the genl lock.

But when a new genl subsystem is (un)registered, these two locks are taken in reverse order.

One solution would be to revert again and add a comment in genl referring 1e82a62fec613, "genetlink: remove genl_bind").

This would need a second change in mptcp to not expose the raw token value anymore, e.g. by hashing the token with a secret key so userspace can still associate subflow events with the correct mptcp connection.

However, Paolo Abeni reminded me to double-check why the netlink table is locked in the first place.

I can't find one. netlink_bind() is already called without this lock when userspace joins a group via NETLINK_ADD_MEMBERSHIP setsockopt. Same holds for the netlink_unbind operation.

Digging through the history, commit f773608026ee1 ("netlink: access nlk groups safely in netlink bind and getname") expanded the lock scope.

commit 3a20773beeeeade ("net: netlink: cap max groups which will be considered in netlink_bind()") ... removed the nlk->ngroups access that the lock scope
extension was all about.

Reduce the lock scope again and always call ->netlink_bind without the table lock.

The Fixes tag should be vs. the patch mentioned in the link below, but that one got squash-merged into the patch that came earlier in the series.

Fixes 4d54cc32112d8d ("mptcp: avoid lock_fast usage in accept path")
Cc Cong Wang <[email protected]>
Cc Xin Long <[email protected]>
Cc Johannes Berg <[email protected]>
Cc Sean Tranchetti <[email protected]>
Cc Paolo Abeni <[email protected]>
Cc Pablo Neira Ayuso <[email protected]>
---
 net/netlink/af_netlink.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
net/netlink/af_netlink.c +2 -2
index 9737c3229c12..901358a5b593 100644
@@ -1021,7 +1021,6 @@ static int netlink_bind(struct socket *sock, struct sockaddr *addr,
 			return -EINVAL;
 	}
 
-	netlink_lock_table();
 	if (nlk->netlink_bind && groups) {
 		int group;
 
@@ -1033,13 +1032,14 @@ static int netlink_bind(struct socket *sock, struct sockaddr *addr,
 			if (!err)
 				continue;
 			netlink_undo_bind(group, groups, sk);
-			goto unlock;
+			return err;
 		}
 	}
 
 	/* No need for barriers here as we return to user-space without
 	 * using any of the bound attributes.
 	 */
+	netlink_lock_table();
 	if (!bound) {
 		err = nladdr->nl_pid ?
 			netlink_insert(sk, nladdr->nl_pid) :
Signature · 1 line
2.40.1
#2 lore raw

PATCH 2/4 genetlink: add CAP_NET_ADMIN test for multicast bind

In reply to Ido Schimmel

This is a partial backport of upstream commit 4d54cc32112d ("mptcp:
avoid lock_fast usage in accept path"). It is only a partial backport because the patch in the link below was erroneously squash-merged into upstream commit 4d54cc32112d ("mptcp: avoid lock_fast usage in accept path"). Below is the original patch description from Florian Westphal:

"
genetlink sets NL_CFG_F_NONROOT_RECV for its netlink socket so anyone can subscribe to multicast messages.

rtnetlink doesn't allow this unconditionally, rtnetlink_bind() restricts bind requests to CAP_NET_ADMIN for a few groups.

This allows to set GENL_UNS_ADMIN_PERM flag on genl mcast groups to mandate CAP_NET_ADMIN.

This will be used by the upcoming mptcp netlink event facility which exposes the token (mptcp connection identifier) to userspace. "

---
 include/net/genetlink.h |  1 +
 net/netlink/genetlink.c | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 33 insertions(+)
include/net/genetlink.h +1 -0
index e55ec1597ce7..7cb3fa8310ed 100644
@@ -14,6 +14,7 @@
  */
 struct genl_multicast_group {
 	char			name[GENL_NAMSIZ];
+	u8			flags;
 };
 
 struct genl_ops;
net/netlink/genetlink.c +32 -0
index 9fd7ba01b9f8..4dce39013d75 100644
@@ -1364,11 +1364,43 @@ static struct genl_family genl_ctrl __ro_after_init = {
 	.netnsok = true,
 };
 
+static int genl_bind(struct net *net, int group)
+{
+	const struct genl_family *family;
+	unsigned int id;
+	int ret = 0;
+
+	genl_lock_all();
+
+	idr_for_each_entry(&genl_fam_idr, family, id) {
+		const struct genl_multicast_group *grp;
+		int i;
+
+		if (family->n_mcgrps == 0)
+			continue;
+
+		i = group - family->mcgrp_offset;
+		if (i < 0 || i >= family->n_mcgrps)
+			continue;
+
+		grp = &family->mcgrps[i];
+		if ((grp->flags & GENL_UNS_ADMIN_PERM) &&
+		    !ns_capable(net->user_ns, CAP_NET_ADMIN))
+			ret = -EPERM;
+
+		break;
+	}
+
+	genl_unlock_all();
+	return ret;
+}
+
 static int __net_init genl_pernet_init(struct net *net)
 {
 	struct netlink_kernel_cfg cfg = {
 		.input		= genl_rcv,
 		.flags		= NL_CFG_F_NONROOT_RECV,
+		.bind		= genl_bind,
 	};
 
 	/* we'll bump the group number right afterwards */
Signature · 1 line
2.40.1
#3 lore raw

PATCH 3/4 psample: Require 'CAP_NET_ADMIN' when joining "packets" group

In reply to Ido Schimmel

commit 44ec98ea5ea9cfecd31a5c4cc124703cb5442832 upstream.

The "psample" generic netlink family notifies sampled packets over the "packets" multicast group. This is problematic since by default generic netlink allows non-root users to listen to these notifications.

Fix by marking the group with the 'GENL_UNS_ADMIN_PERM' flag. This will prevent non-root users or root without the 'CAP_NET_ADMIN' capability (in the user namespace owning the network namespace) from joining the group.

Tested using [1].

Before:

 # capsh -- -c ./psample_repo
 # capsh --drop=cap_net_admin -- -c ./psample_repo

After:

 # capsh -- -c ./psample_repo
 # capsh --drop=cap_net_admin -- -c ./psample_repo
 Failed to join "packets" multicast group

[1]

 $ cat psample.c
 #include <stdio.h>
 #include <netlink/genl/ctrl.h>
 #include <netlink/genl/genl.h>
 #include <netlink/socket.h>

int join_grp(struct nl_sock *sk, const char *grp_name) {
int grp, err;

grp = genl_ctrl_resolve_grp(sk, "psample", grp_name);
if (grp < 0) {
fprintf(stderr, "Failed to resolve \"%s\" multicast group\n",
grp_name);
return grp;
}

err = nl_socket_add_memberships(sk, grp, NFNLGRP_NONE); if (err) {
fprintf(stderr, "Failed to join \"%s\" multicast group\n",
grp_name);
return err;
}

return 0;
}

int main(int argc, char **argv)
{
struct nl_sock *sk;
int err;

sk = nl_socket_alloc();
if (!sk) {
fprintf(stderr, "Failed to allocate socket\n"); return -1;
}

err = genl_connect(sk);
if (err) {
fprintf(stderr, "Failed to connect socket\n"); return err;
}

err = join_grp(sk, "config");
if (err)
return err;

err = join_grp(sk, "packets");
if (err)
return err;

return 0;
}

 $ gcc -I/usr/include/libnl3 -lnl-3 -lnl-genl-3 -o psample_repo psample.c
---
 net/psample/psample.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
net/psample/psample.c +2 -1
index 482c07f2766b..5913da77c71d 100644
@@ -30,7 +30,8 @@ enum psample_nl_multicast_groups {
 
 static const struct genl_multicast_group psample_nl_mcgrps[] = {
 	[PSAMPLE_NL_MCGRP_CONFIG] = { .name = PSAMPLE_NL_MCGRP_CONFIG_NAME },
-	[PSAMPLE_NL_MCGRP_SAMPLE] = { .name = PSAMPLE_NL_MCGRP_SAMPLE_NAME },
+	[PSAMPLE_NL_MCGRP_SAMPLE] = { .name = PSAMPLE_NL_MCGRP_SAMPLE_NAME,
+				      .flags = GENL_UNS_ADMIN_PERM },
 };
 
 static struct genl_family psample_nl_family __ro_after_init;
Signature · 1 line
2.40.1
#4 lore raw

PATCH 4/4 drop_monitor: Require 'CAP_SYS_ADMIN' when joining "events" group

In reply to Ido Schimmel

commit e03781879a0d524ce3126678d50a80484a513c4b upstream.

The "NET_DM" generic netlink family notifies drop locations over the "events" multicast group. This is problematic since by default generic netlink allows non-root users to listen to these notifications.

Fix by adding a new field to the generic netlink multicast group structure that when set prevents non-root users or root without the 'CAP_SYS_ADMIN' capability (in the user namespace owning the network namespace) from joining the group. Set this field for the "events" group. Use 'CAP_SYS_ADMIN' rather than 'CAP_NET_ADMIN' because of the nature of the information that is shared over this group.

Note that the capability check in this case will always be performed against the initial user namespace since the family is not netns aware and only operates in the initial network namespace.

A new field is added to the structure rather than using the "flags" field because the existing field uses uAPI flags and it is inappropriate to add a new uAPI flag for an internal kernel check. In net-next we can rework the "flags" field to use internal flags and fold the new field into it. But for now, in order to reduce the amount of changes, add a new field.

Since the information can only be consumed by root, mark the control plane operations that start and stop the tracing as root-only using the 'GENL_ADMIN_PERM' flag.

Tested using [1].

Before:

 # capsh -- -c ./dm_repo
 # capsh --drop=cap_sys_admin -- -c ./dm_repo

After:

 # capsh -- -c ./dm_repo
 # capsh --drop=cap_sys_admin -- -c ./dm_repo
 Failed to join "events" multicast group

[1]

 $ cat dm.c
 #include <stdio.h>
 #include <netlink/genl/ctrl.h>
 #include <netlink/genl/genl.h>
 #include <netlink/socket.h>

int main(int argc, char **argv)
{
struct nl_sock *sk;
int grp, err;

sk = nl_socket_alloc();
if (!sk) {
fprintf(stderr, "Failed to allocate socket\n"); return -1;
}

err = genl_connect(sk);
if (err) {
fprintf(stderr, "Failed to connect socket\n"); return err;
}

grp = genl_ctrl_resolve_grp(sk, "NET_DM", "events"); if (grp < 0) {
fprintf(stderr,
"Failed to resolve \"events\" multicast group\n");
return grp;
}

err = nl_socket_add_memberships(sk, grp, NFNLGRP_NONE);
if (err) {
fprintf(stderr, "Failed to join \"events\" multicast group\n"); return err;
}

return 0;
}

 $ gcc -I/usr/include/libnl3 -lnl-3 -lnl-genl-3 -o dm_repo dm.c
---
 include/net/genetlink.h | 2 ++
 net/core/drop_monitor.c | 4 +++-
 net/netlink/genetlink.c | 3 +++
 3 files changed, 8 insertions(+), 1 deletion(-)
include/net/genetlink.h +2 -0
index 7cb3fa8310ed..3057c8e6dcfe 100644
@@ -11,10 +11,12 @@
 /**
  * struct genl_multicast_group - generic netlink multicast group
  * @name: name of the multicast group, names are per-family
+ * @cap_sys_admin: whether %CAP_SYS_ADMIN is required for binding
  */
 struct genl_multicast_group {
 	char			name[GENL_NAMSIZ];
 	u8			flags;
+	u8			cap_sys_admin:1;
 };
 
 struct genl_ops;
net/core/drop_monitor.c +3 -1
index ed9dd17f9348..7742ee689141 100644
@@ -183,7 +183,7 @@ static struct sk_buff *reset_per_cpu_data(struct per_cpu_dm_data *data)
 }
 
 static const struct genl_multicast_group dropmon_mcgrps[] = {
-	{ .name = "events", },
+	{ .name = "events", .cap_sys_admin = 1 },
 };
 
 static void send_dm_alert(struct work_struct *work)
@@ -1616,11 +1616,13 @@ static const struct genl_small_ops dropmon_ops[] = {
 		.cmd = NET_DM_CMD_START,
 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
 		.doit = net_dm_cmd_trace,
+		.flags = GENL_ADMIN_PERM,
 	},
 	{
 		.cmd = NET_DM_CMD_STOP,
 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
 		.doit = net_dm_cmd_trace,
+		.flags = GENL_ADMIN_PERM,
 	},
 	{
 		.cmd = NET_DM_CMD_CONFIG_GET,
net/netlink/genetlink.c +3 -0
index 4dce39013d75..e9035de65546 100644
@@ -1387,6 +1387,9 @@ static int genl_bind(struct net *net, int group)
 		if ((grp->flags & GENL_UNS_ADMIN_PERM) &&
 		    !ns_capable(net->user_ns, CAP_NET_ADMIN))
 			ret = -EPERM;
+		if (grp->cap_sys_admin &&
+		    !ns_capable(net->user_ns, CAP_SYS_ADMIN))
+			ret = -EPERM;
 
 		break;
 	}
Signature · 1 line
2.40.1