Merge branch 'so_passrights'

Kuniyuki Iwashima says:
====================

af_unix: Introduce SO_PASSRIGHTS.

As long as recvmsg() or recvmmsg() is used with cmsg, it is not
possible to avoid receiving file descriptors via SCM_RIGHTS.

This series introduces a new socket option, SO_PASSRIGHTS, to allow
disabling SCM_RIGHTS.  The option is enabled by default.

See patch 8 for background/context.

This series is related to [0], but is split into a separate series,
as most of the patches are specific to af_unix.

The v2 of the BPF LSM extension part will be posted later, once
this series is merged into net-next and has landed in bpf-next.

[0]: https://lore.kernel.org/bpf/20250505215802.48449-1-kuniyu@amazon.com/

Changes:
  v5:
    * Patch 4
      * Fix BPF selftest failure (setget_sockopt.c)

  v4: https://lore.kernel.org/netdev/20250515224946.6931-1-kuniyu@amazon.com/
    * Patch 6
      * Group sk->sk_scm_XXX bits by struct
    * Patch 9
      * Remove errno handling

  v3: https://lore.kernel.org/netdev/20250514165226.40410-1-kuniyu@amazon.com/
    * Patch 3
      * Remove inline in scm.c
    * Patch 4 & 5 & 8
      * Return -EOPNOTSUPP in getsockopt()
    * Patch 5
      * Add CONFIG_SECURITY_NETWORK check for SO_PASSSEC
    * Patch 6
      * Add kdoc for sk_scm_unused
      * Update sk_scm_XXX under lock_sock() in setsockopt()
    * Patch 7
      * Update changelog (recent change -> aed6ecef55)

  v2: https://lore.kernel.org/netdev/20250510015652.9931-1-kuniyu@amazon.com/
    * Added patch 4 & 5 to reuse sk_txrehash for scm_recv() flags

  v1: https://lore.kernel.org/netdev/20250508013021.79654-1-kuniyu@amazon.com/
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2025-05-23 10:24:19 +01:00
commit 819aad967d
14 changed files with 362 additions and 190 deletions

View File

@ -150,6 +150,8 @@
#define SO_RCVPRIORITY 82
#define SO_PASSRIGHTS 83
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64

View File

@ -161,6 +161,8 @@
#define SO_RCVPRIORITY 82
#define SO_PASSRIGHTS 83
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64

View File

@ -142,6 +142,8 @@
#define SCM_DEVMEM_DMABUF SO_DEVMEM_DMABUF
#define SO_DEVMEM_DONTNEED 0x4050
#define SO_PASSRIGHTS 0x4051
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64

View File

@ -143,6 +143,8 @@
#define SO_RCVPRIORITY 0x005b
#define SO_PASSRIGHTS 0x005c
#if !defined(__KERNEL__)

View File

@ -36,14 +36,13 @@ struct net;
* in sock->flags, but moved into sk->sk_wq->flags to be RCU protected.
* Eventually all flags will be in sk->sk_wq->flags.
*/
#define SOCKWQ_ASYNC_NOSPACE 0
#define SOCKWQ_ASYNC_WAITDATA 1
#define SOCK_NOSPACE 2
#define SOCK_PASSCRED 3
#define SOCK_PASSSEC 4
#define SOCK_SUPPORT_ZC 5
#define SOCK_CUSTOM_SOCKOPT 6
#define SOCK_PASSPIDFD 7
enum socket_flags {
SOCKWQ_ASYNC_NOSPACE,
SOCKWQ_ASYNC_WAITDATA,
SOCK_NOSPACE,
SOCK_SUPPORT_ZC,
SOCK_CUSTOM_SOCKOPT,
};
#ifndef ARCH_HAS_SOCKET_TYPES
/**

View File

@ -102,123 +102,10 @@ static __inline__ int scm_send(struct socket *sock, struct msghdr *msg,
return __scm_send(sock, msg, scm);
}
#ifdef CONFIG_SECURITY_NETWORK
static inline void scm_passec(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm)
{
struct lsm_context ctx;
int err;
if (test_bit(SOCK_PASSSEC, &sock->flags)) {
err = security_secid_to_secctx(scm->secid, &ctx);
if (err >= 0) {
put_cmsg(msg, SOL_SOCKET, SCM_SECURITY, ctx.len,
ctx.context);
security_release_secctx(&ctx);
}
}
}
static inline bool scm_has_secdata(struct socket *sock)
{
return test_bit(SOCK_PASSSEC, &sock->flags);
}
#else
static inline void scm_passec(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm)
{ }
static inline bool scm_has_secdata(struct socket *sock)
{
return false;
}
#endif /* CONFIG_SECURITY_NETWORK */
static __inline__ void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
{
struct file *pidfd_file = NULL;
int len, pidfd;
/* put_cmsg() doesn't return an error if CMSG is truncated,
* that's why we need to opencode these checks here.
*/
if (msg->msg_flags & MSG_CMSG_COMPAT)
len = sizeof(struct compat_cmsghdr) + sizeof(int);
else
len = sizeof(struct cmsghdr) + sizeof(int);
if (msg->msg_controllen < len) {
msg->msg_flags |= MSG_CTRUNC;
return;
}
if (!scm->pid)
return;
pidfd = pidfd_prepare(scm->pid, 0, &pidfd_file);
if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) {
if (pidfd_file) {
put_unused_fd(pidfd);
fput(pidfd_file);
}
return;
}
if (pidfd_file)
fd_install(pidfd, pidfd_file);
}
static inline bool __scm_recv_common(struct socket *sock, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!msg->msg_control) {
if (test_bit(SOCK_PASSCRED, &sock->flags) ||
test_bit(SOCK_PASSPIDFD, &sock->flags) ||
scm->fp || scm_has_secdata(sock))
msg->msg_flags |= MSG_CTRUNC;
scm_destroy(scm);
return false;
}
if (test_bit(SOCK_PASSCRED, &sock->flags)) {
struct user_namespace *current_ns = current_user_ns();
struct ucred ucreds = {
.pid = scm->creds.pid,
.uid = from_kuid_munged(current_ns, scm->creds.uid),
.gid = from_kgid_munged(current_ns, scm->creds.gid),
};
put_cmsg(msg, SOL_SOCKET, SCM_CREDENTIALS, sizeof(ucreds), &ucreds);
}
scm_passec(sock, msg, scm);
if (scm->fp)
scm_detach_fds(msg, scm);
return true;
}
static inline void scm_recv(struct socket *sock, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!__scm_recv_common(sock, msg, scm, flags))
return;
scm_destroy_cred(scm);
}
static inline void scm_recv_unix(struct socket *sock, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!__scm_recv_common(sock, msg, scm, flags))
return;
if (test_bit(SOCK_PASSPIDFD, &sock->flags))
scm_pidfd_recv(msg, scm);
scm_destroy_cred(scm);
}
void scm_recv(struct socket *sock, struct msghdr *msg,
struct scm_cookie *scm, int flags);
void scm_recv_unix(struct socket *sock, struct msghdr *msg,
struct scm_cookie *scm, int flags);
static inline int scm_recv_one_fd(struct file *f, int __user *ufd,
unsigned int flags)

View File

@ -337,6 +337,12 @@ struct sk_filter;
* @sk_txtime_deadline_mode: set deadline mode for SO_TXTIME
* @sk_txtime_report_errors: set report errors mode for SO_TXTIME
* @sk_txtime_unused: unused txtime flags
* @sk_scm_recv_flags: all flags used by scm_recv()
* @sk_scm_credentials: flagged by SO_PASSCRED to recv SCM_CREDENTIALS
* @sk_scm_security: flagged by SO_PASSSEC to recv SCM_SECURITY
* @sk_scm_pidfd: flagged by SO_PASSPIDFD to recv SCM_PIDFD
* @sk_scm_rights: flagged by SO_PASSRIGHTS to recv SCM_RIGHTS
* @sk_scm_unused: unused flags for scm_recv()
* @ns_tracker: tracker for netns reference
* @sk_user_frags: xarray of pages the user is holding a reference on.
* @sk_owner: reference to the real owner of the socket that calls
@ -523,7 +529,17 @@ struct sock {
#endif
int sk_disconnects;
u8 sk_txrehash;
union {
u8 sk_txrehash;
u8 sk_scm_recv_flags;
struct {
u8 sk_scm_credentials : 1,
sk_scm_security : 1,
sk_scm_pidfd : 1,
sk_scm_rights : 1,
sk_scm_unused : 4;
};
};
u8 sk_clockid;
u8 sk_txtime_deadline_mode : 1,
sk_txtime_report_errors : 1,
@ -2773,9 +2789,14 @@ static inline bool sk_is_udp(const struct sock *sk)
sk->sk_protocol == IPPROTO_UDP;
}
static inline bool sk_is_unix(const struct sock *sk)
{
return sk->sk_family == AF_UNIX;
}
static inline bool sk_is_stream_unix(const struct sock *sk)
{
return sk->sk_family == AF_UNIX && sk->sk_type == SOCK_STREAM;
return sk_is_unix(sk) && sk->sk_type == SOCK_STREAM;
}
static inline bool sk_is_vsock(const struct sock *sk)
@ -2783,6 +2804,13 @@ static inline bool sk_is_vsock(const struct sock *sk)
return sk->sk_family == AF_VSOCK;
}
static inline bool sk_may_scm_recv(const struct sock *sk)
{
return (IS_ENABLED(CONFIG_UNIX) && sk->sk_family == AF_UNIX) ||
sk->sk_family == AF_NETLINK ||
(IS_ENABLED(CONFIG_BT) && sk->sk_family == AF_BLUETOOTH);
}
/**
* sk_eat_skb - Release a skb if it is no longer needed
* @sk: socket to eat this skb from

View File

@ -145,6 +145,8 @@
#define SO_RCVPRIORITY 82
#define SO_PASSRIGHTS 83
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))

View File

@ -404,3 +404,125 @@ struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl)
return new_fpl;
}
EXPORT_SYMBOL(scm_fp_dup);
#ifdef CONFIG_SECURITY_NETWORK
static void scm_passec(struct sock *sk, struct msghdr *msg, struct scm_cookie *scm)
{
struct lsm_context ctx;
int err;
if (sk->sk_scm_security) {
err = security_secid_to_secctx(scm->secid, &ctx);
if (err >= 0) {
put_cmsg(msg, SOL_SOCKET, SCM_SECURITY, ctx.len,
ctx.context);
security_release_secctx(&ctx);
}
}
}
static bool scm_has_secdata(struct sock *sk)
{
return sk->sk_scm_security;
}
#else
static void scm_passec(struct sock *sk, struct msghdr *msg, struct scm_cookie *scm)
{
}
static bool scm_has_secdata(struct sock *sk)
{
return false;
}
#endif
static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
{
struct file *pidfd_file = NULL;
int len, pidfd;
/* put_cmsg() doesn't return an error if CMSG is truncated,
* that's why we need to opencode these checks here.
*/
if (msg->msg_flags & MSG_CMSG_COMPAT)
len = sizeof(struct compat_cmsghdr) + sizeof(int);
else
len = sizeof(struct cmsghdr) + sizeof(int);
if (msg->msg_controllen < len) {
msg->msg_flags |= MSG_CTRUNC;
return;
}
if (!scm->pid)
return;
pidfd = pidfd_prepare(scm->pid, 0, &pidfd_file);
if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) {
if (pidfd_file) {
put_unused_fd(pidfd);
fput(pidfd_file);
}
return;
}
if (pidfd_file)
fd_install(pidfd, pidfd_file);
}
static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!msg->msg_control) {
if (sk->sk_scm_credentials || sk->sk_scm_pidfd ||
scm->fp || scm_has_secdata(sk))
msg->msg_flags |= MSG_CTRUNC;
scm_destroy(scm);
return false;
}
if (sk->sk_scm_credentials) {
struct user_namespace *current_ns = current_user_ns();
struct ucred ucreds = {
.pid = scm->creds.pid,
.uid = from_kuid_munged(current_ns, scm->creds.uid),
.gid = from_kgid_munged(current_ns, scm->creds.gid),
};
put_cmsg(msg, SOL_SOCKET, SCM_CREDENTIALS, sizeof(ucreds), &ucreds);
}
scm_passec(sk, msg, scm);
if (scm->fp)
scm_detach_fds(msg, scm);
return true;
}
void scm_recv(struct socket *sock, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!__scm_recv_common(sock->sk, msg, scm, flags))
return;
scm_destroy_cred(scm);
}
EXPORT_SYMBOL(scm_recv);
void scm_recv_unix(struct socket *sock, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!__scm_recv_common(sock->sk, msg, scm, flags))
return;
if (sock->sk->sk_scm_pidfd)
scm_pidfd_recv(msg, scm);
scm_destroy_cred(scm);
}

View File

@ -1220,15 +1220,6 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
return 0;
}
return -EPERM;
case SO_PASSSEC:
assign_bit(SOCK_PASSSEC, &sock->flags, valbool);
return 0;
case SO_PASSCRED:
assign_bit(SOCK_PASSCRED, &sock->flags, valbool);
return 0;
case SO_PASSPIDFD:
assign_bit(SOCK_PASSPIDFD, &sock->flags, valbool);
return 0;
case SO_TYPE:
case SO_PROTOCOL:
case SO_DOMAIN:
@ -1276,6 +1267,8 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
return 0;
}
case SO_TXREHASH:
if (!sk_is_tcp(sk))
return -EOPNOTSUPP;
if (val < -1 || val > 1)
return -EINVAL;
if ((u8)val == SOCK_TXREHASH_DEFAULT)
@ -1557,6 +1550,33 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
sock_valbool_flag(sk, SOCK_SELECT_ERR_QUEUE, valbool);
break;
case SO_PASSCRED:
if (sk_may_scm_recv(sk))
sk->sk_scm_credentials = valbool;
else
ret = -EOPNOTSUPP;
break;
case SO_PASSSEC:
if (IS_ENABLED(CONFIG_SECURITY_NETWORK) && sk_may_scm_recv(sk))
sk->sk_scm_security = valbool;
else
ret = -EOPNOTSUPP;
break;
case SO_PASSPIDFD:
if (sk_is_unix(sk))
sk->sk_scm_pidfd = valbool;
else
ret = -EOPNOTSUPP;
break;
case SO_PASSRIGHTS:
if (sk_is_unix(sk))
sk->sk_scm_rights = valbool;
else
ret = -EOPNOTSUPP;
break;
case SO_INCOMING_CPU:
reuseport_update_incoming_cpu(sk, val);
@ -1853,11 +1873,24 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
break;
case SO_PASSCRED:
v.val = !!test_bit(SOCK_PASSCRED, &sock->flags);
if (!sk_may_scm_recv(sk))
return -EOPNOTSUPP;
v.val = sk->sk_scm_credentials;
break;
case SO_PASSPIDFD:
v.val = !!test_bit(SOCK_PASSPIDFD, &sock->flags);
if (!sk_is_unix(sk))
return -EOPNOTSUPP;
v.val = sk->sk_scm_pidfd;
break;
case SO_PASSRIGHTS:
if (!sk_is_unix(sk))
return -EOPNOTSUPP;
v.val = sk->sk_scm_rights;
break;
case SO_PEERCRED:
@ -1954,7 +1987,10 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
break;
case SO_PASSSEC:
v.val = !!test_bit(SOCK_PASSSEC, &sock->flags);
if (!IS_ENABLED(CONFIG_SECURITY_NETWORK) || !sk_may_scm_recv(sk))
return -EOPNOTSUPP;
v.val = sk->sk_scm_security;
break;
case SO_PEERSEC:
@ -2102,6 +2138,9 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
break;
case SO_TXREHASH:
if (!sk_is_tcp(sk))
return -EOPNOTSUPP;
/* Paired with WRITE_ONCE() in sk_setsockopt() */
v.val = READ_ONCE(sk->sk_txrehash);
break;

View File

@ -765,6 +765,11 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
spin_unlock(&sk->sk_peer_lock);
}
static bool unix_may_passcred(const struct sock *sk)
{
return sk->sk_scm_credentials || sk->sk_scm_pidfd;
}
static int unix_listen(struct socket *sock, int backlog)
{
int err;
@ -1010,6 +1015,7 @@ static struct sock *unix_create1(struct net *net, struct socket *sock, int kern,
sock_init_data(sock, sk);
sk->sk_scm_rights = 1;
sk->sk_hash = unix_unbound_hash(sk);
sk->sk_allocation = GFP_KERNEL_ACCOUNT;
sk->sk_write_space = unix_write_space;
@ -1411,9 +1417,7 @@ static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
if (err)
goto out;
if ((test_bit(SOCK_PASSCRED, &sock->flags) ||
test_bit(SOCK_PASSPIDFD, &sock->flags)) &&
!READ_ONCE(unix_sk(sk)->addr)) {
if (unix_may_passcred(sk) && !READ_ONCE(unix_sk(sk)->addr)) {
err = unix_autobind(sk);
if (err)
goto out;
@ -1531,9 +1535,7 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
if (err)
goto out;
if ((test_bit(SOCK_PASSCRED, &sock->flags) ||
test_bit(SOCK_PASSPIDFD, &sock->flags)) &&
!READ_ONCE(u->addr)) {
if (unix_may_passcred(sk) && !READ_ONCE(u->addr)) {
err = unix_autobind(sk);
if (err)
goto out;
@ -1625,10 +1627,12 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
/* The way is open! Fastly set all the necessary fields... */
sock_hold(sk);
unix_peer(newsk) = sk;
newsk->sk_state = TCP_ESTABLISHED;
newsk->sk_type = sk->sk_type;
unix_peer(newsk) = sk;
newsk->sk_state = TCP_ESTABLISHED;
newsk->sk_type = sk->sk_type;
newsk->sk_scm_recv_flags = other->sk_scm_recv_flags;
init_peercred(newsk);
newu = unix_sk(newsk);
newu->listener = other;
RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
@ -1709,17 +1713,6 @@ static int unix_socketpair(struct socket *socka, struct socket *sockb)
return 0;
}
static void unix_sock_inherit_flags(const struct socket *old,
struct socket *new)
{
if (test_bit(SOCK_PASSCRED, &old->flags))
set_bit(SOCK_PASSCRED, &new->flags);
if (test_bit(SOCK_PASSPIDFD, &old->flags))
set_bit(SOCK_PASSPIDFD, &new->flags);
if (test_bit(SOCK_PASSSEC, &old->flags))
set_bit(SOCK_PASSSEC, &new->flags);
}
static int unix_accept(struct socket *sock, struct socket *newsock,
struct proto_accept_arg *arg)
{
@ -1756,7 +1749,6 @@ static int unix_accept(struct socket *sock, struct socket *newsock,
unix_state_lock(tsk);
unix_update_edges(unix_sk(tsk));
newsock->state = SS_CONNECTED;
unix_sock_inherit_flags(sock, newsock);
sock_graft(tsk, newsock);
unix_state_unlock(tsk);
return 0;
@ -1865,7 +1857,7 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
{
int err = 0;
UNIXCB(skb).pid = get_pid(scm->pid);
UNIXCB(skb).pid = get_pid(scm->pid);
UNIXCB(skb).uid = scm->creds.uid;
UNIXCB(skb).gid = scm->creds.gid;
UNIXCB(skb).fp = NULL;
@ -1877,28 +1869,19 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
return err;
}
static bool unix_passcred_enabled(const struct socket *sock,
const struct sock *other)
{
return test_bit(SOCK_PASSCRED, &sock->flags) ||
test_bit(SOCK_PASSPIDFD, &sock->flags) ||
!other->sk_socket ||
test_bit(SOCK_PASSCRED, &other->sk_socket->flags) ||
test_bit(SOCK_PASSPIDFD, &other->sk_socket->flags);
}
/*
* Some apps rely on write() giving SCM_CREDENTIALS
* We include credentials if source or destination socket
* asserted SOCK_PASSCRED.
*/
static void maybe_add_creds(struct sk_buff *skb, const struct socket *sock,
const struct sock *other)
static void unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
const struct sock *other)
{
if (UNIXCB(skb).pid)
return;
if (unix_passcred_enabled(sock, other)) {
UNIXCB(skb).pid = get_pid(task_tgid(current));
if (unix_may_passcred(sk) || unix_may_passcred(other)) {
UNIXCB(skb).pid = get_pid(task_tgid(current));
current_uid_gid(&UNIXCB(skb).uid, &UNIXCB(skb).gid);
}
}
@ -1974,9 +1957,7 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
goto out;
}
if ((test_bit(SOCK_PASSCRED, &sock->flags) ||
test_bit(SOCK_PASSPIDFD, &sock->flags)) &&
!READ_ONCE(u->addr)) {
if (unix_may_passcred(sk) && !READ_ONCE(u->addr)) {
err = unix_autobind(sk);
if (err)
goto out;
@ -2093,6 +2074,11 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
goto out_unlock;
}
if (UNIXCB(skb).fp && !other->sk_scm_rights) {
err = -EPERM;
goto out_unlock;
}
if (sk->sk_type != SOCK_SEQPACKET) {
err = security_unix_may_send(sk->sk_socket, other->sk_socket);
if (err)
@ -2139,7 +2125,8 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
if (sock_flag(other, SOCK_RCVTSTAMP))
__net_timestamp(skb);
maybe_add_creds(skb, sock, other);
unix_maybe_add_creds(skb, sk, other);
scm_stat_add(other, skb);
skb_queue_tail(&other->sk_receive_queue, skb);
unix_state_unlock(other);
@ -2167,14 +2154,14 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
#define UNIX_SKB_FRAGS_SZ (PAGE_SIZE << get_order(32768))
#if IS_ENABLED(CONFIG_AF_UNIX_OOB)
static int queue_oob(struct socket *sock, struct msghdr *msg, struct sock *other,
static int queue_oob(struct sock *sk, struct msghdr *msg, struct sock *other,
struct scm_cookie *scm, bool fds_sent)
{
struct unix_sock *ousk = unix_sk(other);
struct sk_buff *skb;
int err;
skb = sock_alloc_send_skb(sock->sk, 1, msg->msg_flags & MSG_DONTWAIT, &err);
skb = sock_alloc_send_skb(sk, 1, msg->msg_flags & MSG_DONTWAIT, &err);
if (!skb)
return err;
@ -2193,12 +2180,16 @@ static int queue_oob(struct socket *sock, struct msghdr *msg, struct sock *other
if (sock_flag(other, SOCK_DEAD) ||
(other->sk_shutdown & RCV_SHUTDOWN)) {
unix_state_unlock(other);
err = -EPIPE;
goto out;
goto out_unlock;
}
maybe_add_creds(skb, sock, other);
if (UNIXCB(skb).fp && !other->sk_scm_rights) {
err = -EPERM;
goto out_unlock;
}
unix_maybe_add_creds(skb, sk, other);
scm_stat_add(other, skb);
spin_lock(&other->sk_receive_queue.lock);
@ -2211,6 +2202,8 @@ static int queue_oob(struct socket *sock, struct msghdr *msg, struct sock *other
other->sk_data_ready(other);
return 0;
out_unlock:
unix_state_unlock(other);
out:
consume_skb(skb);
return err;
@ -2314,7 +2307,13 @@ static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg,
(other->sk_shutdown & RCV_SHUTDOWN))
goto out_pipe_unlock;
maybe_add_creds(skb, sock, other);
if (UNIXCB(skb).fp && !other->sk_scm_rights) {
unix_state_unlock(other);
err = -EPERM;
goto out_free;
}
unix_maybe_add_creds(skb, sk, other);
scm_stat_add(other, skb);
skb_queue_tail(&other->sk_receive_queue, skb);
unix_state_unlock(other);
@ -2324,7 +2323,7 @@ static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg,
#if IS_ENABLED(CONFIG_AF_UNIX_OOB)
if (msg->msg_flags & MSG_OOB) {
err = queue_oob(sock, msg, other, &scm, fds_sent);
err = queue_oob(sk, msg, other, &scm, fds_sent);
if (err)
goto out_err;
sent++;
@ -2846,8 +2845,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
/* Never glue messages from different writers */
if (!unix_skb_scm_eq(skb, &scm))
break;
} else if (test_bit(SOCK_PASSCRED, &sock->flags) ||
test_bit(SOCK_PASSPIDFD, &sock->flags)) {
} else if (unix_may_passcred(sk)) {
/* Copy credentials */
scm_set_cred(&scm, UNIXCB(skb).pid, UNIXCB(skb).uid, UNIXCB(skb).gid);
unix_set_secdata(&scm, skb);

View File

@ -145,6 +145,8 @@
#define SO_RCVPRIORITY 82
#define SO_PASSRIGHTS 83
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))

View File

@ -83,6 +83,14 @@ struct loop_ctx {
struct sock *sk;
};
static bool sk_is_tcp(struct sock *sk)
{
return (sk->__sk_common.skc_family == AF_INET ||
sk->__sk_common.skc_family == AF_INET6) &&
sk->sk_type == SOCK_STREAM &&
sk->sk_protocol == IPPROTO_TCP;
}
static int bpf_test_sockopt_flip(void *ctx, struct sock *sk,
const struct sockopt_test *t,
int level)
@ -91,6 +99,9 @@ static int bpf_test_sockopt_flip(void *ctx, struct sock *sk,
opt = t->opt;
if (opt == SO_TXREHASH && !sk_is_tcp(sk))
return 0;
if (bpf_getsockopt(ctx, level, opt, &old, sizeof(old)))
return 1;
/* kernel initialized txrehash to 255 */

View File

@ -23,6 +23,7 @@ FIXTURE_VARIANT(scm_rights)
int type;
int flags;
bool test_listener;
bool disabled;
};
FIXTURE_VARIANT_ADD(scm_rights, dgram)
@ -31,6 +32,16 @@ FIXTURE_VARIANT_ADD(scm_rights, dgram)
.type = SOCK_DGRAM,
.flags = 0,
.test_listener = false,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, dgram_disabled)
{
.name = "UNIX ",
.type = SOCK_DGRAM,
.flags = 0,
.test_listener = false,
.disabled = true,
};
FIXTURE_VARIANT_ADD(scm_rights, stream)
@ -39,6 +50,16 @@ FIXTURE_VARIANT_ADD(scm_rights, stream)
.type = SOCK_STREAM,
.flags = 0,
.test_listener = false,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_disabled)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = 0,
.test_listener = false,
.disabled = true,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_oob)
@ -47,6 +68,16 @@ FIXTURE_VARIANT_ADD(scm_rights, stream_oob)
.type = SOCK_STREAM,
.flags = MSG_OOB,
.test_listener = false,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_oob_disabled)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = MSG_OOB,
.test_listener = false,
.disabled = true,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_listener)
@ -55,6 +86,16 @@ FIXTURE_VARIANT_ADD(scm_rights, stream_listener)
.type = SOCK_STREAM,
.flags = 0,
.test_listener = true,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_listener_disabled)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = 0,
.test_listener = true,
.disabled = true,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_listener_oob)
@ -63,6 +104,16 @@ FIXTURE_VARIANT_ADD(scm_rights, stream_listener_oob)
.type = SOCK_STREAM,
.flags = MSG_OOB,
.test_listener = true,
.disabled = false,
};
FIXTURE_VARIANT_ADD(scm_rights, stream_listener_oob_disabled)
{
.name = "UNIX-STREAM ",
.type = SOCK_STREAM,
.flags = MSG_OOB,
.test_listener = true,
.disabled = true,
};
static int count_sockets(struct __test_metadata *_metadata,
@ -105,6 +156,9 @@ FIXTURE_SETUP(scm_rights)
ret = unshare(CLONE_NEWNET);
ASSERT_EQ(0, ret);
if (variant->disabled)
return;
ret = count_sockets(_metadata, variant);
ASSERT_EQ(0, ret);
}
@ -113,6 +167,9 @@ FIXTURE_TEARDOWN(scm_rights)
{
int ret;
if (variant->disabled)
return;
sleep(1);
ret = count_sockets(_metadata, variant);
@ -121,6 +178,7 @@ FIXTURE_TEARDOWN(scm_rights)
static void create_listeners(struct __test_metadata *_metadata,
FIXTURE_DATA(scm_rights) *self,
const FIXTURE_VARIANT(scm_rights) *variant,
int n)
{
struct sockaddr_un addr = {
@ -140,6 +198,12 @@ static void create_listeners(struct __test_metadata *_metadata,
ret = listen(self->fd[i], -1);
ASSERT_EQ(0, ret);
if (variant->disabled) {
ret = setsockopt(self->fd[i], SOL_SOCKET, SO_PASSRIGHTS,
&(int){0}, sizeof(int));
ASSERT_EQ(0, ret);
}
addrlen = sizeof(addr);
ret = getsockname(self->fd[i], (struct sockaddr *)&addr, &addrlen);
ASSERT_EQ(0, ret);
@ -164,6 +228,12 @@ static void create_socketpairs(struct __test_metadata *_metadata,
for (i = 0; i < n * 2; i += 2) {
ret = socketpair(AF_UNIX, variant->type, 0, self->fd + i);
ASSERT_EQ(0, ret);
if (variant->disabled) {
ret = setsockopt(self->fd[i], SOL_SOCKET, SO_PASSRIGHTS,
&(int){0}, sizeof(int));
ASSERT_EQ(0, ret);
}
}
}
@ -175,7 +245,7 @@ static void __create_sockets(struct __test_metadata *_metadata,
ASSERT_LE(n * 2, sizeof(self->fd) / sizeof(self->fd[0]));
if (variant->test_listener)
create_listeners(_metadata, self, n);
create_listeners(_metadata, self, variant, n);
else
create_socketpairs(_metadata, self, variant, n);
}
@ -230,7 +300,13 @@ void __send_fd(struct __test_metadata *_metadata,
int ret;
ret = sendmsg(self->fd[receiver * 2 + 1], &msg, variant->flags);
ASSERT_EQ(MSGLEN, ret);
if (variant->disabled) {
ASSERT_EQ(-1, ret);
ASSERT_EQ(-EPERM, -errno);
} else {
ASSERT_EQ(MSGLEN, ret);
}
}
#define create_sockets(n) \