selftests/landlock: Test abstract UNIX socket scoping

Add three tests that examine different scenarios for abstract UNIX
socket:

1) scoped_domains: Base tests of the abstract socket scoping mechanism
   for a landlocked process, same as the ptrace test.

2) scoped_vs_unscoped: Generates three processes with different domains
   and tests if a process with a non-scoped domain can connect to other
   processes.

3) outside_socket: Since the socket's creator credentials are used
   for scoping sockets, this test examines the cases where the socket's
   credentials are different from the process using it.

Move protocol_variant, service_fixture, and sys_gettid() from net_test.c
to common.h, and factor out code into a new set_unix_address() helper.

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
Link: https://lore.kernel.org/r/9321c3d3bcd9212ceb4b50693e29349f8d625e16.1725494372.git.fahimitahera@gmail.com
[mic: Fix commit message, remove useless clang-format tags, move
drop_caps() calls, move and rename variables, rename variants, use more
EXPECT, improve comments, simplify the outside_socket test]
Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
Tahera Fahimi 2024-09-04 18:13:57 -06:00 committed by Mickaël Salaün
parent 5b6b63cd64
commit fefcf0f7cf
No known key found for this signature in database
GPG Key ID: E5E3D0E88C82F6D2
6 changed files with 998 additions and 30 deletions

View File

@ -7,6 +7,7 @@
* Copyright © 2021 Microsoft Corporation
*/
#include <arpa/inet.h>
#include <errno.h>
#include <linux/landlock.h>
#include <linux/securebits.h>
@ -14,6 +15,7 @@
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
@ -226,3 +228,38 @@ enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
}
}
struct protocol_variant {
int domain;
int type;
};
struct service_fixture {
struct protocol_variant protocol;
/* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */
unsigned short port;
union {
struct sockaddr_in ipv4_addr;
struct sockaddr_in6 ipv6_addr;
struct {
struct sockaddr_un unix_addr;
socklen_t unix_addr_len;
};
};
};
static pid_t __maybe_unused sys_gettid(void)
{
return syscall(__NR_gettid);
}
static void __maybe_unused set_unix_address(struct service_fixture *const srv,
const unsigned short index)
{
srv->unix_addr.sun_family = AF_UNIX;
sprintf(srv->unix_addr.sun_path,
"_selftests-landlock-abstract-unix-tid%d-index%d", sys_gettid(),
index);
srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
srv->unix_addr.sun_path[0] = '\0';
}

View File

@ -36,30 +36,6 @@ enum sandbox_type {
TCP_SANDBOX,
};
struct protocol_variant {
int domain;
int type;
};
struct service_fixture {
struct protocol_variant protocol;
/* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */
unsigned short port;
union {
struct sockaddr_in ipv4_addr;
struct sockaddr_in6 ipv6_addr;
struct {
struct sockaddr_un unix_addr;
socklen_t unix_addr_len;
};
};
};
static pid_t sys_gettid(void)
{
return syscall(__NR_gettid);
}
static int set_service(struct service_fixture *const srv,
const struct protocol_variant prot,
const unsigned short index)
@ -92,12 +68,7 @@ static int set_service(struct service_fixture *const srv,
return 0;
case AF_UNIX:
srv->unix_addr.sun_family = prot.domain;
sprintf(srv->unix_addr.sun_path,
"_selftests-landlock-net-tid%d-index%d", sys_gettid(),
index);
srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
srv->unix_addr.sun_path[0] = '\0';
set_unix_address(srv, index);
return 0;
}
return 1;

View File

@ -0,0 +1,624 @@
// 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;
}
TEST_HARNESS_MAIN

View File

@ -0,0 +1,156 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Landlock scoped_domains variants
*
* See the hierarchy variants from ptrace_test.c
*
* Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2019-2020 ANSSI
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
/* clang-format on */
FIXTURE_VARIANT(scoped_domains)
{
bool domain_both;
bool domain_parent;
bool domain_child;
};
/*
* No domain
*
* P1-. P1 -> P2 : allow
* \ P2 -> P1 : allow
* 'P2
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, without_domain) {
/* clang-format on */
.domain_both = false,
.domain_parent = false,
.domain_child = false,
};
/*
* Child domain
*
* P1--. P1 -> P2 : allow
* \ P2 -> P1 : deny
* .'-----.
* | P2 |
* '------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, child_domain) {
/* clang-format on */
.domain_both = false,
.domain_parent = false,
.domain_child = true,
};
/*
* Parent domain
* .------.
* | P1 --. P1 -> P2 : deny
* '------' \ P2 -> P1 : allow
* '
* P2
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, parent_domain) {
/* clang-format on */
.domain_both = false,
.domain_parent = true,
.domain_child = false,
};
/*
* Parent + child domain (siblings)
* .------.
* | P1 ---. P1 -> P2 : deny
* '------' \ P2 -> P1 : deny
* .---'--.
* | P2 |
* '------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, sibling_domain) {
/* clang-format on */
.domain_both = false,
.domain_parent = true,
.domain_child = true,
};
/*
* Same domain (inherited)
* .-------------.
* | P1----. | P1 -> P2 : allow
* | \ | P2 -> P1 : allow
* | ' |
* | P2 |
* '-------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, inherited_domain) {
/* clang-format on */
.domain_both = true,
.domain_parent = false,
.domain_child = false,
};
/*
* Inherited + child domain
* .-----------------.
* | P1----. | P1 -> P2 : allow
* | \ | P2 -> P1 : deny
* | .-'----. |
* | | P2 | |
* | '------' |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, nested_domain) {
/* clang-format on */
.domain_both = true,
.domain_parent = false,
.domain_child = true,
};
/*
* Inherited + parent domain
* .-----------------.
* |.------. | P1 -> P2 : deny
* || P1 ----. | P2 -> P1 : allow
* |'------' \ |
* | ' |
* | P2 |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, nested_and_parent_domain) {
/* clang-format on */
.domain_both = true,
.domain_parent = true,
.domain_child = false,
};
/*
* Inherited + parent and child domain (siblings)
* .-----------------.
* | .------. | P1 -> P2 : deny
* | | P1 . | P2 -> P1 : deny
* | '------'\ |
* | \ |
* | .--'---. |
* | | P2 | |
* | '------' |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, forked_domains) {
/* clang-format on */
.domain_both = true,
.domain_parent = true,
.domain_child = true,
};

View File

@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Landlock scope test helpers
*
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
#define _GNU_SOURCE
#include <sys/types.h>
static void create_scoped_domain(struct __test_metadata *const _metadata,
const __u16 scope)
{
int ruleset_fd;
const struct landlock_ruleset_attr ruleset_attr = {
.scoped = scope,
};
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd)
{
TH_LOG("Failed to create a ruleset: %s", strerror(errno));
}
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
}

View File

@ -0,0 +1,152 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Landlock variants for three processes with various domains.
*
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
enum sandbox_type {
NO_SANDBOX,
SCOPE_SANDBOX,
/* Any other type of sandboxing domain */
OTHER_SANDBOX,
};
/* clang-format on */
FIXTURE_VARIANT(scoped_vs_unscoped)
{
const int domain_all;
const int domain_parent;
const int domain_children;
const int domain_child;
const int domain_grand_child;
};
/*
* .-----------------.
* | ####### | P3 -> P2 : allow
* | P1----# P2 # | P3 -> P1 : deny
* | # | # |
* | # P3 # |
* | ####### |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, deny_scoped) {
.domain_all = OTHER_SANDBOX,
.domain_parent = NO_SANDBOX,
.domain_children = SCOPE_SANDBOX,
.domain_child = NO_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* ###################
* # ####### # P3 -> P2 : allow
* # P1----# P2 # # P3 -> P1 : deny
* # # | # #
* # # P3 # #
* # ####### #
* ###################
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, all_scoped) {
.domain_all = SCOPE_SANDBOX,
.domain_parent = NO_SANDBOX,
.domain_children = SCOPE_SANDBOX,
.domain_child = NO_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* .-----------------.
* | .-----. | P3 -> P2 : allow
* | P1----| P2 | | P3 -> P1 : allow
* | | | |
* | | P3 | |
* | '-----' |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_other_domain) {
.domain_all = OTHER_SANDBOX,
.domain_parent = NO_SANDBOX,
.domain_children = OTHER_SANDBOX,
.domain_child = NO_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* .----. ###### P3 -> P2 : allow
* | P1 |----# P2 # P3 -> P1 : allow
* '----' ######
* |
* P3
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_one_domain) {
.domain_all = NO_SANDBOX,
.domain_parent = OTHER_SANDBOX,
.domain_children = NO_SANDBOX,
.domain_child = SCOPE_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* ###### .-----. P3 -> P2 : allow
* # P1 #----| P2 | P3 -> P1 : allow
* ###### '-----'
* |
* P3
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_grand_parent_scoped) {
.domain_all = NO_SANDBOX,
.domain_parent = SCOPE_SANDBOX,
.domain_children = NO_SANDBOX,
.domain_child = OTHER_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* ###### ###### P3 -> P2 : allow
* # P1 #----# P2 # P3 -> P1 : allow
* ###### ######
* |
* .----.
* | P3 |
* '----'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_parents_domain) {
.domain_all = NO_SANDBOX,
.domain_parent = SCOPE_SANDBOX,
.domain_children = NO_SANDBOX,
.domain_child = SCOPE_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* ###### P3 -> P2 : deny
* # P1 #----P2 P3 -> P1 : deny
* ###### |
* |
* ######
* # P3 #
* ######
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, deny_with_self_and_grandparent_domain) {
.domain_all = NO_SANDBOX,
.domain_parent = SCOPE_SANDBOX,
.domain_children = NO_SANDBOX,
.domain_child = NO_SANDBOX,
.domain_grand_child = SCOPE_SANDBOX,
/* clang-format on */
};