af_unix: Introduce SO_PASSRIGHTS.
authorKuniyuki Iwashima <kuniyu@amazon.com>
Mon, 19 May 2025 20:57:59 +0000 (13:57 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 23 May 2025 09:24:18 +0000 (10:24 +0100)
As long as recvmsg() or recvmmsg() is used with cmsg, it is not
possible to avoid receiving file descriptors via SCM_RIGHTS.

This behaviour has occasionally been flagged as problematic, as
it can be (ab)used to trigger DoS during close(), for example, by
passing a FUSE-controlled fd or a hung NFS fd.

For instance, as noted on the uAPI Group page [0], an untrusted peer
could send a file descriptor pointing to a hung NFS mount and then
close it.  Once the receiver calls recvmsg() with msg_control, the
descriptor is automatically installed, and then the responsibility
for the final close() now falls on the receiver, which may result
in blocking the process for a long time.

Regarding this, systemd calls cmsg_close_all() [1] after each
recvmsg() to close() unwanted file descriptors sent via SCM_RIGHTS.

However, this cannot work around the issue at all, because the final
fput() may still occur on the receiver's side once sendmsg() with
SCM_RIGHTS succeeds.  Also, even filtering by LSM at recvmsg() does
not work for the same reason.

Thus, we need a better way to refuse SCM_RIGHTS at sendmsg().

Let's introduce SO_PASSRIGHTS to disable SCM_RIGHTS.

Note that this option is enabled by default for backward
compatibility.

Link: https://uapi-group.org/kernel-features/#disabling-reception-of-scm_rights-for-af_unix-sockets
Link: https://github.com/systemd/systemd/blob/v257.5/src/basic/fd-util.c#L612-L628
Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
arch/alpha/include/uapi/asm/socket.h
arch/mips/include/uapi/asm/socket.h
arch/parisc/include/uapi/asm/socket.h
arch/sparc/include/uapi/asm/socket.h
include/net/sock.h
include/uapi/asm-generic/socket.h
net/core/sock.c
net/unix/af_unix.c
tools/include/uapi/asm-generic/socket.h

index 3df5f2dd4c0fab2207d543a943adb03d203058d6..8f1f18adcdb593953284d8a6e92549d19a471b24 100644 (file)
 
 #define SO_RCVPRIORITY         82
 
+#define SO_PASSRIGHTS          83
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
index 22fa8f19924a7f64a3faebb7c4b81525d21665b3..31ac655b78371b214b243207f02df1918e00a2f8 100644 (file)
 
 #define SO_RCVPRIORITY         82
 
+#define SO_PASSRIGHTS          83
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
index 96831c98860658bb134e75c51f4d533525e0b0d0..1f2d5b7a7f5db3a79504ea57ea918092fb78039a 100644 (file)
 #define SCM_DEVMEM_DMABUF      SO_DEVMEM_DMABUF
 #define SO_DEVMEM_DONTNEED     0x4050
 
+#define SO_PASSRIGHTS          0x4051
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
index 5b464a568664016b64fe0d978f6ccbe4ad5702dc..adcba73293868f16ab84932a0dd38657b23d036f 100644 (file)
 
 #define SO_RCVPRIORITY           0x005b
 
+#define SO_PASSRIGHTS            0x005c
+
 #if !defined(__KERNEL__)
 
 
index d90a71f66ab8948400a8cc39869c51d5a86d6007..92e7c1aae3ccafe3a806dcee07ec77a469c0f43d 100644 (file)
@@ -341,6 +341,7 @@ struct sk_filter;
   *    @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.
@@ -535,7 +536,8 @@ struct sock {
                        u8      sk_scm_credentials : 1,
                                sk_scm_security : 1,
                                sk_scm_pidfd : 1,
-                               sk_scm_unused : 5;
+                               sk_scm_rights : 1,
+                               sk_scm_unused : 4;
                };
        };
        u8                      sk_clockid;
index aa5016ff3d9115cfc99cad48f7e4f5828d1a34d4..f333a0ac4ee4f0da6db544ea1f8ffcac791d1591 100644 (file)
 
 #define SO_RCVPRIORITY         82
 
+#define SO_PASSRIGHTS          83
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
index 381abf8f25b73573225eda7111c02f370c4a2a22..0cb52e590094db6a10a10f92500b3118d5189f3a 100644 (file)
@@ -1571,6 +1571,13 @@ set_sndbuf:
                        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);
                break;
@@ -1879,6 +1886,13 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
                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:
        {
                struct ucred peercred;
index 900bad88fbd2418a565579ca0d981b0d3ee77680..bd507f74e35e5c3a4d5d3e3963a4af0801434aa2 100644 (file)
@@ -1015,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;
@@ -2073,6 +2074,11 @@ restart_locked:
                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)
@@ -2174,9 +2180,13 @@ static int queue_oob(struct sock *sk, 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;
+       }
+
+       if (UNIXCB(skb).fp && !other->sk_scm_rights) {
+               err = -EPERM;
+               goto out_unlock;
        }
 
        unix_maybe_add_creds(skb, sk, other);
@@ -2192,6 +2202,8 @@ static int queue_oob(struct sock *sk, 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;
@@ -2295,6 +2307,12 @@ static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg,
                    (other->sk_shutdown & RCV_SHUTDOWN))
                        goto out_pipe_unlock;
 
+               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);
index aa5016ff3d9115cfc99cad48f7e4f5828d1a34d4..f333a0ac4ee4f0da6db544ea1f8ffcac791d1591 100644 (file)
 
 #define SO_RCVPRIORITY         82
 
+#define SO_PASSRIGHTS          83
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))