|
20 | 20 | #include <linux/capability.h> |
21 | 21 | #include <sched.h> |
22 | 22 | #include <signal.h> |
| 23 | +#include <stdio.h> |
23 | 24 | #include <stdlib.h> |
24 | 25 | #include <string.h> |
25 | 26 | #include <sys/mman.h> |
|
33 | 34 | #include <array> |
34 | 35 | #include <atomic> |
35 | 36 | #include <memory> |
| 37 | +#include <string> |
36 | 38 | #include <vector> |
37 | 39 |
|
38 | 40 | #include "gmock/gmock.h" |
@@ -661,6 +663,122 @@ TEST(UnixSocketPeerGroupsTest, NotUnixSocket) { |
661 | 663 | SyscallFailsWithErrno(ENODATA)); |
662 | 664 | } |
663 | 665 |
|
| 666 | +// GroupsString formats groups for failure messages. |
| 667 | +std::string GroupsString(const std::vector<gid_t>& groups) { |
| 668 | + std::string s = "{"; |
| 669 | + for (size_t i = 0; i < groups.size(); i++) { |
| 670 | + if (i > 0) { |
| 671 | + s += ","; |
| 672 | + } |
| 673 | + s += std::to_string(groups[i]); |
| 674 | + } |
| 675 | + return s + "}"; |
| 676 | +} |
| 677 | + |
| 678 | +// connect(2) on a SOCK_SEQPACKET socket sets the peer credentials like it does |
| 679 | +// for SOCK_STREAM: the accepted socket reports the client's groups and the |
| 680 | +// client reports the groups the listener had at listen(2). |
| 681 | +TEST(UnixSocketPeerGroupsTest, SeqpacketConnectReportsPeerGroups) { |
| 682 | + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETGID))); |
| 683 | + |
| 684 | + const std::vector<gid_t> listener_groups = {3001}; |
| 685 | + const std::vector<gid_t> client_groups = {3002, 3003}; |
| 686 | + const auto fn = [&] { |
| 687 | + TEST_PCHECK(setgroups(listener_groups.size(), listener_groups.data()) == |
| 688 | + 0); |
| 689 | + struct sockaddr_un addr = |
| 690 | + TEST_CHECK_NO_ERRNO_AND_VALUE(UniqueUnixAddr(true, AF_UNIX)); |
| 691 | + int srv = socket(AF_UNIX, SOCK_SEQPACKET, 0); |
| 692 | + TEST_PCHECK(srv >= 0); |
| 693 | + TEST_PCHECK(bind(srv, AsSockAddr(&addr), sizeof(addr)) == 0); |
| 694 | + TEST_PCHECK(listen(srv, 5) == 0); |
| 695 | + |
| 696 | + // Connect with different groups than the listener had at listen(2). |
| 697 | + TEST_PCHECK(setgroups(client_groups.size(), client_groups.data()) == 0); |
| 698 | + int cli = socket(AF_UNIX, SOCK_SEQPACKET, 0); |
| 699 | + TEST_PCHECK(cli >= 0); |
| 700 | + TEST_PCHECK(connect(cli, AsSockAddr(&addr), sizeof(addr)) == 0); |
| 701 | + int acc = accept(srv, nullptr, nullptr); |
| 702 | + TEST_PCHECK(acc >= 0); |
| 703 | + |
| 704 | + const std::vector<gid_t> acc_groups = |
| 705 | + TEST_CHECK_NO_ERRNO_AND_VALUE(GetPeerGroups(acc)); |
| 706 | + const std::vector<gid_t> cli_groups = |
| 707 | + TEST_CHECK_NO_ERRNO_AND_VALUE(GetPeerGroups(cli)); |
| 708 | + fprintf(stderr, |
| 709 | + "SOCK_SEQPACKET: accepted socket reports %s (want %s), client " |
| 710 | + "reports %s (want %s)\n", |
| 711 | + GroupsString(acc_groups).c_str(), |
| 712 | + GroupsString(client_groups).c_str(), |
| 713 | + GroupsString(cli_groups).c_str(), |
| 714 | + GroupsString(listener_groups).c_str()); |
| 715 | + TEST_CHECK_MSG(acc_groups == client_groups, |
| 716 | + "accepted socket must report the client's groups"); |
| 717 | + TEST_CHECK_MSG(cli_groups == listener_groups, |
| 718 | + "client must report the listener's groups"); |
| 719 | + }; |
| 720 | + EXPECT_THAT(InForkedProcess(fn), IsPosixErrorOkAndHolds(0)); |
| 721 | +} |
| 722 | + |
| 723 | +// A connect(2) that fails leaves the socket without peer credentials, so |
| 724 | +// SO_PEERGROUPS still fails with ENODATA. |
| 725 | +TEST(UnixSocketPeerGroupsTest, FailedConnectLeavesNoPeerGroups) { |
| 726 | + auto addr = ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(true, AF_UNIX)); |
| 727 | + // Bound but not listening, so connect(2) fails with ECONNREFUSED. |
| 728 | + auto bound = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, 0)); |
| 729 | + ASSERT_THAT(bind(bound.get(), AsSockAddr(&addr), sizeof(addr)), |
| 730 | + SyscallSucceeds()); |
| 731 | + |
| 732 | + auto s = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, 0)); |
| 733 | + ASSERT_THAT(connect(s.get(), AsSockAddr(&addr), sizeof(addr)), |
| 734 | + SyscallFailsWithErrno(ECONNREFUSED)); |
| 735 | + |
| 736 | + std::vector<gid_t> buf(NGROUPS_MAX); |
| 737 | + socklen_t len = buf.size() * sizeof(gid_t); |
| 738 | + EXPECT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_PEERGROUPS, buf.data(), &len), |
| 739 | + SyscallFailsWithErrno(ENODATA)); |
| 740 | +} |
| 741 | + |
| 742 | +// connect(2) on a connected socket fails with EISCONN and doesn't change the |
| 743 | +// groups reported for the peer. |
| 744 | +TEST(UnixSocketPeerGroupsTest, ConnectEISCONNKeepsPeerGroups) { |
| 745 | + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETGID))); |
| 746 | + |
| 747 | + const std::vector<gid_t> listener_groups = {4001}; |
| 748 | + const std::vector<gid_t> later_groups = {4002}; |
| 749 | + const auto fn = [&] { |
| 750 | + TEST_PCHECK(setgroups(listener_groups.size(), listener_groups.data()) == |
| 751 | + 0); |
| 752 | + struct sockaddr_un addr = |
| 753 | + TEST_CHECK_NO_ERRNO_AND_VALUE(UniqueUnixAddr(true, AF_UNIX)); |
| 754 | + int srv = socket(AF_UNIX, SOCK_STREAM, 0); |
| 755 | + TEST_PCHECK(srv >= 0); |
| 756 | + TEST_PCHECK(bind(srv, AsSockAddr(&addr), sizeof(addr)) == 0); |
| 757 | + TEST_PCHECK(listen(srv, 5) == 0); |
| 758 | + int cli = socket(AF_UNIX, SOCK_STREAM, 0); |
| 759 | + TEST_PCHECK(cli >= 0); |
| 760 | + TEST_PCHECK(connect(cli, AsSockAddr(&addr), sizeof(addr)) == 0); |
| 761 | + TEST_CHECK(TEST_CHECK_NO_ERRNO_AND_VALUE(GetPeerGroups(cli)) == |
| 762 | + listener_groups); |
| 763 | + |
| 764 | + // Connect again with different groups. This fails and must not replace |
| 765 | + // the peer credentials. |
| 766 | + TEST_PCHECK(setgroups(later_groups.size(), later_groups.data()) == 0); |
| 767 | + TEST_CHECK_MSG(connect(cli, AsSockAddr(&addr), sizeof(addr)) == -1 && |
| 768 | + errno == EISCONN, |
| 769 | + "second connect must fail with EISCONN"); |
| 770 | + |
| 771 | + const std::vector<gid_t> cli_groups = |
| 772 | + TEST_CHECK_NO_ERRNO_AND_VALUE(GetPeerGroups(cli)); |
| 773 | + fprintf(stderr, "after EISCONN: client reports %s (want %s)\n", |
| 774 | + GroupsString(cli_groups).c_str(), |
| 775 | + GroupsString(listener_groups).c_str()); |
| 776 | + TEST_CHECK_MSG(cli_groups == listener_groups, |
| 777 | + "a failed connect must not change the peer groups"); |
| 778 | + }; |
| 779 | + EXPECT_THAT(InForkedProcess(fn), IsPosixErrorOkAndHolds(0)); |
| 780 | +} |
| 781 | + |
664 | 782 | } // namespace |
665 | 783 |
|
666 | 784 | } // namespace testing |
|
0 commit comments