Implement file descriptor passing.

This change refactors the Unix socket / pipe backend to have a ring buffer
containing segments, where each segment has an optional leading ancillary
buffer containing control messages followed by a normal data buffer.

The SCM_RIGHTS control message has been implemented which transfers file
descriptors to the receiving process. File descriptors are reference counted
and cycles are prevented using the following restrictions:

1) Unix sockets cannot be sent on themselves (on either end).
2) Unix sockets themselves being sent cannot be sent on.
3) Unix sockets cannot send a Unix socket being sent on.

This is a compatible ABI change.
This commit is contained in:
Jonas 'Sortie' Termansen 2021-12-28 11:40:09 +01:00
parent b9898086c6
commit 3c43f71084
23 changed files with 1440 additions and 151 deletions

View File

@ -177,7 +177,7 @@ sysroot-system: sysroot-fsh sysroot-base-headers
echo 'ID=sortix' && \
echo 'VERSION_ID="$(VERSION)"' && \
echo 'PRETTY_NAME="Sortix $(VERSION)"' && \
echo 'SORTIX_ABI=1.1' && \
echo 'SORTIX_ABI=1.2' && \
true) > "$(SYSROOT)/etc/sortix-release"
echo /etc/sortix-release >> "$(SYSROOT)/tix/manifest/system"
ln -sf sortix-release "$(SYSROOT)/etc/os-release"

View File

@ -232,6 +232,16 @@ bool Descriptor::IsSeekable()
return seekable;
}
bool Descriptor::pass()
{
return vnode->pass();
}
void Descriptor::unpass()
{
vnode->unpass();
}
int Descriptor::sync(ioctx_t* ctx)
{
// TODO: Possible denial-of-service attack if someone opens the file without

View File

@ -195,6 +195,8 @@ class Unode : public Inode
public:
Unode(Ref<Server> server, ino_t ino, mode_t type);
virtual ~Unode();
virtual bool pass();
virtual void unpass();
virtual void linked();
virtual void unlinked();
virtual int sync(ioctx_t* ctx);
@ -766,6 +768,15 @@ void Unode::UnexpectedResponse(Channel* channel, struct fsm_msg_header* hdr)
errno = EIO;
}
bool Unode::pass()
{
return true;
}
void Unode::unpass()
{
}
void Unode::linked()
{
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2012-2017, 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -58,6 +58,8 @@ public:
Ref<Descriptor> Fork();
bool SetFlags(int new_dflags);
int GetFlags();
bool pass();
void unpass();
int sync(ioctx_t* ctx);
int stat(ioctx_t* ctx, struct stat* st);
int statvfs(ioctx_t* ctx, struct statvfs* stvfs);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2012-2017, 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -54,6 +54,8 @@ public: /* These must never change after construction and is read-only. */
public:
virtual ~Inode() { }
virtual bool pass() = 0;
virtual void unpass() = 0;
virtual void linked() = 0;
virtual void unlinked() = 0;
virtual int sync(ioctx_t* ctx) = 0;
@ -165,6 +167,8 @@ protected:
public:
AbstractInode();
virtual ~AbstractInode();
virtual bool pass();
virtual void unpass();
virtual void linked();
virtual void unlinked();
virtual int sync(ioctx_t* ctx);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2013, 2014, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 2013, 2014, 2017, 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -43,6 +43,8 @@ public:
bool SetSIGPIPEDelivery(bool deliver_sigpipe);
size_t Size();
bool Resize(size_t new_size);
bool pass();
void unpass();
ssize_t readv(ioctx_t* ctx, const struct iovec* iov, int iovcnt);
ssize_t recv(ioctx_t* ctx, uint8_t* buf, size_t count, int flags);
ssize_t recvmsg(ioctx_t* ctx, struct msghdr* msg, int flags);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2013 Jonas 'Sortie' Termansen.
* Copyright (c) 2012, 2013, 2014, 2017 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -124,6 +124,21 @@ public:
size_t Refcount() const { return obj ? obj->Refcount : 0; }
bool IsUnique() const { return obj->IsUnique(); }
// Leak a reference and allow recreating it later from an integer.
uintptr_t Export()
{
if ( obj )
obj->Refer_Renamed();
return (uintptr_t) obj;
}
// Restore a leaked reference from an integer.
void Import(uintptr_t ptr)
{
Reset();
obj = (T*) ptr;
}
private:
T* obj;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2012-2017, 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -55,6 +55,8 @@ public: /* These must never change after construction and is read-only. */
public:
Vnode(Ref<Inode> inode, Ref<Vnode> mountedat, ino_t rootino, dev_t rootdev);
virtual ~Vnode();
bool pass();
void unpass();
int sync(ioctx_t* ctx);
int stat(ioctx_t* ctx, struct stat* st);
int statvfs(ioctx_t* ctx, struct statvfs* stvfs);

View File

@ -61,6 +61,8 @@
#define S_IFFACTORY 0x10000
/* Don't run the factory method if simply stat'ing the inode. */
#define S_IFFACTORY_NOSTAT 0x20000
/* The file object must never be wrapped in another file object. */
#define S_IFNEVERWRAP 0x40000
#endif
#endif

View File

@ -62,6 +62,15 @@ AbstractInode::~AbstractInode()
{
}
bool AbstractInode::pass()
{
return true;
}
void AbstractInode::unpass()
{
}
void AbstractInode::linked()
{
InterlockedIncrement(&stat_nlink);

View File

@ -836,6 +836,8 @@ int sys_mkpartition(int fd, off_t start, off_t length, int flags)
Ref<Inode> inner_inode = desc->vnode->inode;
desc.Reset();
if ( inner_inode->type & S_IFNEVERWRAP )
return errno = EPERM, -1;
if ( !S_ISBLK(inner_inode->type) && !S_ISREG(inner_inode->type) )
return errno = EPERM, -1;
if ( start < 0 || length < 0 )

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2014, 2016, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2014, 2016, 2017, 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -83,6 +83,8 @@ class StreamSocket : public AbstractInode
public:
StreamSocket(uid_t owner, gid_t group, mode_t mode, Ref<Manager> manager);
virtual ~StreamSocket();
virtual bool pass();
virtual void unpass();
virtual Ref<Inode> accept4(ioctx_t* ctx, uint8_t* addr, size_t* addrsize,
int flags);
virtual int bind(ioctx_t* ctx, const uint8_t* addr, size_t addrsize);
@ -163,7 +165,10 @@ StreamSocket::StreamSocket(uid_t owner, gid_t group, mode_t mode,
inode_type = INODE_TYPE_STREAM;
dev = (dev_t) manager.Get();
ino = (ino_t) this;
this->type = S_IFSOCK;
// Never allow wrapping filesystem sockets as they need to be able to
// recognize themselves when passing filesystems, to prevent reference
// cycle loops.
this->type = S_IFSOCK | S_IFNEVERWRAP;
this->stat_uid = owner;
this->stat_gid = group;
this->stat_mode = (mode & S_SETABLE) | this->type;
@ -191,6 +196,23 @@ StreamSocket::~StreamSocket()
free(bound_address);
}
bool StreamSocket::pass()
{
if ( outgoing.pass() )
{
if ( incoming.pass() )
return true;
outgoing.unpass();
}
return false;
}
void StreamSocket::unpass()
{
outgoing.unpass();
incoming.unpass();
}
Ref<Inode> StreamSocket::accept4(ioctx_t* ctx, uint8_t* addr, size_t* addrsize,
int flags)
{

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2015, 2017 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -39,6 +39,7 @@ Partition::Partition(Ref<Inode> inner_inode, off_t start, off_t length)
assert(0 <= start);
assert(0 <= length);
assert(length <= OFF_MAX - start);
assert(!(inner_inode->type & S_IFNEVERWRAP));
assert(S_ISBLK(inner_inode->type) || S_ISREG(inner_inode->type));
this->dev = (dev_t) this;
this->ino = (ino_t) this;

File diff suppressed because it is too large Load Diff

View File

@ -216,6 +216,16 @@ int Vnode::unmount(ioctx_t* ctx, const char* filename, int flags)
return 0;
}
bool Vnode::pass()
{
return inode->pass();
}
void Vnode::unpass()
{
inode->unpass();
}
int Vnode::sync(ioctx_t* ctx)
{
return inode->sync(ctx);

View File

@ -97,9 +97,21 @@ struct cmsghdr
#define SCM_RIGHTS 1
/* TODO: CMSG_DATA(cmsg) */
/* TODO: CMSG_NXTHDR(cmsg) */
/* TODO: CMSH_FIRSTHDR(cmsg) */
#define CMSG_ALIGN(value) \
(-(-(size_t)(value) & ~(__alignof__(struct cmsghdr) - 1)))
#define CMSG_SPACE(size) (sizeof(struct cmsghdr) + CMSG_ALIGN(size))
#define CMSG_LEN(size) (sizeof(struct cmsghdr) + (size))
#define CMSG_DATA(cmsg) ((unsigned char*) ((struct cmsghdr*) (cmsg) + 1))
#define CMSG_FIRSTHDR(mhdr) \
((mhdr)->msg_controllen < sizeof(struct cmsghdr) ? \
(struct cmsghdr*) 0 : \
(struct cmsghdr*) (mhdr)->msg_control)
#define CMSG_NXTHDR(mhdr, cmsg) \
((cmsg)->cmsg_len < sizeof(struct cmsghdr) || \
(char*) (mhdr)->msg_control + (mhdr)->msg_controllen - (char*) (cmsg) <= \
CMSG_ALIGN((cmsg)->cmsg_len) ? \
(struct cmsghdr*) 0 : \
(struct cmsghdr*) (((char*) (cmsg)) + CMSG_ALIGN((cmsg)->cmsg_len)))
struct linger
{
@ -140,6 +152,7 @@ struct linger
#define MSG_WAITALL (1<<7)
#define MSG_DONTWAIT (1<<8)
#define MSG_CMSG_CLOEXEC (1<<9)
#define MSG_CMSG_CLOFORK (1<<10)
#define AF_UNSPEC 0
#define AF_INET 1

View File

@ -25,6 +25,10 @@ test-pthread-once \
test-pthread-self \
test-pthread-tls \
test-signal-raise \
test-unix-socket-fd-cycle \
test-unix-socket-fd-leak \
test-unix-socket-fd-pass \
test-unix-socket-fd-trunc \
test-unix-socket-name \
test-unix-socket-shutdown \

View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* test-unix-socket-fd-cycle.c
* Tests whether Unix socket file descriptor passing cycles are rejected.
*/
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdalign.h>
#include <stdio.h>
#include <unistd.h>
#include "test.h"
int main(void)
{
int a_fds[2];
test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, a_fds) == 0);
int b_fds[2];
test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, b_fds) == 0);
struct msghdr mhdr;
char buf[1] = { 0 };
struct iovec iov;
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
alignas(struct cmsghdr) char cmsgdata[CMSG_SPACE(sizeof(int))];
ssize_t amount;
struct cmsghdr* cmsg;
// Passing a Unix socket on itself isn't permitted.
buf[0] = 'X';
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdata;
mhdr.msg_controllen = sizeof(cmsgdata);
cmsg = CMSG_FIRSTHDR(&mhdr);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
int* cdata = (int*) CMSG_DATA(cmsg);
*cdata = a_fds[1];
amount = sendmsg(a_fds[1], &mhdr, 0);
test_assertx(amount < 0);
test_assert(errno == EPERM);
// Passing a Unix socket on its other end isn't permitted.
*cdata = a_fds[0];
amount = sendmsg(a_fds[1], &mhdr, 0);
test_assertx(amount < 0);
test_assert(errno == EPERM);
// Passing a Unix socket (with no fds passed) on another Unix socket (which
// itself isn't being passed) is allowed.
*cdata = b_fds[1];
amount = sendmsg(a_fds[1], &mhdr, 0);
test_assert(0 <= amount);
test_assertx(amount == 1);
// A Unix socket (itself being passed) is not permitted to pass fds.
// b_fds[1] is already being sent on a_fds[1] (to a_fds[0]).
FILE* file;
test_assert((file = tmpfile()));
*cdata = fileno(file);
amount = sendmsg(b_fds[1], &mhdr, 0);
test_assertx(amount < 0);
test_assert(errno == EPERM);
fclose(file);
// A Unix socket is not permitted to send a socket with fds being sent.
// b_fds[1] is already being sent on a_fds[1] (to a_fds[0]).
*cdata = a_fds[1];
amount = sendmsg(b_fds[0], &mhdr, 0);
test_assertx(amount < 0);
test_assert(errno == EPERM);
// Receive b_fds[1] being sent on a_fds[1] to a_fds[0].
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdata;
mhdr.msg_controllen = sizeof(cmsgdata);
amount = recvmsg(a_fds[0], &mhdr, 0);
test_assert(0 <= amount);
test_assertx(amount == 1);
test_assertx(buf[0] == 'X');
test_assertx(!(mhdr.msg_flags & MSG_CTRUNC));
test_assertx(!mhdr.msg_flags);
test_assertx(mhdr.msg_controllen);
cmsg = CMSG_FIRSTHDR(&mhdr);
test_assertx(cmsg);
test_assertx(cmsg->cmsg_level == SOL_SOCKET);
test_assertx(cmsg->cmsg_type == SCM_RIGHTS);
test_assertx(cmsg->cmsg_len == CMSG_LEN(sizeof(int)));
cdata = (int*) CMSG_DATA(cmsg);
int file_fd = *cdata;
test_assertx(0 <= file_fd);
struct stat gotten_st;
test_assert(fstat(file_fd, &gotten_st) == 0);
struct stat expected_st;
test_assert(fstat(b_fds[1], &expected_st) == 0);
test_assertx(gotten_st.st_ino == expected_st.st_ino);
test_assertx(gotten_st.st_dev == expected_st.st_dev);
test_assertx(!CMSG_NXTHDR(&mhdr, cmsg));
close(file_fd);
return 0;
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* test-unix-socket-fd-leak.c
* Tests whether leaking file descriptors over a Unix socket works.
*/
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdalign.h>
#include <stdio.h>
#include <unistd.h>
#include "test.h"
int main(void)
{
int fds[2];
test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
FILE* file;
test_assert((file = tmpfile()));
struct msghdr mhdr;
char buf[1] = { 0 };
struct iovec iov;
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
alignas(struct cmsghdr) char cmsgdata[CMSG_SPACE(sizeof(int))];
buf[0] = 'X';
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdata;
mhdr.msg_controllen = sizeof(cmsgdata);
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&mhdr);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
int* cdata = (int*) CMSG_DATA(cmsg);
*cdata = fileno(file);
ssize_t amount = sendmsg(fds[1], &mhdr, 0);
test_assert(0 <= amount);
test_assertx(amount == 1);
fclose(file);
close(fds[0]);
close(fds[1]);
return 0;
}

View File

@ -0,0 +1,144 @@
/*
* Copyright (c) 2017, 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* test-unix-socket-fd-pass.c
* Tests whether passing a file descriptor over an Unix socket works.
*/
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdalign.h>
#include <stdio.h>
#include <unistd.h>
#include "test.h"
int main(void)
{
int fds[2];
test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
FILE* file;
test_assert((file = tmpfile()));
struct stat expected_st;
test_assert(fstat(fileno(file), &expected_st) == 0);
struct msghdr mhdr;
char buf[1] = { 0 };
struct iovec iov;
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
alignas(struct cmsghdr) char cmsgdata[CMSG_SPACE(sizeof(int))];
pid_t child_pid;
test_assert(0 <= (child_pid = fork()));
if ( child_pid == 0 )
{
close(fds[0]);
buf[0] = 'X';
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdata;
mhdr.msg_controllen = sizeof(cmsgdata);
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&mhdr);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
int* cdata = (int*) CMSG_DATA(cmsg);
*cdata = fileno(file);
ssize_t amount = sendmsg(fds[1], &mhdr, 0);
test_assert(0 <= amount);
test_assertx(amount == 1);
_exit(0);
}
close(fds[1]);
fclose(file);
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdata;
mhdr.msg_controllen = sizeof(cmsgdata);
ssize_t amount = recvmsg(fds[0], &mhdr, MSG_PEEK);
test_assert(0 <= amount);
test_assertx(amount == 1);
test_assertx(buf[0] == 'X');
test_assertx(!(mhdr.msg_flags & MSG_CTRUNC));
test_assertx(!mhdr.msg_flags);
test_assertx(mhdr.msg_controllen);
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&mhdr);
test_assertx(cmsg);
test_assertx(cmsg->cmsg_level == SOL_SOCKET);
test_assertx(cmsg->cmsg_type == SCM_RIGHTS);
test_assertx(cmsg->cmsg_len == CMSG_LEN(sizeof(int)));
int* cdata = (int*) CMSG_DATA(cmsg);
int file_fd = *cdata;
test_assertx(0 <= file_fd);
struct stat gotten_st;
test_assert(fstat(file_fd, &gotten_st) == 0);
test_assertx(gotten_st.st_ino == expected_st.st_ino);
test_assertx(gotten_st.st_dev == expected_st.st_dev);
test_assertx(!CMSG_NXTHDR(&mhdr, cmsg));
close(file_fd);
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdata;
mhdr.msg_controllen = sizeof(cmsgdata);
amount = recvmsg(fds[0], &mhdr, 0);
test_assert(0 <= amount);
test_assertx(amount == 1);
test_assertx(buf[0] == 'X');
test_assertx(!(mhdr.msg_flags & MSG_CTRUNC));
test_assertx(!mhdr.msg_flags);
test_assertx(mhdr.msg_controllen);
cmsg = CMSG_FIRSTHDR(&mhdr);
test_assertx(cmsg);
test_assertx(cmsg->cmsg_level == SOL_SOCKET);
test_assertx(cmsg->cmsg_type == SCM_RIGHTS);
test_assertx(cmsg->cmsg_len == CMSG_LEN(sizeof(int)));
cdata = (int*) CMSG_DATA(cmsg);
file_fd = *cdata;
test_assertx(0 <= file_fd);
test_assert(fstat(file_fd, &gotten_st) == 0);
test_assertx(gotten_st.st_ino == expected_st.st_ino);
test_assertx(gotten_st.st_dev == expected_st.st_dev);
test_assertx(!CMSG_NXTHDR(&mhdr, cmsg));
close(file_fd);
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdata;
mhdr.msg_controllen = sizeof(cmsgdata);
amount = recvmsg(fds[0], &mhdr, 0);
test_assert(0 <= amount);
test_assertx(amount == 0);
test_assertx(!mhdr.msg_flags);
test_assertx(!mhdr.msg_controllen);
int code;
test_assert(waitpid(child_pid, &code, 0) == child_pid);
test_assert(WIFEXITED(code) && WEXITSTATUS(code) == 0);
return 0;
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* test-unix-socket-fd-trunc.c
* Tests having too little control data when passing file descriptors.
*/
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdalign.h>
#include <stdio.h>
#include <unistd.h>
#include "test.h"
int main(void)
{
int fds[2];
test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
FILE* file1;
test_assert((file1 = tmpfile()));
FILE* file2;
test_assert((file2 = tmpfile()));
struct stat expected_st;
test_assert(fstat(fileno(file1), &expected_st) == 0);
struct msghdr mhdr;
char buf[1] = { 0 };
struct iovec iov;
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
alignas(struct cmsghdr) char cmsgdata[CMSG_SPACE(sizeof(int) * 2)];
buf[0] = 'X';
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdata;
mhdr.msg_controllen = sizeof(cmsgdata);
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&mhdr);
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * 2);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
int* cdata = (int*) CMSG_DATA(cmsg);
cdata[0] = fileno(file1);
cdata[1] = fileno(file2);
ssize_t amount = sendmsg(fds[1], &mhdr, 0);
test_assert(0 <= amount);
test_assertx(amount == 1);
fclose(file1);
fclose(file2);
alignas(struct cmsghdr)
char cmsgdatasmall[CMSG_ALIGN(sizeof(struct cmsghdr)) + sizeof(int)];
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cmsgdatasmall;
mhdr.msg_controllen = sizeof(cmsgdatasmall);
amount = recvmsg(fds[0], &mhdr, 0);
test_assert(0 <= amount);
test_assertx(amount == 1);
test_assertx(buf[0] == 'X');
test_assertx(mhdr.msg_flags == MSG_CTRUNC);
test_assertx(mhdr.msg_controllen);
test_assertx(mhdr.msg_controllen == sizeof(cmsgdatasmall));
cmsg = CMSG_FIRSTHDR(&mhdr);
test_assertx(cmsg);
test_assertx(cmsg->cmsg_level == SOL_SOCKET);
test_assertx(cmsg->cmsg_type == SCM_RIGHTS);
test_assertx(cmsg->cmsg_len == CMSG_LEN(sizeof(int)));
cdata = (int*) CMSG_DATA(cmsg);
int file_fd = *cdata;
test_assertx(0 <= file_fd);
struct stat gotten_st;
test_assert(fstat(file_fd, &gotten_st) == 0);
test_assertx(gotten_st.st_ino == expected_st.st_ino);
test_assertx(gotten_st.st_dev == expected_st.st_dev);
test_assertx(!CMSG_NXTHDR(&mhdr, cmsg));
close(file_fd);
close(fds[0]);
close(fds[1]);
return 0;
}

View File

@ -21,6 +21,7 @@
#define TEST_H
#include <errno.h>
#include <error.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>

View File

@ -69,6 +69,14 @@ releasing Sortix x.y, foo." to allow the maintainer to easily
.Xr grep 1
for it after a release.
.Sh CHANGES
.Ss Implement file descriptor passing
The
.Dv SCM_RIGHTS
control message have been implemented, allowing file descriptors to be passed
over
.Dv AF_UNIX
sockets.
This is a minor compatible ABI change.
.Ss Implement threading primitives that truly sleep
The
.Xr futex 2