1042 lines
30 KiB
C
1042 lines
30 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Landlock tests - Abstract UNIX socket
|
||
|
*
|
||
|
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
|
||
|
*/
|
||
|
|
||
|
#define _GNU_SOURCE
|
||
|
#include <errno.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <linux/landlock.h>
|
||
|
#include <sched.h>
|
||
|
#include <signal.h>
|
||
|
#include <stddef.h>
|
||
|
#include <sys/prctl.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/un.h>
|
||
|
#include <sys/wait.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include "common.h"
|
||
|
#include "scoped_common.h"
|
||
|
|
||
|
/* Number of pending connections queue to be hold. */
|
||
|
const short backlog = 10;
|
||
|
|
||
|
static void create_fs_domain(struct __test_metadata *const _metadata)
|
||
|
{
|
||
|
int ruleset_fd;
|
||
|
struct landlock_ruleset_attr ruleset_attr = {
|
||
|
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
|
||
|
};
|
||
|
|
||
|
ruleset_fd =
|
||
|
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||
|
EXPECT_LE(0, ruleset_fd)
|
||
|
{
|
||
|
TH_LOG("Failed to create a ruleset: %s", strerror(errno));
|
||
|
}
|
||
|
EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
|
||
|
EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
|
||
|
EXPECT_EQ(0, close(ruleset_fd));
|
||
|
}
|
||
|
|
||
|
FIXTURE(scoped_domains)
|
||
|
{
|
||
|
struct service_fixture stream_address, dgram_address;
|
||
|
};
|
||
|
|
||
|
#include "scoped_base_variants.h"
|
||
|
|
||
|
FIXTURE_SETUP(scoped_domains)
|
||
|
{
|
||
|
drop_caps(_metadata);
|
||
|
|
||
|
memset(&self->stream_address, 0, sizeof(self->stream_address));
|
||
|
memset(&self->dgram_address, 0, sizeof(self->dgram_address));
|
||
|
set_unix_address(&self->stream_address, 0);
|
||
|
set_unix_address(&self->dgram_address, 1);
|
||
|
}
|
||
|
|
||
|
FIXTURE_TEARDOWN(scoped_domains)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Test unix_stream_connect() and unix_may_send() for a child connecting to its
|
||
|
* parent, when they have scoped domain or no domain.
|
||
|
*/
|
||
|
TEST_F(scoped_domains, connect_to_parent)
|
||
|
{
|
||
|
pid_t child;
|
||
|
bool can_connect_to_parent;
|
||
|
int status;
|
||
|
int pipe_parent[2];
|
||
|
int stream_server, dgram_server;
|
||
|
|
||
|
/*
|
||
|
* can_connect_to_parent is true if a child process can connect to its
|
||
|
* parent process. This depends on the child process not being isolated
|
||
|
* from the parent with a dedicated Landlock domain.
|
||
|
*/
|
||
|
can_connect_to_parent = !variant->domain_child;
|
||
|
|
||
|
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
|
||
|
if (variant->domain_both) {
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
if (!__test_passed(_metadata))
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
child = fork();
|
||
|
ASSERT_LE(0, child);
|
||
|
if (child == 0) {
|
||
|
int err;
|
||
|
int stream_client, dgram_client;
|
||
|
char buf_child;
|
||
|
|
||
|
EXPECT_EQ(0, close(pipe_parent[1]));
|
||
|
if (variant->domain_child)
|
||
|
create_scoped_domain(
|
||
|
_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_client);
|
||
|
dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_client);
|
||
|
|
||
|
/* Waits for the server. */
|
||
|
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
|
||
|
|
||
|
err = connect(stream_client, &self->stream_address.unix_addr,
|
||
|
self->stream_address.unix_addr_len);
|
||
|
if (can_connect_to_parent) {
|
||
|
EXPECT_EQ(0, err);
|
||
|
} else {
|
||
|
EXPECT_EQ(-1, err);
|
||
|
EXPECT_EQ(EPERM, errno);
|
||
|
}
|
||
|
EXPECT_EQ(0, close(stream_client));
|
||
|
|
||
|
err = connect(dgram_client, &self->dgram_address.unix_addr,
|
||
|
self->dgram_address.unix_addr_len);
|
||
|
if (can_connect_to_parent) {
|
||
|
EXPECT_EQ(0, err);
|
||
|
} else {
|
||
|
EXPECT_EQ(-1, err);
|
||
|
EXPECT_EQ(EPERM, errno);
|
||
|
}
|
||
|
EXPECT_EQ(0, close(dgram_client));
|
||
|
_exit(_metadata->exit_code);
|
||
|
return;
|
||
|
}
|
||
|
EXPECT_EQ(0, close(pipe_parent[0]));
|
||
|
if (variant->domain_parent)
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_server);
|
||
|
dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_server);
|
||
|
ASSERT_EQ(0, bind(stream_server, &self->stream_address.unix_addr,
|
||
|
self->stream_address.unix_addr_len));
|
||
|
ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
|
||
|
self->dgram_address.unix_addr_len));
|
||
|
ASSERT_EQ(0, listen(stream_server, backlog));
|
||
|
|
||
|
/* Signals to child that the parent is listening. */
|
||
|
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||
|
|
||
|
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||
|
EXPECT_EQ(0, close(stream_server));
|
||
|
EXPECT_EQ(0, close(dgram_server));
|
||
|
|
||
|
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
|
||
|
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||
|
_metadata->exit_code = KSFT_FAIL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Test unix_stream_connect() and unix_may_send() for a parent connecting to
|
||
|
* its child, when they have scoped domain or no domain.
|
||
|
*/
|
||
|
TEST_F(scoped_domains, connect_to_child)
|
||
|
{
|
||
|
pid_t child;
|
||
|
bool can_connect_to_child;
|
||
|
int err_stream, err_dgram, errno_stream, errno_dgram, status;
|
||
|
int pipe_child[2], pipe_parent[2];
|
||
|
char buf;
|
||
|
int stream_client, dgram_client;
|
||
|
|
||
|
/*
|
||
|
* can_connect_to_child is true if a parent process can connect to its
|
||
|
* child process. The parent process is not isolated from the child
|
||
|
* with a dedicated Landlock domain.
|
||
|
*/
|
||
|
can_connect_to_child = !variant->domain_parent;
|
||
|
|
||
|
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
|
||
|
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
|
||
|
if (variant->domain_both) {
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
if (!__test_passed(_metadata))
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
child = fork();
|
||
|
ASSERT_LE(0, child);
|
||
|
if (child == 0) {
|
||
|
int stream_server, dgram_server;
|
||
|
|
||
|
EXPECT_EQ(0, close(pipe_parent[1]));
|
||
|
EXPECT_EQ(0, close(pipe_child[0]));
|
||
|
if (variant->domain_child)
|
||
|
create_scoped_domain(
|
||
|
_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
/* Waits for the parent to be in a domain, if any. */
|
||
|
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
|
||
|
|
||
|
stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_server);
|
||
|
dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_server);
|
||
|
ASSERT_EQ(0,
|
||
|
bind(stream_server, &self->stream_address.unix_addr,
|
||
|
self->stream_address.unix_addr_len));
|
||
|
ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr,
|
||
|
self->dgram_address.unix_addr_len));
|
||
|
ASSERT_EQ(0, listen(stream_server, backlog));
|
||
|
|
||
|
/* Signals to the parent that child is listening. */
|
||
|
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
|
||
|
|
||
|
/* Waits to connect. */
|
||
|
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
|
||
|
EXPECT_EQ(0, close(stream_server));
|
||
|
EXPECT_EQ(0, close(dgram_server));
|
||
|
_exit(_metadata->exit_code);
|
||
|
return;
|
||
|
}
|
||
|
EXPECT_EQ(0, close(pipe_child[1]));
|
||
|
EXPECT_EQ(0, close(pipe_parent[0]));
|
||
|
|
||
|
if (variant->domain_parent)
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
/* Signals that the parent is in a domain, if any. */
|
||
|
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||
|
|
||
|
stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_client);
|
||
|
dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_client);
|
||
|
|
||
|
/* Waits for the child to listen */
|
||
|
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
|
||
|
err_stream = connect(stream_client, &self->stream_address.unix_addr,
|
||
|
self->stream_address.unix_addr_len);
|
||
|
errno_stream = errno;
|
||
|
err_dgram = connect(dgram_client, &self->dgram_address.unix_addr,
|
||
|
self->dgram_address.unix_addr_len);
|
||
|
errno_dgram = errno;
|
||
|
if (can_connect_to_child) {
|
||
|
EXPECT_EQ(0, err_stream);
|
||
|
EXPECT_EQ(0, err_dgram);
|
||
|
} else {
|
||
|
EXPECT_EQ(-1, err_stream);
|
||
|
EXPECT_EQ(-1, err_dgram);
|
||
|
EXPECT_EQ(EPERM, errno_stream);
|
||
|
EXPECT_EQ(EPERM, errno_dgram);
|
||
|
}
|
||
|
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||
|
EXPECT_EQ(0, close(stream_client));
|
||
|
EXPECT_EQ(0, close(dgram_client));
|
||
|
|
||
|
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||
|
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
|
||
|
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||
|
_metadata->exit_code = KSFT_FAIL;
|
||
|
}
|
||
|
|
||
|
FIXTURE(scoped_vs_unscoped)
|
||
|
{
|
||
|
struct service_fixture parent_stream_address, parent_dgram_address,
|
||
|
child_stream_address, child_dgram_address;
|
||
|
};
|
||
|
|
||
|
#include "scoped_multiple_domain_variants.h"
|
||
|
|
||
|
FIXTURE_SETUP(scoped_vs_unscoped)
|
||
|
{
|
||
|
drop_caps(_metadata);
|
||
|
|
||
|
memset(&self->parent_stream_address, 0,
|
||
|
sizeof(self->parent_stream_address));
|
||
|
set_unix_address(&self->parent_stream_address, 0);
|
||
|
memset(&self->parent_dgram_address, 0,
|
||
|
sizeof(self->parent_dgram_address));
|
||
|
set_unix_address(&self->parent_dgram_address, 1);
|
||
|
memset(&self->child_stream_address, 0,
|
||
|
sizeof(self->child_stream_address));
|
||
|
set_unix_address(&self->child_stream_address, 2);
|
||
|
memset(&self->child_dgram_address, 0,
|
||
|
sizeof(self->child_dgram_address));
|
||
|
set_unix_address(&self->child_dgram_address, 3);
|
||
|
}
|
||
|
|
||
|
FIXTURE_TEARDOWN(scoped_vs_unscoped)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Test unix_stream_connect and unix_may_send for parent, child and
|
||
|
* grand child processes when they can have scoped or non-scoped domains.
|
||
|
*/
|
||
|
TEST_F(scoped_vs_unscoped, unix_scoping)
|
||
|
{
|
||
|
pid_t child;
|
||
|
int status;
|
||
|
bool can_connect_to_parent, can_connect_to_child;
|
||
|
int pipe_parent[2];
|
||
|
int stream_server_parent, dgram_server_parent;
|
||
|
|
||
|
can_connect_to_child = (variant->domain_grand_child != SCOPE_SANDBOX);
|
||
|
can_connect_to_parent = (can_connect_to_child &&
|
||
|
(variant->domain_children != SCOPE_SANDBOX));
|
||
|
|
||
|
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
|
||
|
|
||
|
if (variant->domain_all == OTHER_SANDBOX)
|
||
|
create_fs_domain(_metadata);
|
||
|
else if (variant->domain_all == SCOPE_SANDBOX)
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
child = fork();
|
||
|
ASSERT_LE(0, child);
|
||
|
if (child == 0) {
|
||
|
int stream_server_child, dgram_server_child;
|
||
|
int pipe_child[2];
|
||
|
pid_t grand_child;
|
||
|
|
||
|
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
|
||
|
|
||
|
if (variant->domain_children == OTHER_SANDBOX)
|
||
|
create_fs_domain(_metadata);
|
||
|
else if (variant->domain_children == SCOPE_SANDBOX)
|
||
|
create_scoped_domain(
|
||
|
_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
grand_child = fork();
|
||
|
ASSERT_LE(0, grand_child);
|
||
|
if (grand_child == 0) {
|
||
|
char buf;
|
||
|
int stream_err, dgram_err, stream_errno, dgram_errno;
|
||
|
int stream_client, dgram_client;
|
||
|
|
||
|
EXPECT_EQ(0, close(pipe_parent[1]));
|
||
|
EXPECT_EQ(0, close(pipe_child[1]));
|
||
|
|
||
|
if (variant->domain_grand_child == OTHER_SANDBOX)
|
||
|
create_fs_domain(_metadata);
|
||
|
else if (variant->domain_grand_child == SCOPE_SANDBOX)
|
||
|
create_scoped_domain(
|
||
|
_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_client);
|
||
|
dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_client);
|
||
|
|
||
|
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
|
||
|
stream_err = connect(
|
||
|
stream_client,
|
||
|
&self->child_stream_address.unix_addr,
|
||
|
self->child_stream_address.unix_addr_len);
|
||
|
stream_errno = errno;
|
||
|
dgram_err = connect(
|
||
|
dgram_client,
|
||
|
&self->child_dgram_address.unix_addr,
|
||
|
self->child_dgram_address.unix_addr_len);
|
||
|
dgram_errno = errno;
|
||
|
if (can_connect_to_child) {
|
||
|
EXPECT_EQ(0, stream_err);
|
||
|
EXPECT_EQ(0, dgram_err);
|
||
|
} else {
|
||
|
EXPECT_EQ(-1, stream_err);
|
||
|
EXPECT_EQ(-1, dgram_err);
|
||
|
EXPECT_EQ(EPERM, stream_errno);
|
||
|
EXPECT_EQ(EPERM, dgram_errno);
|
||
|
}
|
||
|
|
||
|
EXPECT_EQ(0, close(stream_client));
|
||
|
stream_client = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_client);
|
||
|
/* Datagram sockets can "reconnect". */
|
||
|
|
||
|
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
|
||
|
stream_err = connect(
|
||
|
stream_client,
|
||
|
&self->parent_stream_address.unix_addr,
|
||
|
self->parent_stream_address.unix_addr_len);
|
||
|
stream_errno = errno;
|
||
|
dgram_err = connect(
|
||
|
dgram_client,
|
||
|
&self->parent_dgram_address.unix_addr,
|
||
|
self->parent_dgram_address.unix_addr_len);
|
||
|
dgram_errno = errno;
|
||
|
if (can_connect_to_parent) {
|
||
|
EXPECT_EQ(0, stream_err);
|
||
|
EXPECT_EQ(0, dgram_err);
|
||
|
} else {
|
||
|
EXPECT_EQ(-1, stream_err);
|
||
|
EXPECT_EQ(-1, dgram_err);
|
||
|
EXPECT_EQ(EPERM, stream_errno);
|
||
|
EXPECT_EQ(EPERM, dgram_errno);
|
||
|
}
|
||
|
EXPECT_EQ(0, close(stream_client));
|
||
|
EXPECT_EQ(0, close(dgram_client));
|
||
|
|
||
|
_exit(_metadata->exit_code);
|
||
|
return;
|
||
|
}
|
||
|
EXPECT_EQ(0, close(pipe_child[0]));
|
||
|
if (variant->domain_child == OTHER_SANDBOX)
|
||
|
create_fs_domain(_metadata);
|
||
|
else if (variant->domain_child == SCOPE_SANDBOX)
|
||
|
create_scoped_domain(
|
||
|
_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
stream_server_child = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_server_child);
|
||
|
dgram_server_child = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_server_child);
|
||
|
|
||
|
ASSERT_EQ(0, bind(stream_server_child,
|
||
|
&self->child_stream_address.unix_addr,
|
||
|
self->child_stream_address.unix_addr_len));
|
||
|
ASSERT_EQ(0, bind(dgram_server_child,
|
||
|
&self->child_dgram_address.unix_addr,
|
||
|
self->child_dgram_address.unix_addr_len));
|
||
|
ASSERT_EQ(0, listen(stream_server_child, backlog));
|
||
|
|
||
|
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
|
||
|
ASSERT_EQ(grand_child, waitpid(grand_child, &status, 0));
|
||
|
EXPECT_EQ(0, close(stream_server_child))
|
||
|
EXPECT_EQ(0, close(dgram_server_child));
|
||
|
return;
|
||
|
}
|
||
|
EXPECT_EQ(0, close(pipe_parent[0]));
|
||
|
|
||
|
if (variant->domain_parent == OTHER_SANDBOX)
|
||
|
create_fs_domain(_metadata);
|
||
|
else if (variant->domain_parent == SCOPE_SANDBOX)
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
stream_server_parent = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_server_parent);
|
||
|
dgram_server_parent = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_server_parent);
|
||
|
ASSERT_EQ(0, bind(stream_server_parent,
|
||
|
&self->parent_stream_address.unix_addr,
|
||
|
self->parent_stream_address.unix_addr_len));
|
||
|
ASSERT_EQ(0, bind(dgram_server_parent,
|
||
|
&self->parent_dgram_address.unix_addr,
|
||
|
self->parent_dgram_address.unix_addr_len));
|
||
|
|
||
|
ASSERT_EQ(0, listen(stream_server_parent, backlog));
|
||
|
|
||
|
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||
|
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||
|
EXPECT_EQ(0, close(stream_server_parent));
|
||
|
EXPECT_EQ(0, close(dgram_server_parent));
|
||
|
|
||
|
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
|
||
|
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||
|
_metadata->exit_code = KSFT_FAIL;
|
||
|
}
|
||
|
|
||
|
FIXTURE(outside_socket)
|
||
|
{
|
||
|
struct service_fixture address, transit_address;
|
||
|
};
|
||
|
|
||
|
FIXTURE_VARIANT(outside_socket)
|
||
|
{
|
||
|
const bool child_socket;
|
||
|
const int type;
|
||
|
};
|
||
|
|
||
|
/* clang-format off */
|
||
|
FIXTURE_VARIANT_ADD(outside_socket, allow_dgram_child) {
|
||
|
/* clang-format on */
|
||
|
.child_socket = true,
|
||
|
.type = SOCK_DGRAM,
|
||
|
};
|
||
|
|
||
|
/* clang-format off */
|
||
|
FIXTURE_VARIANT_ADD(outside_socket, deny_dgram_server) {
|
||
|
/* clang-format on */
|
||
|
.child_socket = false,
|
||
|
.type = SOCK_DGRAM,
|
||
|
};
|
||
|
|
||
|
/* clang-format off */
|
||
|
FIXTURE_VARIANT_ADD(outside_socket, allow_stream_child) {
|
||
|
/* clang-format on */
|
||
|
.child_socket = true,
|
||
|
.type = SOCK_STREAM,
|
||
|
};
|
||
|
|
||
|
/* clang-format off */
|
||
|
FIXTURE_VARIANT_ADD(outside_socket, deny_stream_server) {
|
||
|
/* clang-format on */
|
||
|
.child_socket = false,
|
||
|
.type = SOCK_STREAM,
|
||
|
};
|
||
|
|
||
|
FIXTURE_SETUP(outside_socket)
|
||
|
{
|
||
|
drop_caps(_metadata);
|
||
|
|
||
|
memset(&self->transit_address, 0, sizeof(self->transit_address));
|
||
|
set_unix_address(&self->transit_address, 0);
|
||
|
memset(&self->address, 0, sizeof(self->address));
|
||
|
set_unix_address(&self->address, 1);
|
||
|
}
|
||
|
|
||
|
FIXTURE_TEARDOWN(outside_socket)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Test unix_stream_connect and unix_may_send for parent and child processes
|
||
|
* when connecting socket has different domain than the process using it.
|
||
|
*/
|
||
|
TEST_F(outside_socket, socket_with_different_domain)
|
||
|
{
|
||
|
pid_t child;
|
||
|
int err, status;
|
||
|
int pipe_child[2], pipe_parent[2];
|
||
|
char buf_parent;
|
||
|
int server_socket;
|
||
|
|
||
|
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
|
||
|
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
|
||
|
|
||
|
child = fork();
|
||
|
ASSERT_LE(0, child);
|
||
|
if (child == 0) {
|
||
|
int client_socket;
|
||
|
char buf_child;
|
||
|
|
||
|
EXPECT_EQ(0, close(pipe_parent[1]));
|
||
|
EXPECT_EQ(0, close(pipe_child[0]));
|
||
|
|
||
|
/* Client always has a domain. */
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
if (variant->child_socket) {
|
||
|
int data_socket, passed_socket, stream_server;
|
||
|
|
||
|
passed_socket = socket(AF_UNIX, variant->type, 0);
|
||
|
ASSERT_LE(0, passed_socket);
|
||
|
stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_server);
|
||
|
ASSERT_EQ(0, bind(stream_server,
|
||
|
&self->transit_address.unix_addr,
|
||
|
self->transit_address.unix_addr_len));
|
||
|
ASSERT_EQ(0, listen(stream_server, backlog));
|
||
|
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
|
||
|
data_socket = accept(stream_server, NULL, NULL);
|
||
|
ASSERT_LE(0, data_socket);
|
||
|
ASSERT_EQ(0, send_fd(data_socket, passed_socket));
|
||
|
EXPECT_EQ(0, close(passed_socket));
|
||
|
EXPECT_EQ(0, close(stream_server));
|
||
|
}
|
||
|
|
||
|
client_socket = socket(AF_UNIX, variant->type, 0);
|
||
|
ASSERT_LE(0, client_socket);
|
||
|
|
||
|
/* Waits for parent signal for connection. */
|
||
|
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
|
||
|
err = connect(client_socket, &self->address.unix_addr,
|
||
|
self->address.unix_addr_len);
|
||
|
if (variant->child_socket) {
|
||
|
EXPECT_EQ(0, err);
|
||
|
} else {
|
||
|
EXPECT_EQ(-1, err);
|
||
|
EXPECT_EQ(EPERM, errno);
|
||
|
}
|
||
|
EXPECT_EQ(0, close(client_socket));
|
||
|
_exit(_metadata->exit_code);
|
||
|
return;
|
||
|
}
|
||
|
EXPECT_EQ(0, close(pipe_child[1]));
|
||
|
EXPECT_EQ(0, close(pipe_parent[0]));
|
||
|
|
||
|
if (variant->child_socket) {
|
||
|
int client_child = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
|
||
|
ASSERT_LE(0, client_child);
|
||
|
ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
|
||
|
ASSERT_EQ(0, connect(client_child,
|
||
|
&self->transit_address.unix_addr,
|
||
|
self->transit_address.unix_addr_len));
|
||
|
server_socket = recv_fd(client_child);
|
||
|
EXPECT_EQ(0, close(client_child));
|
||
|
} else {
|
||
|
server_socket = socket(AF_UNIX, variant->type, 0);
|
||
|
}
|
||
|
ASSERT_LE(0, server_socket);
|
||
|
|
||
|
/* Server always has a domain. */
|
||
|
create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
ASSERT_EQ(0, bind(server_socket, &self->address.unix_addr,
|
||
|
self->address.unix_addr_len));
|
||
|
if (variant->type == SOCK_STREAM)
|
||
|
ASSERT_EQ(0, listen(server_socket, backlog));
|
||
|
|
||
|
/* Signals to child that the parent is listening. */
|
||
|
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||
|
|
||
|
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||
|
EXPECT_EQ(0, close(server_socket));
|
||
|
|
||
|
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
|
||
|
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||
|
_metadata->exit_code = KSFT_FAIL;
|
||
|
}
|
||
|
|
||
|
static const char stream_path[] = TMP_DIR "/stream.sock";
|
||
|
static const char dgram_path[] = TMP_DIR "/dgram.sock";
|
||
|
|
||
|
/* clang-format off */
|
||
|
FIXTURE(various_address_sockets) {};
|
||
|
/* clang-format on */
|
||
|
|
||
|
FIXTURE_VARIANT(various_address_sockets)
|
||
|
{
|
||
|
const int domain;
|
||
|
};
|
||
|
|
||
|
/* clang-format off */
|
||
|
FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_scoped_domain) {
|
||
|
/* clang-format on */
|
||
|
.domain = SCOPE_SANDBOX,
|
||
|
};
|
||
|
|
||
|
/* clang-format off */
|
||
|
FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_other_domain) {
|
||
|
/* clang-format on */
|
||
|
.domain = OTHER_SANDBOX,
|
||
|
};
|
||
|
|
||
|
/* clang-format off */
|
||
|
FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_no_domain) {
|
||
|
/* clang-format on */
|
||
|
.domain = NO_SANDBOX,
|
||
|
};
|
||
|
|
||
|
FIXTURE_SETUP(various_address_sockets)
|
||
|
{
|
||
|
drop_caps(_metadata);
|
||
|
|
||
|
umask(0077);
|
||
|
ASSERT_EQ(0, mkdir(TMP_DIR, 0700));
|
||
|
}
|
||
|
|
||
|
FIXTURE_TEARDOWN(various_address_sockets)
|
||
|
{
|
||
|
EXPECT_EQ(0, unlink(stream_path));
|
||
|
EXPECT_EQ(0, unlink(dgram_path));
|
||
|
EXPECT_EQ(0, rmdir(TMP_DIR));
|
||
|
}
|
||
|
|
||
|
TEST_F(various_address_sockets, scoped_pathname_sockets)
|
||
|
{
|
||
|
socklen_t size_stream, size_dgram;
|
||
|
pid_t child;
|
||
|
int status;
|
||
|
char buf_child, buf_parent;
|
||
|
int pipe_parent[2];
|
||
|
int unnamed_sockets[2];
|
||
|
int stream_pathname_socket, dgram_pathname_socket,
|
||
|
stream_abstract_socket, dgram_abstract_socket, data_socket;
|
||
|
struct service_fixture stream_abstract_addr, dgram_abstract_addr;
|
||
|
struct sockaddr_un stream_pathname_addr = {
|
||
|
.sun_family = AF_UNIX,
|
||
|
};
|
||
|
struct sockaddr_un dgram_pathname_addr = {
|
||
|
.sun_family = AF_UNIX,
|
||
|
};
|
||
|
|
||
|
/* Pathname address. */
|
||
|
snprintf(stream_pathname_addr.sun_path,
|
||
|
sizeof(stream_pathname_addr.sun_path), "%s", stream_path);
|
||
|
size_stream = offsetof(struct sockaddr_un, sun_path) +
|
||
|
strlen(stream_pathname_addr.sun_path);
|
||
|
snprintf(dgram_pathname_addr.sun_path,
|
||
|
sizeof(dgram_pathname_addr.sun_path), "%s", dgram_path);
|
||
|
size_dgram = offsetof(struct sockaddr_un, sun_path) +
|
||
|
strlen(dgram_pathname_addr.sun_path);
|
||
|
|
||
|
/* Abstract address. */
|
||
|
memset(&stream_abstract_addr, 0, sizeof(stream_abstract_addr));
|
||
|
set_unix_address(&stream_abstract_addr, 0);
|
||
|
memset(&dgram_abstract_addr, 0, sizeof(dgram_abstract_addr));
|
||
|
set_unix_address(&dgram_abstract_addr, 1);
|
||
|
|
||
|
/* Unnamed address for datagram socket. */
|
||
|
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM, 0, unnamed_sockets));
|
||
|
|
||
|
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
|
||
|
|
||
|
child = fork();
|
||
|
ASSERT_LE(0, child);
|
||
|
if (child == 0) {
|
||
|
int err;
|
||
|
|
||
|
EXPECT_EQ(0, close(pipe_parent[1]));
|
||
|
EXPECT_EQ(0, close(unnamed_sockets[1]));
|
||
|
|
||
|
if (variant->domain == SCOPE_SANDBOX)
|
||
|
create_scoped_domain(
|
||
|
_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
else if (variant->domain == OTHER_SANDBOX)
|
||
|
create_fs_domain(_metadata);
|
||
|
|
||
|
/* Waits for parent to listen. */
|
||
|
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
|
||
|
EXPECT_EQ(0, close(pipe_parent[0]));
|
||
|
|
||
|
/* Checks that we can send data through a datagram socket. */
|
||
|
ASSERT_EQ(1, write(unnamed_sockets[0], "a", 1));
|
||
|
EXPECT_EQ(0, close(unnamed_sockets[0]));
|
||
|
|
||
|
/* Connects with pathname sockets. */
|
||
|
stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_pathname_socket);
|
||
|
ASSERT_EQ(0, connect(stream_pathname_socket,
|
||
|
&stream_pathname_addr, size_stream));
|
||
|
ASSERT_EQ(1, write(stream_pathname_socket, "b", 1));
|
||
|
EXPECT_EQ(0, close(stream_pathname_socket));
|
||
|
|
||
|
/* Sends without connection. */
|
||
|
dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_pathname_socket);
|
||
|
err = sendto(dgram_pathname_socket, "c", 1, 0,
|
||
|
&dgram_pathname_addr, size_dgram);
|
||
|
EXPECT_EQ(1, err);
|
||
|
|
||
|
/* Sends with connection. */
|
||
|
ASSERT_EQ(0, connect(dgram_pathname_socket,
|
||
|
&dgram_pathname_addr, size_dgram));
|
||
|
ASSERT_EQ(1, write(dgram_pathname_socket, "d", 1));
|
||
|
EXPECT_EQ(0, close(dgram_pathname_socket));
|
||
|
|
||
|
/* Connects with abstract sockets. */
|
||
|
stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_abstract_socket);
|
||
|
err = connect(stream_abstract_socket,
|
||
|
&stream_abstract_addr.unix_addr,
|
||
|
stream_abstract_addr.unix_addr_len);
|
||
|
if (variant->domain == SCOPE_SANDBOX) {
|
||
|
EXPECT_EQ(-1, err);
|
||
|
EXPECT_EQ(EPERM, errno);
|
||
|
} else {
|
||
|
EXPECT_EQ(0, err);
|
||
|
ASSERT_EQ(1, write(stream_abstract_socket, "e", 1));
|
||
|
}
|
||
|
EXPECT_EQ(0, close(stream_abstract_socket));
|
||
|
|
||
|
/* Sends without connection. */
|
||
|
dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_abstract_socket);
|
||
|
err = sendto(dgram_abstract_socket, "f", 1, 0,
|
||
|
&dgram_abstract_addr.unix_addr,
|
||
|
dgram_abstract_addr.unix_addr_len);
|
||
|
if (variant->domain == SCOPE_SANDBOX) {
|
||
|
EXPECT_EQ(-1, err);
|
||
|
EXPECT_EQ(EPERM, errno);
|
||
|
} else {
|
||
|
EXPECT_EQ(1, err);
|
||
|
}
|
||
|
|
||
|
/* Sends with connection. */
|
||
|
err = connect(dgram_abstract_socket,
|
||
|
&dgram_abstract_addr.unix_addr,
|
||
|
dgram_abstract_addr.unix_addr_len);
|
||
|
if (variant->domain == SCOPE_SANDBOX) {
|
||
|
EXPECT_EQ(-1, err);
|
||
|
EXPECT_EQ(EPERM, errno);
|
||
|
} else {
|
||
|
EXPECT_EQ(0, err);
|
||
|
ASSERT_EQ(1, write(dgram_abstract_socket, "g", 1));
|
||
|
}
|
||
|
EXPECT_EQ(0, close(dgram_abstract_socket));
|
||
|
|
||
|
_exit(_metadata->exit_code);
|
||
|
return;
|
||
|
}
|
||
|
EXPECT_EQ(0, close(pipe_parent[0]));
|
||
|
EXPECT_EQ(0, close(unnamed_sockets[0]));
|
||
|
|
||
|
/* Sets up pathname servers. */
|
||
|
stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_pathname_socket);
|
||
|
ASSERT_EQ(0, bind(stream_pathname_socket, &stream_pathname_addr,
|
||
|
size_stream));
|
||
|
ASSERT_EQ(0, listen(stream_pathname_socket, backlog));
|
||
|
|
||
|
dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_pathname_socket);
|
||
|
ASSERT_EQ(0, bind(dgram_pathname_socket, &dgram_pathname_addr,
|
||
|
size_dgram));
|
||
|
|
||
|
/* Sets up abstract servers. */
|
||
|
stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
ASSERT_LE(0, stream_abstract_socket);
|
||
|
ASSERT_EQ(0,
|
||
|
bind(stream_abstract_socket, &stream_abstract_addr.unix_addr,
|
||
|
stream_abstract_addr.unix_addr_len));
|
||
|
|
||
|
dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, dgram_abstract_socket);
|
||
|
ASSERT_EQ(0, bind(dgram_abstract_socket, &dgram_abstract_addr.unix_addr,
|
||
|
dgram_abstract_addr.unix_addr_len));
|
||
|
ASSERT_EQ(0, listen(stream_abstract_socket, backlog));
|
||
|
|
||
|
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||
|
EXPECT_EQ(0, close(pipe_parent[1]));
|
||
|
|
||
|
/* Reads from unnamed socket. */
|
||
|
ASSERT_EQ(1, read(unnamed_sockets[1], &buf_parent, sizeof(buf_parent)));
|
||
|
ASSERT_EQ('a', buf_parent);
|
||
|
EXPECT_LE(0, close(unnamed_sockets[1]));
|
||
|
|
||
|
/* Reads from pathname sockets. */
|
||
|
data_socket = accept(stream_pathname_socket, NULL, NULL);
|
||
|
ASSERT_LE(0, data_socket);
|
||
|
ASSERT_EQ(1, read(data_socket, &buf_parent, sizeof(buf_parent)));
|
||
|
ASSERT_EQ('b', buf_parent);
|
||
|
EXPECT_EQ(0, close(data_socket));
|
||
|
EXPECT_EQ(0, close(stream_pathname_socket));
|
||
|
|
||
|
ASSERT_EQ(1,
|
||
|
read(dgram_pathname_socket, &buf_parent, sizeof(buf_parent)));
|
||
|
ASSERT_EQ('c', buf_parent);
|
||
|
ASSERT_EQ(1,
|
||
|
read(dgram_pathname_socket, &buf_parent, sizeof(buf_parent)));
|
||
|
ASSERT_EQ('d', buf_parent);
|
||
|
EXPECT_EQ(0, close(dgram_pathname_socket));
|
||
|
|
||
|
if (variant->domain != SCOPE_SANDBOX) {
|
||
|
/* Reads from abstract sockets if allowed to send. */
|
||
|
data_socket = accept(stream_abstract_socket, NULL, NULL);
|
||
|
ASSERT_LE(0, data_socket);
|
||
|
ASSERT_EQ(1,
|
||
|
read(data_socket, &buf_parent, sizeof(buf_parent)));
|
||
|
ASSERT_EQ('e', buf_parent);
|
||
|
EXPECT_EQ(0, close(data_socket));
|
||
|
|
||
|
ASSERT_EQ(1, read(dgram_abstract_socket, &buf_parent,
|
||
|
sizeof(buf_parent)));
|
||
|
ASSERT_EQ('f', buf_parent);
|
||
|
ASSERT_EQ(1, read(dgram_abstract_socket, &buf_parent,
|
||
|
sizeof(buf_parent)));
|
||
|
ASSERT_EQ('g', buf_parent);
|
||
|
}
|
||
|
|
||
|
/* Waits for all abstract socket tests. */
|
||
|
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||
|
EXPECT_EQ(0, close(stream_abstract_socket));
|
||
|
EXPECT_EQ(0, close(dgram_abstract_socket));
|
||
|
|
||
|
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
|
||
|
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||
|
_metadata->exit_code = KSFT_FAIL;
|
||
|
}
|
||
|
|
||
|
TEST(datagram_sockets)
|
||
|
{
|
||
|
struct service_fixture connected_addr, non_connected_addr;
|
||
|
int server_conn_socket, server_unconn_socket;
|
||
|
int pipe_parent[2], pipe_child[2];
|
||
|
int status;
|
||
|
char buf;
|
||
|
pid_t child;
|
||
|
|
||
|
drop_caps(_metadata);
|
||
|
memset(&connected_addr, 0, sizeof(connected_addr));
|
||
|
set_unix_address(&connected_addr, 0);
|
||
|
memset(&non_connected_addr, 0, sizeof(non_connected_addr));
|
||
|
set_unix_address(&non_connected_addr, 1);
|
||
|
|
||
|
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
|
||
|
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
|
||
|
|
||
|
child = fork();
|
||
|
ASSERT_LE(0, child);
|
||
|
if (child == 0) {
|
||
|
int client_conn_socket, client_unconn_socket;
|
||
|
|
||
|
EXPECT_EQ(0, close(pipe_parent[1]));
|
||
|
EXPECT_EQ(0, close(pipe_child[0]));
|
||
|
|
||
|
client_conn_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
client_unconn_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, client_conn_socket);
|
||
|
ASSERT_LE(0, client_unconn_socket);
|
||
|
|
||
|
/* Waits for parent to listen. */
|
||
|
ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
|
||
|
ASSERT_EQ(0,
|
||
|
connect(client_conn_socket, &connected_addr.unix_addr,
|
||
|
connected_addr.unix_addr_len));
|
||
|
|
||
|
/*
|
||
|
* Both connected and non-connected sockets can send data when
|
||
|
* the domain is not scoped.
|
||
|
*/
|
||
|
ASSERT_EQ(1, send(client_conn_socket, ".", 1, 0));
|
||
|
ASSERT_EQ(1, sendto(client_unconn_socket, ".", 1, 0,
|
||
|
&non_connected_addr.unix_addr,
|
||
|
non_connected_addr.unix_addr_len));
|
||
|
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
|
||
|
|
||
|
/* Scopes the domain. */
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
/*
|
||
|
* Connected socket sends data to the receiver, but the
|
||
|
* non-connected socket must fail to send data.
|
||
|
*/
|
||
|
ASSERT_EQ(1, send(client_conn_socket, ".", 1, 0));
|
||
|
ASSERT_EQ(-1, sendto(client_unconn_socket, ".", 1, 0,
|
||
|
&non_connected_addr.unix_addr,
|
||
|
non_connected_addr.unix_addr_len));
|
||
|
ASSERT_EQ(EPERM, errno);
|
||
|
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
|
||
|
|
||
|
EXPECT_EQ(0, close(client_conn_socket));
|
||
|
EXPECT_EQ(0, close(client_unconn_socket));
|
||
|
_exit(_metadata->exit_code);
|
||
|
return;
|
||
|
}
|
||
|
EXPECT_EQ(0, close(pipe_parent[0]));
|
||
|
EXPECT_EQ(0, close(pipe_child[1]));
|
||
|
|
||
|
server_conn_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
server_unconn_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, server_conn_socket);
|
||
|
ASSERT_LE(0, server_unconn_socket);
|
||
|
|
||
|
ASSERT_EQ(0, bind(server_conn_socket, &connected_addr.unix_addr,
|
||
|
connected_addr.unix_addr_len));
|
||
|
ASSERT_EQ(0, bind(server_unconn_socket, &non_connected_addr.unix_addr,
|
||
|
non_connected_addr.unix_addr_len));
|
||
|
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||
|
|
||
|
/* Waits for child to test. */
|
||
|
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
|
||
|
ASSERT_EQ(1, recv(server_conn_socket, &buf, 1, 0));
|
||
|
ASSERT_EQ(1, recv(server_unconn_socket, &buf, 1, 0));
|
||
|
|
||
|
/*
|
||
|
* Connected datagram socket will receive data, but
|
||
|
* non-connected datagram socket does not receive data.
|
||
|
*/
|
||
|
ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
|
||
|
ASSERT_EQ(1, recv(server_conn_socket, &buf, 1, 0));
|
||
|
|
||
|
/* Waits for all tests to finish. */
|
||
|
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||
|
EXPECT_EQ(0, close(server_conn_socket));
|
||
|
EXPECT_EQ(0, close(server_unconn_socket));
|
||
|
|
||
|
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
|
||
|
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||
|
_metadata->exit_code = KSFT_FAIL;
|
||
|
}
|
||
|
|
||
|
TEST(self_connect)
|
||
|
{
|
||
|
struct service_fixture connected_addr, non_connected_addr;
|
||
|
int connected_socket, non_connected_socket, status;
|
||
|
pid_t child;
|
||
|
|
||
|
drop_caps(_metadata);
|
||
|
memset(&connected_addr, 0, sizeof(connected_addr));
|
||
|
set_unix_address(&connected_addr, 0);
|
||
|
memset(&non_connected_addr, 0, sizeof(non_connected_addr));
|
||
|
set_unix_address(&non_connected_addr, 1);
|
||
|
|
||
|
connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
non_connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||
|
ASSERT_LE(0, connected_socket);
|
||
|
ASSERT_LE(0, non_connected_socket);
|
||
|
|
||
|
ASSERT_EQ(0, bind(connected_socket, &connected_addr.unix_addr,
|
||
|
connected_addr.unix_addr_len));
|
||
|
ASSERT_EQ(0, bind(non_connected_socket, &non_connected_addr.unix_addr,
|
||
|
non_connected_addr.unix_addr_len));
|
||
|
|
||
|
child = fork();
|
||
|
ASSERT_LE(0, child);
|
||
|
if (child == 0) {
|
||
|
/* Child's domain is scoped. */
|
||
|
create_scoped_domain(_metadata,
|
||
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||
|
|
||
|
/*
|
||
|
* The child inherits the sockets, and cannot connect or
|
||
|
* send data to them.
|
||
|
*/
|
||
|
ASSERT_EQ(-1,
|
||
|
connect(connected_socket, &connected_addr.unix_addr,
|
||
|
connected_addr.unix_addr_len));
|
||
|
ASSERT_EQ(EPERM, errno);
|
||
|
|
||
|
ASSERT_EQ(-1, sendto(connected_socket, ".", 1, 0,
|
||
|
&connected_addr.unix_addr,
|
||
|
connected_addr.unix_addr_len));
|
||
|
ASSERT_EQ(EPERM, errno);
|
||
|
|
||
|
ASSERT_EQ(-1, sendto(non_connected_socket, ".", 1, 0,
|
||
|
&non_connected_addr.unix_addr,
|
||
|
non_connected_addr.unix_addr_len));
|
||
|
ASSERT_EQ(EPERM, errno);
|
||
|
|
||
|
EXPECT_EQ(0, close(connected_socket));
|
||
|
EXPECT_EQ(0, close(non_connected_socket));
|
||
|
_exit(_metadata->exit_code);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Waits for all tests to finish. */
|
||
|
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||
|
EXPECT_EQ(0, close(connected_socket));
|
||
|
EXPECT_EQ(0, close(non_connected_socket));
|
||
|
|
||
|
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
|
||
|
WEXITSTATUS(status) != EXIT_SUCCESS)
|
||
|
_metadata->exit_code = KSFT_FAIL;
|
||
|
}
|
||
|
|
||
|
TEST_HARNESS_MAIN
|