Merge tag 'landlock-7.2-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux

Pull landlock fix from Mickaël Salaün:
 "This fixes TCP Fast Open support, specific test environments, and doc
  warnings"

* tag 'landlock-7.2-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux:
  selftests/landlock: Skip scoped_signal subtest with MSG_OOB if not available
  selftests/landlock: Fix screwed up pointers in the scoped_signal_test
  landlock: Update formatting
  landlock: Fix kernel-doc for the nested quiet layer flag
  selftests/landlock: Add test for TCP fast open
  landlock: Fix TCP Fast Open connection bypass
This commit is contained in:
Linus Torvalds
2026-07-16 13:22:17 -07:00
5 changed files with 154 additions and 31 deletions

View File

@@ -351,6 +351,14 @@ static int hook_socket_sendmsg(struct socket *const sock,
access_mask_t access_request;
int ret = 0;
if ((msg->msg_flags & MSG_FASTOPEN) && address && sk_is_tcp(sock->sk)) {
ret = current_check_access_socket(
sock, address, addrlen, LANDLOCK_ACCESS_NET_CONNECT_TCP,
true);
if (ret != 0)
return ret;
}
if (sk_is_udp(sock->sk))
access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
else

View File

@@ -35,8 +35,8 @@ struct landlock_layer {
*/
struct {
/**
* @quiet: Suppresses denial logs for the object covered by this
* rule in this domain. For filesystem rules, this inherits
* @flags.quiet: Suppresses denial logs for the object covered by
* this rule in this domain. For filesystem rules, this inherits
* down the file hierarchy.
*/
u8 quiet : 1;

View File

@@ -95,8 +95,7 @@ static int hook_ptrace_access_check(struct task_struct *const child,
if (!parent_subject)
return 0;
scoped_guard(rcu)
{
scoped_guard(rcu) {
const struct landlock_ruleset *const child_dom =
landlock_get_task_domain(child);
err = domain_ptrace(parent_subject->domain, child_dom);
@@ -370,8 +369,7 @@ static int hook_task_kill(struct task_struct *const p,
if (!subject)
return 0;
scoped_guard(rcu)
{
scoped_guard(rcu) {
is_scoped = domain_is_scoped(subject->domain,
landlock_get_task_domain(p),
signal_scope.scope);
@@ -422,8 +420,7 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
if (task_tgid(tsk) == landlock_file(fown->file)->fown_tg)
return 0;
scoped_guard(rcu)
{
scoped_guard(rcu) {
is_scoped = domain_is_scoped(subject->domain,
landlock_get_task_domain(tsk),
signal_scope.scope);

View File

@@ -1281,6 +1281,103 @@ TEST_F(protocol, connect_unspec)
EXPECT_EQ(0, close(bind_fd));
}
TEST_F(protocol, tcp_fastopen)
{
const bool restricted = variant->sandbox == TCP_SANDBOX &&
variant->prot.type == SOCK_STREAM &&
(variant->prot.protocol == IPPROTO_TCP ||
variant->prot.protocol == IPPROTO_IP) &&
(variant->prot.domain == AF_INET ||
variant->prot.domain == AF_INET6);
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP,
};
int bind_fd, client_fd, status;
char buf;
pid_t child;
bind_fd = socket_variant(&self->srv0);
ASSERT_LE(0, bind_fd);
EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
if (self->srv0.protocol.type == SOCK_STREAM)
EXPECT_EQ(0, listen(bind_fd, backlog));
child = fork();
ASSERT_LE(0, child);
if (child == 0) {
int connect_fd, ret;
/* Closes listening socket for the child. */
EXPECT_EQ(0, close(bind_fd));
connect_fd = socket_variant(&self->srv0);
ASSERT_LE(0, connect_fd);
if (variant->sandbox == TCP_SANDBOX) {
const int ruleset_fd = landlock_create_ruleset(
&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd);
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
}
/* Fast Open with no address. */
ret = sendto_variant(connect_fd, NULL, NULL, 0, MSG_FASTOPEN);
if (self->srv0.protocol.domain == AF_UNIX) {
EXPECT_EQ(-ENOTCONN, ret);
} else if (self->srv0.protocol.type == SOCK_DGRAM) {
EXPECT_EQ(-EDESTADDRREQ, ret);
} else {
EXPECT_EQ(-EINVAL, ret);
}
/* Fast Open to a denied address. */
ret = sendto_variant(connect_fd, &self->srv0, "A", 1,
MSG_FASTOPEN);
if (restricted) {
EXPECT_EQ(-EACCES, ret);
} else if (self->srv0.protocol.domain == AF_UNIX &&
self->srv0.protocol.type == SOCK_STREAM) {
EXPECT_EQ(-EOPNOTSUPP, ret);
} else {
EXPECT_EQ(0, ret);
}
EXPECT_EQ(0, close(connect_fd));
_exit(_metadata->exit_code);
return;
}
client_fd = bind_fd;
if (!restricted && self->srv0.protocol.type == SOCK_STREAM &&
self->srv0.protocol.domain != AF_UNIX) {
client_fd = accept(bind_fd, NULL, 0);
ASSERT_LE(0, client_fd);
}
if (restricted) {
EXPECT_EQ(-1, read(client_fd, &buf, 1));
EXPECT_EQ(ENOTCONN, errno);
} else if (self->srv0.protocol.domain == AF_UNIX &&
self->srv0.protocol.type == SOCK_STREAM) {
EXPECT_EQ(-1, read(client_fd, &buf, 1));
EXPECT_EQ(EINVAL, errno);
} else {
EXPECT_EQ(1, read(client_fd, &buf, 1));
EXPECT_EQ('A', buf);
}
EXPECT_EQ(child, waitpid(child, &status, 0));
EXPECT_EQ(1, WIFEXITED(status));
EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
if (client_fd != bind_fd)
EXPECT_LE(0, close(client_fd));
EXPECT_EQ(0, close(bind_fd));
}
TEST_F(protocol, sendmsg_stream)
{
int srv0_fd, tmp_fd, client_fd, res;

View File

@@ -249,12 +249,12 @@ TEST_F(scoped_domains, check_access_signal)
_metadata->exit_code = KSFT_FAIL;
}
enum thread_return {
THREAD_INVALID = 0,
THREAD_SUCCESS = 1,
THREAD_ERROR = 2,
THREAD_TEST_FAILED = 3,
};
/* clang-format off */
#define THREAD_INVALID ((void *)0)
#define THREAD_SUCCESS ((void *)1)
#define THREAD_ERROR ((void *)2)
#define THREAD_TEST_FAILED ((void *)3)
/* clang-format on */
static void *thread_sync(void *arg)
{
@@ -262,15 +262,15 @@ static void *thread_sync(void *arg)
char buf;
if (read(pipe_read, &buf, 1) != 1)
return (void *)THREAD_ERROR;
return THREAD_ERROR;
return (void *)THREAD_SUCCESS;
return THREAD_SUCCESS;
}
TEST(signal_scoping_thread_before)
{
pthread_t no_sandbox_thread;
enum thread_return ret = THREAD_INVALID;
void *ret = THREAD_INVALID;
int thread_pipe[2];
drop_caps(_metadata);
@@ -285,7 +285,7 @@ TEST(signal_scoping_thread_before)
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(0, pthread_join(no_sandbox_thread, &ret));
EXPECT_EQ(THREAD_SUCCESS, ret);
EXPECT_EQ(0, close(thread_pipe[0]));
@@ -295,7 +295,7 @@ TEST(signal_scoping_thread_before)
TEST(signal_scoping_thread_after)
{
pthread_t scoped_thread;
enum thread_return ret = THREAD_INVALID;
void *ret = THREAD_INVALID;
int thread_pipe[2];
drop_caps(_metadata);
@@ -310,7 +310,7 @@ TEST(signal_scoping_thread_after)
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(0, pthread_join(scoped_thread, &ret));
EXPECT_EQ(THREAD_SUCCESS, ret);
EXPECT_EQ(0, close(thread_pipe[0]));
@@ -327,20 +327,20 @@ void *thread_setuid(void *ptr)
char buf;
if (read(arg->pipe_read, &buf, 1) != 1)
return (void *)THREAD_ERROR;
return THREAD_ERROR;
/* libc's setuid() should update all thread's credentials. */
if (getuid() != arg->new_uid)
return (void *)THREAD_TEST_FAILED;
return THREAD_TEST_FAILED;
return (void *)THREAD_SUCCESS;
return THREAD_SUCCESS;
}
TEST(signal_scoping_thread_setuid)
{
struct thread_setuid_args arg;
pthread_t no_sandbox_thread;
enum thread_return ret = THREAD_INVALID;
void *ret = THREAD_INVALID;
int pipe_parent[2];
int prev_uid;
@@ -367,7 +367,7 @@ TEST(signal_scoping_thread_setuid)
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(0, pthread_join(no_sandbox_thread, &ret));
EXPECT_EQ(THREAD_SUCCESS, ret);
clear_cap(_metadata, CAP_SETUID);
@@ -400,6 +400,24 @@ static int setup_signal_handler(int signal)
return sigaction(SIGURG, &sa, NULL);
}
/*
* MSG_OOB might be disabled in the kernel via the CONFIG_AF_UNIX_OOB
* switch, so this function can be used for probing for its availability.
*/
static bool has_af_unix_oob(void)
{
bool available = false;
int sp[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == 0) {
available = (send(sp[0], ".", 1, MSG_OOB) == 1);
close(sp[0]);
close(sp[1]);
}
return available;
}
/* clang-format off */
FIXTURE(fown) {};
/* clang-format on */
@@ -462,6 +480,9 @@ TEST_F(fown, sigurg_socket)
int pipe_parent[2], pipe_child[2];
pid_t child;
if (!has_af_unix_oob())
SKIP(return, "CONFIG_AF_UNIX_OOB / MSG_OOB not available");
memset(&server_address, 0, sizeof(server_address));
set_unix_address(&server_address, 0);
@@ -667,20 +688,20 @@ static void *thread_setown_scoped(void *arg)
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0)
return (void *)THREAD_ERROR;
return THREAD_ERROR;
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
landlock_restrict_self(ruleset_fd, 0)) {
close(ruleset_fd);
return (void *)THREAD_ERROR;
return THREAD_ERROR;
}
close(ruleset_fd);
/* Makes this process group own the SIGIO source. */
if (fcntl(fd, F_SETSIG, SIGURG) || fcntl(fd, F_SETOWN, -getpgrp()) ||
fcntl(fd, F_SETFL, O_ASYNC))
return (void *)THREAD_ERROR;
return THREAD_ERROR;
return (void *)THREAD_SUCCESS;
return THREAD_SUCCESS;
}
/*
@@ -702,7 +723,7 @@ TEST(sigio_to_pgid_self)
{
int trigger[2];
pthread_t thread;
enum thread_return ret = THREAD_INVALID;
void *ret = THREAD_INVALID;
int i;
drop_caps(_metadata);
@@ -722,7 +743,7 @@ TEST(sigio_to_pgid_self)
*/
ASSERT_EQ(0, pthread_create(&thread, NULL, thread_setown_scoped,
&trigger[0]));
ASSERT_EQ(0, pthread_join(thread, (void **)&ret));
ASSERT_EQ(0, pthread_join(thread, &ret));
ASSERT_EQ(THREAD_SUCCESS, ret);
/* Fans SIGURG out to the process group. */