Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions pkg/sentry/socket/control/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,13 @@ func NewSCMCredentials(t *kernel.Task, cred linux.ControlMessageCredentials) (SC
if err != nil {
return nil, err
}
tg := t.ThreadGroup()
if kernel.ThreadID(cred.PID) != tg.ID() && !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.PIDNamespace().UserNamespace()) {
if kernel.ThreadID(cred.PID) != t.ThreadGroup().ID() && !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.PIDNamespace().UserNamespace()) {
return nil, linuxerr.EPERM
}
tg := t.PIDNamespace().ThreadGroupWithID(kernel.ThreadID(cred.PID))
if tg == nil {
return nil, linuxerr.ESRCH
}
return &scmCredentials{tg.PIDNamespacedIDs(), kuid, kgid}, nil
}

Expand Down
2 changes: 2 additions & 0 deletions test/syscalls/linux/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3816,6 +3816,8 @@ cc_library(
],
deps = select_gtest() + [
":unix_domain_socket_test_util",
"//test/util:capability_util",
"//test/util:cleanup",
"//test/util:socket_util",
"//test/util:test_util",
"//test/util:thread_util",
Expand Down
150 changes: 148 additions & 2 deletions test/syscalls/linux/socket_unix_cmsg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@

#include <errno.h>
#include <net/if.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>

#include <vector>

#include "gtest/gtest.h"
#include "absl/strings/string_view.h"
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
#include "test/util/cleanup.h"
#include "test/util/linux_capability_util.h"
#include "test/util/socket_util.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
Expand Down Expand Up @@ -722,6 +724,150 @@ TEST_P(UnixSocketPairCmsgTest, BasicCredPass) {
EXPECT_EQ(sent_creds.gid, received_creds.gid);
}

// A privileged sender (CAP_SYS_ADMIN) may attach SCM_CREDENTIALS naming the PID
// of a different, live process. The receiver must observe that process's PID.
TEST_P(UnixSocketPairCmsgTest, BasicCredPassDifferentPID) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());

// Pipe to signal to child when to exit.
int pipefd[2];
ASSERT_THAT(pipe(pipefd), SyscallSucceeds());

pid_t child = fork();
if (child == 0) {
close(pipefd[1]);
char c;
RetryEINTR(read)(pipefd[0], &c, 1);
_exit(0);
}
ASSERT_THAT(child, SyscallSucceeds());
ASSERT_THAT(close(pipefd[0]), SyscallSucceeds());

auto cleanup = Cleanup([&child, &pipefd] {
ASSERT_THAT(close(pipefd[1]), SyscallSucceeds());
ASSERT_THAT(RetryEINTR(waitpid)(child, nullptr, 0),
SyscallSucceedsWithValue(child));
});

char sent_data[20];
RandomizeBuffer(sent_data, sizeof(sent_data));

struct ucred sent_creds;
sent_creds.pid = child;
ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());

ASSERT_NO_FATAL_FAILURE(
SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));

SetSoPassCred(sockets->second_fd());

char received_data[20];
struct ucred received_creds;
ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
received_data, sizeof(received_data)));

EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
// The received PID must be the child's, not the sender's.
EXPECT_EQ(received_creds.pid, child);
EXPECT_NE(received_creds.pid, getpid());
EXPECT_EQ(received_creds.uid, sent_creds.uid);
EXPECT_EQ(received_creds.gid, sent_creds.gid);
}

// A sender without CAP_SYS_ADMIN should not be able to send SCM_CREDENTIALS
// messages with a different PID.
TEST_P(UnixSocketPairCmsgTest, CredPassDifferentPIDUnprivileged) {
// Drop CAP_SYS_ADMIN
AutoCapability cap(CAP_SYS_ADMIN, false);

auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());

// Pipe to signal to child when to exit.
int pipefd[2];
ASSERT_THAT(pipe(pipefd), SyscallSucceeds());

pid_t child = fork();
if (child == 0) {
close(pipefd[1]);
char c;
RetryEINTR(read)(pipefd[0], &c, 1);
_exit(0);
}
ASSERT_THAT(child, SyscallSucceeds());
ASSERT_THAT(close(pipefd[0]), SyscallSucceeds());

auto cleanup = Cleanup([&child, &pipefd] {
ASSERT_THAT(close(pipefd[1]), SyscallSucceeds());
ASSERT_THAT(RetryEINTR(waitpid)(child, nullptr, 0),
SyscallSucceedsWithValue(child));
});

char sent_data[20];
RandomizeBuffer(sent_data, sizeof(sent_data));

struct ucred sent_creds;
sent_creds.pid = child;
ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
struct msghdr msg = {};
char control[CMSG_SPACE(sizeof(struct ucred))];
msg.msg_control = control;
msg.msg_controllen = sizeof(control);

struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_CREDENTIALS;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
memcpy(CMSG_DATA(cmsg), &sent_creds, sizeof(struct ucred));

struct iovec iov;
iov.iov_base = sent_data;
iov.iov_len = sizeof(sent_data);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0),
SyscallFailsWithErrno(EPERM));
}

// Sending SCM_CREDENTIALS naming a PID that does not exist fails with ESRCH.
TEST_P(UnixSocketPairCmsgTest, CredPassNonexistentPID) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());

char sent_data[20];
RandomizeBuffer(sent_data, sizeof(sent_data));

struct ucred sent_creds;
sent_creds.pid = -1;
ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());

struct msghdr msg = {};
char control[CMSG_SPACE(sizeof(struct ucred))];
msg.msg_control = control;
msg.msg_controllen = sizeof(control);

struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_CREDENTIALS;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
memcpy(CMSG_DATA(cmsg), &sent_creds, sizeof(struct ucred));

struct iovec iov;
iov.iov_base = sent_data;
iov.iov_len = sizeof(sent_data);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0),
SyscallFailsWithErrno(ESRCH));
}

TEST_P(UnixSocketPairCmsgTest, SendNullCredsBeforeSoPassCredRecvEnd) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());

Expand Down
Loading