Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/tools/testing/selftests/landlock/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 12 kB image not shown  

Quelle  scoped_signal_test.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * Landlock tests - Signal Scoping
 *
 * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
 */


#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/landlock.h>
#include <pthread.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "common.h"
#include "scoped_common.h"

/* This variable is used for handling several signals. */
static volatile sig_atomic_t is_signaled;

/* clang-format off */
FIXTURE(scoping_signals) {};
/* clang-format on */

FIXTURE_VARIANT(scoping_signals)
{
 int sig;
};

/* clang-format off */
FIXTURE_VARIANT_ADD(scoping_signals, sigtrap) {
 /* clang-format on */
 .sig = SIGTRAP,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(scoping_signals, sigurg) {
 /* clang-format on */
 .sig = SIGURG,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(scoping_signals, sighup) {
 /* clang-format on */
 .sig = SIGHUP,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(scoping_signals, sigtstp) {
 /* clang-format on */
 .sig = SIGTSTP,
};

FIXTURE_SETUP(scoping_signals)
{
 drop_caps(_metadata);

 is_signaled = 0;
}

FIXTURE_TEARDOWN(scoping_signals)
{
}

static void scope_signal_handler(int sig, siginfo_t *info, void *ucontext)
{
 if (sig == SIGTRAP || sig == SIGURG || sig == SIGHUP || sig == SIGTSTP)
  is_signaled = 1;
}

/*
 * In this test, a child process sends a signal to parent before and
 * after getting scoped.
 */

TEST_F(scoping_signals, send_sig_to_parent)
{
 int pipe_parent[2];
 int status;
 pid_t child;
 pid_t parent = getpid();
 struct sigaction action = {
  .sa_sigaction = scope_signal_handler,
  .sa_flags = SA_SIGINFO,

 };

 ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
 ASSERT_LE(0, sigaction(variant->sig, &action, NULL));

 /* The process should not have already been signaled. */
 EXPECT_EQ(0, is_signaled);

 child = fork();
 ASSERT_LE(0, child);
 if (child == 0) {
  char buf_child;
  int err;

  EXPECT_EQ(0, close(pipe_parent[1]));

  /*
 * The child process can send signal to parent when
 * domain is not scoped.
 */

  err = kill(parent, variant->sig);
  ASSERT_EQ(0, err);
  ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
  EXPECT_EQ(0, close(pipe_parent[0]));

  create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

  /*
 * The child process cannot send signal to the parent
 * anymore.
 */

  err = kill(parent, variant->sig);
  ASSERT_EQ(-1, err);
  ASSERT_EQ(EPERM, errno);

  /*
 * No matter of the domain, a process should be able to
 * send a signal to itself.
 */

  ASSERT_EQ(0, is_signaled);
  ASSERT_EQ(0, raise(variant->sig));
  ASSERT_EQ(1, is_signaled);

  _exit(_metadata->exit_code);
  return;
 }
 EXPECT_EQ(0, close(pipe_parent[0]));

 /* Waits for a first signal to be received, without race condition. */
 while (!is_signaled && !usleep(1))
  ;
 ASSERT_EQ(1, is_signaled);
 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
 EXPECT_EQ(0, close(pipe_parent[1]));
 is_signaled = 0;

 ASSERT_EQ(child, waitpid(child, &status, 0));
 if (WIFSIGNALED(status) || !WIFEXITED(status) ||
     WEXITSTATUS(status) != EXIT_SUCCESS)
  _metadata->exit_code = KSFT_FAIL;

 EXPECT_EQ(0, is_signaled);
}

/* clang-format off */
FIXTURE(scoped_domains) {};
/* clang-format on */

#include "scoped_base_variants.h"

FIXTURE_SETUP(scoped_domains)
{
 drop_caps(_metadata);
}

FIXTURE_TEARDOWN(scoped_domains)
{
}

/*
 * This test ensures that a scoped process cannot send signal out of
 * scoped domain.
 */

TEST_F(scoped_domains, check_access_signal)
{
 pid_t child;
 pid_t parent = getpid();
 int status;
 bool can_signal_child, can_signal_parent;
 int pipe_parent[2], pipe_child[2];
 char buf_parent;
 int err;

 can_signal_parent = !variant->domain_child;
 can_signal_child = !variant->domain_parent;

 if (variant->domain_both)
  create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

 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) {
  char buf_child;

  EXPECT_EQ(0, close(pipe_child[0]));
  EXPECT_EQ(0, close(pipe_parent[1]));

  if (variant->domain_child)
   create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

  ASSERT_EQ(1, write(pipe_child[1], ".", 1));
  EXPECT_EQ(0, close(pipe_child[1]));

  /* Waits for the parent to send signals. */
  ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
  EXPECT_EQ(0, close(pipe_parent[0]));

  err = kill(parent, 0);
  if (can_signal_parent) {
   ASSERT_EQ(0, err);
  } else {
   ASSERT_EQ(-1, err);
   ASSERT_EQ(EPERM, errno);
  }
  /*
 * No matter of the domain, a process should be able to
 * send a signal to itself.
 */

  ASSERT_EQ(0, raise(0));

  _exit(_metadata->exit_code);
  return;
 }
 EXPECT_EQ(0, close(pipe_parent[0]));
 EXPECT_EQ(0, close(pipe_child[1]));

 if (variant->domain_parent)
  create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

 ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
 EXPECT_EQ(0, close(pipe_child[0]));

 err = kill(child, 0);
 if (can_signal_child) {
  ASSERT_EQ(0, err);
 } else {
  ASSERT_EQ(-1, err);
  ASSERT_EQ(EPERM, errno);
 }
 ASSERT_EQ(0, raise(0));

 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
 EXPECT_EQ(0, close(pipe_parent[1]));
 ASSERT_EQ(child, waitpid(child, &status, 0));

 if (WIFSIGNALED(status) || !WIFEXITED(status) ||
     WEXITSTATUS(status) != EXIT_SUCCESS)
  _metadata->exit_code = KSFT_FAIL;
}

enum thread_return {
 THREAD_INVALID = 0,
 THREAD_SUCCESS = 1,
 THREAD_ERROR = 2,
 THREAD_TEST_FAILED = 3,
};

static void *thread_sync(void *arg)
{
 const int pipe_read = *(int *)arg;
 char buf;

 if (read(pipe_read, &buf, 1) != 1)
  return (void *)THREAD_ERROR;

 return (void *)THREAD_SUCCESS;
}

TEST(signal_scoping_thread_before)
{
 pthread_t no_sandbox_thread;
 enum thread_return ret = THREAD_INVALID;
 int thread_pipe[2];

 drop_caps(_metadata);
 ASSERT_EQ(0, pipe2(thread_pipe, O_CLOEXEC));

 ASSERT_EQ(0, pthread_create(&no_sandbox_thread, NULL, thread_sync,
        &thread_pipe[0]));

 /* Enforces restriction after creating the thread. */
 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

 EXPECT_EQ(0, pthread_kill(no_sandbox_thread, 0));
 EXPECT_EQ(1, write(thread_pipe[1], ".", 1));

 EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret));
 EXPECT_EQ(THREAD_SUCCESS, ret);

 EXPECT_EQ(0, close(thread_pipe[0]));
 EXPECT_EQ(0, close(thread_pipe[1]));
}

TEST(signal_scoping_thread_after)
{
 pthread_t scoped_thread;
 enum thread_return ret = THREAD_INVALID;
 int thread_pipe[2];

 drop_caps(_metadata);
 ASSERT_EQ(0, pipe2(thread_pipe, O_CLOEXEC));

 /* Enforces restriction before creating the thread. */
 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

 ASSERT_EQ(0, pthread_create(&scoped_thread, NULL, thread_sync,
        &thread_pipe[0]));

 EXPECT_EQ(0, pthread_kill(scoped_thread, 0));
 EXPECT_EQ(1, write(thread_pipe[1], ".", 1));

 EXPECT_EQ(0, pthread_join(scoped_thread, (void **)&ret));
 EXPECT_EQ(THREAD_SUCCESS, ret);

 EXPECT_EQ(0, close(thread_pipe[0]));
 EXPECT_EQ(0, close(thread_pipe[1]));
}

struct thread_setuid_args {
 int pipe_read, new_uid;
};

void *thread_setuid(void *ptr)
{
 const struct thread_setuid_args *arg = ptr;
 char buf;

 if (read(arg->pipe_read, &buf, 1) != 1)
  return (void *)THREAD_ERROR;

 /* libc's setuid() should update all thread's credentials. */
 if (getuid() != arg->new_uid)
  return (void *)THREAD_TEST_FAILED;

 return (void *)THREAD_SUCCESS;
}

TEST(signal_scoping_thread_setuid)
{
 struct thread_setuid_args arg;
 pthread_t no_sandbox_thread;
 enum thread_return ret = THREAD_INVALID;
 int pipe_parent[2];
 int prev_uid;

 disable_caps(_metadata);

 /* This test does not need to be run as root. */
 prev_uid = getuid();
 arg.new_uid = prev_uid + 1;
 EXPECT_LT(0, arg.new_uid);

 ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
 arg.pipe_read = pipe_parent[0];

 /* Capabilities must be set before creating a new thread. */
 set_cap(_metadata, CAP_SETUID);
 ASSERT_EQ(0, pthread_create(&no_sandbox_thread, NULL, thread_setuid,
        &arg));

 /* Enforces restriction after creating the thread. */
 create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

 EXPECT_NE(arg.new_uid, getuid());
 EXPECT_EQ(0, setuid(arg.new_uid));
 EXPECT_EQ(arg.new_uid, getuid());
 EXPECT_EQ(1, write(pipe_parent[1], ".", 1));

 EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret));
 EXPECT_EQ(THREAD_SUCCESS, ret);

 clear_cap(_metadata, CAP_SETUID);
 EXPECT_EQ(0, close(pipe_parent[0]));
 EXPECT_EQ(0, close(pipe_parent[1]));
}

const short backlog = 10;

static volatile sig_atomic_t signal_received;

static void handle_sigurg(int sig)
{
 if (sig == SIGURG)
  signal_received = 1;
 else
  signal_received = -1;
}

static int setup_signal_handler(int signal)
{
 struct sigaction sa = {
  .sa_handler = handle_sigurg,
 };

 if (sigemptyset(&sa.sa_mask))
  return -1;

 sa.sa_flags = SA_SIGINFO | SA_RESTART;
 return sigaction(SIGURG, &sa, NULL);
}

/* clang-format off */
FIXTURE(fown) {};
/* clang-format on */

enum fown_sandbox {
 SANDBOX_NONE,
 SANDBOX_BEFORE_FORK,
 SANDBOX_BEFORE_SETOWN,
 SANDBOX_AFTER_SETOWN,
};

FIXTURE_VARIANT(fown)
{
 const enum fown_sandbox sandbox_setown;
};

/* clang-format off */
FIXTURE_VARIANT_ADD(fown, no_sandbox) {
 /* clang-format on */
 .sandbox_setown = SANDBOX_NONE,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(fown, sandbox_before_fork) {
 /* clang-format on */
 .sandbox_setown = SANDBOX_BEFORE_FORK,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(fown, sandbox_before_setown) {
 /* clang-format on */
 .sandbox_setown = SANDBOX_BEFORE_SETOWN,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(fown, sandbox_after_setown) {
 /* clang-format on */
 .sandbox_setown = SANDBOX_AFTER_SETOWN,
};

FIXTURE_SETUP(fown)
{
 drop_caps(_metadata);
}

FIXTURE_TEARDOWN(fown)
{
}

/*
 * Sending an out of bound message will trigger the SIGURG signal
 * through file_send_sigiotask.
 */

TEST_F(fown, sigurg_socket)
{
 int server_socket, recv_socket;
 struct service_fixture server_address;
 char buffer_parent;
 int status;
 int pipe_parent[2], pipe_child[2];
 pid_t child;

 memset(&server_address, 0, sizeof(server_address));
 set_unix_address(&server_address, 0);

 ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
 ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));

 if (variant->sandbox_setown == SANDBOX_BEFORE_FORK)
  create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

 child = fork();
 ASSERT_LE(0, child);
 if (child == 0) {
  int client_socket;
  char buffer_child;

  EXPECT_EQ(0, close(pipe_parent[1]));
  EXPECT_EQ(0, close(pipe_child[0]));

  ASSERT_EQ(0, setup_signal_handler(SIGURG));
  client_socket = socket(AF_UNIX, SOCK_STREAM, 0);
  ASSERT_LE(0, client_socket);

  /* Waits for the parent to listen. */
  ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));
  ASSERT_EQ(0, connect(client_socket, &server_address.unix_addr,
         server_address.unix_addr_len));

  /*
 * Waits for the parent to accept the connection, sandbox
 * itself, and call fcntl(2).
 */

  ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));
  /* May signal itself. */
  ASSERT_EQ(1, send(client_socket, ".", 1, MSG_OOB));
  EXPECT_EQ(0, close(client_socket));
  ASSERT_EQ(1, write(pipe_child[1], ".", 1));
  EXPECT_EQ(0, close(pipe_child[1]));

  /* Waits for the message to be received. */
  ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));
  EXPECT_EQ(0, close(pipe_parent[0]));

  if (variant->sandbox_setown == SANDBOX_BEFORE_SETOWN) {
   ASSERT_EQ(0, signal_received);
  } else {
   /*
 * A signal is only received if fcntl(F_SETOWN) was
 * called before any sandboxing or if the signal
 * receiver is in the same domain.
 */

   ASSERT_EQ(1, signal_received);
  }
  _exit(_metadata->exit_code);
  return;
 }
 EXPECT_EQ(0, close(pipe_parent[0]));
 EXPECT_EQ(0, close(pipe_child[1]));

 server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
 ASSERT_LE(0, server_socket);
 ASSERT_EQ(0, bind(server_socket, &server_address.unix_addr,
     server_address.unix_addr_len));
 ASSERT_EQ(0, listen(server_socket, backlog));
 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));

 recv_socket = accept(server_socket, NULL, NULL);
 ASSERT_LE(0, recv_socket);

 if (variant->sandbox_setown == SANDBOX_BEFORE_SETOWN)
  create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

 /*
 * Sets the child to receive SIGURG for MSG_OOB.  This uncommon use is
 * a valid attack scenario which also simplifies this test.
 */

 ASSERT_EQ(0, fcntl(recv_socket, F_SETOWN, child));

 if (variant->sandbox_setown == SANDBOX_AFTER_SETOWN)
  create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);

 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));

 /* Waits for the child to send MSG_OOB. */
 ASSERT_EQ(1, read(pipe_child[0], &buffer_parent, 1));
 EXPECT_EQ(0, close(pipe_child[0]));
 ASSERT_EQ(1, recv(recv_socket, &buffer_parent, 1, MSG_OOB));
 EXPECT_EQ(0, close(recv_socket));
 EXPECT_EQ(0, close(server_socket));
 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
 EXPECT_EQ(0, close(pipe_parent[1]));

 ASSERT_EQ(child, waitpid(child, &status, 0));
 if (WIFSIGNALED(status) || !WIFEXITED(status) ||
     WEXITSTATUS(status) != EXIT_SUCCESS)
  _metadata->exit_code = KSFT_FAIL;
}

TEST_HARNESS_MAIN

Messung V0.5
C=95 H=95 G=94

¤ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.