Skip to content

Commit 2583a4a

Browse files
relkochtagvisor-bot
authored andcommitted
SCM_CREDENTIALS: use PID from ucred rather than caller's PID
Previously, the caller-provided PID was used for the CAP_SYS_ADMIN check, but wasn't actually passed along. FUTURE_COPYBARA_INTEGRATE_REVIEW=#13590 from relkochta:relkochta/fix-scmcred baafb8a PiperOrigin-RevId: 940099397
1 parent 31c3363 commit 2583a4a

3 files changed

Lines changed: 155 additions & 4 deletions

File tree

pkg/sentry/socket/control/control.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,13 @@ func NewSCMCredentials(t *kernel.Task, cred linux.ControlMessageCredentials) (SC
7070
if err != nil {
7171
return nil, err
7272
}
73-
tg := t.ThreadGroup()
74-
if kernel.ThreadID(cred.PID) != tg.ID() && !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.PIDNamespace().UserNamespace()) {
73+
if kernel.ThreadID(cred.PID) != t.ThreadGroup().ID() && !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.PIDNamespace().UserNamespace()) {
7574
return nil, linuxerr.EPERM
7675
}
76+
tg := t.PIDNamespace().ThreadGroupWithID(kernel.ThreadID(cred.PID))
77+
if tg == nil {
78+
return nil, linuxerr.ESRCH
79+
}
7780
return &scmCredentials{tg.PIDNamespacedIDs(), kuid, kgid}, nil
7881
}
7982

test/syscalls/linux/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3816,6 +3816,8 @@ cc_library(
38163816
],
38173817
deps = select_gtest() + [
38183818
":unix_domain_socket_test_util",
3819+
"//test/util:capability_util",
3820+
"//test/util:cleanup",
38193821
"//test/util:socket_util",
38203822
"//test/util:test_util",
38213823
"//test/util:thread_util",

test/syscalls/linux/socket_unix_cmsg.cc

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616

1717
#include <errno.h>
1818
#include <net/if.h>
19-
#include <stdio.h>
2019
#include <sys/ioctl.h>
2120
#include <sys/socket.h>
2221
#include <sys/types.h>
2322
#include <sys/un.h>
23+
#include <sys/wait.h>
24+
#include <unistd.h>
2425

2526
#include <vector>
2627

2728
#include "gtest/gtest.h"
28-
#include "absl/strings/string_view.h"
2929
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
30+
#include "test/util/cleanup.h"
31+
#include "test/util/linux_capability_util.h"
3032
#include "test/util/socket_util.h"
3133
#include "test/util/test_util.h"
3234
#include "test/util/thread_util.h"
@@ -722,6 +724,150 @@ TEST_P(UnixSocketPairCmsgTest, BasicCredPass) {
722724
EXPECT_EQ(sent_creds.gid, received_creds.gid);
723725
}
724726

727+
// A privileged sender (CAP_SYS_ADMIN) may attach SCM_CREDENTIALS naming the PID
728+
// of a different, live process. The receiver must observe that process's PID.
729+
TEST_P(UnixSocketPairCmsgTest, BasicCredPassDifferentPID) {
730+
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
731+
732+
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
733+
734+
// Pipe to signal to child when to exit.
735+
int pipefd[2];
736+
ASSERT_THAT(pipe(pipefd), SyscallSucceeds());
737+
738+
pid_t child = fork();
739+
if (child == 0) {
740+
close(pipefd[1]);
741+
char c;
742+
RetryEINTR(read)(pipefd[0], &c, 1);
743+
_exit(0);
744+
}
745+
ASSERT_THAT(child, SyscallSucceeds());
746+
ASSERT_THAT(close(pipefd[0]), SyscallSucceeds());
747+
748+
auto cleanup = Cleanup([&child, &pipefd] {
749+
ASSERT_THAT(close(pipefd[1]), SyscallSucceeds());
750+
ASSERT_THAT(RetryEINTR(waitpid)(child, nullptr, 0),
751+
SyscallSucceedsWithValue(child));
752+
});
753+
754+
char sent_data[20];
755+
RandomizeBuffer(sent_data, sizeof(sent_data));
756+
757+
struct ucred sent_creds;
758+
sent_creds.pid = child;
759+
ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
760+
ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
761+
762+
ASSERT_NO_FATAL_FAILURE(
763+
SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));
764+
765+
SetSoPassCred(sockets->second_fd());
766+
767+
char received_data[20];
768+
struct ucred received_creds;
769+
ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
770+
received_data, sizeof(received_data)));
771+
772+
EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
773+
// The received PID must be the child's, not the sender's.
774+
EXPECT_EQ(received_creds.pid, child);
775+
EXPECT_NE(received_creds.pid, getpid());
776+
EXPECT_EQ(received_creds.uid, sent_creds.uid);
777+
EXPECT_EQ(received_creds.gid, sent_creds.gid);
778+
}
779+
780+
// A sender without CAP_SYS_ADMIN should not be able to send SCM_CREDENTIALS
781+
// messages with a different PID.
782+
TEST_P(UnixSocketPairCmsgTest, CredPassDifferentPIDUnprivileged) {
783+
// Drop CAP_SYS_ADMIN
784+
AutoCapability cap(CAP_SYS_ADMIN, false);
785+
786+
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
787+
788+
// Pipe to signal to child when to exit.
789+
int pipefd[2];
790+
ASSERT_THAT(pipe(pipefd), SyscallSucceeds());
791+
792+
pid_t child = fork();
793+
if (child == 0) {
794+
close(pipefd[1]);
795+
char c;
796+
RetryEINTR(read)(pipefd[0], &c, 1);
797+
_exit(0);
798+
}
799+
ASSERT_THAT(child, SyscallSucceeds());
800+
ASSERT_THAT(close(pipefd[0]), SyscallSucceeds());
801+
802+
auto cleanup = Cleanup([&child, &pipefd] {
803+
ASSERT_THAT(close(pipefd[1]), SyscallSucceeds());
804+
ASSERT_THAT(RetryEINTR(waitpid)(child, nullptr, 0),
805+
SyscallSucceedsWithValue(child));
806+
});
807+
808+
char sent_data[20];
809+
RandomizeBuffer(sent_data, sizeof(sent_data));
810+
811+
struct ucred sent_creds;
812+
sent_creds.pid = child;
813+
ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
814+
ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
815+
struct msghdr msg = {};
816+
char control[CMSG_SPACE(sizeof(struct ucred))];
817+
msg.msg_control = control;
818+
msg.msg_controllen = sizeof(control);
819+
820+
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
821+
cmsg->cmsg_level = SOL_SOCKET;
822+
cmsg->cmsg_type = SCM_CREDENTIALS;
823+
cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
824+
memcpy(CMSG_DATA(cmsg), &sent_creds, sizeof(struct ucred));
825+
826+
struct iovec iov;
827+
iov.iov_base = sent_data;
828+
iov.iov_len = sizeof(sent_data);
829+
msg.msg_iov = &iov;
830+
msg.msg_iovlen = 1;
831+
832+
ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0),
833+
SyscallFailsWithErrno(EPERM));
834+
}
835+
836+
// Sending SCM_CREDENTIALS naming a PID that does not exist fails with ESRCH.
837+
TEST_P(UnixSocketPairCmsgTest, CredPassNonexistentPID) {
838+
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
839+
840+
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
841+
842+
char sent_data[20];
843+
RandomizeBuffer(sent_data, sizeof(sent_data));
844+
845+
struct ucred sent_creds;
846+
sent_creds.pid = -1;
847+
ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
848+
ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
849+
850+
struct msghdr msg = {};
851+
char control[CMSG_SPACE(sizeof(struct ucred))];
852+
msg.msg_control = control;
853+
msg.msg_controllen = sizeof(control);
854+
855+
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
856+
cmsg->cmsg_level = SOL_SOCKET;
857+
cmsg->cmsg_type = SCM_CREDENTIALS;
858+
cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
859+
memcpy(CMSG_DATA(cmsg), &sent_creds, sizeof(struct ucred));
860+
861+
struct iovec iov;
862+
iov.iov_base = sent_data;
863+
iov.iov_len = sizeof(sent_data);
864+
msg.msg_iov = &iov;
865+
msg.msg_iovlen = 1;
866+
867+
ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0),
868+
SyscallFailsWithErrno(ESRCH));
869+
}
870+
725871
TEST_P(UnixSocketPairCmsgTest, SendNullCredsBeforeSoPassCredRecvEnd) {
726872
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
727873

0 commit comments

Comments
 (0)