Page MenuHomeFreeBSD

D58405.id.diff
No OneTemporary

D58405.id.diff

diff --git a/lib/libsys/connectat.2 b/lib/libsys/connectat.2
--- a/lib/libsys/connectat.2
+++ b/lib/libsys/connectat.2
@@ -24,7 +24,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd February 13, 2013
+.Dd July 25, 2026
.Dt CONNECTAT 2
.Os
.Sh NAME
@@ -66,6 +66,48 @@
field of the sockaddr_un structure is a relative path, it is located relative
to the directory associated with the file descriptor
.Fa fd .
+.Pp
+.It
+If the
+.Fa sun_path
+field is empty, that is,
+.Fa namelen
+equals
+.Li offsetof(struct sockaddr_un, sun_path) ,
+then
+.Fa fd
+does not resolve a path but instead names the peer socket directly.
+.Pp
+The descriptor may be the peer socket itself, or a descriptor for a file, such
+as an
+.Dv O_PATH
+handle opened with
+.Xr open 2 .
+In the latter case, the
+.Dv O_PATH
+file description may point either to a traditional socket file bound to the
+peer, or to a
+.Pa /dev/fd/N
+node.
+The
+.Pa /dev/fd/N
+node must name the socket descriptor itself; the named descriptor is resolved a
+single level and not chased further.
+With a standard
+.Xr fdescfs 5
+mount this means a node naming a traditional socket file, or another
+.Pa /dev/fd/N
+node, does not resolve to a peer.
+.Pp
+In all cases,
+.Fa fd
+must carry the
+.Dv CAP_CONNECTAT
+capability right, and the target of a
+.Pa /dev/fd/N
+node must also carry that right; see
+.Xr unix 4
+for the details of this form.
.El
.Sh RETURN VALUES
.Rv -std connectat
@@ -92,6 +134,14 @@
is neither
.Dv AT_FDCWD
nor a file descriptor associated with a directory.
+.It Bq Er EINVAL
+The
+.Fa sun_path
+field is empty but
+.Fa fd
+is
+.Dv AT_FDCWD ,
+so no descriptor names the peer.
.El
.Sh SEE ALSO
.Xr bindat 2 ,
diff --git a/share/man/man4/unix.4 b/share/man/man4/unix.4
--- a/share/man/man4/unix.4
+++ b/share/man/man4/unix.4
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd June 3, 2026
+.Dd July 25, 2026
.Dt UNIX 4
.Os
.Sh NAME
@@ -125,6 +125,95 @@
or
.Xr sendto 2
must be writable.
+.Ss Naming a peer
+A peer can be named either by a pathname or, using
+.Xr connectat 2 ,
+directly by a descriptor.
+Across these two forms a peer can be named five ways, all of which reach the
+same socket:
+.Bl -column "A bound socket's file" "an O_PATH descriptor" "an ordinary pathname" -offset indent
+.It Em Target Ta Em "By descriptor" Ta Em "By pathname"
+.It "The peer socket" Ta "the socket's fd" Ta "(none)"
+.It "A bound socket's file" Ta "an O_PATH descriptor" Ta "an ordinary pathname"
+.It "A /dev/fd/N node" Ta "an O_PATH descriptor" Ta Pa /dev/fd/N
+.El
+.Pp
+A pathname, accepted by both
+.Xr connect 2
+and
+.Xr connectat 2 ,
+names either a bound socket's file created by
+.Xr bind 2 ,
+or the
+.Pa /dev/fd/N
+node that
+.Xr fdescfs 5
+provides for descriptor
+.Ar N .
+In the latter case the
+.Pa /dev/fd/N
+node must name the socket descriptor itself; the named descriptor is resolved a
+single level and not chased further.
+With a standard
+.Xr fdescfs 5
+mount this means a node naming a traditional socket file, or another
+.Pa /dev/fd/N
+node, does not resolve to a peer; a
+.Cm nodup
+mount, however, dereferences such a descriptor before the lookup completes.
+.Pp
+.Xr connectat 2
+also supports both of the above cases.
+The path is allowed to be empty
+\(em that is, a
+.Fa namelen
+equal to
+.Li offsetof(struct sockaddr_un, sun_path)
+\(em
+in which case the given file descriptor will be used directly, like with most
+.Sy *at
+system calls.
+(Note:
+.Pa /dev/fd/N
+nodes are not recommended to be used for the
+.Xr connectat 2 ,
+since the descriptor can just be named directly, and this is both more
+efficient and less userland code, but since
+.Xr connect 2
+is implemented in terms of
+.Xr connectat 2
+this is provided for free.)
+.Pp
+Besides supporting cases that
+.Xr connect 2
+also supports,
+.Xr connectat 2
+additionally supports directly naming the peer by the
+.Fa fd
+descriptor (and an empty path).
+The descriptor may be the peer socket itself.
+In all cases, the descriptor passed must carry the
+.Dv CAP_CONNECTAT
+capability right, and in the
+.Pa /dev/fd/N
+form the target descriptor must
+.Em also
+carry that capability.
+A descriptor limited to
+.Dv CAP_CONNECTAT
+alone is thus a pure
+.Dq connect-to-me
+token, usable as a connection target but not listened on, accepted from, or
+read.
+.Pp
+For the descriptor forms, possession of the descriptor is the authorization,
+as with descriptor passing over
+.Dv SCM_RIGHTS ;
+the file system access-control and
+.Xr mac 4
+checks that apply to the pathname forms are not repeated.
+Because a descriptor alone suffices, a datagram socket may connect to an
+unbound peer, which no pathname could name.
.Sh CONTROL MESSAGES
The
.Ux Ns -domain
@@ -460,6 +549,7 @@
The order is preserved for writes coming through a particular connection.
.Sh SEE ALSO
.Xr connect 2 ,
+.Xr connectat 2 ,
.Xr dup 2 ,
.Xr fchmod 2 ,
.Xr fcntl 2 ,
@@ -471,6 +561,8 @@
.Xr socket 2 ,
.Xr CMSG_DATA 3 ,
.Xr intro 4 ,
+.Xr mac 4 ,
+.Xr fdescfs 5 ,
.Xr sysctl 8
.Rs
.%T "An Introductory 4.3 BSD Interprocess Communication Tutorial"
diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c
--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -290,9 +290,7 @@
static int uipc_connect2(struct socket *, struct socket *);
static int uipc_ctloutput(struct socket *, struct sockopt *);
-static int unp_connect(struct socket *, struct sockaddr *,
- struct thread *);
-static int unp_connectat(int, struct socket *, struct sockaddr *,
+static int unp_connectat(int, struct socket *, const char *, int,
struct thread *, struct socket **);
static int unp_connect_peer(struct socket *, struct unpcb *,
struct sockaddr **, struct thread *, bool);
@@ -728,22 +726,38 @@
static int
uipc_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
{
- int error;
+ const char *path;
+ int error, len;
KASSERT(td == curthread, ("uipc_connect: td != curthread"));
- error = unp_connect(so, nam, td);
- return (error);
+
+ error = unp_sun_path(nam, &path, &len);
+ if (error != 0)
+ return (error);
+ /*
+ * unp_connectat() does not early exit on empty paths, because that is
+ * explicitly supported when naming the peer by file descriptor, but
+ * connect(2) only ever passes AT_FDCWD, so reject it here. This
+ * preserves historical behavior.
+ */
+ if (len == 0)
+ return (EINVAL);
+ return (unp_connectat(AT_FDCWD, so, path, len, td, NULL));
}
static int
uipc_connectat(int fd, struct socket *so, struct sockaddr *nam,
struct thread *td)
{
- int error;
+ const char *path;
+ int error, len;
KASSERT(td == curthread, ("uipc_connectat: td != curthread"));
- error = unp_connectat(fd, so, nam, td, NULL);
- return (error);
+
+ error = unp_sun_path(nam, &path, &len);
+ if (error != 0)
+ return (error);
+ return (unp_connectat(fd, so, path, len, td, NULL));
}
static void
@@ -2075,7 +2089,12 @@
SOCK_SENDBUF_UNLOCK(so);
if (addr != NULL) {
- if ((error = unp_connectat(AT_FDCWD, so, addr, td, &peer)))
+ const char *path;
+ int len;
+
+ if ((error = unp_sun_path(addr, &path, &len)))
+ goto out3;
+ if ((error = unp_connectat(AT_FDCWD, so, path, len, td, &peer)))
goto out3;
UNP_PCB_LOCK_ASSERT(unp);
unp2 = unp->unp_conn;
@@ -2894,16 +2913,10 @@
return (error);
}
-static int
-unp_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
-{
-
- return (unp_connectat(AT_FDCWD, so, nam, td, NULL));
-}
-
/*
- * Connect socket 'so' to the unix-domain peer named by 'nam', resolved
- * relative to descriptor 'fd' (AT_FDCWD for connect(2)).
+ * Connect socket 'so' to the unix-domain peer named by the 'len'-byte 'path'
+ * (an empty path names the peer directly by descriptor), resolved relative to
+ * descriptor 'fd' (AT_FDCWD for connect(2)).
*
* 'referenced_peerp' selects how the peer is returned. If NULL, on exit the
* peer's PCB is unlocked and the peer is unreferenced, symmetrically releasing
@@ -2920,24 +2933,18 @@
* path, which enqueues under the peer's PCB lock.
*/
static int
-unp_connectat(int fd, struct socket *so, struct sockaddr *nam,
+unp_connectat(int fd, struct socket *so, const char *path, int len,
struct thread *td, struct socket **referenced_peerp)
{
struct socket *so2;
struct unpcb *unp;
char buf[SOCK_MAXADDRLEN];
struct sockaddr *sa;
- const char *path;
- int error, len;
+ int error;
bool connreq;
CURVNET_ASSERT_SET();
- error = unp_sun_path(nam, &path, &len);
- if (error != 0)
- return (error);
- if (len == 0)
- return (EINVAL);
bcopy(path, buf, len);
buf[len] = 0;
@@ -2983,6 +2990,7 @@
sa = malloc(sizeof(struct sockaddr_un), M_SONAME, M_WAITOK);
else
sa = NULL;
+
error = unp_connectat_peer(td, fd, buf, &so2);
if (error != 0)
goto out;
@@ -3006,9 +3014,77 @@
}
/*
- * Resolve a connectat(2) target -- descriptor 'fd' and the pathname in 'buf' --
- * to a referenced peer unix socket in '*so2p'. The caller must release it with
- * sorele().
+ * Resolve descriptor 'fd' to the referenced unix-domain socket it *is* (as
+ * opposed to one it names through the file system) in '*so2p'. Returns
+ * ENOTSOCK if 'fd' is not a socket -- letting an empty-path caller fall back to
+ * a vnode lookup -- or EPROTOTYPE if it is a socket of another domain. The
+ * caller must release the returned socket with sorele().
+ */
+static int
+unp_socket_fd_peer(struct thread *td, int fd, struct socket **so2p)
+{
+ struct socket *so2;
+ struct file *fp;
+ cap_rights_t rights;
+ int error;
+
+ error = getsock(td, fd, cap_rights_init_one(&rights, CAP_CONNECTAT),
+ &fp);
+ if (error != 0)
+ return (error);
+ so2 = fp->f_data;
+ if (so2->so_proto->pr_domain->dom_family != AF_UNIX)
+ error = EPROTOTYPE;
+ else {
+ soref(so2);
+ *so2p = so2;
+ }
+ fdrop(fp, td);
+ return (error);
+}
+
+/*
+ * Resolve a synthetic descriptor vnode -- as fdescfs fabricates for a /dev/fd/N
+ * path -- to the peer socket named by the descriptor it stands for.
+ *
+ * Such a node has no object of its own; VOP_OPEN reports the underlying
+ * descriptor in td_dupfd and fails with ENODEV, the same convention open(2)
+ * follows via dupfdopen() for /dev/fd. We honour it here and resolve that
+ * descriptor as the peer, so a plain connect(2) to /dev/fd/N reaches the
+ * socket. Does not consume 'vp'.
+ */
+static int
+unp_dupfd_peer(struct vnode *vp, struct thread *td, struct socket **so2p)
+{
+ int dupfd, error;
+
+ ASSERT_VOP_LOCKED(vp, __func__);
+
+ td->td_dupfd = -1;
+ error = VOP_OPEN(vp, FREAD, td->td_ucred, td, NULL);
+ dupfd = td->td_dupfd;
+ td->td_dupfd = 0;
+ if (error == ENODEV && dupfd >= 0)
+ return (unp_socket_fd_peer(td, dupfd, so2p));
+ if (error == 0) {
+ /* Not the dupfd convention: an openable node is not a peer. */
+ (void)VOP_CLOSE(vp, FREAD, td->td_ucred, td);
+ error = ECONNREFUSED;
+ }
+ return (error);
+}
+
+/*
+ * Resolve a connectat(2) target -- descriptor 'fd' together with the pathname
+ * in 'buf' (null when len == 0) -- to a referenced peer unix socket in
+ * '*so2p', covering all four ways a peer can be named:
+ *
+ * empty path + socket fd the descriptor is the peer socket
+ * empty path + O_PATH vnode EMPTYPATH resolves the socket's vnode
+ * /dev/fd/N pathname fdescfs names a descriptor
+ * ordinary pathname a bound socket looked up by path
+ *
+ * The caller must release the returned socket with sorele().
*/
static int
unp_connectat_peer(struct thread *td, int fd, const char *buf,
@@ -3018,6 +3094,19 @@
cap_rights_t rights;
int error;
+ /*
+ * An empty sun_path means 'fd' names the peer directly. If it is a
+ * socket, it is the peer, so return success (or its error) with no
+ * fallback; if not, it may be an O_PATH handle for a bound socket's
+ * vnode, so fall through to an EMPTYPATH lookup.
+ */
+ if (*buf == '\0') {
+ error = unp_socket_fd_peer(td, fd, so2p);
+ if (error != ENOTSOCK)
+ return (error);
+ }
+
+ /* Resolve the path to a vnode. */
NDINIT_ATRIGHTS(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF |
(fd == AT_FDCWD ? 0 : EMPTYPATH), UIO_SYSSPACE, buf, fd,
cap_rights_init_one(&rights, CAP_CONNECTAT));
@@ -3027,12 +3116,25 @@
NDFREE_PNBUF(&nd);
/*
- * Resolve the vnode to a referenced peer socket and drop the vnode
- * before connecting: the reference keeps the peer stable, so no vnode
- * lock is held across unp_connect_peer() (which matters for the
- * return_locked datagram fast path).
+ * Dispatch on the resolved vnode, then drop it: for the socket cases the
+ * returned reference keeps the peer stable, so the caller holds no vnode
+ * lock across unp_connect_peer() (which matters for the return_locked
+ * datagram fast path).
+ *
+ * A synthetic descriptor node -- as fdescfs fabricates for a /dev/fd/N
+ * path -- carries no type of its own (VNON); opening it yields the
+ * descriptor it stands for, which we resolve as the peer socket.
+ * Otherwise the path must name a bound socket's vnode (VSOCK), which
+ * unp_vnode_peer() connects to, rejecting any other type with ENOTSOCK.
+ *
+ * unp_dupfd_peer() resolves that descriptor exactly once: if it is not
+ * a socket the connect fails, so this does *not* recur through a chain
+ * of O_PATH handles of /dev/fd nodes, which could be arbitrarily long.
*/
- error = unp_vnode_peer(nd.ni_vp, td, so2p);
+ if (nd.ni_vp->v_type == VNON)
+ error = unp_dupfd_peer(nd.ni_vp, td, so2p);
+ else
+ error = unp_vnode_peer(nd.ni_vp, td, so2p);
vput(nd.ni_vp);
return (error);
}

File Metadata

Mime Type
text/plain
Expires
Thu, Jul 30, 10:54 AM (11 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
35716981
Default Alt Text
D58405.id.diff (13 KB)

Event Timeline