Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F164186359
D58405.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
D58405.id.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D58405: unix: allow `connectat(2)` to name the peer socket by descriptor
Attached
Detach File
Event Timeline
Log In to Comment