// 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