[PATCH RFC v3 81/90] net: use net_may() for privileged socket options

From: Josef Bacik

Date: Thu Nov 05 2026 - 10:55:44 EST


Some socket options affect how the network namespace treats packets, not
just the socket, and require a capability. Convert their checks.

These accept CAP_NET_ADMIN or CAP_NET_RAW today, and now accept
NET_RIGHT_SOCKOPT or NET_RIGHT_RAW: SO_MARK, both as an option and as a
control message, SO_PRIORITY outside of the unprivileged range,
IP_TRANSPARENT and IPV6_TRANSPARENT.

This one requires CAP_NET_ADMIN, and now NET_RIGHT_SOCKOPT: SO_TXTIME with
a clock other than CLOCK_MONOTONIC.

These require CAP_NET_RAW, and now NET_RIGHT_RAW: SO_BINDTODEVICE on a
socket that is already bound to a device, and the IPv6 hop-by-hop and
destination options.

Not converted: IP_IPSEC_POLICY, IP_XFRM_POLICY and their IPv6
counterparts, and SO_ATTACH_FILTER on a TCP socket.

No functional change.

Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
net/core/sock.c | 16 ++++++++--------
net/ipv4/ip_sockglue.c | 4 ++--
net/ipv6/ipv6_sockglue.c | 7 +++----
3 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/net/core/sock.c b/net/core/sock.c
index 352310066e35..ba870aa6b967 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -462,8 +462,8 @@ static int sock_set_timeout(long *timeo_p, sockptr_t optval, int optlen,
static bool sk_set_prio_allowed(const struct sock *sk, int val)
{
return ((val >= TC_PRIO_BESTEFFORT && val <= TC_PRIO_INTERACTIVE) ||
- sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_RAW) ||
- sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN));
+ sockopt_net_may(sk, NET_RIGHT_RAW) ||
+ sockopt_net_may(sk, NET_RIGHT_SOCKOPT));
}

static bool sock_needs_netstamp(const struct sock *sk)
@@ -642,7 +642,7 @@ static int sock_bindtoindex_locked(struct sock *sk, int ifindex)

/* Sorry... */
ret = -EPERM;
- if (sk->sk_bound_dev_if && !ns_capable(net->user_ns, CAP_NET_RAW))
+ if (sk->sk_bound_dev_if && !net_may(net, NET_RIGHT_RAW))
goto out;

ret = -EINVAL;
@@ -1540,8 +1540,8 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
break;

case SO_MARK:
- if (!sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_RAW) &&
- !sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) {
+ if (!sockopt_net_may(sk, NET_RIGHT_RAW) &&
+ !sockopt_net_may(sk, NET_RIGHT_SOCKOPT)) {
ret = -EPERM;
break;
}
@@ -1649,7 +1649,7 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
* scheduler has enough safe guards.
*/
if (sk_txtime.clockid != CLOCK_MONOTONIC &&
- !sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) {
+ !sockopt_net_may(sk, NET_RIGHT_SOCKOPT)) {
ret = -EPERM;
break;
}
@@ -3172,8 +3172,8 @@ int __sock_cmsg_send(struct sock *sk, struct cmsghdr *cmsg,

switch (cmsg->cmsg_type) {
case SO_MARK:
- if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_RAW) &&
- !ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
+ if (!net_may(sock_net(sk), NET_RIGHT_RAW) &&
+ !net_may(sock_net(sk), NET_RIGHT_SOCKOPT))
return -EPERM;
if (cmsg->cmsg_len != CMSG_LEN(sizeof(u32)))
return -EINVAL;
diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
index e06c1f48ecad..cc9e0bc952f2 100644
--- a/net/ipv4/ip_sockglue.c
+++ b/net/ipv4/ip_sockglue.c
@@ -1008,8 +1008,8 @@ int do_ip_setsockopt(struct sock *sk, int level, int optname,
inet_assign_bit(MC_ALL, sk, val);
return 0;
case IP_TRANSPARENT:
- if (!!val && !sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_RAW) &&
- !sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
+ if (!!val && !sockopt_net_may(sk, NET_RIGHT_RAW) &&
+ !sockopt_net_may(sk, NET_RIGHT_SOCKOPT))
return -EPERM;
if (optlen < 1)
return -EINVAL;
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index 4b3536571c98..d2880493d33b 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -304,12 +304,11 @@ static int ipv6_set_opt_hdr(struct sock *sk, int optname, sockptr_t optval,
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct ipv6_opt_hdr *new = NULL;
- struct net *net = sock_net(sk);
struct ipv6_txoptions *opt;
int err;

/* hop-by-hop / destination options are privileged option */
- if (optname != IPV6_RTHDR && !sockopt_ns_capable(net->user_ns, CAP_NET_RAW))
+ if (optname != IPV6_RTHDR && !sockopt_net_may(sk, NET_RIGHT_RAW))
return -EPERM;

/* remove any sticky options header with a zero option
@@ -405,8 +404,8 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
inet_assign_bit(FREEBIND, sk, valbool);
return 0;
case IPV6_TRANSPARENT:
- if (valbool && !sockopt_ns_capable(net->user_ns, CAP_NET_RAW) &&
- !sockopt_ns_capable(net->user_ns, CAP_NET_ADMIN))
+ if (valbool && !sockopt_net_may(sk, NET_RIGHT_RAW) &&
+ !sockopt_net_may(sk, NET_RIGHT_SOCKOPT))
return -EPERM;
if (optlen < sizeof(int))
return -EINVAL;

--
2.55.0